<?xml version="1.0"?>
<rss version="2.0"
     xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>JBake</title>
        <link >https://cheroliv.com/</link>
        <atom:link href="https://cheroliv.com/feed.xml" rel="self" type="application/rss+xml" />
        <description>JBake Bootstrap Template</description>
        <language>en-gb</language>
        <pubDate>Thu, 8 Oct 2026 11:37:10 +0000</pubDate>
        <lastBuildDate>Thu, 8 Oct 2026 11:37:10 +0000</lastBuildDate>

        <item>
            <title>HyperFrames-Gradle : Mengubah AsciiDoc menjadi Video MP4</title>
            <link >https://cheroliv.com/blog/2026/0126_hyperframes_asciidoc_video_post.html</link>
            <pubDate>Sun, 31 May 2026 00:00:00 +0000</pubDate>
            <guid isPermaLink="false">blog/2026/0126_hyperframes_asciidoc_video_post.html</guid>
            <description>&amp;lt;div id=&amp;quot;toc&amp;quot; class=&amp;quot;toc&amp;quot;&amp;gt;
&amp;lt;div id=&amp;quot;toctitle&amp;quot;&amp;gt;Table des matières&amp;lt;/div&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel1&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_lubang_di_pipeline_video&amp;quot;&amp;gt;Lubang di Pipeline Video&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_hyperframes_mesin&amp;quot;&amp;gt;HyperFrames : Mesin&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_arsitektur_plugin&amp;quot;&amp;gt;Arsitektur Plugin&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_pipeline_dalam_empat_tahap&amp;quot;&amp;gt;Pipeline dalam Empat Tahap&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_bahasa_khusus_domain_asciidoc&amp;quot;&amp;gt;Bahasa Khusus Domain AsciiDoc&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_bridge_node_js&amp;quot;&amp;gt;Bridge Node.js&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_dua_plugin_video_dua_penggunaan&amp;quot;&amp;gt;Dua Plugin Video, Dua Penggunaan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_6_epic_dari_roadmap&amp;quot;&amp;gt;6 EPIC dari Roadmap&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_mengapa_itu_bekerja&amp;quot;&amp;gt;Mengapa itu bekerja&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_kesimpulan_rantai_yang_hilang&amp;quot;&amp;gt;Kesimpulan: Rantai yang Hilang&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_referensi&amp;quot;&amp;gt;Referensi&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div id=&amp;quot;preamble&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;waktu membaca : 8 minutes&amp;lt;/div&amp;gt;
&amp;lt;p&amp;gt;Dua minggu lalu, saya meluncurkan`capsule-gradle`— sebuah plugin yang mengubah sebuah dek reveal.js dalam kapsul video WebM dengan TTS. Hari ini, saya menambahkan plugin baru: &amp;lt;code&amp;gt;hyperframes-gradle&amp;lt;/code&amp;gt;. Melakukan sesuatu yang tidak dilakukan oleh capsule-gradle : mengubah AsciiDoc &amp;lt;strong&amp;gt;secara langsung&amp;lt;/strong&amp;gt; menjadi video MP4, tanpa melalui reveal.js.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Ini mengapa plugin ini ada, bagaimana ia terkait dengan yang sudah ada, dan apa yang mesin HyperFrames HeyGen menyumbangkan pada pipeline produksi konten&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_lubang_di_pipeline_video&amp;quot;&amp;gt;Lubang di Pipeline Video&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Pipeline dokumenter saya menghasilkan dua jenis video :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;olist arabic&amp;quot;&amp;gt;
&amp;lt;ol class=&amp;quot;arabic&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Kapsul revisi&amp;lt;/strong&amp;gt; (capsule-gradle) — sebuah deck reveal.js di-rekam dalam WebM dengan&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;narasi TTS. Sempurna untuk mereview modul.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;olist arabic&amp;quot;&amp;gt;
&amp;lt;ol class=&amp;quot;arabic&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Slides animasi&amp;lt;/strong&amp;gt; (slider-gradle) — sebuah deck reveal.js HTML interaktif untuk&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;pelatih tatap muka.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Namun tidak ada satu format ketiga: &amp;lt;strong&amp;gt;video standalone&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;table class=&amp;quot;tableblock frame-all grid-all stretch&amp;quot;&amp;gt;
&amp;lt;colgroup&amp;gt;
&amp;lt;col style=&amp;quot;width: 5.5555%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 5.5555%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 5.5555%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 5.5555%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 5.5555%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 5.5555%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 5.5555%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 5.5555%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 5.5555%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 5.5555%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 5.5555%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 5.5555%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 5.5555%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 5.5555%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 5.5555%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 5.5555%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 5.5555%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 5.5565%;&amp;quot;&amp;gt;
&amp;lt;/colgroup&amp;gt;
&amp;lt;tbody&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Kasus penggunaan&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Solusi saat ini&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Kekurangan&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Slide instruktur&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;slider-gradle → reveal.js&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;✅&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Kapsul revisi&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;capsule-gradle → WebM&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;✅&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;Video teaser&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;tidak ada&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;❌&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;Demo teknik animasi&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;Tidak ada&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;❌&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;Dokumen-ke-video&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;tidak ada&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;❌&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Kasus penggunaan pertama kritis: bagaimana menghasilkan secara otomatis sebuah video 60 detik yang menampilkan sebuah proyek atau modul, dengan animasi GSAP, narasi TTS, dan latar belakang musik — semuanya dari file AsciiDoc yang sama Bagaimana dengan slide-nya?&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Capsule-gradle tidak dapat merespons: ia &amp;lt;strong&amp;gt;sudah&amp;lt;/strong&amp;gt; memerlukan sebuah deck reveal.js. slider-gradle menghasilkan dek ini dengan baik, tetapi tidak dirancang untuk rendering video. Kami membutuhkan pipeline &amp;lt;strong&amp;gt;langsung&amp;lt;/strong&amp;gt; : AsciiDoc → video.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_hyperframes_mesin&amp;quot;&amp;gt;HyperFrames : Mesin&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Saya menemukanhttps://github.com/heygen-com/hyperframes[HyperFrames] il y a beberapa hari. Ini adalah sebuah framework open-source (Apache 2.0) yang dibuat oleh HeyGen, unicorn dari video AI. 22 700 bintang di GitHub. Pitch tetap dalam sebuah kalimat :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Tulis HTML. Merender video. Dibuat untuk agen.&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Prinsip : Anda menulis HTML dengan`data-*`atribut, Anda mereferensikan suatu animasi GSAP (atau CSS, Lottie, Three.js…), dan mesin merender semuanya dalam MP4 melalui Puppeteer (headless Chrome) + FFmpeg. Hasil deterministik: input yang sama, frame yang sama, video yang sama.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-html hljs&amp;quot; data-lang=&amp;quot;html&amp;quot;&amp;gt;&amp;amp;lt;div id=&amp;quot;stage&amp;quot; data-composition-id=&amp;quot;intro&amp;quot; data-start=&amp;quot;0&amp;quot; data-width=&amp;quot;1920&amp;quot; data-height=&amp;quot;1080&amp;quot;&amp;amp;gt;
  &amp;amp;lt;video class=&amp;quot;clip&amp;quot; data-start=&amp;quot;0&amp;quot; data-duration=&amp;quot;6&amp;quot; data-track-index=&amp;quot;0&amp;quot;
         src=&amp;quot;background.mp4&amp;quot; muted playsinline&amp;amp;gt;&amp;amp;lt;/video&amp;amp;gt;

  &amp;amp;lt;h1 id=&amp;quot;title&amp;quot; class=&amp;quot;clip&amp;quot; data-start=&amp;quot;1&amp;quot; data-duration=&amp;quot;4&amp;quot; data-track-index=&amp;quot;1&amp;quot;&amp;amp;gt;
    Formation Docker &amp;amp;amp; Kubernetes
  &amp;amp;lt;/h1&amp;amp;gt;

  &amp;amp;lt;audio data-start=&amp;quot;0&amp;quot; data-duration=&amp;quot;6&amp;quot; data-track-index=&amp;quot;2&amp;quot;
         data-volume=&amp;quot;0.3&amp;quot; src=&amp;quot;music.wav&amp;quot;&amp;amp;gt;&amp;amp;lt;/audio&amp;amp;gt;

  &amp;amp;lt;script src=&amp;quot;https://cdn.jsdelivr.net/npm/gsap@3/dist/gsap.min.js&amp;quot;&amp;amp;gt;&amp;amp;lt;/script&amp;amp;gt;
  &amp;amp;lt;script&amp;amp;gt;
    const tl = gsap.timeline({ paused: true });
    tl.from(&amp;quot;#title&amp;quot;, { opacity: 0, y: 40, duration: 0.8 }, 1);
    window.__timelines = window.__timelines || {};
    window.__timelines.intro = tl;
  &amp;amp;lt;/script&amp;amp;gt;
&amp;amp;lt;/div&amp;amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Apa yang membuat HyperFrames berbeda dari Remotion? Tanpa React, tanpa bundler, tanpa JSX. HTML mentah yang dapat ditulis secara alami oleh agen IA. Ini dibuat khusus &amp;lt;strong&amp;gt;exactement&amp;lt;/strong&amp;gt; untuk pipeliniku : planner-gradle dengan deepseek dapat menghasilkan HTML ini dari rencana SPG/SPD, dan plugin Gradle dapat mengonversinya menjadi MP4.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_arsitektur_plugin&amp;quot;&amp;gt;Arsitektur Plugin&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;hyperframes-gradle mengikuti pola yang sama dengan semua plugin saya : yang &amp;lt;a href=&amp;quot;0124_architecture_plugin_gradle_racine_consommateur_independant_post.html&amp;quot;&amp;gt;pola « Plugin Independen + Akar Konsumen ».&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-text hljs&amp;quot; data-lang=&amp;quot;text&amp;quot;&amp;gt;hyperframes-gradle/
├── settings.gradle.kts          ← racine consommateur (dogfood)
├── build.gradle.kts              ← 3 lignes : apply plugin hyperframes
├── hyperframes-plugin/           ← BUILD INDÉPENDANT
│   ├── gradlew                    ← son propre wrapper
│   ├── build.gradle.kts           ← java-gradle-plugin
│   ├── src/main/kotlin/education/cccp/hyperframes/
│   │   ├── HyperframesPlugin.kt
│   │   ├── GenerateHyperframesHtmlTask.kt
│   │   ├── RenderHyperframesTask.kt
│   │   └── HyperframesExtension.kt
│   ├── .agents/                   ← gouvernance agent
│   └── doc/
│       └── HYPERFRAMES_ARCHITECTURE.adoc
└── video.yml                      ← configuration dogfood&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_pipeline_dalam_empat_tahap&amp;quot;&amp;gt;Pipeline dalam Empat Tahap&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultFontSize 11

title Pipeline hyperframes-gradle

rectangle &amp;quot;AsciiDoc
sumber&amp;quot; as adoc #LightYellow
rectangle &amp;quot;AsciidoctorJ&amp;quot; as asciidoctorj #LightBlue
rectangle &amp;quot;HTML HyperFrames\n(data-* attrs)&amp;quot; as html #LightGreen
rectangle &amp;quot;HyperFrames CLI\n(Node.js 22+)&amp;quot; as cli #Lavender
rectangle &amp;quot;video.mp4&amp;quot; as mp4 #Gold

adoc -right-&amp;amp;gt; asciidoctorj : parse blocs customs
asciidoctorj -right-&amp;amp;gt; html : génère HTML
html -right-&amp;amp;gt; cli : ProcessBuilder\nnpx hyperframes render
cli -right-&amp;amp;gt; mp4 : Puppeteer + FFmpeg
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;olist arabic&amp;quot;&amp;gt;
&amp;lt;ol class=&amp;quot;arabic&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;AsciidoctorJ&amp;lt;/strong&amp;gt; mengurai sumber AsciiDoc dan mengekstrak blok khusus&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;code&amp;gt;[hyperframes-composition]&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;[hyperframes-track]&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;[hyperframes-animation]&amp;lt;/code&amp;gt;.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;olist arabic&amp;quot;&amp;gt;
&amp;lt;ol class=&amp;quot;arabic&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;GenerateHyperframesHtmlTask&amp;lt;/strong&amp;gt; menghasilkan HTML HyperFrames dengan&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;`data-*`atribut yang sesuai.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;olist arabic&amp;quot;&amp;gt;
&amp;lt;ol class=&amp;quot;arabic&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;RenderHyperframesTask&amp;lt;/strong&amp;gt; memanggil CLI HyperFrames melalui`ProcessBuilder`:&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;code&amp;gt;npx hyperframes render --input index.html --output video.mp4&amp;lt;/code&amp;gt;.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;olist arabic&amp;quot;&amp;gt;
&amp;lt;ol class=&amp;quot;arabic&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;MP4 telah dideploykan di`output/`dengan sebuah`metadata.json`untuk orkestrasi.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_bahasa_khusus_domain_asciidoc&amp;quot;&amp;gt;Bahasa Khusus Domain AsciiDoc&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Nilai tambah sebenarnya dari plugin adalah DSL AsciiDoc. Pengguna Dia tidak pernah menulis HTML. Dia mengannota dokumen AsciiDoc yang sudah ada:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-asciidoc hljs&amp;quot; data-lang=&amp;quot;asciidoc&amp;quot;&amp;gt;= Formation Docker &amp;amp;amp; Kubernetes
:hyperframes-width: 1920
:hyperframes-height: 1080
:hyperframes-fps: 30

[hyperframes-composition, id=&amp;quot;intro&amp;quot;]
== Introduction

Le titre apparaît avec un fondu GSAP sur fond vidéo.

[hyperframes-track, index=0, start=0, duration=6]
video::assets/background.mp4[muted, playsinline]

[hyperframes-track, index=1, start=1, duration=4]
Formation Docker &amp;amp;amp; Kubernetes

[hyperframes-animation, type=gsap]&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;const tl = gsap.timeline({ paused: true }); tl.from(&amp;quot;#title&amp;quot;, { opacity: 0, y: 40, duration: 0.8 }, 1); window.&amp;lt;em&amp;gt;timelines = window.&amp;lt;/em&amp;gt;timelines || {}; window.__timelines.intro = tl;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Atribut docinfo (&amp;lt;code&amp;gt;:hyperframes-width:&amp;lt;/code&amp;gt;) mengatur dimensi. Blok khusus menentukan komposisi dan jalur. Blok `[hyperframes-animation]`mengandung kode GSAP mentah — yang agen IA dapat menghasilkan dari sebuah deskripsi dalam bahasa alami.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_bridge_node_js&amp;quot;&amp;gt;Bridge Node.js&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Satu-satunya titik gesek teknis: HyperFrames ditulis dalam Node.js/TypeScript, dan plugin saya adalah dalam Kotlin/JVM. Solusi adalah sebuah`ProcessBuilder`:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;class RenderHyperframesTask : DefaultTask() {

    @TaskAction
    fun render() {
        val process = ProcessBuilder(
            &amp;quot;npx&amp;quot;, &amp;quot;hyperframes&amp;quot;, &amp;quot;render&amp;quot;,
            &amp;quot;--input&amp;quot;, inputHtml.absolutePath,
            &amp;quot;--output&amp;quot;, outputMp4.absolutePath
        )
            .inheritIO()
            .start()

        val exitCode = process.waitFor()
        require(exitCode == 0) {
            &amp;quot;HyperFrames render failed with exit code $exitCode&amp;quot;
        }
    }
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Tidak ada kaitan kode JVM ↔ Node.js. Kontraknya melalui baris perintah. dan file di disk. Ini pola yang sama seperti`plantuml-gradle` (memanggil CLI PlantUML) atau`slider-gradle`(yang memanggil AsciidoctorJ). Tidak ada yang baru di bawah matahari — hanya diterapkan pada alat yang lebih baru.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_dua_plugin_video_dua_penggunaan&amp;quot;&amp;gt;Dua Plugin Video, Dua Penggunaan&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;table class=&amp;quot;tableblock frame-all grid-all stretch&amp;quot;&amp;gt;
&amp;lt;colgroup&amp;gt;
&amp;lt;col style=&amp;quot;width: 4.7619%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 4.7619%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 4.7619%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 4.7619%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 4.7619%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 4.7619%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 4.7619%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 4.7619%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 4.7619%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 4.7619%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 4.7619%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 4.7619%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 4.7619%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 4.7619%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 4.7619%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 4.7619%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 4.7619%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 4.7619%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 4.7619%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 4.7619%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 4.762%;&amp;quot;&amp;gt;
&amp;lt;/colgroup&amp;gt;
&amp;lt;tbody&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;capsule-gradle&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;hyperframes-gradle&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Source&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Deck reveal.js (HTML yang ada)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;AsciiDoc ke HTML HyperFrames&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Kasus penggunaan&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Kapsul revisi&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Video penjelasan mandiri&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Alam&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Slides → video&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Dokumen → video&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;dirender&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Playwright Java → WebM&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;HyperFrames CLI → MP4&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;animasi&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;transisi reveal.js asli&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;GSAP/CSS atribut data&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;tumpukan&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;100% JVM (Kotlin)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;JVM + Node.js CLI eksternal&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Mereka tidak saling menggantikan. Mereka saling melengkapi. Rantai nilai lengkap:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;AsciiDoc ──→ slider-gradle ──→ deck reveal.js ──→ capsule-gradle ──→ WebM (kapsul revisi)&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;AsciiDoc ──→ hyperframes-gradle ──→ MP4 (video mandiri)&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Pengguna menulis satu file AsciiDoc. slider-gradle menghasilkan slide. capsule-gradle menghasilkan kapsul revisi. hyperframes-gradle menghasilkan video teaser dan demonstrasi teknis&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_6_epic_dari_roadmap&amp;quot;&amp;gt;6 EPIC dari Roadmap&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Plugin ini terstruktur dalam 6 EPICs :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;table class=&amp;quot;tableblock frame-all grid-all stretch&amp;quot;&amp;gt;
&amp;lt;colgroup&amp;gt;
&amp;lt;col style=&amp;quot;width: 4.1666%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 4.1666%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 4.1666%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 4.1666%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 4.1666%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 4.1666%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 4.1666%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 4.1666%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 4.1666%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 4.1666%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 4.1666%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 4.1666%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 4.1666%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 4.1666%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 4.1666%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 4.1666%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 4.1666%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 4.1666%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 4.1666%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 4.1666%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 4.1666%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 4.1666%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 4.1666%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 4.1682%;&amp;quot;&amp;gt;
&amp;lt;/colgroup&amp;gt;
&amp;lt;tbody&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;epik&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Deskripsi&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Prioritas&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;HF-0&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Bootstrap tata kelola + perencanaan arsitektur&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;✅ SELESAI&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;HF-1&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Plugin Gradle stub + AsciidoctorJ integrasi&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;P0&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;HF-2&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Jembatan CLI HyperFrames (ProcessBuilder → npx → MP4)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;P1&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;HF-3&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;DSL AsciiDoc customs (blok, docinfo, templates)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;P1&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;HF-4&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Integrasi runner-gradle (metadata.json)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;P2&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;HF-5&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Template siap pakai (title-card, code-diff, captions)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;P3&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;HF-6&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;CI + penerbitan Maven Central / Gradle Portal&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;P3&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Sesi 000 (bootstrap) sudah selesai. Sesi 001 akan menyerang HF-1 : stub plugin Gradle dan integrasi AsciidoctorJ.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_mengapa_itu_bekerja&amp;quot;&amp;gt;Mengapa itu bekerja&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Tiga alasan:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;olist arabic&amp;quot;&amp;gt;
&amp;lt;ol class=&amp;quot;arabic&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;HyperFrames adalah &amp;quot;built for agents&amp;quot;&amp;lt;/strong&amp;gt;. Pipeline saya bergantung pada agen IA&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;(planner-gradle + deepseek) yang menghasilkan konten. HyperFrames menerima HTML mentah — format yang paling dikuasai oleh LLMs. Tanpa React, tanpa dari JSX, tidak ada kurva belajar untuk agen.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;olist arabic&amp;quot;&amp;gt;
&amp;lt;ol class=&amp;quot;arabic&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;DSL AsciiDoc alami&amp;lt;/strong&amp;gt;. Seluruh ekosistem saya berbicara AsciiDoc.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;slider-gradle, codex-gradle, training-gradle — semua mengonsumsi`.adoc`. Menambahkan blok kustom`[hyperframes-composition]`adalah sebuah ekstensi logis, bukan putus.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;olist arabic&amp;quot;&amp;gt;
&amp;lt;ol class=&amp;quot;arabic&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Jembatan Node.js adalah pola yang terbukti&amp;lt;/strong&amp;gt;. ProcessBuilder di luar JVM,&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Ini adalah apa yang sudah saya lakukan untuk PlantUML, Graphviz, Piper. Menambahkan HyperFrames tidak mengubah apa pun pada arsitektur — ini adalah alat eksternal tambahan, dikendalikan dari Gradle&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;admonitionblock note&amp;quot;&amp;gt;
&amp;lt;table&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;icon&amp;quot;&amp;gt;
&amp;lt;i class=&amp;quot;fa icon-note&amp;quot; title=&amp;quot;ℹ️&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Kekuatan sejati pola adalah bahwa tidak ada &amp;lt;strong&amp;gt;satupun&amp;lt;/strong&amp;gt; dependensi npm dalam pembangunan Gradle. Plugin ini tidak mengimpor kode Node.js. Menjalankan sebuah perintah shell. Jika HyperFrames berkembang atau rusak, plugin tidak terhubung — cukup memperbarui perintah.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_kesimpulan_rantai_yang_hilang&amp;quot;&amp;gt;Kesimpulan: Rantai yang Hilang&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Dengan hyperframes-gradle, pipeline video saya lengkap. Saya punya :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;The &amp;lt;strong&amp;gt;slides interaktif&amp;lt;/strong&amp;gt; (slider-gradle)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;kapsul revisi&amp;lt;/strong&amp;gt; (capsule-gradle)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;video standalone&amp;lt;/strong&amp;gt; (hyperframes-gradle)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Tiga format. Satu file sumber: AsciiDoc.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Tujuan berikutnya adalah HF-1: membuat plugin terkompilasi dan menghasilkan satu Pertama HTML HyperFrames dari dokumen AsciiDoc yang diannotasi. Sesi 001 sudah terkerangka.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;hyperframes-gradle ada. Tata kelola telah diatur. Backlog Telah ditulis. Arsitektur sudah didokumentasikan. Plugin telah lahir.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_referensi&amp;quot;&amp;gt;Referensi&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Article 0124 — Arsitektur Plugin Independen + Akar Konsumen&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://github.com/heygen-com/hyperframes&amp;quot;&amp;gt;HyperFrames — GitHub&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://hyperframes.heygen.com/introduction&amp;quot;&amp;gt;HyperFrames — Dokumentasi&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;code&amp;gt;foundry/public/hyperframes-gradle/hyperframes-plugin/doc/HYPERFRAMES_ARCHITECTURE.adoc&amp;lt;/code&amp;gt;— Arsitektur detail&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>Audit dari Konsumsi LLM saya: SQLite, OpenCode, Ollama Cloud, dan Mengapa Abonnement Pro saya Meledak Pasar Global</title>
            <link >https://cheroliv.com/blog/2026/0125_audit_conso_opencode_methodologie_comparaison_prix_llm_post.html</link>
            <pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate>
            <guid isPermaLink="false">blog/2026/0125_audit_conso_opencode_methodologie_comparaison_prix_llm_post.html</guid>
            <description>&amp;lt;div id=&amp;quot;toc&amp;quot; class=&amp;quot;toc&amp;quot;&amp;gt;
&amp;lt;div id=&amp;quot;toctitle&amp;quot;&amp;gt;Table des matières&amp;lt;/div&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel1&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_memahami_stack_siapa_melakukan_apa&amp;quot;&amp;gt;Memahami Stack: Siapa melakukan apa?&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_pendahuluan_mengapa_audit&amp;quot;&amp;gt;Pendahuluan: Mengapa audit?&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_basis_data_opencode_tambang_emas&amp;quot;&amp;gt;Basis Data OpenCode: Tambang Emas&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_skema&amp;quot;&amp;gt;Skema&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_kode_ekstraksi&amp;quot;&amp;gt;Kode Ekstraksi&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_hasil_73_hari_konsumsi&amp;quot;&amp;gt;Hasil : 73 Hari Konsumsi&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_tampilan_global&amp;quot;&amp;gt;Tampilan Global&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_ventilasi_berdasarkan_model_dan_penyedia&amp;quot;&amp;gt;Ventilasi berdasarkan Model dan Penyedia&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_model_intra_sesi_apa_yang_tidak_terlihat_oleh_tabel_sesi&amp;quot;&amp;gt;Model Intra-Sesi : Apa yang tidak terlihat oleh Tabel Sesi&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_perbandingan_tarif_penyedia_barat&amp;quot;&amp;gt;Perbandingan Tarif — Penyedia Barat&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_anthropic_claude&amp;quot;&amp;gt;Anthropic — Claude&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_openai_gpt&amp;quot;&amp;gt;OpenAI — GPT&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_google_gemini&amp;quot;&amp;gt;Google — Gemini&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_penyedia_inferensi_huggingface&amp;quot;&amp;gt;Penyedia Inferensi HuggingFace&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_groq&amp;quot;&amp;gt;Groq&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_perbandingan_tarif_pemasok_tiongkok&amp;quot;&amp;gt;Perbandingan Tarif — Pemasok Tiongkok&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_api_langsung_deepseek_platform_deepseek_com&amp;quot;&amp;gt;API Langsung DeepSeek (platform.deepseek.com)&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_kimi_moonshot_platform_kimi_com&amp;quot;&amp;gt;Kimi Moonshot (platform.kimi.com)&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_alibaba_bailian_bailian_aliyun_com&amp;quot;&amp;gt;Alibaba Bailian (bailian.aliyun.com)&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_tabel_ringkasan_semua_penyedia&amp;quot;&amp;gt;Tabel Ringkasan — Semua Penyedia&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_biaya_tersembunyi_prompt_engineering_dan_iterasi&amp;quot;&amp;gt;Biaya Tersembunyi: Prompt Engineering dan Iterasi&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_menggabungkan_langganan_ollama_cloud_pro_metode_docker&amp;quot;&amp;gt;Menggabungkan Langganan Ollama Cloud Pro: Metode Docker&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_apa_yang_tidak_diukur_oleh_audit&amp;quot;&amp;gt;Apa yang Tidak Diukur oleh Audit&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_kesimpulan_putusan_angka&amp;quot;&amp;gt;Kesimpulan : Putusan Angka&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_referensi&amp;quot;&amp;gt;Referensi&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_sumber_tarif&amp;quot;&amp;gt;Sumber Tarif&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div id=&amp;quot;preamble&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;waktu membaca : 22 minutes&amp;lt;/div&amp;gt;
&amp;lt;p&amp;gt;Saya sering ditanya berapa biaya konsumsi AI saya. Saya menjawab « 20 euro per bulan » dan mereka menatap saya dengan mata bulat. Hari ini, saya membuka kotak hitam untuk Anda. Artikel ini adalah audit lengkap dari 73 hari vibe coding saya: metodologi untuk mengekstrak data dari basis SQLite OpenCode, kode SQL untuk mengueri tabel, dan perbandingan komprehensif dengan &amp;lt;strong&amp;gt;semua&amp;lt;/strong&amp;gt; penyedia di pasar — Barat seperti juga Cina. Hasilnya sangat tidak simetris hingga saya harus memeriksa perhitungan saya tiga kali.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;dlist&amp;quot;&amp;gt;
&amp;lt;dl&amp;gt;
&amp;lt;dt class=&amp;quot;hdlist1&amp;quot;&amp;gt;tik&amp;lt;/dt&amp;gt;
&amp;lt;dd&amp;gt;
&amp;lt;p&amp;gt;[]&amp;lt;/p&amp;gt;
&amp;lt;/dd&amp;gt;
&amp;lt;/dl&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_memahami_stack_siapa_melakukan_apa&amp;quot;&amp;gt;Memahami Stack: Siapa melakukan apa?&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Sebelum menyelami angka, mari kita jelaskan arsitektur. Tiga aktor berbeda ada dalam setup saya, dan penting untuk tidak salah mengidentifikasi mereka:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultFontSize 12

title Tiga Aktor Stack LLM saya
left to right direction

actor &amp;quot;Saya (Pengembang)&amp;quot; as Dev

package &amp;quot;OpenCode\n(github.com/anomalyco/opencode)&amp;quot; as OC {
  rectangle &amp;quot;CLI pemrograman vibe
Sumber terbuka, gratis&amp;quot; as CLI
  note right of CLI
    C&amp;#39;est l&amp;#39;outil que j&amp;#39;utilise
    pour coder avec des agents IA.
    Il ne fournit AUCUN modèle.
    C&amp;#39;est un terminal + un orchestrateur.
  end note
}

package &amp;quot;Ollama (Instalasi Lokal)&amp;quot; as OL {
  rectangle &amp;quot;Klien inferensi
Diinstal pada komputer saya&amp;quot; as Client
  note right of Client
    C&amp;#39;est le logiciel qui fait tourner
    les modèles. Il peut les exécuter
    en local OU les déléguer au cloud
    selon le tag (:cloud).
    Il s&amp;#39;authentifie via clé SSH.
  end note
}

package &amp;quot;Ollama Cloud
(ollama.com)&amp;quot; as OCloud {
  rectangle &amp;quot;Layanan inferensi cloud
Langganan Pro : 20€/bulan&amp;quot; as Cloud
  note right of Cloud
    C&amp;#39;est le fournisseur d&amp;#39;inférence.
    Mes modèles avec le tag :cloud
    sont exécutés sur LEURS serveurs.
    L&amp;#39;abonnement Pro donne accès
    à des quotas supérieurs.
  end note
}

Dev --&amp;amp;gt; CLI : &amp;quot;Saya mengetik beberapa prompt&amp;quot;
CLI --&amp;amp;gt; Client : &amp;quot;Panggil model ini&amp;quot;
Client --&amp;amp;gt; Cloud : &amp;quot;Kunci SSH + permintaan
(model : cloud)&amp;quot;
Client --&amp;amp;gt; Client : &amp;quot;Eksekusi lokal
(model tanpa :cloud)&amp;quot;

@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;admonitionblock important&amp;quot;&amp;gt;
&amp;lt;table&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;icon&amp;quot;&amp;gt;
&amp;lt;i class=&amp;quot;fa icon-important&amp;quot; title=&amp;quot;❗&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;OpenCode BUKAN seorang penyedia model.&amp;lt;/strong&amp;gt;Ini adalah CLI sumber terbuka (&amp;lt;a href=&amp;quot;https://github.com/anomalyco/opencode&amp;quot;&amp;gt;github.com/anomalyco/opencode&amp;lt;/a&amp;gt;) yang mengorkestrasi agen AI. Tidak menjual langganan, tidak menyediakan inferensi. Ini ekuivalen dengan`git`untuk pembuatan versi: sebuah alat, bukan layanan. &amp;lt;strong&amp;gt;Ollama Cloud adalah penyedia inferensi.&amp;lt;/strong&amp;gt;Instalasi lokal Ollama saya berperangai sebagai &amp;lt;strong&amp;gt;client&amp;lt;/strong&amp;gt; — ia mengautentikasi diri kepada server Ollama melalui kunci SSH unik (Device Key) dan mendelegasikan eksekusi model yang ditandai kepada mereka`:cloud`. Langganan Pro (20€/bulan) membuka kuota yang lebih tinggi dan memungkinkan menggabungkan beberapa akun pada satu mesin melalui Docker.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_pendahuluan_mengapa_audit&amp;quot;&amp;gt;Pendahuluan: Mengapa audit?&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Dua bulan lalu, saya menggunakan model secara gratis di instalasi lokal Ollama saya. Kemudian saya berlangganan pertama Ollama Cloud Pro seharga 20€/bulan, yang memungkinkan saya menggunakan model cloud (yang memiliki tag`:cloud`) via penyedia`ollama-cloud`. Sebuah langganan kedua mengikuti sebulan kemudian, pada instance Docker terpisah dengan akun dan kunci SSH milik sendiri — metode akumulasi yang dijelaskan dalam &amp;lt;a href=&amp;quot;0120_cumuler_deux_abonnements_ollama_pro_docker_post.html&amp;quot;&amp;gt;artikel saya tentang akumulasi langganan&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Pertanyaan yang mengganggu saya: &amp;lt;strong&amp;gt;apakah 20€ per bulan ini sudah layak ?&amp;lt;/strong&amp;gt; Tidak dengan tebak-tebakan. Dengan angka.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Untuk menjawab, saya membutuhkan data yang tepat. Bukan estimasi. Bukan rata-rata. Konsumsi saya yang &amp;lt;strong&amp;gt;sebenarnya&amp;lt;/strong&amp;gt;, token demi token, model demi model, sesi demi sesi.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Dan OpenCode — CLI-nya — menyimpan semua itu di database SQLite.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_basis_data_opencode_tambang_emas&amp;quot;&amp;gt;Basis Data OpenCode: Tambang Emas&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;OpenCode menyimpan seluruh riwayat sesi-nya dalam file SQLite yang terletak di`~/.local/share/opencode/opencode.db`. La mienne pèse 660 Mo pour 73 jours d&amp;amp;#8217;utilisation — une base de données de production tout à fait respectable.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;$ ls -lh ~/.local/share/opencode/opencode.db
-rw-r--r-- 1 cheroliv cheroliv 660M mai 18 12:25 opencode.db&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_skema&amp;quot;&amp;gt;Skema&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Basis data berisi sekitar dua puluh tabel, tetapi tiga di antaranya sangat penting untuk audit kami :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultFontSize 12

title Skema Sederhana — OpenCode SQLite
entity &amp;quot;sesi&amp;quot; as S {
  * id : TEXT
  --
  * model : TEXT (JSON)
  * cost : REAL
  * tokens_input : INTEGER
  * tokens_output : INTEGER
  * tokens_reasoning : INTEGER
  * tokens_cache_read : INTEGER
  * tokens_cache_write : INTEGER
  * time_created : INTEGER
  * project_id : TEXT
}

entity &amp;quot;pesan&amp;quot; as M {
  * id : TEXT
  --
  * session_id : TEXT &amp;amp;lt;&amp;amp;lt;FK&amp;amp;gt;&amp;amp;gt;
  * data : TEXT (JSON)
  * time_created : INTEGER
}

entity &amp;quot;proyek&amp;quot; as P {
  * id : TEXT
  --
  * worktree : TEXT
  * name : TEXT
}

S ||--o{ M : &amp;quot;session_id&amp;quot;
S }o--|| P : &amp;quot;project_id&amp;quot;

note right of S
  La table session agrège
  tokens + coût + modèle.
  C&amp;#39;est notre point d&amp;#39;entrée
  principal.
end note

note right of M
  Les messages individuels.
  Le champ data contient
  le modèle utilisé (format JSON)
  même si la session change
  de modèle en cours de route.
end note

@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Meja`session`adalah sempurna untuk audit global: setiap sesi memiliki jumlah token yang diagregasikan, model yang digunakan, dan biayanya. Kolom`model`adalah JSON mentah :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-json hljs&amp;quot; data-lang=&amp;quot;json&amp;quot;&amp;gt;{
  &amp;quot;id&amp;quot;: &amp;quot;deepseek-v4-pro:cloud&amp;quot;,
  &amp;quot;providerID&amp;quot;: &amp;quot;ollama-cloud&amp;quot;,
  &amp;quot;variant&amp;quot;: &amp;quot;default&amp;quot;
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;admonitionblock important&amp;quot;&amp;gt;
&amp;lt;table&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;icon&amp;quot;&amp;gt;
&amp;lt;i class=&amp;quot;fa icon-important&amp;quot; title=&amp;quot;❗&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Lapangan`cost`dalam basis OpenCode hanya mencerminkan &amp;lt;strong&amp;gt;biaya API eksternal&amp;lt;/strong&amp;gt; yang dibayar sesuai penggunaan melalui kunci API (Google Gemini, OpenAI, dst.). Panggilan melalui Ollama — baik lokal maupun melalui Ollama Cloud Pro — ditandai`cost=0`karena biayanya adalah tarif tetap (langganan), bukan per token. Biaya sebenarnya dari panggilan-panggilan ini adalah langganan bulanan.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_kode_ekstraksi&amp;quot;&amp;gt;Kode Ekstraksi&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Tidak perlu menginstal apa pun. Node.js 22+ menyertakan modul SQLite eksperimental (&amp;lt;code&amp;gt;node:sqlite&amp;lt;/code&amp;gt;) yang berfungsi dengan sempurna. Berikut skrip yang memberi saya semuanya:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-javascript hljs&amp;quot; data-lang=&amp;quot;javascript&amp;quot;&amp;gt;const { DatabaseSync } = require(&amp;#39;node:sqlite&amp;#39;);
const db = new DatabaseSync(
  `${process.env.HOME}/.local/share/opencode/opencode.db`,
  { readonly: true }
);

// 1. Statistiques globales
const global = db.prepare(`
  SELECT
    COUNT(*) as sessions,
    SUM(tokens_input) as total_input,
    SUM(tokens_output) as total_output,
    SUM(tokens_reasoning) as total_reasoning,
    SUM(tokens_cache_read) as total_cache_read,
    SUM(cost) as total_cost
  FROM session
`).get();

// 2. Ventilation par modèle et fournisseur
const byModel = db.prepare(`
  SELECT
    json_extract(model, &amp;#39;$.id&amp;#39;) as model_id,
    json_extract(model, &amp;#39;$.providerID&amp;#39;) as provider,
    COUNT(*) as sessions,
    SUM(tokens_input) as total_input,
    SUM(tokens_output) as total_output,
    SUM(cost) as total_cost
  FROM session
  WHERE model IS NOT NULL
  GROUP BY model_id, provider
  ORDER BY SUM(tokens_input) DESC
`).all();

// 3. Par mois
const monthly = db.prepare(`
  SELECT
    strftime(&amp;#39;%Y-%m&amp;#39;, datetime(time_created/1000, &amp;#39;unixepoch&amp;#39;)) as month,
    COUNT(*) as sessions,
    SUM(tokens_input) as total_input,
    SUM(tokens_output) as total_output,
    SUM(cost) as total_cost
  FROM session
  WHERE time_created IS NOT NULL
  GROUP BY month
  ORDER BY month
`).all();

// 4. Messages par modèle (détection des changements intra-session)
const msgByModel = db.prepare(`
  SELECT
    json_extract(m.data, &amp;#39;$.model.providerID&amp;#39;) as provider,
    json_extract(m.data, &amp;#39;$.model.modelID&amp;#39;) as model_id,
    COUNT(*) as messages
  FROM message m
  WHERE json_extract(m.data, &amp;#39;$.model&amp;#39;) IS NOT NULL
  GROUP BY provider, model_id
  ORDER BY COUNT(*) DESC
`).all();

console.log({ global, byModel, monthly, msgByModel });
db.close();&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;admonitionblock note&amp;quot;&amp;gt;
&amp;lt;table&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;icon&amp;quot;&amp;gt;
&amp;lt;i class=&amp;quot;fa icon-note&amp;quot; title=&amp;quot;ℹ️&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Bendera`--experimental-sqlite`diperlukan pada Node.js 22.x. Pada Node.js 24+, modul ini stabil dan tidak lagi memerlukan flag.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_hasil_73_hari_konsumsi&amp;quot;&amp;gt;Hasil : 73 Hari Konsumsi&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_tampilan_global&amp;quot;&amp;gt;Tampilan Global&amp;lt;/h3&amp;gt;
&amp;lt;table class=&amp;quot;tableblock frame-all grid-all stretch&amp;quot;&amp;gt;
&amp;lt;colgroup&amp;gt;
&amp;lt;col style=&amp;quot;width: 33.3333%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 33.3333%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 33.3334%;&amp;quot;&amp;gt;
&amp;lt;/colgroup&amp;gt;
&amp;lt;thead&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;metrik&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;Nilai&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;konteks&amp;lt;/th&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/thead&amp;gt;
&amp;lt;tbody&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;Sesi OpenCode&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;1 022&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;~14 sesi/hari rata-rata&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;Masukan Token&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;2,62 miliar&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Dari mana 94,5M cache hit&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;Tokens Output&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;21 juta&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Ratio input/output ~125:1&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;Token Penalaran&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;141 000&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Sangat kecil — hampir tidak ada pemikiran&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;Biaya API eksternal&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;$0,22&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Biaya di luar langganan (Gemini + free tiers)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Angka \$0,22 ini penting: ini mewakili semua yang saya bayar &amp;lt;strong&amp;gt;di atas&amp;lt;/strong&amp;gt; langganan Ollama Cloud Pro saya. Artinya hampir tidak ada. Langganan mencakup intinya.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultFontSize 12

title Evolusi Bulanan — Input Token
rectangle &amp;quot;Maret 2026
9 sesi
37,7M token&amp;quot; as MARS #FFE082
rectangle &amp;quot;April 2026
486 sessions
1,33 Md tokens&amp;quot; as AVR #81C784
rectangle &amp;quot;Mei 2026
527 sesi
1,25 miliar token&amp;quot; as MAI #64B5F6

MARS -right-&amp;amp;gt; AVR : ×35
AVR -right-&amp;amp;gt; MAI : stable (période en cours)

note bottom of MAI
  Mai comptabilisé jusqu&amp;#39;au 18.
  Projection mois complet : ~2,1 Md tokens
  si la tendance se maintient.
end note
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Pembangkitan antara Maret dan April spektakuler: ×35 dalam sebulan. Ini adalah transisi dari penggunaan « test » ke penggunaan « production » — ketika saya mulai menjalankan agen OpenCode secara kontinu untuk plugin Gradle saya.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_ventilasi_berdasarkan_model_dan_penyedia&amp;quot;&amp;gt;Ventilasi berdasarkan Model dan Penyedia&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Bidang`model`dari meja`session`merupakan JSON,`json_extract()`memungkinkan ventilasi dengan baik :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;table class=&amp;quot;tableblock frame-all grid-all stretch&amp;quot;&amp;gt;
&amp;lt;colgroup&amp;gt;
&amp;lt;col style=&amp;quot;width: 27.2727%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 18.1818%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 18.1818%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 18.1818%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 18.1819%;&amp;quot;&amp;gt;
&amp;lt;/colgroup&amp;gt;
&amp;lt;thead&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;Model × Penyedia&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;Sesi&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;(empty)&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;keluaran&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;Biaya&amp;lt;/th&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/thead&amp;gt;
&amp;lt;tbody&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;deepseek-v4-pro @ ollama-cloud&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;63&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;220 M&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;1,94 M&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;\$0&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;deepseek-v4-pro @ ollama-b&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;124&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;463 M&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;4,15 M&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;\$0&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;deepseek-v4-pro @ ollama&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;100&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;252 M&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;2,25 M&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;\$0&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;deepseek-v4-flash-free @ opencode&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;16&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;1,3 M&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;279 K&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;\$0&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;qwen3.6-plus-free @ opencode&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;1&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;369 K&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;25 K&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;\$0&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;gemini-2.5-flash @ google&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;1&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;211 K&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;19 K&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;\$0,04&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;div class=&amp;quot;admonitionblock important&amp;quot;&amp;gt;
&amp;lt;table&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;icon&amp;quot;&amp;gt;
&amp;lt;i class=&amp;quot;fa icon-important&amp;quot; title=&amp;quot;❗&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Tiga penyedia berbeda untuk tepat sama model DeepSeek V4 Pro:
* &amp;lt;code&amp;gt;ollama-cloud&amp;lt;/code&amp;gt;→ via &amp;lt;strong&amp;gt;langganan Pro berbayar&amp;lt;/strong&amp;gt; (cloud dikelola oleh Ollama)
* &amp;lt;code&amp;gt;ollama-b&amp;lt;/code&amp;gt;→ &amp;lt;strong&amp;gt;langganan Pro kedua&amp;lt;/strong&amp;gt;, terisolasi dalam kontainer Docker yang terpisah
* &amp;lt;code&amp;gt;ollama&amp;lt;/code&amp;gt;→ instance native locale, &amp;lt;strong&amp;gt;gratis&amp;lt;/strong&amp;gt; (berjalan langsung di komputer saya)
Jumlah dari tiga menghasilkan 936M input, 8,4M output — ini adalah konsumsi total saya DeepSeek V4 Pro, semua provider digabung. Hanya`ollama-cloud`(220M input) memerlukan langganan. Sisa 716M gratis (local atau akumulasi langganan).&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_model_intra_sesi_apa_yang_tidak_terlihat_oleh_tabel_sesi&amp;quot;&amp;gt;Model Intra-Sesi : Apa yang tidak terlihat oleh Tabel Sesi&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Meja`session`hanya menangkap model &amp;lt;strong&amp;gt;utama&amp;lt;/strong&amp;gt; dari sesi. Namun selama satu sesi yang sama, agen OpenCode dapat beralih antara beberapa model. Tabel`message`mengungkapkan :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-text hljs&amp;quot; data-lang=&amp;quot;text&amp;quot;&amp;gt;Modèles utilisés dans les messages (ollama-cloud provider) :
├─ deepseek-v4-pro      1 441 msgs  (32%)
├─ qwen3.5:397b         1 217 msgs  (27%)
├─ glm-5.1                712 msgs  (16%)
├─ kimi-k2.6:cloud        634 msgs  (14%)
├─ qwen3-coder:480b       414 msgs   (9%)
├─ gpt-oss:20b             34 msgs  (0,8%)
├─ qwen3-coder-next        27 msgs  (0,6%)
└─ gemma4:31b               7 msgs  (0,2%)&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;admonitionblock caution&amp;quot;&amp;gt;
&amp;lt;table&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;icon&amp;quot;&amp;gt;
&amp;lt;i class=&amp;quot;fa icon-caution&amp;quot; title=&amp;quot;🔒&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Saya tidak lagi menggunakan Qwen, GLM, Kimi maupun model-model lainnya. Mereka muncul di riwayat karena saya telah mencoba banyak hal di April. Sejak pertengahan Mei, saya terkunci pada DeepSeek V4 Pro — ia adalah satu-satunya yang mampu menahan beban pada konteks panjang dengan governance agen (lihat &amp;lt;a href=&amp;quot;0112_comparaison_kimi_glm_deepseek_long_contexte_plugin_gradle_opencode_post.html&amp;quot;&amp;gt;perbandingan saya Kimi/GLM/DeepSeek&amp;lt;/a&amp;gt;). 32% pesan DeepSeek di riwayat adalah menyesatkan: hari ini itu 100%.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_perbandingan_tarif_penyedia_barat&amp;quot;&amp;gt;Perbandingan Tarif — Penyedia Barat&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Pertanyaan yang menyakitkan: berapa biaya konsumsi yang sama dengan penyedia tradisional ?&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Dasar perhitungan saya untuk perbandingan :&amp;lt;strong&amp;gt;220M tokens input + 1,9M tokens output&amp;lt;/strong&amp;gt;— &amp;lt;strong&amp;gt;hanya&amp;lt;/strong&amp;gt; volume yang dilewati oleh`ollama-cloud`(yang satu-satunya yang sesuai dengan langganan berbayar saya). Sisa 716M gratis (Ollama lokal dan instance Docker B).&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;table class=&amp;quot;tableblock frame-all grid-all stretch&amp;quot;&amp;gt;
&amp;lt;colgroup&amp;gt;
&amp;lt;col style=&amp;quot;width: 23.0769%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 15.3846%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 15.3846%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 15.3846%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 15.3846%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 15.3847%;&amp;quot;&amp;gt;
&amp;lt;/colgroup&amp;gt;
&amp;lt;thead&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;pemasok&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;model&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;\$/M&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;keluaran $/M&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;Biaya/bulan&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;× abonemen saya&amp;lt;/th&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/thead&amp;gt;
&amp;lt;tbody&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;Setup saya&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;DeepSeek V4 Pro&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;0&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;0&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;\~20€&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;1×&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_anthropic_claude&amp;quot;&amp;gt;Anthropic — Claude&amp;lt;/h3&amp;gt;
&amp;lt;table class=&amp;quot;tableblock frame-all grid-all stretch&amp;quot;&amp;gt;
&amp;lt;colgroup&amp;gt;
&amp;lt;col style=&amp;quot;width: 27.2727%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 18.1818%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 18.1818%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 18.1818%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 18.1819%;&amp;quot;&amp;gt;
&amp;lt;/colgroup&amp;gt;
&amp;lt;thead&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;Model&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;$/M&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;Keluaran \$/M&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;Biaya/bulan&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;× langganan saya&amp;lt;/th&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/thead&amp;gt;
&amp;lt;tbody&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Opus 4.7&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;\$5&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;\$25&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;\$1 148&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;57×&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Sonnet 4.6&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;\$3&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;\$15&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;\$689&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;34×&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Haiku 4.5&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;\$1&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;\$5&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;\$230&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;11×&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Bahkan Haiku 4.5, model paling « ekon omi » dari Claude, biayanya 11× langganan saya. Dan ia tidak mengenal apa-apa dari kode saya, tidak menghasilkan dalam gaya saya, dan halusinasi terhadap API Gradle Kotlin DSL.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_openai_gpt&amp;quot;&amp;gt;OpenAI — GPT&amp;lt;/h3&amp;gt;
&amp;lt;table class=&amp;quot;tableblock frame-all grid-all stretch&amp;quot;&amp;gt;
&amp;lt;colgroup&amp;gt;
&amp;lt;col style=&amp;quot;width: 27.2727%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 18.1818%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 18.1818%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 18.1818%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 18.1819%;&amp;quot;&amp;gt;
&amp;lt;/colgroup&amp;gt;
&amp;lt;thead&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;Model&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;$/m&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;Keluaran $/M&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;Biaya/bulan&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;× langganan saya&amp;lt;/th&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/thead&amp;gt;
&amp;lt;tbody&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;GPT 5.5&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;\$5&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;\$30&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;\$1 157&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;58 kali&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;GPT 5.4&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;\$2,50&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;\$15&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;\$579&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;29×&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;GPT 5.4 Mini&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;\$0,75&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;\$4,50&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;\$174&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;9×&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_google_gemini&amp;quot;&amp;gt;Google — Gemini&amp;lt;/h3&amp;gt;
&amp;lt;table class=&amp;quot;tableblock frame-all grid-all stretch&amp;quot;&amp;gt;
&amp;lt;colgroup&amp;gt;
&amp;lt;col style=&amp;quot;width: 27.2727%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 18.1818%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 18.1818%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 18.1818%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 18.1819%;&amp;quot;&amp;gt;
&amp;lt;/colgroup&amp;gt;
&amp;lt;thead&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;model&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;\$/M&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;$/M&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;Biaya/bulan&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;× langganan saya&amp;lt;/th&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/thead&amp;gt;
&amp;lt;tbody&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Gemini 3.1 Pro&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;\$2,00&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;\$10&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;\$459&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;23×&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Gemini 3 Flash&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;\$0,30&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;\$1,50&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;\$69&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;3,5×&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Gemini 3 Flash berharga 69€ adalah kompetitor « proche » yang satu-satunya dalam harga murni — tapi ini adalah model ringan padat, bukan MoE 1.6T seperti DeepSeek V4 Pro. Kualitas kode yang dihasilkan tidak berhubungan sama sekali.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_penyedia_inferensi_huggingface&amp;quot;&amp;gt;Penyedia Inferensi HuggingFace&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;HuggingFace tidak melakukan inferensi sendiri — ia mengagregasi &amp;lt;strong&amp;gt;providers&amp;lt;/strong&amp;gt; pihak ketiga (Novita, Together, DeepInfra, Cerebras, SambaNova, Fireworks, Groq…) melalui API yang terpadu. Kita memilih provider yang termurah atau tercepat. Berikut tarif yang dicatat pada Mei 2026 untuk DeepSeek V4 Pro :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;table class=&amp;quot;tableblock frame-all grid-all stretch&amp;quot;&amp;gt;
&amp;lt;colgroup&amp;gt;
&amp;lt;col style=&amp;quot;width: 27.2727%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 18.1818%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 18.1818%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 18.1818%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 18.1819%;&amp;quot;&amp;gt;
&amp;lt;/colgroup&amp;gt;
&amp;lt;thead&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;penyedia HF&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;\$/M&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;$/M&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;Biaya/bulan&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;× langganan saya&amp;lt;/th&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/thead&amp;gt;
&amp;lt;tbody&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Novita (termurah)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;\$1,67&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;\$3,38&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;$374&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;19×&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;DeepInfra&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;\$1,74&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;\$3,48&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;\$389&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;19 kali&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Bersama&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;\$2,10&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;\$4,40&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;$470&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;23×&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;kembang api&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;tarif tidak publik&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;—&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;(empty)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;—&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Meski provider termurah di HuggingFace (Novita) menagih 19× harga langganan saya. Berbeda dengan Ollama Cloud, HuggingFace menagih &amp;lt;strong&amp;gt;en plus&amp;lt;/strong&amp;gt; biaya platform jika Anda tidak memiliki langganan PRO HF ($9/bulan).&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_groq&amp;quot;&amp;gt;Groq&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Groq tidak memiliki &amp;lt;strong&amp;gt;satu pun&amp;lt;/strong&amp;gt; model yang saya gunakan — katalog mereka hanya mencakup GPT-OSS, Llama, dan Qwen 32B. Tidak dapat dibandingkan.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_perbandingan_tarif_pemasok_tiongkok&amp;quot;&amp;gt;Perbandingan Tarif — Pemasok Tiongkok&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Uji yang sebenarnya adalah pasar China. Jika DeepSeek adalah model China, API aslinya seharusnya lebih murah, bukan?&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_api_langsung_deepseek_platform_deepseek_com&amp;quot;&amp;gt;API Langsung DeepSeek (platform.deepseek.com)&amp;lt;/h3&amp;gt;
&amp;lt;table class=&amp;quot;tableblock frame-all grid-all stretch&amp;quot;&amp;gt;
&amp;lt;colgroup&amp;gt;
&amp;lt;col style=&amp;quot;width: 33.3333%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 22.2222%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 22.2222%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 22.2223%;&amp;quot;&amp;gt;
&amp;lt;/colgroup&amp;gt;
&amp;lt;thead&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;Periode&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;$/M&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;Keluaran $/M&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;Biaya/bulan&amp;lt;/th&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/thead&amp;gt;
&amp;lt;tbody&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Promo -75% (akhir 31 Mei)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;\$0,435&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;\$0,87&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;98 dolar&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Tarif penuh (mulai Juni)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;\$1,74&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;\$3,48&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;$390&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Bahkan dengan promosi agresif DeepSeek (-75% diperpanjang), biaya API langsung&amp;lt;strong&amp;gt;5×&amp;lt;/strong&amp;gt;lebih mahal. Di tarif normal,&amp;lt;strong&amp;gt;20×&amp;lt;/strong&amp;gt;. Dan ini &amp;lt;strong&amp;gt;hanya&amp;lt;/strong&amp;gt; DeepSeek — tidak ada akses ke model lain.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_kimi_moonshot_platform_kimi_com&amp;quot;&amp;gt;Kimi Moonshot (platform.kimi.com)&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Kimi hanya menawarkan model-modelnya sendiri. Tidak ada DeepSeek.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_alibaba_bailian_bailian_aliyun_com&amp;quot;&amp;gt;Alibaba Bailian (bailian.aliyun.com)&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Penyedia tunggal yang mengagregasi semua model China — tetapi melalui penjualan ulang dengan margin. Harga diisi secara dinamis dengan JavaScript, sehingga scraping menjadi tidak mungkin.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_tabel_ringkasan_semua_penyedia&amp;quot;&amp;gt;Tabel Ringkasan — Semua Penyedia&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;table class=&amp;quot;tableblock frame-all grid-all stretch&amp;quot;&amp;gt;
&amp;lt;colgroup&amp;gt;
&amp;lt;col style=&amp;quot;width: 23.0769%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 15.3846%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 15.3846%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 15.3846%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 15.3846%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 15.3847%;&amp;quot;&amp;gt;
&amp;lt;/colgroup&amp;gt;
&amp;lt;thead&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;pemasok&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;model&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;$/M&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;Keluaran \$/M&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;Biaya/bulan&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;× langganan saya&amp;lt;/th&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/thead&amp;gt;
&amp;lt;tbody&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;Ollama Cloud Pro&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;DeepSeek V4 Pro&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;0&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;0&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;~20€&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;1×&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Gemini 3 Kilat&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Gemini 3 Flash&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;\$0,30&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;\$1,50&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;\$69&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;3,5×&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;DeepSeek API (promosi)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;DeepSeek V4 Pro&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;\$0,44&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;\$0,87&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;\$98&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;5×&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;GPT 5.4 Mini&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;GPT 5.4 Mini&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;\$0,75&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;\$4,50&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;\$174&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;9×&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Claude Haiku 4.5&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Claude Haiku 4.5&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;\$1&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;\$5&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;\$230&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;11×&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;HuggingFace/Novita&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;DeepSeek V4 Pro&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;\$1,67&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;\$3,38&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;\$374&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;19×&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;DeepSeek API (biasa)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;DeepSeek V4 Pro&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;\$1,74&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;\$3,48&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;\$390&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;20×&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Together AI&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;DeepSeek V4 Pro&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;\$2,10&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;\$4,40&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;\$470&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;23×&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;GPT 5.4&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;GPT 5.4&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;\$2,50&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;\$15&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;\$579&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;29×&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Claude Sonnet 4.6&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Claude Sonnet 4.6&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;\$3&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;\$15&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;\$689&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;34×&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Claude Opus 4.7&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Claude Opus 4.7&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;\$5&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;\$25&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;\$1 148&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;57×&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;GPT 5.5&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;GPT 5.5&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;\$5&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;\$30&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;\$1 157&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;58×&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultFontSize 11

title Biaya Bulanan yang Diproyeksikan — 220M Input + 1,9M Output
rectangle &amp;quot;Ollama Cloud Pro
~20 euro&amp;quot; as OC #4CAF50
rectangle &amp;quot;Gemini Flash
$69&amp;quot; as GF #8BC34A
rectangle &amp;quot;DeepSeek promo
\$98&amp;quot; as DSP #FFC107
rectangle &amp;quot;GPT Mini
\$174&amp;quot; as GM #FF9800
rectangle &amp;quot;Claude Haiku
$230&amp;quot; as CH #FF5722
rectangle &amp;quot;HF/Novita
\$374&amp;quot; as HF #F44336
rectangle &amp;quot;DeepSeek normal\n\$390&amp;quot; as DSN #F44336
rectangle &amp;quot;GPT 5.4\n\$579&amp;quot; as G4 #D32F2F
rectangle &amp;quot;Claude Sonnet
\$689&amp;quot; as CS #D32F2F
rectangle &amp;quot;Claude Opus\n\$1 148&amp;quot; as CO #B71C1C
rectangle &amp;quot;GPT 5.5
\$1 157&amp;quot; as G5 #880E4F

note bottom of OC
  DeepSeek V4 Pro 1.6T MoE
  + accès à tout le catalogue
  Ollama Cloud (Qwen, GLM, Kimi...)
  Inclus dans l&amp;#39;abonnement
end note
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_biaya_tersembunyi_prompt_engineering_dan_iterasi&amp;quot;&amp;gt;Biaya Tersembunyi: Prompt Engineering dan Iterasi&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Biaya token hanya bagian dari persamaan. Biaya yang sebenarnya adalah waktu pengembang.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Dengan OpenCode + Ollama Cloud Pro + agen governan saya (EAGER/LAZY/Hot/Warm/Cold, dijelaskan dalam &amp;lt;a href=&amp;quot;0108_gouvernance_agent_opencode_eager_lazy_post.html&amp;quot;&amp;gt;artikel tentang governan&amp;lt;/a&amp;gt;), saya dapat :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;0 prompt engineering berulang&amp;lt;/strong&amp;gt;: konteks disuntikan secara otomatis&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;90%+ benar pada percobaan pertama&amp;lt;/strong&amp;gt;: kode berhasil dikompilasi pada percobaan pertama&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Tidak ada halusinasi deepseek selama 8 sesi berturut-turut&amp;lt;/strong&amp;gt;(dokumentasi dalam &amp;lt;a href=&amp;quot;0112_comparaison_kimi_glm_deepseek_long_contexte_plugin_gradle_opencode_post.html&amp;quot;&amp;gt;perbandingan Kimi/GLM/DeepSeek&amp;lt;/a&amp;gt;)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Dengan Claude atau GPT &amp;lt;strong&amp;gt;tanpa&amp;lt;/strong&amp;gt; fine-tune maupun tata kelola :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;2-3 iterasi per tugas secara rata-rata&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Prompt engineering sistematik&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Halusinasi terhadap API Kotlin/Gradle ketika konteks melebihi 30K token&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Biaya « efektif » bulanan — tokens × iterasi × waktu koreksi — adalah 27× hingga 54× dari solusi saya, seperti yang saya demonstrasikan dalam &amp;lt;a href=&amp;quot;0122_ratio_efficacite_27x_45x_flotte_experts_ia_vs_claude_gpt_post.html&amp;quot;&amp;gt;artikel tentang rasio efektivitas&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;em&amp;gt;__&amp;lt;/em&amp;gt; Bukan hanya soal harga per token. Claude Opus dengan harga 57× yang memerlukan 3× lebih banyak iterasi, ini biaya efektif ~170× lebih tinggi. Untuk mengatakan dengan terang: membayar seorang magang dengan gaji seorang partner McKinsey. &amp;amp;lt;/think&amp;amp;gt; (empty)&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_menggabungkan_langganan_ollama_cloud_pro_metode_docker&amp;quot;&amp;gt;Menggabungkan Langganan Ollama Cloud Pro: Metode Docker&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Jika satu langganan tidak cukup, kita menggabungkannya. Prinsipnya adalah mengisolasi setiap langganan dalam instalasi Ollama masing-masing, dengan kunci SSH otentikasi masing-masing. Docker membuatnya trivial.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Metode lengkapnya dijelaskan dalam &amp;lt;a href=&amp;quot;0120_cumuler_deux_abonnements_ollama_pro_docker_post.html&amp;quot;&amp;gt;artikel tentang penumpukan Docker&amp;lt;/a&amp;gt;, ini prinsipnya:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultFontSize 12

title Architecture — Dua Langganan Ollama Cloud Pro pada satu mesin
left to right direction

node &amp;quot;Mesin Fisik Saya&amp;quot; {
  frame &amp;quot;Ollama Instance A (asli)\nport :11434&amp;quot; as Native {
    database &amp;quot;Identitas A
/usr/share/ollama/.ollama/id_ed25519&amp;quot;
  }
  frame &amp;quot;Ollama Instansi B (Docker)
port :11435&amp;quot; as Docker {
    database &amp;quot;Identitas B
~/ollama-b-data/id_ed25519&amp;quot;
  }
}

cloud &amp;quot;Ollama Cloud&amp;quot; {
  actor &amp;quot;Akun Pro A
(email 1)&amp;quot; as A
  actor &amp;quot;Akun Pro B\n(email 2)&amp;quot; as B
}

Native --&amp;amp;gt; A : &amp;quot;Kunci Perangkat A&amp;quot;
Docker --&amp;amp;gt; B : &amp;quot;Kunci Perangkat B&amp;quot;

note bottom of Docker
  Conteneur Docker isolé :
  - Clé SSH distincte
  - Port réseau distinct (11435)
  - Volume persistant (~/ollama-b-data)
  - restart: always
end note
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Pola itu sederhana:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;olist arabic&amp;quot;&amp;gt;
&amp;lt;ol class=&amp;quot;arabic&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Membuat volume`~/ollama-b-data`&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Menjalankan kontainer Docker`ollama/ollama:0.20.2`dipesetakan pada`11435:11434`&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Mengambil kunci SSH dari kontainer → menyimpannya di ollama.com&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Melakukan`ollama signin`dengan email kedua&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Mengkonfigurasi OpenCode dengan dua penyedia (`ollama`pada :11434,`ollama-b`pada :11435)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Dua akun, satu kartu biru, tanpa konflik.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_apa_yang_tidak_diukur_oleh_audit&amp;quot;&amp;gt;Apa yang Tidak Diukur oleh Audit&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;table class=&amp;quot;tableblock frame-all grid-all stretch&amp;quot;&amp;gt;
&amp;lt;colgroup&amp;gt;
&amp;lt;col style=&amp;quot;width: 40%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 60%;&amp;quot;&amp;gt;
&amp;lt;/colgroup&amp;gt;
&amp;lt;thead&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;area buta&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;Dampak&amp;lt;/th&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/thead&amp;gt;
&amp;lt;tbody&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;Kualitas kode yang dihasilkan&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;2,6 miliar token tidak memberitahu apakah kode dapat dikompilasi. Delapan sesi DeepSeek V4 Pro saya memiliki tingkat first-shot &amp;amp;gt;90%.&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;Ekosistem OpenCode&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;CLI gratis, tetapi nilai-nya (agen, sandbox, riwayat, caching prompt) sangat besar. Tidak termasuk dalam harga langganan — ini adalah alat terpisah.&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;Biaya peluang&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Menghabiskan 2 jam per hari untuk debug halusinasi dari model yang kurang unggul membiayai lebih mahal daripada 20€/bulan.&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;Kepiayaan hati&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Paket tetap = tidak ada kejutan. Dengan per-token, lonjakan aktivitas bisa sangat menyakitkan.&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;div class=&amp;quot;admonitionblock warning&amp;quot;&amp;gt;
&amp;lt;table&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;icon&amp;quot;&amp;gt;
&amp;lt;i class=&amp;quot;fa icon-warning&amp;quot; title=&amp;quot;⚠️&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Harga yang disebut adalah harga Mei 2026. Dalam enam bulan, semuanya akan menurun. Namun perbandingan kekuatan struktural — langganan tetap vs pembayaran per penggunaan — akan tetap menguntungkan untuk langganan bagi konsumen besar.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_kesimpulan_putusan_angka&amp;quot;&amp;gt;Kesimpulan : Putusan Angka&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Setelah 73 hari, 1 022 sesi OpenCode, dan 2,6 miliar token :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;table class=&amp;quot;tableblock frame-all grid-all stretch&amp;quot;&amp;gt;
&amp;lt;colgroup&amp;gt;
&amp;lt;col style=&amp;quot;width: 60%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 40%;&amp;quot;&amp;gt;
&amp;lt;/colgroup&amp;gt;
&amp;lt;thead&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;metrik&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;Putusan&amp;lt;/th&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/thead&amp;gt;
&amp;lt;tbody&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Biaya API eksternal kumulatif&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;$0.22&amp;lt;/strong&amp;gt;(hampir nol)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Biaya langganan Ollama Cloud Pro&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;~20 euro/bulan&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Setara Claude Opus&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;$1 148/bulan&amp;lt;/strong&amp;gt;— 57× lebih mahal&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Setara GPT 5.5&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;$1 157/bulan&amp;lt;/strong&amp;gt;— 58× lebih mahal&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Setara HuggingFace/Novita&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;\$374/bulan&amp;lt;/strong&amp;gt;— 19× lebih mahal&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Setara langsung dengan API DeepSeek&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;$390/bulan&amp;lt;/strong&amp;gt;— 20× lebih mahal&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Ekivalen termurah (Gemini Flash)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;$69/bulan&amp;lt;/strong&amp;gt;— 3,5× lebih mahal, model yang lebih rendah&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;Penawaran terbaik di pasar&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;Ollama Cloud Pro seharga 20€/bulan&amp;lt;/strong&amp;gt;— tak terkalahkan&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;(No content) Dengan biaya 20€ per bulan untuk mengakses DeepSeek V4 Pro (1,6T parameter, MoE, CSA+HCA, konteks 1M) melalui Ollama Cloud, saya memiliki nilai terbaik di pasar dunia. Bahkan penyedia China langsung 5× hingga 20× lebih mahal. Bahkan HuggingFace, dengan agregasinya dari penyedia low-cost, adalah 19× lebih mahal. Pasar Barat (Claude, GPT) berada pada angka 50-60× — sebuah planet lain.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Rasio ini sangat tidak seimbang sehingga tidak bisa bertahan selamanya. Nikmati selagi masih ada. &amp;amp;lt;/think&amp;amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Ya, 20 balles saya setiap bulan, ini adalah investasi teknis terbaik dalam hidup saya sebagai pengembang. Dan sekarang kamu memiliki angka untuk membuktikannya.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;hr&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_referensi&amp;quot;&amp;gt;Referensi&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;0108_gouvernance_agent_opencode_eager_lazy_post.html&amp;quot;&amp;gt;Pasal 0108 — Tata Kelola Agent EAGER/LAZY/Hot/Warm/Cold&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;0112_comparaison_kimi_glm_deepseek_long_contexte_plugin_gradle_opencode_post.html&amp;quot;&amp;gt;Artikel 0112 — DeepSeek V4 Pro vs Kimi K2.6 vs GLM 5.1&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;0119_benchmark_dgx_spark_vs_cloud_abonnement_llm_post.html&amp;quot;&amp;gt;Artikel 0119 — Perbandingan Benchmark DGX Spark vs Cloud vs Langganan LLM&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Article 0120 — Menggabungkan Dua Langganan Ollama Cloud Pro dengan Docker&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;0121_plugin_gradle_piloter_deux_instances_ollama_pro_post.html&amp;quot;&amp;gt;Article 0121 — Plugin Gradle pour Piloter Deux Instances Ollama&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;0122_ratio_efficacite_27x_45x_flotte_experts_ia_vs_claude_gpt_post.html&amp;quot;&amp;gt;Makalah 0122 — Rasio Efektivitas 27× sampai 54×&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_sumber_tarif&amp;quot;&amp;gt;Sumber Tarif&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://ollama.com/pricing&amp;quot;&amp;gt;Ollama Cloud — Tarif Pro \$20/bulan&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://api-docs.deepseek.com/quick_start/pricing&amp;quot;&amp;gt;DeepSeek API — Harga resmi V4 Pro&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://platform.kimi.com/docs/pricing/chat-k26&amp;quot;&amp;gt;Kimi Moonshot — Tarif K2.6&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://huggingface.co/inference/models&amp;quot;&amp;gt;HuggingFace Inference Providers — Tarif per provider&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://www.anthropic.com/pricing&amp;quot;&amp;gt;Anthropic — Biaya API Claude&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://openai.com/api/pricing/&amp;quot;&amp;gt;OpenAI — Harga GPT API&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://together.ai/pricing&amp;quot;&amp;gt;Together AI — Tarif inferensi&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://groq.com/pricing&amp;quot;&amp;gt;Groq — Tarif API&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://deepinfra.com/pricing&amp;quot;&amp;gt;DeepInfra — Harga GPU dan model&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://help.aliyun.com/zh/model-studio/text-generation-model/&amp;quot;&amp;gt;Alibaba Bailian — Model yang tersedia&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://github.com/anomalyco/opencode&amp;quot;&amp;gt;OpenCode — CLI open source di GitHub&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>Arsitektur « Plugin Independen + Akar Konsumen » : Mengapa Build Gradle Saya Menduplikat?</title>
            <link >https://cheroliv.com/blog/2026/0124_architecture_plugin_gradle_racine_consommateur_independant_post.html</link>
            <pubDate>Thu, 14 May 2026 00:00:00 +0000</pubDate>
            <guid isPermaLink="false">blog/2026/0124_architecture_plugin_gradle_racine_consommateur_independant_post.html</guid>
            <description>&amp;lt;div id=&amp;quot;toc&amp;quot; class=&amp;quot;toc&amp;quot;&amp;gt;
&amp;lt;div id=&amp;quot;toctitle&amp;quot;&amp;gt;Table des matières&amp;lt;/div&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel1&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_masalah_tiga_arsitektur_yang_menyita_waktu_saya&amp;quot;&amp;gt;Masalah: Tiga Arsitektur yang Menyita Waktu Saya&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_opsi_1_plugin_terisolasi_tidak_ada_dogfooding&amp;quot;&amp;gt;Opsi 1: Plugin Terisolasi (tidak ada dogfooding)&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_opsi_2_monorepo_klasik_includeplugin&amp;quot;&amp;gt;Opsi 2 : Monorepo Klasik (&amp;lt;code&amp;gt;include(&amp;quot;:plugin&amp;quot;)&amp;lt;/code&amp;gt;)&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_opsi_3_build_komposit_includebuild&amp;quot;&amp;gt;Opsi 3 : Build Komposit (&amp;lt;code&amp;gt;includeBuild()&amp;lt;/code&amp;gt;)&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_solusi_dua_build_independen_akar_konsumen&amp;quot;&amp;gt;Solusi: Dua Build Independen, Akar Konsumen&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_alur_kerja&amp;quot;&amp;gt;Alur Kerja&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_workflow_konkret&amp;quot;&amp;gt;Workflow konkret&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_mengapa_arsitektur_ini_menaklukkan_saya&amp;quot;&amp;gt;Mengapa arsitektur ini menaklukkan saya&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_dogfooding_asli_umpan_balik_instan&amp;quot;&amp;gt;Dogfooding Asli, Umpan Balik Instan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_2_kloning_tanpa_konfigurasi&amp;quot;&amp;gt;2. Kloning Tanpa Konfigurasi&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_3_builds_terisolasi_ci_mandiri&amp;quot;&amp;gt;3. Builds terisolasi, CI mandiri&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_anatomi_dari_subdirektori_name_plugin&amp;quot;&amp;gt;Anatomi dari Subdirektori `{name}-plugin/&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_katalog_versi_dua_file_terpisah&amp;quot;&amp;gt;Katalog Versi: Dua File Terpisah&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_apa_yang_tidak_kita_lakukan&amp;quot;&amp;gt;Apa yang tidak kita lakukan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_perbandingan_tiga_arsitektur_terhadap_dogfooding&amp;quot;&amp;gt;Perbandingan: Tiga Arsitektur Terhadap Dogfooding&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_kontrak_dag_build_akar_tidak_pernah_berasal_dari_subfolder&amp;quot;&amp;gt;Kontrak DAG: Build akar tidak pernah berasal dari subfolder&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_conclusion_duplikasi_tampak_yang_menghemat_waktu&amp;quot;&amp;gt;Conclusion : Duplikasi Tampak yang Menghemat Waktu&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_referensi&amp;quot;&amp;gt;Referensi&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div id=&amp;quot;preamble&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;waktu membaca : 11 minutes&amp;lt;/div&amp;gt;
&amp;lt;p&amp;gt;Anda mengklon repositori plugin Gradle. Anda membuka terminal. Apa yang Anda ketik? Selanjutnya?&amp;lt;code&amp;gt;./gradlew tasks&amp;lt;/code&amp;gt;, tentunya. Namun dalam folder mana? Akar? Sub-modul? Apakah harus membaca README terlebih dahulu untuk mengetahui cara membangun? Jika Anda ragu, bukankah dalam sekejap, arsitektur proyeknya rusak&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Ini adalah bagaimana saya sudah menyelesaikan masalah ini sekali untuk selamanya — dan mengapa ini pattern saat ini adalah tanda tangan semua plugin Gradle saya di`foundry/public/`.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_masalah_tiga_arsitektur_yang_menyita_waktu_saya&amp;quot;&amp;gt;Masalah: Tiga Arsitektur yang Menyita Waktu Saya&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Sebelum mencapai pola saat ini, saya mencoba-coba antara tiga pendekatan Klasik untuk mengatur plugin Gradle. Masing-masing memiliki kelemahan yang tidak dapat diterima.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_opsi_1_plugin_terisolasi_tidak_ada_dogfooding&amp;quot;&amp;gt;Opsi 1: Plugin Terisolasi (tidak ada dogfooding)&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Proyek hanya berisi plugin. Tidak ada contoh konsumsi. Tidak ada proyek yang mengerjakannya. Untuk mengujinya, harus membuat proyek eksternal, y mereferensikan plugin melalui`mavenLocal`atau satu build komposit, dan hanya periksa bahwa itu berfungsi&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-shell hljs&amp;quot; data-lang=&amp;quot;shell&amp;quot;&amp;gt;$ git clone mon-plugin
$ cd mon-plugin
$ ./gradlew build          # le plugin compile
$ # ... et maintenant ? comment je l&amp;#39;essaie ?&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;admonitionblock warning&amp;quot;&amp;gt;
&amp;lt;table&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;icon&amp;quot;&amp;gt;
&amp;lt;i class=&amp;quot;fa icon-warning&amp;quot; title=&amp;quot;⚠️&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Plugin Gradle tanpa contoh konsumsi adalah sebuah pustaka tanpa tes integrasi. Anda tidak pernah tahu jika perubahan terakhir a merusak pengalaman pengguna&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_opsi_2_monorepo_klasik_includeplugin&amp;quot;&amp;gt;Opsi 2 : Monorepo Klasik (&amp;lt;code&amp;gt;include(&amp;quot;:plugin&amp;quot;)&amp;lt;/code&amp;gt;)&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Gradle`init`menghasilkan`settings.gradle.kts`dengan`include(&amp;quot;plugin&amp;quot;)`. Root dan sub-modul membagikan daemon yang sama, konfigurasi yang sama, katalog yang sama. Praktis, tetapi terhubung.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-text hljs&amp;quot; data-lang=&amp;quot;text&amp;quot;&amp;gt;.
├── settings.gradle.kts   → include(&amp;quot;plugin&amp;quot;)
├── build.gradle.kts      → plugins { id(&amp;quot;mon-plugin&amp;quot;) }
├── plugin/
│   └── build.gradle.kts  → java-gradle-plugin
└── gradle/
    └── libs.versions.toml&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Yang menyangkut:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;akar&amp;lt;strong&amp;gt;harus&amp;lt;/strong&amp;gt;memiliki versi Gradle yang sama dengan sub-modul&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;`libs.versions.toml`dibagikan — katalog versi umum, dependensi&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;yang melarikan diri dari satu modul ke modul lain&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Tidak dapat membangun plugin secara independen dari root.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;CI harus membangun kedua modul, meski hanya plugin yang berubah.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_opsi_3_build_komposit_includebuild&amp;quot;&amp;gt;Opsi 3 : Build Komposit (&amp;lt;code&amp;gt;includeBuild()&amp;lt;/code&amp;gt;)&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Kami memisahkan keduanya menjadi build Gradle terpisah dan kami menghubungkannya melalui &amp;lt;code&amp;gt;includeBuild(&amp;quot;mon-plugin&amp;quot;)`dalam`settings.gradle.kts&amp;lt;/code&amp;gt;.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Ini sudah lebih baik. Build plugin terisolasi. Tapi konsumen harus secara eksplisit merujuk pada build eksternal — dan output dari `./gradlew tasks`pada akar bergantung pada konfigurasi yang benar dari komposit. Kloning bukan zero-config : Anda harus mengetahui bahwa plugin berada di folder terpisah, yang direferensi oleh root, etc.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-shell hljs&amp;quot; data-lang=&amp;quot;shell&amp;quot;&amp;gt;.
├── settings.gradle.kts   → includeBuild(&amp;quot;plugin-build/&amp;quot;)
├── build.gradle.kts      → plugins { id(&amp;quot;mon-plugin&amp;quot;) }
└── plugin-build/
    ├── settings.gradle.kts
    └── build.gradle.kts&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Saya mencari yang lebih baik. Lebih baik lagi.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_solusi_dua_build_independen_akar_konsumen&amp;quot;&amp;gt;Solusi: Dua Build Independen, Akar Konsumen&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Berikut adalah pola yang akhirnya saya mengadopsi:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-text hljs&amp;quot; data-lang=&amp;quot;text&amp;quot;&amp;gt;.
├── settings.gradle.kts          ← racine consommateur
├── build.gradle.kts              ← 3 lignes : apply plugin + dogfood
├── gradle/
│   └── libs.versions.toml        ← catalogue du consommateur
├── {name}-plugin/                ← BUILD INDÉPENDANT
│   ├── gradlew                    ← son propre wrapper
│   ├── settings.gradle.kts        ← rootProject.name = &amp;quot;{name}-plugin&amp;quot;
│   ├── build.gradle.kts           ← java-gradle-plugin, signing, publish
│   ├── gradle/
│   │   ├── libs.versions.toml     ← catalogue du plugin
│   │   └── wrapper/
│   ├── src/                       ← sources du plugin
│   ├── .agents/                   ← gouvernance agent
│   └── *.adoc                     ← AGENT, PROMPT_REPRISE, snapshot, etc.
└── site.yml / slides-context.yml / ...   ← configs dogfood&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;admonitionblock important&amp;quot;&amp;gt;
&amp;lt;table&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;icon&amp;quot;&amp;gt;
&amp;lt;i class=&amp;quot;fa icon-important&amp;quot; title=&amp;quot;❗&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Kunci :`{name}-plugin/`adalah sebuah proyek Gradle yang lengkap dan mandiri Dia memiliki wrapper sendiri, settings sendiri, katalog de versi. Ia diklon, dibuild, ditest, dan dipublikasikan tanpa akar tahu akan keberadaannya&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Root, hanya &amp;lt;strong&amp;gt;satu&amp;lt;/strong&amp;gt; hal: menerapkan plugin.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;plugins {
    alias(libs.plugins.bakery)
}

repositories {
    mavenLocal()
    mavenCentral()
}

bakery { configPath = file(&amp;quot;site.yml&amp;quot;).absolutePath }&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Tiga baris dalam kasus`bakery-gradle`. Tidak ada lagi. Nol`include()&amp;lt;code&amp;gt;, nol`includeBuild()&amp;lt;/code&amp;gt;, nol sub-proyek. Sebuah build Gradle klasik yang menerapkan plugin seperti apa pun Konsumen mana yang akan melakukannya?&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_alur_kerja&amp;quot;&amp;gt;Alur Kerja&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultFontSize 12

left to right direction

package &amp;quot;RACINE (Konsumen)&amp;quot; #CCFFCC {
    usecase &amp;quot;Klona repositori&amp;quot; as Clone
    usecase &amp;quot;./gradlew tasks&amp;quot; as Tasks
    usecase &amp;quot;./gradlew bake&amp;quot; as Dogfood
    note bottom of Dogfood
        Exerce le plugin
        Feedback immédiat
        Zéro config
    end note
}

package &amp;quot;{name}-plugin/ (Build Mandiri)&amp;quot; #CCE5FF {
    usecase &amp;quot;./gradlew build&amp;quot; as BuildPlugin
    usecase &amp;quot;./gradlew publishToMavenLocal&amp;quot; as MavenLocal
    usecase &amp;quot;./gradlew test&amp;quot; as TestPlugin
    note bottom of BuildPlugin
        Cycle de vie isolé
        Tests unitaires + Cucumber
        CI dédiée possible
    end note
}

Clone -down-&amp;amp;gt; Tasks
Tasks -down-&amp;amp;gt; Dogfood
Dogfood ..&amp;amp;gt; MavenLocal : &amp;quot;bergantung pada plugin
diterbitkan secara lokal&amp;quot;
BuildPlugin -up-&amp;amp;gt; MavenLocal
TestPlugin -up-&amp;amp;gt; BuildPlugin

@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_workflow_konkret&amp;quot;&amp;gt;Workflow konkret&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-shell hljs&amp;quot; data-lang=&amp;quot;shell&amp;quot;&amp;gt;# 1. Builder le plugin
$ cd codebase-plugin
$ ./gradlew publishToMavenLocal

# 2. L&amp;#39;exercer depuis la racine
$ cd ..
$ ./gradlew indexCodebase queryCodebase snapshot

# La boucle est fermée. Le plugin est testé dans des conditions
# réelles de consommation, par le projet même qui l&amp;#39;héberge.&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_mengapa_arsitektur_ini_menaklukkan_saya&amp;quot;&amp;gt;Mengapa arsitektur ini menaklukkan saya&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Tiga manfaat yang, ketika digabungkan, setara dengan biaya « duplication» :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_dogfooding_asli_umpan_balik_instan&amp;quot;&amp;gt;Dogfooding Asli, Umpan Balik Instan&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Uji terbaik sebuah plugin Gradle adalah menggunakannya. Bukan sebuah unit test yang dimock. Bukan sebuah`GradleRunner`dengan sebuah proyek uji. Sebuah build &amp;lt;strong&amp;gt;nyata&amp;lt;/strong&amp;gt; yang menerapkan plugin pada file yang nyata.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-shell hljs&amp;quot; data-lang=&amp;quot;shell&amp;quot;&amp;gt;$ git clone bakery-gradle
$ cd bakery-gradle
$ ./gradlew bake    # ← le plugin est exercé immédiatement&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Jika plugin rusak, build root memberitahu. Tidak perlu pergi Mencari proyek pengujian eksternal. Dogfooding adalah &amp;lt;strong&amp;gt;pertama&amp;lt;/strong&amp;gt;. Tugas yang diluncurkan oleh kontributor baru. Ini adalah smoke test muktamad.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;admonitionblock tip&amp;quot;&amp;gt;
&amp;lt;table&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;icon&amp;quot;&amp;gt;
&amp;lt;i class=&amp;quot;fa icon-tip&amp;quot; title=&amp;quot;💡&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Aturan ini sederhana: jika akar tersebut dikompilasi dan`./gradlew tasks` Menampilkan tugas plugin Anda, plugin berfungsi. Tidak ada kejutan. dalam produksi.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_2_kloning_tanpa_konfigurasi&amp;quot;&amp;gt;2. Kloning Tanpa Konfigurasi&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Un `git clone &amp;amp;amp;&amp;amp;amp; ./gradlew tasks`dan orang baru melihat semuanya berjalan tanpa mengonfigurasi apa pun. Yang`build.gradle.kts`akar adalah dokumentasi hidup penggunaan plugin. Yang`site.yml`di samping menunjukkan konfigurasi yang diharapkan.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Bandingkan dengan alternatif: sebuah README dengan tiga paragraf yang jelaskan bagaimana builder plugin dan bagaimana builder proyek dari uji. Seorang kontributor baru membaca README secara diagonal, salah, membuka sebuah isu — padahal informasi tersebut mungkin adalah &amp;lt;strong&amp;gt;dapat dieksekusi&amp;lt;/strong&amp;gt;.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;admonitionblock important&amp;quot;&amp;gt;
&amp;lt;table&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;icon&amp;quot;&amp;gt;
&amp;lt;i class=&amp;quot;fa icon-important&amp;quot; title=&amp;quot;❗&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Documentation yang paling kuat bukan yang dibaca. Itu adalah yang kita&amp;lt;strong&amp;gt;Menjalankan&amp;lt;/strong&amp;gt;. Build akar adalah dokumentasi file eksekusi plugin.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_3_builds_terisolasi_ci_mandiri&amp;quot;&amp;gt;3. Builds terisolasi, CI mandiri&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Plugin memiliki wrapper Gradle sendiri, siklus hidupnya sendiri, Tes-tes pribadinya. Anda dapat:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Mengupgrade Gradle di plugin tanpa menyentuh root&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Menambahkan dependensi di dalam plugin tanpa bocor ke root&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Menghancurkan plugin tanpa memengaruhi build dasar (selama Anda tidak&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;jangan publikasikan versi yang rusak)&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Memiliki sebuah CI yang membangun dan menguji plugin, dan yang lain yang menjalankan&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;root — secara independen&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-text hljs&amp;quot; data-lang=&amp;quot;text&amp;quot;&amp;gt;.github/workflows/
├── test-plugin.yml      → codebase-plugin/.gradlew build
├── test-root.yml        → .gradlew tasks (vérifie que le plugin est consommable)
└── publish.yml          → codebase-plugin/.gradlew publish&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_anatomi_dari_subdirektori_name_plugin&amp;quot;&amp;gt;Anatomi dari Subdirektori `{name}-plugin/&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Mari kita lihat lebih dekat apa yang ada di folder plugin :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-text hljs&amp;quot; data-lang=&amp;quot;text&amp;quot;&amp;gt;codebase-plugin/
├── gradlew                           ← wrapper indépendant
├── settings.gradle.kts               ← @Suppress(&amp;quot;UnstableApiUsage&amp;quot;)
│                                       + foobar-resolver-convention
├── build.gradle.kts                  ← java-gradle-plugin + signing + publish
├── gradle/
│   ├── libs.versions.toml            ← catalogue complet (langchain4j, pgvector...)
│   └── wrapper/
├── buildSrc/                         ← classes utilitaires buildSrc
│   ├── build.gradle.kts
│   └── src/main/kotlin/
│       ├── codebase/                 ← CodebaseYmlAnonymizer, CodebaseConfiguration
│       ├── benchmark/                ← BenchmarkConfig, BenchmarkProtocol
│       ├── readme/                   ← ReadmeYmlAnonymizer
│       ├── site/                     ← SiteYmlAnonymizer
│       ├── slider/                   ← SliderYmlAnonymizer
│       └── snapshot/                 ← SnapshotManager
├── src/
│   ├── main/kotlin/codebase/
│   │   ├── CodebasePlugin.kt         ← class Plugin&amp;amp;lt;Project&amp;amp;gt;
│   │   ├── rag/                      ← pgvector, embedding, anonymization...
│   │   ├── benchmark/                ← BenchmarkRunner, export, comparison...
│   │   └── walker/                   ← WorkspaceWalker
│   └── test/
│       ├── kotlin/codebase/scenarios/ ← steps Cucumber
│       ├── features/                  ← .feature files
│       └── resources/datasets/        ← fixtures .adoc, .yml, .json
├── .agents/                          ← gouvernance agent (INDEX, SESSIONS, etc.)
├── AGENT.adoc                        ← règles agent
├── PROMPT_REPRISE.adoc              ← mission session
├── BACKLOG.adoc                      ← backlog produit
├── snapshot.adoc                     ← snapshot auto-généré du projet
└── embeds.yml                        ← config RAG embeds&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Semua ada di sini. Tidak ada dispersi antara akar dan sub-direktori. Pengembang yang bekerja pada plugin tidak pernah membutuhkan meninggalkan`codebase-plugin/`. Pengembang yang menggunakan plugin hanya melihat akar — dan`build.gradle.kts`dari 3 baris Dia memberitahunya semua yang perlu diketahui.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_katalog_versi_dua_file_terpisah&amp;quot;&amp;gt;Katalog Versi: Dua File Terpisah&amp;lt;/h3&amp;gt;
&amp;lt;table class=&amp;quot;tableblock frame-all grid-all stretch&amp;quot;&amp;gt;
&amp;lt;colgroup&amp;gt;
&amp;lt;col style=&amp;quot;width: 25%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 37.5%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 37.5%;&amp;quot;&amp;gt;
&amp;lt;/colgroup&amp;gt;
&amp;lt;tbody&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;gradle/libs.versions.toml&amp;lt;/code&amp;gt;(akar)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;{name}-plugin/gradle/libs.versions.toml&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Jumlah dependensi&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;2-3 (plugin + readme opsional)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;30+ (langchain4j, pgvector, cucumber&amp;amp;#8230;&amp;amp;#8203;)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;peran&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Mengonsumi plugin&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Bangun plugin&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Siapa yang membacanya?&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Pengguna plugin&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Pengembang plugin&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;root memiliki katalog yang secara sengaja minimal. plugin memiliki katalog Lengkap. Kebingungan tidak mungkin: setiap build memiliki ruang lingkupnya sendiri. ketergantungan.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;admonitionblock note&amp;quot;&amp;gt;
&amp;lt;table&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;icon&amp;quot;&amp;gt;
&amp;lt;i class=&amp;quot;fa icon-note&amp;quot; title=&amp;quot;ℹ️&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Jika Anda telah menghabiskan satu jam untuk men-debug tabrakan dependensi antara plugin Anda dan proyek pengujian Anda, Anda memahami nilai dari ini pemisahan. Katalog independen menghilangkan masalah ini dari konstruksi.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_apa_yang_tidak_kita_lakukan&amp;quot;&amp;gt;Apa yang tidak kita lakukan&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Pola ini bukan ajaib. Ia mengimpos sebuah batas yang saya mengakibatkan saya dengan sukarela:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Root tidak membangun plugin.&amp;lt;/strong&amp;gt; Anda harus`publishToMavenLocal` ou men-deploy pada repositori sebelum root dapat menggunakannya&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Ini adalah biaya kecil, dan ini adalah kontrain yang &amp;lt;strong&amp;gt;baik&amp;lt;/strong&amp;gt;. Root Menggunakan plugin sebagai klien eksternal — melalui Maven. Tepat seperti yang dilakukan oleh proyek pihak ketiga. Jika plugin tidak dapat dipublikasikan, root memberitahu Anda itu segera.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-shell hljs&amp;quot; data-lang=&amp;quot;shell&amp;quot;&amp;gt;# La seule &amp;quot;friction&amp;quot; du pattern
$ cd codebase-plugin &amp;amp;amp;&amp;amp;amp; ./gradlew publishToMavenLocal &amp;amp;amp;&amp;amp;amp; cd ..
$ ./gradlew tasks --group=codebase&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_perbandingan_tiga_arsitektur_terhadap_dogfooding&amp;quot;&amp;gt;Perbandingan: Tiga Arsitektur Terhadap Dogfooding&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;table class=&amp;quot;tableblock frame-all grid-all stretch&amp;quot;&amp;gt;
&amp;lt;colgroup&amp;gt;
&amp;lt;col style=&amp;quot;width: 20%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 20%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 20%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 20%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 20%;&amp;quot;&amp;gt;
&amp;lt;/colgroup&amp;gt;
&amp;lt;tbody&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Plugin terisolasi&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;repositori tunggal`include()`&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;komposit`includeBuild()`&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;Akar + plugin independen&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;`git clone &amp;amp;amp;&amp;amp;amp; gradlew tasks`memberikan tugas plugin&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;❌&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;✅&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;✅&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;✅&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Bangun plugin independen dari root&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;✅&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;❌&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;✅&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;✅&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Katalog versi terpisah&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;✅&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;❌&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;✅&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;✅&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Tidak`include()` ni &amp;lt;code&amp;gt;includeBuild()&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;✅&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;❌&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;❌&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;✅&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Dogfood native tanpa konfigurasi&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;❌&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;✅&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Terjemahkan teks berikut dari Prancis ke Indonesia, dengan menjaga semua rentang kode yang dikutip dengan backtick (&amp;lt;code&amp;gt;&amp;amp;#8230;&amp;amp;#8203;&amp;lt;/code&amp;gt;) tanpa perubahan apa pun — isi, spasi, dan posisinya harus tetap utuh. Terjemahkan hanya fragmen yang diberikan tanpa menambah konteks. Keluarkan hanya hasil terjemahan, tanpa penjelasan, komentar, atau teks tambahan.&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;✅&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;CI plugin independen&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;✅&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;❌&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;✅&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;(Empty)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Kolom kanan mencentang semua kotak. Itu sebabnya saya tidak Saya akan kembali lebih ke belakang.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_kontrak_dag_build_akar_tidak_pernah_berasal_dari_subfolder&amp;quot;&amp;gt;Kontrak DAG: Build akar tidak pernah berasal dari subfolder&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Arsitektur ini terintegrasi secara alami dalam DAG N0→N3 dari workspace saya. Pola adalah: * plugin N2 adalah hub dari ketergantungannya sendiri, akar N3 adalah sebuah terminal yang menerapkan hubs*&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultFontSize 11

title Kontrak DAG — Konsumen Root vs Plugin Independen
package &amp;quot;N3 — AKAR (setoran)&amp;quot; #FFCCCC {
    [build.gradle.kts (3 lignes)] as ROOT
    [libs.versions.toml (minimal)] as ROOT_TOML
    note right of ROOT
        plugins { bakery }
        bakery { configPath = ... }
        Aucune logique métier
    end note
}

package &amp;quot;N2 — {name}-plugin/&amp;quot; #CCFFCC {
    [build.gradle.kts (complet)] as PLUGIN
    [src/main/kotlin/] as SRC
    [gradle/libs.versions.toml] as PLUGIN_TOML
    [buildSrc/] as BUILDSRC
    note bottom of PLUGIN
        java-gradle-plugin
        signing + publish
        Tests unitaires + Cucumber
        CI dédiée
    end note
}

ROOT_TOML --&amp;amp;gt; ROOT : &amp;quot;versi plugin&amp;quot;
ROOT -down-&amp;amp;gt; PLUGIN : &amp;quot;Publikasikan ke repositori Maven lokal&amp;quot;
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Le &amp;lt;code&amp;gt;codebase-gradle/build.gradle.kts`buat 6 baris. Tidak ada`src/&amp;lt;/code&amp;gt;, tidak ada`buildSrc/&amp;lt;code&amp;gt;, tidak ada`gradle/rag-bench.gradle.kts&amp;lt;/code&amp;gt;. hanya &amp;lt;code&amp;gt;plugins { alias(libs.plugins.codebase) }`dan repositori. Semua kompleksitas berada di`codebase-plugin/&amp;lt;/code&amp;gt;.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_conclusion_duplikasi_tampak_yang_menghemat_waktu&amp;quot;&amp;gt;Conclusion : Duplikasi Tampak yang Menghemat Waktu&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Saat saya menunjukkan struktur ini kepada seseorang, reaksi pertama sering : « Tapi kamu memiliki dua`gradlew`, dua`settings.gradle.kts`, dua`libs.versions.toml`— ini adalah duplikasi!&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Ya. Dan tidak.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Duplikasi » berarti mereproduksi informasi yang sama di dua tempat. Di sini, ini adalah dua file &amp;lt;strong&amp;gt;distincts&amp;lt;/strong&amp;gt; yang melayani dua kegunaan &amp;lt;strong&amp;gt;distincts&amp;lt;/strong&amp;gt; : katalog plugin (30+ dependensi untuk builder) dan katalog dari akar (2-3 dependensi untuk dikonsumsi). wrapper plugin (versi terkunci untuk pengembangan) dan wrapper dari akar (versi yang berpotensi berbeda, untuk latihan plugin).&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Bukan duplikasi. Ini &amp;lt;strong&amp;gt;pemisahan tanggung jawab&amp;lt;/strong&amp;gt; diterapkan ke sistem build. Setiap build Melakukan satu hal, dan hanya satu. Akar mengkonsumsi. Plugin membangun.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Biaya? Satu pesanan`publishToMavenLocal`antara build plugin dan pembangunan akar. Keuntungan? Kejelasan arsitektur yang menghemat jam-jam waktu debugging di hilir&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Sejak saya men-deploy pola ini di`bakery-gradle`, &amp;lt;code&amp;gt;plantuml-gradle&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;codebase-gradle&amp;lt;/code&amp;gt;, dan plugins lain`foundry/public/&amp;lt;code&amp;gt;, saya tidak punya Tidak pernah lagi ragu ketika membuka terminal di salah satu repositori saya. Yang refleks pertama —&amp;lt;/code&amp;gt;./gradlew tasks`— selalu berjalan, selalu memberi tugas yang baik, dan memberitahu saya secara instan jika semuanya sehat.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Ini dia, arsitektur yang baik. Dia tidak dapat dibaca dalam README. Dia diuji dalam terminal, dalam kurang dari sepuluh detik.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_referensi&amp;quot;&amp;gt;Referensi&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Bagian 0105 — Mengintegrasikan Graphify dalam alur kerja Gradle&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Artikel 0117 — Granularisasi OSS/CSS&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;0123_knowledge_graph_moteur_recommandation_articles_connexes_post.html&amp;quot;&amp;gt;Artikel 0123 — Knowledge Graph sebagai mesin rekomendasi&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>Knowledge Graph sebagai mesin rekomendasi: bagaimana saya membunuh artikel terkait berurutan</title>
            <link >https://cheroliv.com/blog/2026/0123_knowledge_graph_moteur_recommandation_articles_connexes_post.html</link>
            <pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate>
            <guid isPermaLink="false">blog/2026/0123_knowledge_graph_moteur_recommandation_articles_connexes_post.html</guid>
            <description>&amp;lt;div id=&amp;quot;toc&amp;quot; class=&amp;quot;toc&amp;quot;&amp;gt;
&amp;lt;div id=&amp;quot;toctitle&amp;quot;&amp;gt;Table des matières&amp;lt;/div&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel1&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_panggung_selasa_12_mei_1730&amp;quot;&amp;gt;Panggung: Selasa, 12 Mei, 17:30&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_diagnosa_tiga_jenis_hubungan_yang_tidak_terlihat_oleh_template&amp;quot;&amp;gt;Diagnosa: Tiga Jenis Hubungan yang tidak terlihat oleh template&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_solusi_sebuah_pipeline_gradle_bukan_panggilan_llm_panas&amp;quot;&amp;gt;Solusi : Sebuah Pipeline Gradle, Bukan Panggilan LLM Panas&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_arsitektur_bakery_mengimpor_graphify_bukan_engine&amp;quot;&amp;gt;Arsitektur : Bakery Mengimpor Graphify, Bukan Engine&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_pipeline_scan_grafik_template&amp;quot;&amp;gt;Pipeline: Scan → Grafik → Template&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_fallback_kronologis&amp;quot;&amp;gt;Fallback Kronologis&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_ontologi_yang_emergent_ketika_artikel_berkelompok_tanpa_saling_mengenal&amp;quot;&amp;gt;Ontologi yang Emergent: Ketika Artikel Berkelompok Tanpa Saling Mengenal&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_kontrak_dag_siapa_yang_penting_bagi_siapa&amp;quot;&amp;gt;Kontrak DAG: Siapa yang penting bagi siapa&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_apa_yang_kita_dapatkan_dan_apa_yang_kita_tidak_dapatkan&amp;quot;&amp;gt;Apa yang Kita Dapatkan (Dan Apa yang Kita Tidak Dapatkan)&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_perspektif_blog_sebagai_knowledge_graph_publik&amp;quot;&amp;gt;Perspektif: Blog sebagai Knowledge Graph Publik&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_kesimpulan_sistem_file_tahu_yang_template_abaikan&amp;quot;&amp;gt;Kesimpulan: sistem file tahu yang template abaikan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_referensi&amp;quot;&amp;gt;referensi&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div id=&amp;quot;preamble&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;waktu membaca : 14 minutes&amp;lt;/div&amp;gt;
&amp;lt;p&amp;gt;Anda sedang membaca sebuah artikel tentang integrasi pgvector dengan LangChain4j. Di bagian bawah halaman, JBake Anda disarankan « Artikel terkait ». Anda mengklik. Ini adalah artikel tentang&amp;amp;#8230;&amp;amp;#8203; konfigurasi. dari Kitty Terminal. Yang satu-satunya titik persamaan di antara keduanya? Mereka telah diterbitkan kurang dari Selisih sebelas hari. Ini bukan rekomendasi. Ini adalah kalender yang disamarkan sebagai editorial.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_panggung_selasa_12_mei_1730&amp;quot;&amp;gt;Panggung: Selasa, 12 Mei, 17:30&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Saya membaca kembali salah satu artikel saya — &amp;lt;a href=&amp;quot;0106_knowledge_graph_outil_compréhension_codebase_post.html&amp;quot;&amp;gt;itu tentang Knowledge Graph sebagai alat untuk memahami codebase&amp;lt;/a&amp;gt; Konten tersebut padat: node, edge, komunitas, PlantUML, onboarding. Sebuah artikel teknik membaca 15 menit yang menggerakkan`graphify-gradle`, &amp;lt;code&amp;gt;plantuml-gradle&amp;lt;/code&amp;gt;, dan konsep topologi grafik.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Di bagian bawah halaman, artikel terkait menyarankan kepada saya:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;olist arabic&amp;quot;&amp;gt;
&amp;lt;ol class=&amp;quot;arabic&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Sebuah artikel tentang Firebase Contact Form (0113)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Sebuah artikel tentang mekanisme Eager/Lazy (0108)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Sebuah artikel tentang migrasi skrip Gradle ke plugin (0102)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Sebuah artikel tentang akumulasi langganan Ollama Pro (0120)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Tiga dari empat artikel ini tidak memiliki &amp;lt;strong&amp;gt;tidak ada&amp;lt;/strong&amp;gt; hubungan dengan Knowledge Graph. Mereka ada karena mereka adalah yang terakhir dipublikasikan — kedekatan temporal, tidak lingkungan semantik&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Saya melihat template`post.thyme`:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-xhtml hljs&amp;quot; data-lang=&amp;quot;xhtml&amp;quot;&amp;gt;&amp;amp;lt;!-- Articles connexes (liens internes SEO) --&amp;amp;gt;
&amp;amp;lt;th:block th:each=&amp;quot;post,postStat : ${published_posts}&amp;quot;&amp;amp;gt;
    &amp;amp;lt;th:block th:if=&amp;quot;${!post.uri.equals(content.uri) and postStat.index lt 4}&amp;quot;&amp;amp;gt;
        ...
    &amp;amp;lt;/th:block&amp;amp;gt;
&amp;amp;lt;/th:block&amp;amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;`published_posts`adalah sebuah daftar kronologis.`postStat.index lt 4`mengambil empat pertama yang bukan post saat ini. Itu saja. Tidak ada logika de kemiripan. Tidak ada konsep konten. Hanya sebuah lingkaran`for`bersamaran sebagai rekomendasi.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;admonitionblock warning&amp;quot;&amp;gt;
&amp;lt;table&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;icon&amp;quot;&amp;gt;
&amp;lt;i class=&amp;quot;fa icon-warning&amp;quot; title=&amp;quot;⚠️&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Ini bukan bug dari JBake. Ini adalah perilaku default dari semua generator situs statis: daftar postingan datar dan diurutkan berdasarkan tanggal. Template melakukan apa yang dapat dilakukan dengan apa yang dimilikinya.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Tapi itu bukan alasan untuk menerimanya.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_diagnosa_tiga_jenis_hubungan_yang_tidak_terlihat_oleh_template&amp;quot;&amp;gt;Diagnosa: Tiga Jenis Hubungan yang tidak terlihat oleh template&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Korpus artikel saya — 23 posting dalam 4 bulan — memiliki struktur kaya yang template kronologis sepenuhnya mengalahkan :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;olist arabic&amp;quot;&amp;gt;
&amp;lt;ol class=&amp;quot;arabic&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Referensi eksplisit&amp;lt;/strong&amp;gt; : saya menggunakan`xref:`secara masif dalam artikel-artikel saya.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Artikel 0122 mereferensikan 0106 (knowledge graph) dan 0116 (kompartimentasi) epistemik). Ini adalah &amp;lt;strong&amp;gt;hard linking&amp;lt;/strong&amp;gt; redaksi — saya sengaja memutuskan untuk menghubungkan konsep-konsep ini.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;olist arabic&amp;quot;&amp;gt;
&amp;lt;ol class=&amp;quot;arabic&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Tag berbagi&amp;lt;/strong&amp;gt; : setiap artikel memiliki beberapa`:jbake-tags:`. Artikel tentang&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Graphify + PlantUML (0105) memiliki`gradle, graphify, plantuml, knowledge-graph`. Artikel tentang Knowledge Graph (0106) memiliki &amp;lt;code&amp;gt;knowledge-graph, graphify, plantuml&amp;lt;/code&amp;gt;. Tiga tag bersama dari tujuh — 43% kesamaan.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;olist arabic&amp;quot;&amp;gt;
&amp;lt;ol class=&amp;quot;arabic&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Co-occurring named entities&amp;lt;/strong&amp;gt; : « pgvector », « RAG », « embedding&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;muncul bersama dalam empat artikel yang berbeda. « LangChain4j »,  Ollama », « plugin Gradle » dalam enam lainnya. Bukan tag yang dideklarasikan — ini adalah &amp;lt;strong&amp;gt;pola muncul&amp;lt;/strong&amp;gt; dari korpus yang hanya sebuah NLP dapat mendeteksi.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultTextAlignment center

title Tiga lapisan hubungan antara artikel
package &amp;quot;Lapisan 1 — Referensi eksplisit&amp;quot; #CCFFCC {
    [Article A] as A1
    [Article B] as B1
    A1 -down-&amp;amp;gt; B1 : xref:
    note right of A1
        Liens durs
        Déterministes
        Déjà dans graph.json
    end note
}

package &amp;quot;Lapisan 2 — Tags dibagikan&amp;quot; #FFFFCC {
    [Article A] as A2
    [Article C] as C2
    A2 ..&amp;amp;gt; C2 : tag_cooccurrence
    note right of A2
        Poids = tags communs / tags totaux
        Déterministes
        À extraire via graphify
    end note
}

package &amp;quot;Lapisan 3 — Ontologi emergens&amp;quot; #FFCCCC {
    [Corpus A] as A3
    [Corpus B] as B3
    A3 ..&amp;amp;gt; B3 : entity_overlap
    note right of A3
        Clusters NLP
        Co-occurrences d&amp;#39;entités nommées
        TF-IDF / cosine similarity
    end note
}

@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Hari ini, situs saya tidak menggunakan &amp;lt;strong&amp;gt;satu pun&amp;lt;/strong&amp;gt; dari ketiga lapisan ini. Ia menggunakan lapisan nol: urutan pemasukan dalam daftar Java.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_solusi_sebuah_pipeline_gradle_bukan_panggilan_llm_panas&amp;quot;&amp;gt;Solusi : Sebuah Pipeline Gradle, Bukan Panggilan LLM Panas&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Accuan pertama akan memanggil LLM saat proses bake: « Untuk artikel ini, menemukan tiga artikel yang paling serupa dalam korpus. Jangan lakukan itu.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;admonitionblock important&amp;quot;&amp;gt;
&amp;lt;table&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;icon&amp;quot;&amp;gt;
&amp;lt;i class=&amp;quot;fa icon-important&amp;quot; title=&amp;quot;❗&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Satu panggilan LLM untuk setiap`./gradlew bake`Memakan waktu, uang, dan Memperkenalkan non-determinisme dalam build Anda. Respons dari LLM bisa beralih antara dua build tanpa mengubah konten. CI Anda menjadi tidak dapat direproduksi, tes Anda menjadi tidak stabil.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Solusinya deterministik: sebuah pipeline Gradle yang pra-kalkulasi grafik. kesamaan dan menyimpannya di`graph.json`. template membaca hasil — dia tidak pernah memicu sebuah perhitungan.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_arsitektur_bakery_mengimpor_graphify_bukan_engine&amp;quot;&amp;gt;Arsitektur : Bakery Mengimpor Graphify, Bukan Engine&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Ini adalah titik arsitektur yang paling penting. Godaan akan menjadi mengkabel kolaborasi di`engine/build.gradle.kts`mesin mengaplikasikan graphify untuk pemindaian, bakery untuk memanggang, dan engine menjembatani antara kedua&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Ini adalah anti-pola. Engine adalah &amp;lt;strong&amp;gt;terminal konsumen&amp;lt;/strong&amp;gt; — menerapkan plugin, dia tidak mengimplementasikan logika bisnis. Aturan adalah : &amp;lt;strong&amp;gt;Beban bukti berada pada plugin bersifat proprietary.&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;Arsitektur yang tepat — Bakery mengimpor Graphify&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE
skinparam packageBackgroundColor #FFF3CD

title Bakery mengimpor Graphify — Engine tidak tahu apa-apa
package &amp;quot;N3 — MESIN&amp;quot; #FFCCCC {
    [engine/build.gradle.kts] as ENG
    note right of ENG
        plugins { bakery }
        Zéro connaissance de graphify
        4 tâches, ~150 lignes
    end note
}

package &amp;quot;N2 — BAKERY&amp;quot; #CCFFCC {
    [bakery-plugin] as BAK
    note right of BAK
        plugins { graphify } ← import interne
        Lit graph.json
        Résout articles connexes
        Expose le modèle à JBake
    end note
}

package &amp;quot;N0 — membuat grafik&amp;quot; #CCE5FF {
    [graphify-plugin] as GRF
    note right of GRF
        Scan workspace → graph.json
        Enrichi avec :
        - xref: edges
        - co-occurrences de tags
        - entités nommées (NLP)
    end note
}

ENG -down-&amp;amp;gt; BAK : &amp;quot;plugins { bakery }&amp;quot;
BAK -down-&amp;amp;gt; GRF : &amp;quot;plugins { graphify } ← N2→N0 OK&amp;quot;
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Kontrak DAG dipatuhi: bakery (N2) mengimpor graphify (N0), N2 &amp;amp;gt; N0, tidak ada pelanggaran. Mesin (N3) mengimpor bakery (N2), N3 &amp;amp;gt; N2, OK.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Engine tidak memiliki &amp;lt;strong&amp;gt;satu pun&amp;lt;/strong&amp;gt; baris kode yang mereferensikan`graph.json`, &amp;lt;code&amp;gt;relatedPosts&amp;lt;/code&amp;gt;, atau apa pun konsep kesamaan. Dia menerapkan bakery, titik. La kolaborasi adalah internal bakery&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_pipeline_scan_grafik_template&amp;quot;&amp;gt;Pipeline: Scan → Grafik → Template&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Ini adalah pipeline lengkap dalam tiga langkah:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;olist arabic&amp;quot;&amp;gt;
&amp;lt;ol class=&amp;quot;arabic&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Pemindaian&amp;lt;/strong&amp;gt; (menggrafikan) :`graphify-plugin`memindai workspace. Di dalamnya&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;bentuk saat ini, ia sudah mendeteksi`xref:`di antara`.adoc`dan menunjukkan di`graph.json`seperti edges jenis`reference`.&amp;lt;br&amp;gt;
Kita melengkapinya untuk konten redaksi : Uraikan metadata JBake (&amp;lt;code&amp;gt;:jbake-tags:&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;:jbake-description:&amp;lt;/code&amp;gt;) setiap`.adoc`blog * Menghitung ko-occurence tag → edges`tag_cooccurrence`dengan berat * Mengekstrak entitas bernama dari deskripsi melalui TF-IDF → tepi`entity_overlap` Menyuntikkan sebuah bagian`blog_articles`dalam`graph.json`yang ada&amp;lt;br&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;// Extrait de l&amp;#39;enrichissement graphify pour le blog
fun enrichBlogSection(graphJson: File, blogDir: File): GraphJson {
    val articles = blogDir.listFiles { f -&amp;amp;gt; f.extension == &amp;quot;adoc&amp;quot; }
        .map { parseJbakeMetadata(it) }

    val nodes = articles.map { ArticleNode(it.slug, it.title, it.tags) }
    val edges = mutableListOf&amp;amp;lt;GraphEdge&amp;amp;gt;()

    // Couche 1 : xref (déjà fait par scanWorkspace)

    // Couche 2 : co-occurrences de tags
    for (a in articles) {
        for (b in articles) {
            if (a.slug == b.slug) continue
            val common = a.tags.intersect(b.tags)
            if (common.isNotEmpty()) {
                edges.add(GraphEdge(
                    source = a.slug,
                    target = b.slug,
                    type = &amp;quot;tag_cooccurrence&amp;quot;,
                    weight = common.size.toDouble() / (a.tags.size + b.tags.size)
                ))
            }
        }
    }

    return graphJson.copy(
        blogArticles = BlogSection(nodes, edges)
    )
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;olist arabic&amp;quot;&amp;gt;
&amp;lt;ol class=&amp;quot;arabic&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Bake&amp;lt;/strong&amp;gt; (bakery) : pada saat`./gradlew bake`, `BakeryPlugin`tempat tidur&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;`graph.json`dan menyelesaikan artikel terkait untuk setiap post&amp;lt;br&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;// BakeryPlugin — résolution des articles connexes
fun resolveRelatedPosts(
    currentSlug: String,
    graph: GraphJson,
    maxResults: Int = 4
): List&amp;amp;lt;RelatedPost&amp;amp;gt; {
    val edges = graph.blogArticles.edges
        .filter { it.source == currentSlug || it.target == currentSlug }

    return edges
        .sortedByDescending { it.weight }
        .take(maxResults)
        .map { edge -&amp;amp;gt;
            val relatedSlug = if (edge.source == currentSlug) edge.target else edge.source
            graph.blogArticles.nodes.first { it.slug == relatedSlug }
        }
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;olist arabic&amp;quot;&amp;gt;
&amp;lt;ol class=&amp;quot;arabic&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Template&amp;lt;/strong&amp;gt; (&amp;lt;code&amp;gt;post.thyme&amp;lt;/code&amp;gt;) : model JBake sekarang menerima sebuah peta&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;terstruktur alih-alih sebuah daftar kronologis yang datar&amp;lt;br&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-xhtml hljs&amp;quot; data-lang=&amp;quot;xhtml&amp;quot;&amp;gt;&amp;amp;lt;!-- Articles connexes basés sur le Knowledge Graph --&amp;amp;gt;
&amp;amp;lt;th:block th:if=&amp;quot;${relatedPosts != null and !relatedPosts.empty}&amp;quot;&amp;amp;gt;
    &amp;amp;lt;section class=&amp;quot;mt-5 pt-4 border-top&amp;quot;&amp;amp;gt;
        &amp;amp;lt;h2 class=&amp;quot;h4 mb-3&amp;quot;&amp;amp;gt;Articles connexes&amp;amp;lt;/h2&amp;amp;gt;
        &amp;amp;lt;th:block th:each=&amp;quot;related : ${relatedPosts}&amp;quot;&amp;amp;gt;
            &amp;amp;lt;div class=&amp;quot;mb-2&amp;quot;&amp;amp;gt;
                &amp;amp;lt;a th:href=&amp;quot;${content.rootpath} + ${related.uri}&amp;quot;
                   th:text=&amp;quot;${related.title}&amp;quot; class=&amp;quot;fw-semibold&amp;quot;&amp;amp;gt;&amp;amp;lt;/a&amp;amp;gt;
                &amp;amp;lt;br/&amp;amp;gt;
                &amp;amp;lt;small class=&amp;quot;text-muted&amp;quot;&amp;amp;gt;
                    &amp;amp;lt;th:block th:each=&amp;quot;reason,iterStat : ${related.reasons}&amp;quot;&amp;amp;gt;
                        &amp;amp;lt;span class=&amp;quot;badge bg-light text-dark&amp;quot;
                              th:text=&amp;quot;${reason}&amp;quot;&amp;amp;gt;&amp;amp;lt;/span&amp;amp;gt;
                    &amp;amp;lt;/th:block&amp;amp;gt;
                &amp;amp;lt;/small&amp;amp;gt;
            &amp;amp;lt;/div&amp;amp;gt;
        &amp;amp;lt;/th:block&amp;amp;gt;
    &amp;amp;lt;/section&amp;amp;gt;
&amp;amp;lt;/th:block&amp;amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Template-nya sama — dia tidak tahu &amp;lt;strong&amp;gt;dari mana&amp;lt;/strong&amp;gt; data berasal. Hanya kontrak antara bakery dan JBake yang berubah:`published_posts`(daftar kronologis) menjadi`relatedPosts`(peta yang berbobot berdasarkan graf).&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;admonitionblock note&amp;quot;&amp;gt;
&amp;lt;table&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;icon&amp;quot;&amp;gt;
&amp;lt;i class=&amp;quot;fa icon-note&amp;quot; title=&amp;quot;ℹ️&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Lencana « alasan » (contoh:`xref 0122`, &amp;lt;code&amp;gt;tag:gradle&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;cluster:pgvector-rag&amp;lt;/code&amp;gt;) jelaskan kepada pembaca &amp;lt;strong&amp;gt;mengapa&amp;lt;/strong&amp;gt; artikel ini relevan. Bukan hanya transparansi — ini adalah pedagogi tentang topologi dari konten pribadi Anda ====&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_fallback_kronologis&amp;quot;&amp;gt;Fallback Kronologis&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Si `graph.json`tidak hadir (build lokal tanpa pemindaian sebelumnya, pertama penerapan, CI yang belum diintegrasikan pemindaian graphify), templat harus menurunkan dengan anggun:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;fun resolveRelatedPosts(currentSlug: String, graph: GraphJson?): List&amp;amp;lt;RelatedPost&amp;amp;gt; {
    if (graph != null &amp;amp;amp;&amp;amp;amp; graph.blogArticles != null) {
        return resolveFromGraph(currentSlug, graph)
    }
    // Fallback chronologique — même comportement qu&amp;#39;aujourd&amp;#39;hui
    logger.warn(&amp;quot;[bakery] graph.json absent — fallback chronologique&amp;quot;)
    return resolveFromChronology(currentSlug)
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Perilaku default sama dengan perilaku saat ini. Le mesin rekomendasi adalah sebuah &amp;lt;strong&amp;gt;perbaikan progresif&amp;lt;/strong&amp;gt; — bukan sebuah perubahan yang merusak.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_ontologi_yang_emergent_ketika_artikel_berkelompok_tanpa_saling_mengenal&amp;quot;&amp;gt;Ontologi yang Emergent: Ketika Artikel Berkelompok Tanpa Saling Mengenal&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Lapisan yang paling menarik adalah yang ketiga: ontologi emergente. Artikel yang tidak dikutip, yang tidak memiliki tag yang sama, tetapi yang &amp;lt;strong&amp;gt;berbicara tentang hal yang sama&amp;lt;/strong&amp;gt; tanpa mengetahuinya.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Mari kita ambil contoh konkret. Tiga artikel dari korpus saya:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;olist arabic&amp;quot;&amp;gt;
&amp;lt;ol class=&amp;quot;arabic&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;0119 — Perbandingan DGX Spark vs Cloud Langganan LLM&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;0121 — Plugin Gradle Mengendalikan Dua Instansi Ollama Pro&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;0122 — Rasio Efisiensi 27x 45x Armada Ahli AI&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Ketiga artikel ini tidak memiliki &amp;lt;strong&amp;gt;aucun&amp;lt;/strong&amp;gt; xref di antara mereka. Tag mereka tidak mereka hanya tumpang tindih hingga 20% (« ollama », « llm ») umum. Namun NLP ringan pada deskripsi menunjukkan klaster yang jelas: « biaya », « langganan »,  awaan », « GPU », « kunci API », « Ollama Pro », « efektivitas », « rasio&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Mereka membentuk kluster ontologis : &amp;lt;strong&amp;gt;ekonomi LLM self-hosted vs cloud&amp;lt;/strong&amp;gt;. Klaster ini tidak dideklarasikan di mana pun. Ia muncul dari korpus.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE
skinparam packageBackgroundColor #E8F8F5

title Cluster Ontologique Émergent — &amp;quot;Ekonomi LLM Self-Hosted&amp;quot;

package &amp;quot;Cluster terdeteksi : ekonomi-llm&amp;quot; #D5F5E3 {
    [0119 — Benchmark DGX Spark\nvs Cloud Abonnement] as A
    [0121 — Plugin Gradle Piloter\nDeux Instances Ollama Pro] as B
    [0122 — Ratio Efficacité\n27x 45x Flotte Experts] as C
}

A .. B : entity_overlap (0.73)
B .. C : entity_overlap (0.81)
A .. C : entity_overlap (0.68)

note bottom of A
    Aucun xref entre ces articles.
    Tags communs : &amp;quot;ollama&amp;quot;, &amp;quot;llm&amp;quot;.
    Cluster découvert par NLP :
    co-occurrences &amp;quot;biaya&amp;quot;, &amp;quot;GPU&amp;quot;,
    &amp;quot;langganan&amp;quot;, &amp;quot;Ollama Pro&amp;quot;.
end note
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Ini kluster ontologi menjadi sebuah edge komposit dalam`graph.json` :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-json hljs&amp;quot; data-lang=&amp;quot;json&amp;quot;&amp;gt;{
  &amp;quot;source&amp;quot;: &amp;quot;0119-benchmark-dgx-spark&amp;quot;,
  &amp;quot;target&amp;quot;: &amp;quot;cluster:economie-llm&amp;quot;,
  &amp;quot;type&amp;quot;: &amp;quot;entity_cluster&amp;quot;,
  &amp;quot;weight&amp;quot;: 0.73,
  &amp;quot;metadata&amp;quot;: {
    &amp;quot;clusterLabel&amp;quot;: &amp;quot;Économie LLM Self-Hosted vs Cloud&amp;quot;,
    &amp;quot;commonEntities&amp;quot;: [&amp;quot;coût&amp;quot;, &amp;quot;GPU&amp;quot;, &amp;quot;abonnement&amp;quot;, &amp;quot;Ollama Pro&amp;quot;, &amp;quot;ratio&amp;quot;],
    &amp;quot;articlesInCluster&amp;quot;: [
      &amp;quot;0119-benchmark-dgx-spark&amp;quot;,
      &amp;quot;0121-ollama-pro-deux-instances&amp;quot;,
      &amp;quot;0122-ratio-efficacite-flotte-experts&amp;quot;
    ]
  }
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;NLP sengaja ringan — TF-IDF + cosine similarity pada deskripsi. Tidak perlu BERT, tidak perlu model bahasa. Corpus terdiri dari 23 artikel, matriks kemiripan muat dalam satu file JSON berukuran 50 KB.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;admonitionblock tip&amp;quot;&amp;gt;
&amp;lt;table&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;icon&amp;quot;&amp;gt;
&amp;lt;i class=&amp;quot;fa icon-tip&amp;quot; title=&amp;quot;💡&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Kekuatan NLP tidak berasal dari sofistikasi algoritme tapi dari &amp;lt;strong&amp;gt;ukuran korpus&amp;lt;/strong&amp;gt; dan dari &amp;lt;strong&amp;gt;kualitas deskripsi&amp;lt;/strong&amp;gt;. Se `:jbake-description:`ditulis dengan baik dari 150 karakter mengandung lebih dari sinyal untuk TF-IDF bahwa sebuah artikel penuh berjumlah 3000 kata.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_kontrak_dag_siapa_yang_penting_bagi_siapa&amp;quot;&amp;gt;Kontrak DAG: Siapa yang penting bagi siapa&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Ini adalah titik di mana disiplin arsitektur membayar. DAG N0→N3 ditentukan dalam`engine/build.gradle.kts`memberi sebuah aturan sederhana: Tidak ada proyek yang merupakan proyek tingkat lebih tinggi.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;table class=&amp;quot;tableblock frame-all grid-all stretch&amp;quot;&amp;gt;
&amp;lt;colgroup&amp;gt;
&amp;lt;col style=&amp;quot;width: 18.1818%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 18.1818%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 18.1818%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 18.1818%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 27.2728%;&amp;quot;&amp;gt;
&amp;lt;/colgroup&amp;gt;
&amp;lt;thead&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;Plugin Pengguna&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;Plugin yang diimpor&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;Tingkat konsumen&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;Tingkat yang diimpor&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;Sah?&amp;lt;/th&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/thead&amp;gt;
&amp;lt;tbody&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;bakery-gradle&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;graphify-gradle&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;N2&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;N0&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;✅ N2 &amp;amp;gt; N0 — OK&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;mesin&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;bakery-gradle&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;N3&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;N2&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;✅ N3 &amp;amp;gt; N2 — OK&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;mesin&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;graphify-gradle&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;N3&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;N0&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;✅ Teknik OK, tetapi &amp;lt;strong&amp;gt;konseptual salah&amp;lt;/strong&amp;gt; — kolaborasi internal pada bakery&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Engine menerapkan bakery. Bakery menerapkan graphify. Itu saja. Jika suatu hari saya ingin menambahkan vector store codebase (N1) untuk penelitian semantik cross-corpus, bakery juga mengimpornya. Engine tidak berubah.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;admonitionblock important&amp;quot;&amp;gt;
&amp;lt;table&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;icon&amp;quot;&amp;gt;
&amp;lt;i class=&amp;quot;fa icon-important&amp;quot; title=&amp;quot;❗&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Polanya adalah : &amp;lt;strong&amp;gt;plugin N2 adalah hub dari dependensi pribadinya&amp;lt;/strong&amp;gt; Engine N3 bukan sebuah hub — ini adalah sebuah terminal yang menerapkan hub.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_apa_yang_kita_dapatkan_dan_apa_yang_kita_tidak_dapatkan&amp;quot;&amp;gt;Apa yang Kita Dapatkan (Dan Apa yang Kita Tidak Dapatkan)&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Keuntungan utama adalah editorial, bukan teknis:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;table class=&amp;quot;tableblock frame-all grid-all stretch&amp;quot;&amp;gt;
&amp;lt;colgroup&amp;gt;
&amp;lt;col style=&amp;quot;width: 50%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 50%;&amp;quot;&amp;gt;
&amp;lt;/colgroup&amp;gt;
&amp;lt;thead&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;sebelum&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;Setelah&amp;lt;/th&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/thead&amp;gt;
&amp;lt;tbody&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Artikel terkait = 4 posting terakhir&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Artikel terkait = 4 teratas berdasarkan bobot dalam Knowledge Graph&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Membaca dari RAG → rekomendasi Kitty Terminal&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Pembaca membaca RAG → rekomendasi pgvector, chunking, vector store&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Tidak ada transparansi tentang mengapa&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Badge`xref 0122`, &amp;lt;code&amp;gt;tag:gradle&amp;lt;/code&amp;gt;, `cluster:rag`terlihat&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;JBake standar, nol usaha, nol nilai&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Pipeline Gradle deterministik, dapat direproduksi, dapat diuji&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Tidak ada kurva belajar untuk pembaca&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Pembaca menemukan &amp;lt;strong&amp;gt;topologi&amp;lt;/strong&amp;gt; dari konten saya&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Apa yang &amp;lt;strong&amp;gt;tidak&amp;lt;/strong&amp;gt; kita dapatkan:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Ini bukan mesin rekomendasi « sebenarnya » (tidak ada kolaboratif&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;pemfilteran, tidak ada pengujian A/B, tidak ada loop umpan balik)&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Kualitas tergantung pada kekayaan metadata JBake — jika Anda&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;deskripsi kosong, TF-IDF tidak melihat apa-apa&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;NLP offline — artikel baru hanya kluster di&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;scan berikutnya (ini bagus: build tetap deterministik)&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_perspektif_blog_sebagai_knowledge_graph_publik&amp;quot;&amp;gt;Perspektif: Blog sebagai Knowledge Graph Publik&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Fitur ini membuka perspektif yang lebih luas: dan jika`cheroliv.com` menjadi dirinya sendiri sebuah &amp;lt;strong&amp;gt;knowledge graph navigable&amp;lt;/strong&amp;gt; ?&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Artikel adalah node. Tag adalah komunitas. xref adalah tepi. Pembaca tidak lagi membaca artikel yang terisolasi — ia menavigasi di dalam &amp;lt;strong&amp;gt;graf pengetahuan&amp;lt;/strong&amp;gt; yang artikel saat ini adalah titik masuk.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Bayangkan sebuah halaman utama yang menampilkan bukan daftar kronologis dari postingan terbaru, tetapi sebuah &amp;lt;strong&amp;gt;peta corpus&amp;lt;/strong&amp;gt; : kluster ontologis, artikel pivot (yang memiliki banyak sisi), jalur bacaan disarankan (« Jika Anda menyukai artikel tentang Graphify, baca selanjutnya itu tentang Knowledge Graph sebagai alat pemahaman&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Ini bukan lagi sebuah blog. Ini sebuah &amp;lt;strong&amp;gt;atlas semantik&amp;lt;/strong&amp;gt;.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Dan ini bukan science fiction. Yang`graph.json`sudah ada. Dia hanya memerlukan antarmuka navigasi.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_kesimpulan_sistem_file_tahu_yang_template_abaikan&amp;quot;&amp;gt;Kesimpulan: sistem file tahu yang template abaikan&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Apa yang saya rancang pada malam Selasa ini adalah penggantian sebuah heuristik malas (urutan kronologis) oleh representasi akurat dari struktur korpus saya (Knowledge Graph).&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;template`post.thyme`tidak berubah. Yang berubah adalah apa yang kita Memberinya makan. Sebelumnya: sebuah daftar Java yang diurutkan berdasarkan`date`.Setelah : graf berbobot yang berasal dari tiga lapisan analisis — xrefs, co-occurrences tag, dan ontologi yang muncul melalui NLP.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Lingkaran sudah selesai. graphify memindai workspace. Bakery membaca le Graf memberi makan template. Pembaca melihat rekomendasi. Dan engine — maestro — bahkan tidak tahu bahwa semua ini ada.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Ini adalah arsitektur yang baik. Setiap plugin melakukan satu hal. Dan terminal konsumen tidak perlu mengetahui caranya.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_referensi&amp;quot;&amp;gt;referensi&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;0105_integrer_graphify_workflow_gradle_post.html&amp;quot;&amp;gt;Article 0105 — Mengintegrasikan Graphify dalam alur kerja Gradle&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;0106_knowledge_graph_outil_comprehension_codebase_post.html&amp;quot;&amp;gt;Pasal 0106 — Knowledge Graph sebagai alat pemahaman&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Artikel 0117 — Granularisasi OSS/CSS&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;0121_plugin_gradle_piloter_deux_instances_ollama_pro_post.html&amp;quot;&amp;gt;Artikel 0121 — Plugin Gradle Mengendalikan Dua Instansi Ollama Pro&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Article 0122 — 27× hingga 45× lebih efektif : armada ahli IA&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>27× hingga 45× lebih efektif daripada Claude dan GPT: bagaimana armada dari 23 ahli IA yang di-fine-tune menghancurkan pasar</title>
            <link >https://cheroliv.com/blog/2026/0122_ratio_efficacite_27x_45x_flotte_experts_ia_vs_claude_gpt_post.html</link>
            <pubDate>Mon, 11 May 2026 00:00:00 +0000</pubDate>
            <guid isPermaLink="false">blog/2026/0122_ratio_efficacite_27x_45x_flotte_experts_ia_vs_claude_gpt_post.html</guid>
            <description>&amp;lt;div id=&amp;quot;toc&amp;quot; class=&amp;quot;toc&amp;quot;&amp;gt;
&amp;lt;div id=&amp;quot;toctitle&amp;quot;&amp;gt;Table des matières&amp;lt;/div&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel1&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_masalah_mengapa_fine_tune_dan_bukan_rag&amp;quot;&amp;gt;Masalah: mengapa fine-tune dan bukan RAG?&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_ide_sebuah_armada_ahli_bukan_hanya_satu_model&amp;quot;&amp;gt;Ide: sebuah armada ahli, bukan hanya satu model&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_codebase_di_bawah_lupa_7_900_file_144_000_baris&amp;quot;&amp;gt;Codebase di bawah lupa: 7 900 file, 144 000 baris&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div id=&amp;quot;preamble&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;waktu membaca : 22 minutes&amp;lt;/div&amp;gt;
&amp;lt;p&amp;gt;Ketika seseorang menggunakan LLM untuk pengembangan perangkat lunak sehari-hari, ia akhirnya selalu menabrak dinding yang sama: model tidak tahu &amp;lt;strong&amp;gt;Anda&amp;lt;/strong&amp;gt; codebase. Dia tidak tahu bahwa Anda menggunakan pola seperti ituhttps://fr.wikipedia.org/wiki/Graphe_orient%C3%A9_acyclique[DAG], seperti konvensi Gradle, seperti stratifikasi epistemik dalam Andahttps://fr.wikipedia.org/wiki/AsciiDoc[.adoc]. Lalu kita menyuntikkan konteks kepadanya, kita mengulang, kita memperbaiki, kita mengiterasi. Dan waktu — dan uang — berlalu.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Saya lalu menanyakan secara tajam: berapa biaya tim model yang fine-tuned di workspace saya, yang mampu mengeluarkan kode &amp;lt;strong&amp;gt;di gaya saya&amp;lt;/strong&amp;gt; tanpa perlu menjelaskan kepada mereka setiap kali? Dan terutama: bagaimana pendekatan ini membandingkan dengan solusi proprietari klasik — Claude, GPT, Gemini, Grok?&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Jawaban itu membuat saya terkejut. Sangat kurang dari yang saya bayangkan. Dan perbandingan biaya/efektivitas dengan para raksasa sektor itu tidak patut. Berikut detail lengkapnya.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_masalah_mengapa_fine_tune_dan_bukan_rag&amp;quot;&amp;gt;Masalah: mengapa fine-tune dan bukan RAG?&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;RAG, itu bagus. &amp;lt;a href=&amp;quot;0106_knowledge_graph_outil_comprehension_codebase_post.html&amp;quot;&amp;gt;knowledge graph saya&amp;lt;/a&amp;gt; dan &amp;lt;a href=&amp;quot;0116_compartimentage_epistemique_automatise_llm_post.html&amp;quot;&amp;gt;kompartemen epistemik saya&amp;lt;/a&amp;gt; berfungsi. Namun RAG memiliki kelemahan dasar: pada setiap permintaan, Anda harus menyuntikan kembali konteks yang relevan. Prompt Anda menjadi lebih panjang, latensi Anda meledak, dan model tidak pernah &amp;lt;strong&amp;gt;meminternalisasi&amp;lt;/strong&amp;gt; pola Anda — ia membacanya setiap kali seperti dokumen eksternal.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Fine-tune, itu adalah otot memori. Model &amp;lt;strong&amp;gt;tahu&amp;lt;/strong&amp;gt; bahwa kamu menulis`build.gradle.kts`dengan tugas yang tercatat melalui`tasks.register`, yang kamu`AGENTS.adoc`Mereka mengikuti sintaksis standar, bahwa workspace Anda terstruktur menjadi boroughs. Dia tidak perlu dikingatkan.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;admonitionblock note&amp;quot;&amp;gt;
&amp;lt;table&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;icon&amp;quot;&amp;gt;
&amp;lt;i class=&amp;quot;fa icon-note&amp;quot; title=&amp;quot;ℹ️&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Fine-tune bukan pengganti RAG. Ini adalah lapisan tambahan. RAG tetap relevan untuk data yang sering berubah (log, tiket, commit terbaru). Fine-tune digunakan untuk pengetahuan &amp;lt;strong&amp;gt;struktur&amp;lt;/strong&amp;gt; dan &amp;lt;strong&amp;gt;gaya&amp;lt;/strong&amp;gt; jangka panjang.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_ide_sebuah_armada_ahli_bukan_hanya_satu_model&amp;quot;&amp;gt;Ide: sebuah armada ahli, bukan hanya satu model&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Hanya satu model yang fine-tuned di seluruh workspace saya sudah cukup baik. Tapi itu tidak optimal. Workspace saya mencakup bidang yang sangat berbeda :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Gradle plugin (Kotlin, konvensi plugin, komposit build)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Mesin DAG (Kotlin, penjadwalan, eksekusi)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Agen LLM (Python, LangChain4j, RAG)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Dokumentasi arsitektur (AsciiDoc, tata kelola)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Infrastuktur/DevOps (Docker, GitHub Actions, CI/CD)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Satu model tunggal seharusnya baik di mana-mana, tetapi tidak hebat di mana pun. Solusi? Sebuah&amp;lt;strong&amp;gt;armada model khusus&amp;lt;/strong&amp;gt;, dengan sebuah orchestrator yang mengarahkan setiap permintaan ke ahli yang tepat.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FAFAFA
skinparam defaultFontSize 13

package &amp;quot;Ollama Cloud&amp;quot; {
  rectangle &amp;quot;DeepSeek V4-Pro
1.6T / 49B aset&amp;quot; as V4Pro #FF6B6B
  rectangle &amp;quot;6× V4-Flash-FT
292B / 13B aset&amp;quot; as Flash #4ECDC4
  rectangle &amp;quot;6× Gemma4 26B-FT\n26B / 3.8B aktif&amp;quot; as Gemma #FFE66D
  rectangle &amp;quot;5× Qwen3.6 35B-FT
35B / 3B aktif&amp;quot; as Qwen36 #A8E6CF
  rectangle &amp;quot;5× Qwen3.5 35B-FT
35B / 3B aktif&amp;quot; as Qwen35 #FF8B94
}

note bottom of Qwen35
  À partir de 17€/mois
  (1× Ollama Pro annual)
  Extensible par cumul
  de comptes Pro selon
  la méthode Docker.
end note

actor Utilisateur as User

User --&amp;amp;gt; V4Pro : Requête
V4Pro --&amp;amp;gt; Flash : &amp;quot;tugas Gradle kompleks&amp;quot;
V4Pro --&amp;amp;gt; Gemma : &amp;quot;pemformatan AsciiDoc&amp;quot;
V4Pro --&amp;amp;gt; Qwen36 : &amp;quot;refaktorisasi Kotlin&amp;quot;
V4Pro --&amp;amp;gt; Qwen35 : &amp;quot;skrip Bash&amp;quot;

note right of V4Pro
  L&amp;#39;orkestrateur route
  la requête vers l&amp;#39;expert
  le plus pertinent.
  Zéro prompt engineering
  répétitif.
end note

@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Total armada :&amp;lt;strong&amp;gt;1 orkestrator + 22 ahli = 23 model&amp;lt;/strong&amp;gt;. Dan berapa biaya inferensi bulanan?&amp;lt;strong&amp;gt;17€/bulan&amp;lt;/strong&amp;gt;langganan Ollama Pro tahunan — dapat diperluas dengan menggabungkan akun jika kuota melebihi batas, seperti yang ditunjukkan dalam &amp;lt;a href=&amp;quot;0120_cumuler_deux_abonnements_ollama_pro_docker_post.html&amp;quot;&amp;gt;metode Docker multi-akun saya&amp;lt;/a&amp;gt;. Kami mulai dari 17€, menambahkan 20€ per tingkat sesuai kebutuhan nyata. Tidak ada pemborosan.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_codebase_di_bawah_lupa_7_900_file_144_000_baris&amp;quot;&amp;gt;Codebase di bawah lupa: 7 900 file, 144 000 baris&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Sebelum memperkirakan biaya pelatihan, Anda perlu tahu apa yang akan dilatih. Saya telah melakukan audit ruang kerja saya dengan mengecualikan secara ketat semua yang diabaikan oleh Git:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-none hljs&amp;quot;&amp;gt;----
----
build/    out/    node_modules/    runtimes/    .gradle/    dist/
target/   venv/   __pycache__/     .cache/      .tox/
----

Berikut hasilnya untuk file yang terlacak saja :

[cols=&amp;quot;2,2&amp;quot;]
|===
|metrik |Nilai

|File sumber yang dilacak |**7 896** |Baris kode (LOC) |**143 805** |Repositori Git |**26** |Bahasa Utama |Kotlin (1 903), AsciiDoc (2 765), Python (5 029)
|===

[IMPORTANT]
====
5 000+ file-file Python ini meliputi`runtimes/`buku catatan dan skrip utilitas. Inti aplikasi (plugins Gradle, mesin DAG, agen) adalah terutama dalam Kotlin dan AsciiDoc.
====

Dari massa ini, saya memperkirakan saya dapat menghasilkan dataset sintetis dari**~170 juta token**via LLM instruktur (distilasi basis kode menjadi pasangan instruksi/respons).

=== Detail dataset menurut tipe ahli

[source,text]
----
Dataset maître (généraliste V4-Flash) :    50M tokens
Expert Gradle/Build        :    15M tokens
Expert Kotlin/DAG          :    15M tokens
Expert AsciiDoc/Gouvernance:    15M tokens
Expert Agent/LLM/Python    :    15M tokens
Expert Infra/CI-CD         :    15M tokens
Micro-experts Qwen/Gemma   :   ~5M tokens chacun
----

== Pemilihan model: 100% MoE, 0% padat

Inilah tempat arsitektur menjadi sangat efisien.

Sebuah model *dense* mengaktifkan seluruh bobotnya pada setiap token. Sebuah model *MoE* (Mixture of Experts) hanya mengaktifkan sebagian dari parameter-nya. Biaya inferensi — dan fine-tuning — bersifat proporsional terhadap parameter.**diaktifkan**, bukan untuk parameter**tersimpan**.

Mari kita lihat apa yang ditawarkan oleh pasar dalam MoE hari ini di Ollama Cloud :

[cols=&amp;quot;3,2,2,1,3&amp;quot;]
|===
|Model |Total parameter |diaktifkan |Aktivasi |Ollama Cloud

|DeepSeek V4-Pro |1.6T |49B |3.1% |✅ asli |DeepSeek V4-Flash |292B |13B |4.5% |✅ asli |Gemma 4 26B-A4B |26B |3,8 miliar |14.6% |✅ asli |Qwen3.6-35B-A3B |35B |3B |8.6% |❌ (belum terintegrasi) |Qwen3.5-35B-A3B |35B |3B |8.6% |✅ asli
|❌ Qwen3.5-27B |27B |27B |100% padat |dihilangkan |❌ Qwen3.5-4B/9B |4B/9B |4B/9B |100% padat |dihapuskan |❌ Gemma 4 31B |31B |31B |100% padat |tereliminasi
|🧪 Gemma 4 E2B/E4B |5B/8B |5B/8B |100% tebal |pipeline validasi
|===

[CAUTION]
====
Rasio aktivasi adalah kriteria diskriminan. Gemma 4 26B-A4B hanya mengaktifkan 3.8B dari 26B parameternya — yaitu 7× lebih sedikit daripada model dense 27B, untuk performa agen coding yang dapat dibandingkan (77.1% vs 80% pada LiveCodeBench v6).
====

=== Mengapa saya meninggalkan model-model padat

Pengecualian: Gemma 4 E2B dan E4B. Kedua model ini, berlisensi Apache 2.0, sangat cocok untuk menguji dan memvalidasikan pipeline pelatihan dengan biaya rendah. Sebuah run 2-3 jam pada E2B membiayai kurang dari $5 — jika dataset atau hyperparameter-nya bermasalah, kita tahu dengan hanya beberapa dolar. Setelah pipeline selesai disiapkan, mereka bahkan dapat digunakan untuk tugas ultra-mikro (reformatting trivial, klasifikasi biner). Namun untuk ahli di armada utama, saya tetap menggunakan MoE. Inilah mengapa :

.Tapi bukan hanya itu: Gemma 4 E2B/E4B, ahli anonimisasi halus

[NOTE]
====
Google telah merancang model-model kecil ini agar kompetitif dalam pemanggilan alat asli — mereka tahu memanggil fungsi, memanipulasi regex, mencari pola. Setelah di-fine-tuning pada contoh anonymisasi (email, token, UUID, jalur file), mereka menjadi**penganonim dataset**ultra-cepat. Kami memberi mereka blok teks mentah, mereka memanggil`sed`(blank)`regex`via panggilan tool dan mengembalikan blok yang telah dibersihkan. Biaya inferensial sangat kecil: 5B diaktifkan, hal ini dapat dijalankan pada satu GPU kelas menengah. Model-model ini bukanlah ahli bisnis — mereka adalah**pekerja terlatih**dalam rantai produksi dataset.
====

* VRAM pelatihannya (bahkan dalam QLoRA) adalah proporsional dengan**27B total**
* Waktu pelatihan-nya sebanding dengan**27B diaktifkan**

Mari kita bandingkan dengan Qwen3.6-35B-A3B (MoE) :

[cols=&amp;quot;3,2,2,2&amp;quot;]
|===
| |Qwen3.5-27B (dense) |Qwen3.6-35B-A3B (MoE) |rasio

|VRAM QLoRA |~55 GB |~35 GB |1.6× |jam GPU (8M token) |sekitar 12 jam |~4 jam |**3 kali** |Biaya per fine-tune |~$24 |~$8 |**3×** |SWE-bench Terverifikasi |75.0% |73.4% |serupa
|===

MoE membutuhkan 3× lebih sedikit untuk dilatih demi performa agent-coding dengan selisih sekitar 5%. Pada 22 ahli, selisihnya tercatat dalam ribuan euro.

== Estimasi pelatihan : 811 H100-jam

Saya telah merinci biaya pelatihan model per model, dalam QLoRA 4-bit, dengan dataset yang dijelaskan di atas.

=== Micro-experts MoE ringan (3-3.8B diaktifkan)

Model-model ini membutuhkan 1 H100 (80 GB) per run. VRAM model dalam Q4 sekitar 13-18 GB, meninggalkan ruang yang cukup untuk keadaan optimizer LoRA dan aktivasi.

[cols=&amp;quot;4,2,3,2&amp;quot;]
|===
|Model |Jumlah runs |jam GPU/run |Total GPU-j

|Gemma 4 26B-A4B-FT |6 |3.5 h |**21 jam** |Qwen3.6-35B-A3B-FT |5 |4.0 h |**20 jam** |Qwen3.5-35B-A3B-FT |5 |4.0 h |**20 jam** |Subtotal micro | | |*61 h*
|===

=== Eksekutif V4-Flash (13B diaktifkan / 292B tersimpan)

DeepSeek V4-Flash adalah kasus khusus. Meski hanya 13B yang diaktifkan per token, model lengkap dalam Q4 berukuran ~142 GB — semua ahli harus berada di VRAM selama pelatihan. Oleh karena itu,**3× H100**dengan run agar nyaman

[cols=&amp;quot;4,2,3,3,2&amp;quot;]
|===
|Model |Kumpulan data |GPUs |Jam |Total GPU-jam

|V4-Flash-Base umum |50M token |3 kali H100 |50 h |**150 jam** |V4-Flash ahli ×5 |15M tok |3 kali H100 |40 h |**600 jam** |*Subtotal eksekutif* | | | |*750 h*
|===

[cols=&amp;quot;3,2,1&amp;quot;]
|===
|**JUMLAH** | |**811 H100-h**
|===

[NOTE]
====
Sebagai perbandingan, full fine-tune V4-Flash (tanpa QLoRA, semua bobot) akan membutuhkan sekitar 4.000 jam H100 hanya untuk 6 model. QLoRA membagi biaya dengan 5. Ini adalah satu-satunya cara untuk membuat proyek tersebut secara ekonomis dapat dijalankan dalam skala ini.
====

=== Tarif GPU: siapa yang menyewa apa di Mei 2026

Saya telah melakukan benchmarking pada penyedia utama H100. Harga per meter, untuk 811 jam.

[cols=&amp;quot;3,2,2,2&amp;quot;]
|===
|pemilik |$/H100·h |Total 811jam |Keandalan

|**Vast.ai tempat** |$1.60 |**$1 298** |★★☆ dapat diinterupsi |**Crusoe Awan** |$2.00 |**$1 622** |★★★★ datacenter andalan |FluidStack |$1.98 |$1 606 |(empty) |TensorDock |$1.80 |$1 460 |★★☆ |RunPod |$2.29 |$1 857 |kompromi yang baik |CoreWeave |$2.25 |$1 825 |★★★★ perusahaan |Lambda Labs |$2.49 |$2 019 |★★★★ premium
|===

[TIP]
====
Vast.ai spot adalah yang termurah tetapi run kamu dapat terhenti kapan saja. Untuk pelatihan, ini berisiko. Crusoe seharga $2,00/jam memiliki rasio keandalan/harga terbaik. Biaya tambahan sebesar $324 dibandingkan Vast layak untuk ketenangan pikiran.
====

=== Wall-clock: Berapa lama untuk melatih semuanya?

Dengan satu node 3× H100, 31 run (1 umum + 30 ahli) memerlukan sekitar**12 hari**Secara berurutan. Jika kamu menyewa 2 node secara paralel, ia turun menjadi**6 hari**.

Dalam praktik, micro-experts dapat berjalan pada node terpisah (1 H100 masing-masing) sementara V4-Flash mengisi node 3× H100. Dengan perencanaan yang cerdas:

----

Nœud A (3× H100) : V4-Flash généraliste → V4-Flash experts ×5       (11 jours) Nœud B (1× H100) : Gemma 4 26B ×6 → Qwen 36 ×5 → Qwen 35 ×5        (6 jours)

7 hari sewa, semuanya lipat.

== Inferensi: 23 model di Ollama Cloud mulai dari 17€/bulan

Ini bagian paling elegan dari arsitektur.

Ollama Cloud memungkinkan mengunggah model pribadi (fungsi rencana Pro). 22 model fine-tuned saya telah dipush ke`.gguf`di ruang Ollama saya, ditandai sebagai pribadi, dan dapat diakses melalui API standar.

[plantuml, inference-flow, svg]
----
@startuml
skinparam backgroundColor #FAFAFA
skinparam defaultFontSize 12

|Utilisateur|
start
:Tâche de développement;

|Orkestrateur (V4-Pro)|
:Classifier la tâche\n(domaine + complexité);
if (Tâche simple,\nspécialisée ?) then (oui)
  :Router vers micro-expert\n(Gemma 4 26B-A4B-FT\n ou Qwen3.6-35B-A3B-FT);
elseif (Tâche complexe,\nconventions fortes ?) then (oui)
  :Router vers exécutif\n(V4-Flash-FT);
else (non)
  :Résoudre avec V4-Pro\n(architecture, décision);
endif

|Modèle ciblé|
:Génère le code/output\n**dans mon style**,\n90%+ first-shot correct;
:Retourne la réponse;

stop
@enduml
----

=== Kami mulai dengan 17€/bulan, kami melakukan scaling jika diperlukan.

Paket Pro Ollama (17€/bulan secara tahunan) mengizinkan 3 model cloud bersaing. Dalam arsitektur ini, saya tidak pernah membutuhkan lebih dari 2-3 ahli secara bersamaan:

* Tahap klasifikasi (V4-Pro) mengisi 1 slot, beberapa detik
* Ahli yang ditargetkan menempati 1 slot, waktu untuk menghasilkan responsnya.
* Slot ke-3 tetap cadang untuk tugas paralel

Jika penggunaan meningkat, saya menambahkan akun Pro tambahan melalui metode Docker yang dijelaskan dalam xref:0120_cumuler_deux_abonnements_ollama_pro_docker_post.adoc[artikel saya tentang penggabungan langganan]. Tidak ada rencana Max seharga 100€/bulan — saya hanya menambahkan apa yang saya benar-benar butuh.

Satu-satunya faktor yang membatasi adalah**kuota waktu GPU**bulanan Ollama (50× rencana gratis untuk Pro). Untuk penggunaan saya beberapa ratus permintaan per hari, ini cukup.

[IMPORTANT]
====
Biaya marginal dari sebuah panggilan ke V4-Pro (49B aktif) vs Gemma 4 26B-A4B (3.8B aktif) adalah**Bahkan bagi saya : nol**. Paket Ollama adalah yang menyerap perbedaan komputasi. Satu-satunya indikator saya adalah latensi.
====

=== Routing dalam praktik&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Volume de requêtes estimé : ~500/jour&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;├─ 50% → Gemma 4 26B-A4B-FT    (micro, 3.8B actifs)
│        Formatage AsciiDoc, scripts simples, conventions nommage
│
├─ 25% → V4-Flash-FT            (exécutif, 13B actifs)
│        Plugins Gradle, refactoring Kotlin, génération DAG
│
├─ 15% → Qwen 35B MoE-FT       (micro, 3B actifs)
│        Tâches intermédiaires, review de code, documentation
│
└─ 10% → V4-Pro                 (orkestrateur, 49B actifs)
         Décisions architecturales, refactoring transverse,
         problèmes vraiment nouveaux&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;Massa lalu lintas melewati jalan kecil. Model besar hanya berintervensi sebagai pilihan terakhir.

== Perbandingan yang menyakitkan : Claude, GPT, Gemini, Grok vs armada saya

Inilah tempat di mana selisih menjadi sangat besar. Mari kita lihat berapa biaya yang akan dibutuhkan untuk volume kerja yang sama (500 permintaan/hari, ~300 token input + 2 000 token output rata-rata) dengan solusi proprietari pasar pada Mei 2026.

=== Anthropic — Claude (biaya Mei 2026)

Anthropic baru-baru ini menurunkan harga-nya. Tekanan dari solusi open-weight seperti DeepSeek dan Qwen terasa.

[cols=&amp;quot;3,2,2,2,2&amp;quot;]
|===
|Model |$/MTok |Keluaran $/MTok |Biaya/hari |**Biaya per bulan**

|Opus 4.7 (yang paling cerdas) |$5 |$25 |$25.75 |**$772** |Sonnet 4.6 (seimbang) |$3 |$15 |$15.45 |**$463** |Haiku 4.5 (paling cepat) |$1 |$5 |$5.15 |**$154**
|===

[CAUTION]
====
Tarif ini adalah *setelah* penurunan harga terakhir Anthropic (Mei 2026). Sebelum itu, Opus berada pada $15/$75 per MTok. Khawatir terhadap open-weight itu nyata.
====

Meski model termurah Claude — Haiku 4.5 ke**154€/bulan**— biaya 9× lebih daripada langganan Ollama Pro saya seharga 17€. Dan Haiku tidak mengenal codebase saya, tidak menghasilkan dalam gaya saya, dan memerlukan teknik prompt yang sistematis.

Yang paling cerdas — Opus 4.7 — meledakkan anggaran pada**772€/bulan**untuk volume modeste 500 permintaan per hari. Atau**45 kali lebih mahal**apa solusi saya.

=== OpenAI — GPT (tarif Mei 2026)

[cols=&amp;quot;3,2,2,2,2&amp;quot;]
|===
|Model |$/MTok |$/MTok |Biaya/hari |**Biaya per bulan**

|GPT 5.5 (yang paling cerdas) |$5 |$30 |$30.90 |**$927** |GPT 5.4 (standar) |$2.50 |$15 |$15.45 |**$463** |GPT 5.4 Mini |$0.75 |$4.50 |$4.64 |**$139**
|===

Bahkan model « ekonomi » OpenAI — GPT 5.4 Mini seharga 139€/bulan — membayar 8× langganan saya, untuk model yang *tidak tahu* tentang konvensi saya dan memerlukan prompt engineering yang berat.

GPT 5.5, produk unggulan mereka :**927€/bulan**. Untuk *zero-shot* pada codebase saya. Dengan 2-3 iterasi per tugas karena gaya tidak dikuasai, biaya sebenarnya per bulan mudah menglipat ganda.

=== Google — Gemini (tarif Mei 2026)

[cols=&amp;quot;3,2,2,2,2&amp;quot;]
|===
|model |$/MTok |$/MTok |Biaya/hari |**Biaya/bulan**

|Gemini 3.1 Pro (Tinggi) |$3.50 |$17.50 |$18.00 |**$540** |Gemini 3.1 Pro |$2 |$10 |$10.30 |**$309** |Gemini 3 Flash |$0.30 |$1.50 |$1.54 |**$46**
|===

Gemini 3 Flash seharga 46€/bulan adalah kompetitif dalam harga murni — tetapi tanpa fine-tune, ia menghasilkan kode « generik ». Bukan kode *Cheroliv*.

=== Grok — xAI (tarif Mei 2026)

[cols=&amp;quot;3,2,2,2,2&amp;quot;]
|===
|model |$/MTok |$/MTok |Biaya per hari |**Biaya/bulan**

|Grok 4 |$4 |$16 |$16.48 |**$494** |Grok 4 Mini |$0.80 |$3.20 |$3.30 |**$99**
|===

=== Microsoft — Azure OpenAI (tarif Mei 2026)

Azure menerapkan harga yang sama seperti API OpenAI langsung, tetapi juga menagih biaya infrastruktur dan jaringan. Harus memperhitungkan biaya tambahan sebesar 10-20% untuk layanan terkelola.

[cols=&amp;quot;3,2,2,2,2&amp;quot;]
|===
|model |$/MTok |Output $/MTok |Biaya/hari |**Biaya/bulan**

|GPT 5.4 (Azure, Global) |$2.50 |$15 |$15.45 |**$463** |GPT 5.4 + infrastruktur |biaya tambahan 15% |biaya tambahan 15% |$17.77 |**$533**
|===

[NOTE]
====
Tarif di atas berlaku untuk API, bukan untuk langganan « chat » (Claude Pro seharga $20/bulan, ChatGPT Plus seharga $20/bulan). Di sini dibandingkan penggunaan *pengembang*, di mana kamu menjalankan ratusan permintaan per hari melalui API untuk menghasilkan, merefaktor, mendokumentasi. Langganan untuk umum memiliki kuota yang melebihi jauh sebelum 500 permintaan per hari.
====

=== Tabel perbandingan akhir

[cols=&amp;quot;3,2,2,2,2,2&amp;quot;]
|===
|Solusi |pengetahuan codebase |Gaya terintegrasi |Biaya/bulan |Iterasi |**Biaya efektif**

|**Armada saya (Ollama Pro)** |✅ Disetel halus |dalam berat |**17€** |0-1 |**17 euro** |Claude Opus 4.7 |❌ Nol contoh |❌ Prompt yang diperlukan |$772 |2-3 |$1 500-2 300 |Claude Haiku 4.5 |❌ Zero-shot |❌ Prompt yang diperlukan |$154 |3-4 |$460-615 |GPT 5.5 |❌ Zero-shot |❌ Prompt yang diperlukan |$927 |2-3 |$1 854-2 781 |GPT 5.4 |Zero-shot |❌ Prompt yang diperlukan |$463 |2-3 |$925-1 390 |GPT 5.4 Mini |❌ Zero-shot |Prompt yang diperlukan |$139 |3-5 |$417-695 |Gemini 3.1 Pro Tinggi |❌ Zero-shot |❌ Prompt yang diperlukan |$540 |2-3 |$1 080-1 620 |Grok 4 |❌ Zero-shot |❌ Prompt diperlukan |$494 |2-3 |$988-1 482 |**Azure GPT 5.4** |nol-pembelajaran |❌ Prompt yang diperlukan |$533 |2-3 |$1 066-1 600
|===

[plantuml, cost-comparison, svg]&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;@startuml
skinparam backgroundColor #FAFAFA&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;title Laporan biaya/keefektifan — armada saya vs pasar (Mei 2026)
rectangle &amp;quot;17€/bulan&amp;quot; as OURS #4ECDC4
rectangle &amp;quot;Opus 4.7\n772€&amp;quot; as OPUS #FF6B6B
rectangle &amp;quot;Sonnet 4.6
463€&amp;quot; as SONNET #FF8B94
rectangle &amp;quot;GPT 5.5
927€&amp;quot; as GPT #FFD93D
rectangle &amp;quot;Gemini 3.1 Pro
540€&amp;quot; as GEMINI #6C5CE7
rectangle &amp;quot;Grok 4
494€&amp;quot; as GROK #A8E6CF
rectangle &amp;quot;Haiku 4.5\n154€&amp;quot; as HAIKU #DFE6E9
rectangle &amp;quot;GPT 5.4 Mini
139€&amp;quot; as MINI #B2BEC3&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;note bottom of OURS
  Avec fine-tune intégré.
  Zéro prompt engineering.
  90%+ first-shot correct.
end note&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;@enduml&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;17€/bulan untuk 23 pakar IA yang fine-tuned. Produk flagship Claude Opus 4.7 berbiaya**45× lebih mahal**untuk kualitas yang lebih rendah pada *saya* codebase. GPT 5.5 berbiaya**54× lebih mahal**. Bahkan model termurah di pasar (Gemini Flash seharga 46€) masih berbiaya**2.7× lebih**tanpa pengetahuan tentang ruang kerja saya.

Verdict itu tanpa banding: fine-tuning pada arsitektur MoE, pada 2026, bukan hanya « lebih murah ». Ini adalah faktor efisiensi dari**27× hingga 54×**terkait rasio biaya/kualitas dibanding solusi proprietari. Anthropic, OpenAI, Google — semua mengetahui hal itu, dan itu sebabnya mereka menurunkan harga mereka dalam panik.

== Faktor efisiensi : 5-8× vs menggunakan V4-Pro saja

Mari kita bandingkan kedua ekstrem.

=== Skenario A : V4-Pro saja untuk semua

[cols=&amp;quot;3,2&amp;quot;]
|===
|Metrik |Nilai

|Pengetahuan tentang codebase |0 (zero-shot) |Penamaan konvensi |40-60% benar pada percobaan pertama |Prompt engineering diperlukan |Ya, sistematik |Iterasi per tugas |2-3 |Biaya inferensi |$100/bulan (Ollama Max) |Latensi rata-rata |~20 tok/s + jaringan
|===

=== Skenario B : Armada MoE yang terkoordinasi

[cols=&amp;quot;3,2&amp;quot;]
|===
|Metrik |nilai

|Pengetahuan tentang codebase |Terintegrasi (fine-tune) |konvensi penamaan |90-95% tembakan pertama benar |Rekayasa Prompt |Tidak (gaya ada dalam bobot) |Iterasi per tugas |0-1 |Biaya inferensi |$60/bulan (3× Ollama Pro) |latensi rata-rata |~50 tok/s (model ringan)
|===

[cols=&amp;quot;4,2,2,2&amp;quot;]
|===
|Dimensi |hanya V4-Pro |Armada MoE |faktor

|Biaya bulanan cloud |$100 |**$60** |1.7× |Konteks untuk disuntikkan |Tinggi (prompt) |**nol** |∞ |Iterasi rata-rata |2-3 |**0-1** |2-3× |Latensi yang dirasakan |20 tok/s |50 tok/s |**2.5×** |First-shot correct |50% |90%+ |**1.8×**
|===

**Faktor efisiensi global : 5-8×**— dalam produktivitas pengembang yang dirasakan.

[NOTE]
====
Faktor tidak diukur dalam token atau dalam dolar. Ia diukur dalam «berapa banyak waktu yang saya habiskan untuk menjelaskan kepada model hal yang seharusnya sudah diketahui». Dengan model yang fine-tuned, waktu tersebut cenderung menuju nol.
====

== Anggaran lengkap : sebuah investasi one-shot, sebuah paket konyol

[cols=&amp;quot;3,2&amp;quot;]
|===
|jabatan |Jumlah

|Pelatihan GPU (811 H100-h, Crusoe $2/jam) |**$1 622** |Pembuatan dataset sintetis (API LLM) |**$400** |Inference (Ollama Pro tahunan, $17/bulan) |**$204** |**Total tahun pertama** |**$2 226** |**Setara bulanan** |**$186** |**Biaya per permintaan yang diperkirakan** |&amp;amp;lt; $0.01
|===

[plantuml, budget-breakdown, svg]&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;@startuml
skinparam backgroundColor #FAFAFA&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;rectangle &amp;quot;Anggaran Tahun 1: $2 226&amp;quot; as Total #E8E8E8 {
  rectangle &amp;quot;Pelatihan GPU
$1 622 (73%)&amp;quot; as Train #FF6B6B
  rectangle &amp;quot;Set data
distilasi
$400 (18%)&amp;quot; as Data #FFE66D
  rectangle &amp;quot;Ollama Pro\n$204 (9%)&amp;quot; as Cloud #4ECDC4
}&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;note bottom of Total
   $186/mois équivalent année 1.
   Dès l&amp;amp;#8217;an 2 : $17/mois.
   Amorti par ~$235 de CA
   mensuel avec marge 95%.
end note&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;@enduml&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;$186/bulan tahun pertama,**$17/bulan**sejak tahun 2. Harga langganan Netflix premium, untuk sebuah tim dari 23 ahli AI yang tersedia 24/7, yang menulis kode *gaya saya* tanpa saya perlu menjelaskan konvensi saya kepada mereka.

=== Perhitungan rentabilitas

Dengan edster.cloud dan margin ~95%, titik impas berada di**~$2 340 CA tambahan**pada tahun. Dalam praktik:

* 1 misi piloting software → $3 000-5 000 → investasi tertutup + keuntungan
* 1 pelatihan korporat → $5 000-10 000 → nyaman
* Upsell implisit : &amp;quot;tim AI saya menguasai pabrik perangkat lunak saya&amp;quot; → diferensiasi komersial

Mulai tahun 2, pelatihan tidak lagi perlu dibayar (kecuali pembaruan dataset). Biaya tahunan menurun hingga**$204**($17/bulan). Rasio biaya/nilai adalah seperti seekor unicorn : hampir nol.

== batas latihan

*perkiraan kasar*. Beberapa variabel dapat mempengaruhi anggaran :

[cols=&amp;quot;2,3&amp;quot;]
|===
|variabel |Dampak

|Pembuatan dataset sintetis |Jika API distilasi jelek (halusinasi, pasangan mal terbentuk), diperlukan waktu manusia untuk mengkurasi. Biaya tersembunyi. |Kuota waktu GPU Ollama |Jika penggunaan melebihi kuota paket Pro, akan perlu mengupgrade ke level yang lebih tinggi atau beralih ke GPU khusus. |Kualitas efektif fine-tunes |Fine-tune gagal → harus diulang. Risiko sebenarnya adalah hyperparameter, bukan lokasi GPU. |Evolusi cepat model |Qwen3.6/Gemma 4 telah dirilis kurang dari 1 bulan yang lalu. Dalam 6 bulan, generasi berikutnya akan lebih baik dan lebih murah untuk di-fine-tuning.
|===

[WARNING]
====
Jangan luncurkan 811 jam pelatihan tanpa terlebih dahulu menguji pipeline pada *satu* micro-model. Sebuah run 3-4 jam pada Qwen 35B-A3B biaya $8. Jika datasetnya rusak, Anda akan mengetahui itu untuk $8, bukan untuk $1 622.
====

== Kesimpulan: fine-tune telah menjadi mewah yang dapat diakses

Satu tahun yang lalu, melakukan fine-tuning pada model 292B parameter (bahkan dengan QLoRA) merupakan bagian dari penelitian. Saat ini, pada Mei 2026, ini adalah proyek yang dapat dilakukan untuk pengembang independen:

* **811 H100-jam**pelatihan — $1 622 di Crusoe
* **23 model khusus**dimuat di Ollama Cloud —**$17/bulan**
* **0 server, 0 GPU lokal, 0 maintenance infrastruktur**

Demokratisasi datang dari tiga faktor konvergen :

1. **Arsitektur MoE**— yang memungkinkan untuk secara efisien melakukan fine-tuning pada model di mana hanya sebagian dari bobotnya yang diaktifkan
2. **Rencana tetap dari Ollama Cloud**— yang mengubah biaya variabel (per-token) menjadi biaya tetap (langganan)
3. **Model open-weight berkualitas tinggi**— DeepSeek V4, Qwen3.6, Gemma 4 berada di bawah lisensi MIT/Apache 2.0

Peradaban maju dengan meningkatkan jumlah operasi penting yang dapat kita lakukan tanpa berpikir tentangnya.

Fine-tuning, itu tepat seperti itu: suatu operasi yang kamu delegasikan ke mesin sehingga tidak perlu lagi berpikir tentangnya. Dan pada 2026, ini lebih murah daripada seorang developer junior yang dibayar bulanan.

---

_Artikel dipublikasikan pada {jbake-date}_

== model-model fine-tuned dari armada

[cols=&amp;quot;2,2,2,2,3&amp;quot;]
|===
|Ukuran diaktifkan |Jumlah |Model dasar |bidang |(Note: Since no French text was provided for translation beyond the instructions and the label &amp;quot;Dataset&amp;quot;, which appears to be metadata rather than content to translate, the output is empty as per the requirement to translate only the given fragment.)

|13B |1 |DeepSeek-V4-Flash-Base |Ruang kerja umum |50 juta token |13B |5 |DeepSeek-V4-Flash |Gradle, Kotlin, DAG, Doc, Infra |15M token |3,8 miliar |6 |Gemma 4 26B-A4B |Mikrotugas, pemformatan, skrip |5 juta token |3B |5 |Qwen3.6-35B-A3B |Refactoring, review, dokumen menengah |5M tokens |3B |5 |Qwen3.5-35B-A3B |Tugas legacy, kompatibilitas |5M token
|===

== Sumber daya

=== Model open-weight di HuggingFace

* https://huggingface.co/deepseek-ai/DeepSeek-V4-Flash-Base[DeepSeek V4-Flash-Base — 292B parameter, lisensi MIT]
* https://huggingface.co/Qwen/Qwen3.6-35B-A3B[Qwen3.6-35B-A3B — 35B/3B diaktifkan, lisensi Apache 2.0]
* https://huggingface.co/Qwen/Qwen3.5-35B-A3B[Qwen3.5-35B-A3B — 35B/3B diaktifkan, lisensi Apache 2.0]
* https://huggingface.co/google/gemma-4-26B-A4B-it[Gemma 4 26B-A4B — 26B/3.8B diaktifkan, lisensi Apache 2.0]

=== Sumber tarif (Mei 2026)

* https://ollama.com/pricing[Tarif Ollama Cloud — rencana Pro $17/bulan per tahun]
* https://www.anthropic.com/pricing[Tarif Anthropic — Claude API (Opus 4.7 $5/$25, Sonnet 4.6 $3/$15, Haiku 4.5 $1/$5)]
* https://openai.com/api/pricing/[Tarif OpenAI — GPT API (GPT 5.5 $5/$30, GPT 5.4 $2.50/$15)]
* https://deepinfra.com/pricing[Tarif DeepInfra — GPU instances H100 $1.79/h, B200 $2.79/h]
* Vast.ai, Crusoe Cloud, FluidStack, TensorDock, RunPod, CoreWeave, Lambda Labs — catatan harga manual, berusia Mei 2026

=== Pengumuman resmi

* https://qwen.ai/blog?id=qwen3.6-35b-a3b[Pengumuman resmi Qwen3.6-35B-A3B — blog Qwen]— pengkodean berbasis agen, Apache 2.0, konteks 262K&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>Plugin Gradle Rumah untuk Mengemudi Dua Instansi Ollama Pro Secara Paralel</title>
            <link >https://cheroliv.com/blog/2026/0121_plugin_gradle_piloter_deux_instances_ollama_pro_post.html</link>
            <pubDate>Fri, 8 May 2026 00:00:00 +0000</pubDate>
            <guid isPermaLink="false">blog/2026/0121_plugin_gradle_piloter_deux_instances_ollama_pro_post.html</guid>
            <description>&amp;lt;div id=&amp;quot;toc&amp;quot; class=&amp;quot;toc&amp;quot;&amp;gt;
&amp;lt;div id=&amp;quot;toctitle&amp;quot;&amp;gt;Table des matières&amp;lt;/div&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel1&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_pendahuluan&amp;quot;&amp;gt;Pendahuluan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_mengapa_gradle_untuk_infrastruktur&amp;quot;&amp;gt;Mengapa Gradle untuk infrastruktur?&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_build_gradle_kts_lengkap&amp;quot;&amp;gt;build.gradle.kts lengkap&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_anatomi_tugas_utama&amp;quot;&amp;gt;Anatomi Tugas Utama&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_startollamab_peluncur&amp;quot;&amp;gt;startOllamaB` — Peluncur&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_signinb_interaktif&amp;quot;&amp;gt;signInB` — Interaktif&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_pullmodelb_yang_dapat_dikonfigurasi&amp;quot;&amp;gt;pullModelB` — Yang dapat dikonfigurasi&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_statusollamaall_diagnosis&amp;quot;&amp;gt;statusOllamaAll` — diagnosis&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_cleanollamab_pembersihan_yang_menjaga_identitas&amp;quot;&amp;gt;cleanOllamaB` — Pembersihan yang Menjaga Identitas&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_workflow_lengkap_dari_nol_sampai_dua_sesi&amp;quot;&amp;gt;Workflow Lengkap — Dari Nol Sampai Dua Sesi&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_mengapa_tidak_menjadikannya_sebuah_plugin_gradle_yang_sebenarnya&amp;quot;&amp;gt;Mengapa tidak menjadikannya sebuah plugin Gradle yang sebenarnya?&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_pelajaran_yang_dipelajari&amp;quot;&amp;gt;Pelajaran yang dipelajari&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_kesimpulan&amp;quot;&amp;gt;Kesimpulan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_sumber_daya&amp;quot;&amp;gt;Sumber daya&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div id=&amp;quot;preamble&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;waktu baca : 18 minutes&amp;lt;/div&amp;gt;
&amp;lt;p&amp;gt;Mengapa mengetik perintah docker secara manual ketika kita dapat mengenkapsulasi semuanya dalam tugas Gradle? Ini adalah cara saya mengindustrialisasikan konfigurasi dual-Ollama Pro saya dengan nol skrip shell eksternal. Memulai, menghentikan, identitas, sign-in, pull — semuanya lewat`./gradlew`.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_pendahuluan&amp;quot;&amp;gt;Pendahuluan&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Dalam artikel sebelumnya, saya menunjukkan cara memungkinkan dua akun Ollama Pro untuk coexist pada mesin yang sama dengan mengisolasi instansi kedua dalam kontainer Docker. Penyiapan berfungsi, tetapi pengelolaan sehari-hari menjadi sangat merepotkan: harus mengingat nama kontainer, menjalankan perintah yang benar.&amp;lt;code&amp;gt;docker exec&amp;lt;/code&amp;gt;, checker port mana yang aktif&amp;amp;#8230;&amp;amp;#8203;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Anda tahu saat itu ketika Anda membuka 14 terminal, lalu Anda mengetik`docker exec ollama-instance-b ollama list`untuk yang keempat, dan Anda berpikir « pasti harus ada cara yang lebih bersih » ?&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Ini adalah Gradle.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Bukan hanya untuk membangun kode. Gradle seperti seorang maestro dari infrastruktur lokal. Tugas-tugas yang melakukan provisioning, verifikasi, pengujian, dan pembersihan. Semua ini di-versionkan dalam sebuah`build.gradle.kts`yang tinggal di samping`docker-compose.yml`.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
!theme plain

skinparam handwritten false
skinparam shadowing false
skinparam BoxPadding 8
skinparam DefaultFontSize 11
skinparam ArrowThickness 1.5

left to right direction

actor &amp;quot;pengembang&amp;quot; as Dev

package &amp;quot;build.gradle.kts&amp;quot; as GradleFile {

  usecase &amp;quot;startOllamaB&amp;quot; as Start
  usecase &amp;quot;stopOllamaB&amp;quot; as Stop
  usecase &amp;quot;checkIdentity&amp;quot; as Check
  usecase &amp;quot;signInB&amp;quot; as SignIn
  usecase &amp;quot;pullModelB&amp;quot; as Pull
  usecase &amp;quot;testOllamaB&amp;quot; as Test
  usecase &amp;quot;statusOllamaAll&amp;quot; as Status
  usecase &amp;quot;cleanOllamaB&amp;quot; as Clean
}

package &amp;quot;Docker Daemon&amp;quot; as DockerDaemon {

  node &amp;quot;ollama-instance-b&amp;quot; as ContainerB {
    component &amp;quot;:11435&amp;quot; as Port
    database &amp;quot;~/ollama-b-data&amp;quot; as Volume
  }
}

package &amp;quot;Ollama Asli&amp;quot; as Native {
  component &amp;quot;:11434&amp;quot; as NatPort
}

Dev --&amp;amp;gt; Start
Dev --&amp;amp;gt; SignIn
Dev --&amp;amp;gt; Pull
Dev --&amp;amp;gt; Test
Dev --&amp;amp;gt; Status
Dev --&amp;amp;gt; Check
Dev --&amp;amp;gt; Stop
Dev --&amp;amp;gt; Clean

Start ..&amp;amp;gt; ContainerB : docker run -d
Stop ..&amp;amp;gt; ContainerB : docker stop
Check ..&amp;amp;gt; Volume : cat id_ed25519.pub
SignIn ..&amp;amp;gt; ContainerB : ollama signin
Pull ..&amp;amp;gt; ContainerB : ollama pull
Test ..&amp;amp;gt; ContainerB : curl /v1/chat/completions
Status ..&amp;amp;gt; ContainerB : docker ps
Status ..&amp;amp;gt; Native : curl :11434
Clean ..&amp;amp;gt; ContainerB : docker rm -f

note bottom of GradleFile
  Zéro script bash externe.
  Tout est dans le build.gradle.kts,
  versionné avec le projet.
end note

@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_mengapa_gradle_untuk_infrastruktur&amp;quot;&amp;gt;Mengapa Gradle untuk infrastruktur?&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Pertanyaan itu masuk akal. Gradle adalah sebuah build tool. Normalnya, ia mengkompilasi, menguji, dan memaketkan. Tidak, ia menjalankan kontainer Docker.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Namun Gradle juga merupakan mesin graf dependensi — dan itulah yang membuatnya menarik untuk kasus kita.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Kami ingin:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;olist arabic&amp;quot;&amp;gt;
&amp;lt;ol class=&amp;quot;arabic&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Agar kontainer dijalankan sebelum sign-in&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Pastikan identitas terverifikasi sebelum pull model&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Agar sebuah uji API gagal secara diam-diam jika kontainer tidak aktif&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Dari grafik dependensi murni. Gradle dirancang untuk itu. DSL Kotlin membuat penulisan tugas menjadi lancar, dan le`doLast`memungkinkan mengeksekusi perintah sistem seolah-olah itu adalah kode aplikatif.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Dan terutama : tidak ada bashrc yang harus dipelihara, tidak ada script yang terbuang di`~/bin`, tidak ada Makefile di samping docker-compose. Semua berada dalam satu file, di lokasi yang sama dengan kode proyek.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_build_gradle_kts_lengkap&amp;quot;&amp;gt;build.gradle.kts lengkap&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Ini adalah file yang akhirnya saya tulis. Melakukan semuanya, mulai dari penyediaan volume hingga curl verifikasi, melewati proses sign-in interaktif.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;import java.io.ByteArrayOutputStream

plugins {
    base
}

// -------------------------------------------------------------------------
// Configuration
// -------------------------------------------------------------------------
val instanceName   = &amp;quot;ollama-instance-b&amp;quot;
val instancePort   = 11435
val instanceImage  = &amp;quot;ollama/ollama:0.20.2&amp;quot;
val dataDir        = file(&amp;quot;${System.getProperty(&amp;quot;user.home&amp;quot;)}/ollama-b-data&amp;quot;)
val nativePort     = 11434

// Modèles à gérer — cloud Pro + un modèle local gratuit pour les tests
val proModels      = listOf(&amp;quot;deepseek-v4-pro:cloud&amp;quot;, &amp;quot;gemma4:31b-cloud&amp;quot;)
val freeModels     = listOf(&amp;quot;qwen3:0.6b&amp;quot;)

// -------------------------------------------------------------------------
// Tâche préparatoire — création du volume si absent
// -------------------------------------------------------------------------
val prepareVolume by tasks.registering {
    group       = &amp;quot;ollama&amp;quot;
    description = &amp;quot;Crée le répertoire de volume pour l&amp;#39;instance B si absent&amp;quot;
    doLast {
        if (!dataDir.exists()) {
            dataDir.mkdirs()
            println(&amp;quot;Volume créé : ${dataDir.absolutePath}&amp;quot;)
        } else {
            println(&amp;quot;Volume existant : ${dataDir.absolutePath}&amp;quot;)
        }
    }
}

// -------------------------------------------------------------------------
// Démarrage du conteneur Docker
// -------------------------------------------------------------------------
val startOllamaB by tasks.registering(Exec::class) {
    group       = &amp;quot;ollama&amp;quot;
    description = &amp;quot;Lance le conteneur Docker pour l&amp;#39;instance Ollama B&amp;quot;
    dependsOn(prepareVolume)

    commandLine(
        &amp;quot;docker&amp;quot;, &amp;quot;run&amp;quot;, &amp;quot;-d&amp;quot;,
        &amp;quot;--name&amp;quot;, instanceName,
        &amp;quot;-p&amp;quot;, &amp;quot;$instancePort:11434&amp;quot;,
        &amp;quot;-v&amp;quot;, &amp;quot;${dataDir.absolutePath}:/root/.ollama&amp;quot;,
        &amp;quot;-e&amp;quot;, &amp;quot;OLLAMA_HOST=0.0.0.0&amp;quot;,
        &amp;quot;--restart&amp;quot;, &amp;quot;always&amp;quot;,
        instanceImage
    )
    isIgnoreExitValue = true

    doLast {
        val alreadyRunning = executionResult.get().exitValue != 0
        if (alreadyRunning) {
            logger.lifecycle(&amp;quot;Le conteneur &amp;#39;$instanceName&amp;#39; existe déjà — tentative de démarrage&amp;quot;)
        } else {
            logger.lifecycle(&amp;quot;Conteneur &amp;#39;$instanceName&amp;#39; lancé&amp;quot;)
        }
    }
}

// -------------------------------------------------------------------------
// Arrêt du conteneur
// -------------------------------------------------------------------------
val stopOllamaB by tasks.registering(Exec::class) {
    group       = &amp;quot;ollama&amp;quot;
    description = &amp;quot;Arrête le conteneur Docker de l&amp;#39;instance B&amp;quot;

    commandLine(&amp;quot;docker&amp;quot;, &amp;quot;stop&amp;quot;, instanceName)
    isIgnoreExitValue = true

    doLast {
        logger.lifecycle(&amp;quot;Conteneur &amp;#39;$instanceName&amp;#39; arrêté&amp;quot;)
    }
}

// -------------------------------------------------------------------------
// Affichage de la clé publique (Device Key)
// -------------------------------------------------------------------------
val checkIdentity by tasks.registering(Exec::class) {
    group       = &amp;quot;ollama&amp;quot;
    description = &amp;quot;Affiche la clé publique SSH de l&amp;#39;instance B&amp;quot;
    dependsOn(startOllamaB)

    commandLine(&amp;quot;docker&amp;quot;, &amp;quot;exec&amp;quot;, instanceName, &amp;quot;cat&amp;quot;, &amp;quot;/root/.ollama/id_ed25519.pub&amp;quot;)
    standardOutput = ByteArrayOutputStream()

    doLast {
        val pubKey = standardOutput.toString().trim()
        println(&amp;quot;=&amp;quot;.repeat(60))
        println(&amp;quot;Device Key (Instance B)&amp;quot;)
        println(&amp;quot;=&amp;quot;.repeat(60))
        println(pubKey)
        println(&amp;quot;=&amp;quot;.repeat(60))
        println(&amp;quot;→ Enregistre cette clé sur https://ollama.com/settings/keys&amp;quot;)
        println(&amp;quot;→ Puis exécute : ./gradlew signInB&amp;quot;)
        println(&amp;quot;=&amp;quot;.repeat(60))
    }
}

// -------------------------------------------------------------------------
// Sign-in interactif (Ouvrira le navigateur)
// -------------------------------------------------------------------------
val signInB by tasks.registering(Exec::class) {
    group       = &amp;quot;ollama&amp;quot;
    description = &amp;quot;Lance l&amp;#39;authentification interactive Ollama pour le compte B&amp;quot;
    dependsOn(startOllamaB)

    standardInput  = System.`in`
    standardOutput = System.out
    errorOutput    = System.err

    commandLine(&amp;quot;docker&amp;quot;, &amp;quot;exec&amp;quot;, &amp;quot;-it&amp;quot;, instanceName, &amp;quot;ollama&amp;quot;, &amp;quot;signin&amp;quot;)

    doFirst {
        logger.lifecycle(&amp;quot;Authentification pour le Compte Pro B...&amp;quot;)
        logger.lifecycle(&amp;quot;Ouvre ton navigateur et connecte-toi avec l&amp;#39;email du compte B&amp;quot;)
    }
}

// -------------------------------------------------------------------------
// Pull d&amp;#39;un modèle (paramétrable via propriété Gradle)
// -------------------------------------------------------------------------
val pullModelB by tasks.registering(Exec::class) {
    group       = &amp;quot;ollama&amp;quot;
    description = &amp;quot;Pull un modèle sur l&amp;#39;instance B. Usage : ./gradlew pullModelB -Pmodel=nom:tag&amp;quot;
    dependsOn(startOllamaB)

    val modelName = project.findProperty(&amp;quot;model&amp;quot;) as? String ?: &amp;quot;qwen3:0.6b&amp;quot;

    commandLine(&amp;quot;docker&amp;quot;, &amp;quot;exec&amp;quot;, instanceName, &amp;quot;ollama&amp;quot;, &amp;quot;pull&amp;quot;, modelName)

    doFirst {
        logger.lifecycle(&amp;quot;Pull du modèle &amp;#39;$modelName&amp;#39; sur l&amp;#39;instance B...&amp;quot;)
    }

    doLast {
        logger.lifecycle(&amp;quot;Modèle &amp;#39;$modelName&amp;#39; pullé avec succès&amp;quot;)
    }
}

// -------------------------------------------------------------------------
// Pull de tous les modèles Pro (batch)
// -------------------------------------------------------------------------
val pullAllProModels by tasks.registering {
    group       = &amp;quot;ollama&amp;quot;
    description = &amp;quot;Pull tous les modèles cloud Pro sur l&amp;#39;instance B&amp;quot;
    dependsOn(startOllamaB)

    doLast {
        proModels.forEach { model -&amp;amp;gt;
            logger.lifecycle(&amp;quot;→ Pull $model...&amp;quot;)
            val process = ProcessBuilder(&amp;quot;docker&amp;quot;, &amp;quot;exec&amp;quot;, instanceName, &amp;quot;ollama&amp;quot;, &amp;quot;pull&amp;quot;, model)
                .inheritIO()
                .start()
            process.waitFor()
        }
        logger.lifecycle(&amp;quot;Tous les modèles Pro pullés&amp;quot;)
    }
}

// -------------------------------------------------------------------------
// Pull de tous les modèles gratuits (batch / première install)
// -------------------------------------------------------------------------
val pullAllFreeModels by tasks.registering {
    group       = &amp;quot;ollama&amp;quot;
    description = &amp;quot;Pull tous les modèles locaux gratuits sur l&amp;#39;instance B&amp;quot;
    dependsOn(startOllamaB)

    doLast {
        freeModels.forEach { model -&amp;amp;gt;
            logger.lifecycle(&amp;quot;→ Pull $model...&amp;quot;)
            val process = ProcessBuilder(&amp;quot;docker&amp;quot;, &amp;quot;exec&amp;quot;, instanceName, &amp;quot;ollama&amp;quot;, &amp;quot;pull&amp;quot;, model)
                .inheritIO()
                .start()
            process.waitFor()
        }
        logger.lifecycle(&amp;quot;Tous les modèles gratuits pullés&amp;quot;)
    }
}

// -------------------------------------------------------------------------
// Liste des modèles disponibles sur l&amp;#39;instance B
// -------------------------------------------------------------------------
val listModelsB by tasks.registering(Exec::class) {
    group       = &amp;quot;ollama&amp;quot;
    description = &amp;quot;Liste les modèles disponibles sur l&amp;#39;instance B&amp;quot;
    dependsOn(startOllamaB)

    commandLine(&amp;quot;docker&amp;quot;, &amp;quot;exec&amp;quot;, instanceName, &amp;quot;ollama&amp;quot;, &amp;quot;list&amp;quot;)
}

// -------------------------------------------------------------------------
// Health check — test API sur les deux instances
// -------------------------------------------------------------------------
val testOllamaB by tasks.registering {
    group       = &amp;quot;ollama&amp;quot;
    description = &amp;quot;Teste la connectivité API de l&amp;#39;instance B avec un simple chat&amp;quot;
    dependsOn(startOllamaB)

    doLast {
        val model = project.findProperty(&amp;quot;model&amp;quot;) as? String ?: &amp;quot;qwen3:0.6b&amp;quot;
        logger.lifecycle(&amp;quot;Test de l&amp;#39;instance B avec le modèle &amp;#39;$model&amp;#39;...&amp;quot;)

        val payload = &amp;quot;&amp;quot;&amp;quot;
            {
              &amp;quot;model&amp;quot;: &amp;quot;$model&amp;quot;,
              &amp;quot;messages&amp;quot;: [{&amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;, &amp;quot;content&amp;quot;: &amp;quot;Réponds uniquement par OK&amp;quot;}],
              &amp;quot;max_tokens&amp;quot;: 5
            }
        &amp;quot;&amp;quot;&amp;quot;.trimIndent()

        val process = ProcessBuilder(
            &amp;quot;curl&amp;quot;, &amp;quot;-s&amp;quot;, &amp;quot;http://localhost:$instancePort/v1/chat/completions&amp;quot;,
            &amp;quot;-H&amp;quot;, &amp;quot;Content-Type: application/json&amp;quot;,
            &amp;quot;-d&amp;quot;, payload
        ).start()

        val response = process.inputStream.bufferedReader().readText()
        process.waitFor()

        if (response.contains(&amp;quot;\&amp;quot;id\&amp;quot;:\&amp;quot;chatcmpl&amp;quot;) || response.contains(&amp;quot;\&amp;quot;choices\&amp;quot;&amp;quot;)) {
            println(&amp;quot;✓ Instance B OK — port $instancePort répond&amp;quot;)
            println(&amp;quot;  Response: ${response.take(120)}...&amp;quot;)
        } else {
            println(&amp;quot;✗ Instance B INACTIVE — vérifie le conteneur avec &amp;#39;./gradlew statusOllamaAll&amp;#39;&amp;quot;)
            println(&amp;quot;  Raw: $response&amp;quot;)
        }
    }
}

// -------------------------------------------------------------------------
// Health check sur les deux instances simultanément
// -------------------------------------------------------------------------
val statusOllamaAll by tasks.registering {
    group       = &amp;quot;ollama&amp;quot;
    description = &amp;quot;Vérifie le statut des deux instances Ollama (native + Docker)&amp;quot;

    doLast {
        // Instance native
        runCatching {
            val process = ProcessBuilder(
                &amp;quot;curl&amp;quot;, &amp;quot;-s&amp;quot;, &amp;quot;-o&amp;quot;, &amp;quot;/dev/null&amp;quot;, &amp;quot;-w&amp;quot;, &amp;quot;%{http_code}&amp;quot;,
                &amp;quot;http://localhost:$nativePort/api/tags&amp;quot;
            ).start()
            val code = process.inputStream.bufferedReader().readText().trim()
            process.waitFor()
            println(if (code == &amp;quot;200&amp;quot;) &amp;quot;✓ Instance Native   — port $nativePort OK (HTTP $code)&amp;quot;
                    else &amp;quot;✗ Instance Native — port $nativePort (HTTP $code)&amp;quot;)
        }.getOrElse {
            println(&amp;quot;✗ Instance Native — injoignable sur le port $nativePort&amp;quot;)
        }

        // Instance Docker
        runCatching {
            val process = ProcessBuilder(
                &amp;quot;curl&amp;quot;, &amp;quot;-s&amp;quot;, &amp;quot;-o&amp;quot;, &amp;quot;/dev/null&amp;quot;, &amp;quot;-w&amp;quot;, &amp;quot;%{http_code}&amp;quot;,
                &amp;quot;http://localhost:$instancePort/api/tags&amp;quot;
            ).start()
            val code = process.inputStream.bufferedReader().readText().trim()
            process.waitFor()
            println(if (code == &amp;quot;200&amp;quot;) &amp;quot;✓ Instance Docker B — port $instancePort OK (HTTP $code)&amp;quot;
                    else &amp;quot;✗ Instance Docker B — port $instancePort (HTTP $code)&amp;quot;)
        }.getOrElse {
            println(&amp;quot;✗ Instance Docker B — injoignable sur le port $instancePort&amp;quot;)
        }

        // Info conteneur
        runCatching {
            val process = ProcessBuilder(&amp;quot;docker&amp;quot;, &amp;quot;ps&amp;quot;, &amp;quot;--filter&amp;quot;, &amp;quot;name=$instanceName&amp;quot;,
                &amp;quot;--format&amp;quot;, &amp;quot;table {{.Names}}\\t{{.Status}}\\t{{.Ports}}&amp;quot;).start()
            val status = process.inputStream.bufferedReader().readText()
            process.waitFor()
            println()
            println(&amp;quot;Conteneur Docker :&amp;quot;)
            println(status)
        }
    }
}

// -------------------------------------------------------------------------
// Nettoyage complet
// -------------------------------------------------------------------------
val cleanOllamaB by tasks.registering(Exec::class) {
    group       = &amp;quot;ollama&amp;quot;
    description = &amp;quot;Arrête et supprime le conteneur Docker de l&amp;#39;instance B&amp;quot;
    dependsOn(stopOllamaB)

    commandLine(&amp;quot;docker&amp;quot;, &amp;quot;rm&amp;quot;, instanceName)
    isIgnoreExitValue = true

    doLast {
        logger.lifecycle(&amp;quot;Conteneur &amp;#39;$instanceName&amp;#39; supprimé&amp;quot;)
        logger.lifecycle(&amp;quot;Le volume ${dataDir.absolutePath} est conservé (identité persistante)&amp;quot;)
    }
}

// -------------------------------------------------------------------------
// Affichage des instructions
// -------------------------------------------------------------------------
val helpOllama by tasks.registering {
    group       = &amp;quot;ollama&amp;quot;
    description = &amp;quot;Affiche l&amp;#39;aide des tâches Ollama disponibles&amp;quot;

    doLast {
        println(&amp;quot;&amp;quot;&amp;quot;
        ╔══════════════════════════════════════════════════════════════╗
        ║         Tâches Gradle — Pilotage Dual Ollama Pro            ║
        ╠══════════════════════════════════════════════════════════════╣
        ║  ./gradlew startOllamaB      Démarrer l&amp;#39;instance Docker B   ║
        ║  ./gradlew stopOllamaB       Arrêter l&amp;#39;instance Docker B    ║
        ║  ./gradlew checkIdentity     Afficher la clé SSH publique   ║
        ║  ./gradlew signInB           Sign-in interactif compte B    ║
        ║  ./gradlew pullModelB        Pull un modèle (-Pmodel=...)   ║
        ║  ./gradlew pullAllProModels  Pull tous les modèles Pro      ║
        ║  ./gradlew pullAllFreeModels Pull tous les modèles gratuits ║
        ║  ./gradlew listModelsB       Lister les modèles instance B  ║
        ║  ./gradlew testOllamaB       Test API avec un chat simple   ║
        ║  ./gradlew statusOllamaAll   Santé des 2 instances          ║
        ║  ./gradlew cleanOllamaB      Supprimer le conteneur         ║
        ║  ./gradlew helpOllama        Cette aide                     ║
        ╚══════════════════════════════════════════════════════════════╝
        &amp;quot;&amp;quot;&amp;quot;.trimIndent())
    }
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Ini. Dua belas tugas, satu titik masuk unik, dan tanpa gesekan.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_anatomi_tugas_utama&amp;quot;&amp;gt;Anatomi Tugas Utama&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_startollamab_peluncur&amp;quot;&amp;gt;startOllamaB` — Peluncur&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Tidak ada yang sulit : satu`docker run -d`dengan parameter yang tepat. Triknya ada di`isIgnoreExitValue = true`.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;isIgnoreExitValue = true&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Jika kontainer sudah ada, perintah`docker run`gagal (exit code ≠ 0). Tanpa properti ini, Gradle akan menghentikan seluruh build dengan kesalahan. Di sini, kegagalan bersenyap — dan`doLast`mendeteksi apakah itu merupakan &amp;#39;yang sudah ada&amp;#39; atau kesalahan yang sebenarnya&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Le `doLast`adalah penting : ia berputar &amp;lt;strong&amp;gt;setelah&amp;lt;/strong&amp;gt; eksekusi perintah, yang memungkinkan untuk memeriksa`executionResult.get().exitValue`dan mencatat pesan yang sesuai.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_signinb_interaktif&amp;quot;&amp;gt;signInB` — Interaktif&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Ini adalah satu-satunya tugas yang membutuhkan interaksi manusia. Gradle mengirimkan`System.in`dans processus agar prompt`ollama signin`supaya dapat ditampilkan normal :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;standardInput  = System.`in`
standardOutput = System.out
errorOutput    = System.err&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Tanpa itu, sign-in akan terblokir karena stdin akan ditutup dan Anda tidak akan pernah melihat prompt.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_pullmodelb_yang_dapat_dikonfigurasi&amp;quot;&amp;gt;pullModelB` — Yang dapat dikonfigurasi&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Sebuah tugas generik yang menerima parameter melalui`-P` :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;./gradlew pullModelB -Pmodel=deepseek-v4-pro:cloud
./gradlew pullModelB                           # fallback : qwen3:0.6b&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Cadangan penting :`project.findProperty(&amp;quot;model&amp;quot;) as? String ?: &amp;quot;qwen3:0.6b&amp;quot;`memastikan bahwa tugas tidak meledak jika kita lupa parameter.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_statusollamaall_diagnosis&amp;quot;&amp;gt;statusOllamaAll` — diagnosis&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Dua`curl`sehat di pelabuhan`11434` et &amp;lt;code&amp;gt;11435&amp;lt;/code&amp;gt;, dengan cadangan di`runCatching`Agar tidak crash jika salah satu instance down. Outputnya terstruktur :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-text hljs&amp;quot; data-lang=&amp;quot;text&amp;quot;&amp;gt;✓ Instance Native   — port 11434 OK (HTTP 200)
✓ Instance Docker B — port 11435 OK (HTTP 200)

Conteneur Docker :
NAMES               STATUS         PORTS
ollama-instance-b   Up 3 hours     0.0.0.0:11435-&amp;amp;gt;11434/tcp&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Dengan sekilas saja, kamu tahu persis apa yang berputar dan apa yang salah.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_cleanollamab_pembersihan_yang_menjaga_identitas&amp;quot;&amp;gt;cleanOllamaB` — Pembersihan yang Menjaga Identitas&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;commandLine(&amp;quot;docker&amp;quot;, &amp;quot;rm&amp;quot;, instanceName)
isIgnoreExitValue = true&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Kontainer terhapus, tetapi volume`~/ollama-b-data`(a single space)&amp;lt;strong&amp;gt;terawat&amp;lt;/strong&amp;gt;. Identitas SSH bertahan saat pembersihan. Ke yang berikutnya`startOllamaB`, kunci yang sama akan digunakan kembali — tidak perlu melakukan sign-in lagi atau mendaftar ulang Device Key.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;admonitionblock caution&amp;quot;&amp;gt;
&amp;lt;table&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;icon&amp;quot;&amp;gt;
&amp;lt;i class=&amp;quot;fa icon-caution&amp;quot; title=&amp;quot;🔒&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;code&amp;gt;cleanOllamaB`Jangan menyentuh volume. Jika kamu ingin menghapus identitas B (dan memulai dari nol), kamu harus menghapus&amp;lt;/code&amp;gt;~/ollama-b-data`Secara manual. Ini bersifat sukarela — satu`clean`tidak seharusnya menghancurkan kredensial&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_workflow_lengkap_dari_nol_sampai_dua_sesi&amp;quot;&amp;gt;Workflow Lengkap — Dari Nol Sampai Dua Sesi&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Berikut adalah urutan ideal bagi seseorang yang datang ke proyek dan ingin menyelesaikan semuanya tanpa membuka dokumentasi :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-text hljs&amp;quot; data-lang=&amp;quot;text&amp;quot;&amp;gt;# 1. Premier contact — que faire ?
$ ./gradlew helpOllama

# 2. On démarre l&amp;#39;instance B
$ ./gradlew startOllamaB

# 3. On récupère la Device Key et on l&amp;#39;enregistre sur ollama.com
$ ./gradlew checkIdentity
# → copier-coller de la clé sur https://ollama.com/settings/keys

# 4. Authentification interactive
$ ./gradlew signInB

# 5. Pull d&amp;#39;un petit modèle gratuit pour tester la plomberie
$ ./gradlew pullModelB

# 6. Test API
$ ./gradlew testOllamaB

# 7. Si tout est vert, pull des modèles Pro
$ ./gradlew pullAllProModels

# 8. Vérification finale des deux instances
$ ./gradlew statusOllamaAll&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Kurang dari dua menit, semua sudah disediakan. Tidak perlu membaca README, tidak perlu mengatur variabel lingkungan. Tugas`helpOllama`berfungsi sebagai dokumentasi terintegrasi.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
!theme plain
skinparam DefaultFontSize 11

actor Développeur as Dev
participant &amp;quot;Gradle
(build.gradle.kts)&amp;quot; as Gradle
participant &amp;quot;Docker\nDaemon&amp;quot; as Docker
participant &amp;quot;Ollama
Instans B&amp;quot; as OllamaB
participant &amp;quot;Ollama\nCloud&amp;quot; as Cloud

== Provisionnement ==
Dev -&amp;amp;gt; Gradle : helpOllama
Gradle --&amp;amp;gt; Dev : Aide et instructions

Dev -&amp;amp;gt; Gradle : startOllamaB
Gradle -&amp;amp;gt; Docker : docker run -d ollama/ollama:0.20.2
Docker -&amp;amp;gt; OllamaB : Génération clés SSH (id_ed25519)
Docker --&amp;amp;gt; Gradle : conteneur lancé
Gradle --&amp;amp;gt; Dev : OK

Dev -&amp;amp;gt; Gradle : checkIdentity
Gradle -&amp;amp;gt; Docker : cat id_ed25519.pub
Gradle --&amp;amp;gt; Dev : Clé publique SSH

Dev -&amp;amp;gt; Cloud : Enregistrement clé SSH\n(sur ollama.com/settings/keys)

== Authentification ==
Dev -&amp;amp;gt; Gradle : signInB
Gradle -&amp;amp;gt; Docker : ollama signin (interactif)
Dev &amp;amp;lt;-&amp;amp;gt; Cloud : Login navigateur (compte B)
Docker -&amp;amp;gt; Cloud : Échange token ⇔ Device Key
Gradle --&amp;amp;gt; Dev : Authentifié

== Vérification ==
Dev -&amp;amp;gt; Gradle : pullModelB
Gradle -&amp;amp;gt; Docker : ollama pull qwen3:0.6b
Gradle --&amp;amp;gt; Dev : Modèle pullé

Dev -&amp;amp;gt; Gradle : testOllamaB
Gradle -&amp;amp;gt; Docker : curl /v1/chat/completions
Gradle --&amp;amp;gt; Dev : ✓ Instance B OK

== Production ==
Dev -&amp;amp;gt; Gradle : pullAllProModels
Gradle -&amp;amp;gt; Docker : ollama pull deepseek-v4-pro:cloud
Gradle -&amp;amp;gt; Docker : ollama pull gemma4:31b-cloud
Gradle --&amp;amp;gt; Dev : Modèles Pro pullés

Dev -&amp;amp;gt; Gradle : statusOllamaAll
Gradle --&amp;amp;gt; Dev : ✓ Native ✓ Docker B
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_mengapa_tidak_menjadikannya_sebuah_plugin_gradle_yang_sebenarnya&amp;quot;&amp;gt;Mengapa tidak menjadikannya sebuah plugin Gradle yang sebenarnya?&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Pertanyaan yang sah. Ekstrak ini`build.gradle.kts`Mempunyai plugin Gradle mandiri akan memiliki keuntungan: penggunaan kembali antar proyek, konfigurasi deklaratif, ekstensi DSL.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Tapi untuk saat ini, ini tidak masuk akal. Build ini tidak memiliki logika bisnis kompleks — ini hanyalah pembungkus (wrapper) dari perintah Docker dan curl. Menambahkan plugin Gradle lengkap (dengan ekstensi, tes unit, publikasi) akan tidak sebanding untuk 12 tugas utilitas.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Le `build.gradle.kts`inline berada dalam proyek yang membutuhkannya. Dia mudah untuk disalin ke tempat lain. Suatu hari jika saya memiliki tiga proyek yang bergantung padanya, saya akan mengubahnya menjadi plugin. Saat ini, KISS.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;admonitionblock note&amp;quot;&amp;gt;
&amp;lt;table&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;icon&amp;quot;&amp;gt;
&amp;lt;i class=&amp;quot;fa icon-note&amp;quot; title=&amp;quot;ℹ️&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Hari ketika kamu punya tiga`build.gradle.kts`sama di tiga proyek, ini adalah sinyal bahwa harus mengekstrak sebuah plugin. Sebelum itu, duplikasi lebih murah daripada abstraksi. Kearifan dev.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_pelajaran_yang_dipelajari&amp;quot;&amp;gt;Pelajaran yang dipelajari&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;olist arabic&amp;quot;&amp;gt;
&amp;lt;ol class=&amp;quot;arabic&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Gradle bukan hanya sebuah build tool&amp;lt;/strong&amp;gt;— Mesin grafik dependensi menjadikannya sebuah orchestrator infrastruktur yang menakutkan.&amp;lt;code&amp;gt;doLast&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;isIgnoreExitValue&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;dependsOn&amp;lt;/code&amp;gt;: ketiga primitif ini cukup untuk mengendalikan kontainer Docker dengan keandalan yang sama seperti pipeline CI.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;isIgnoreExitValue` adalah temanmu&amp;lt;/strong&amp;gt;— Dalam sebuah dunia ideal,&amp;lt;code&amp;gt;docker run`pada kontainer yang sudah ada akan mengembalikan peringatan daripada kode keluar kesalahan. Di dunia nyata, kamu menempatkan`isIgnoreExitValue = true`dan Anda mengelola diagnosa di`doLast&amp;lt;/code&amp;gt;.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Tugas yang dapat dikonfigurasi menghilangkan ambiguitas&amp;lt;/strong&amp;gt; — Un &amp;lt;code&amp;gt;pullModelB`generik dengan&amp;lt;/code&amp;gt;-Pmodel=&amp;amp;#8230;&amp;amp;#8203;`Mengganti N tugas khusus. Satu tugas, satu dokumentasi, satu perilaku.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;runCatching` untuk pemeriksaan kesehatan&amp;lt;/strong&amp;gt;Ketika Anda menguji N endpoint, crash pada endpoint pertama tidak boleh menghalangi pengujian endpoint lainnya.&amp;lt;code&amp;gt;runCatching&amp;lt;/code&amp;gt;+`getOrElse`per endpoint, dan diagnosis tetap lengkap bahkan jika terjadi kerusakan parsial.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Tugas &amp;lt;code&amp;gt;helpOllama&amp;lt;/code&amp;gt; sebagai dokumentasi yang dapat dieksekusi&amp;lt;/strong&amp;gt; — Un `println()`Yang diformat dengan baik mengganti README tiga paragraphe. Ini bersifat auto-dokumentasi, selalu terkini, dan dapat diakses tanpa keluar dari terminal.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_kesimpulan&amp;quot;&amp;gt;Kesimpulan&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Kami beralih dari perintah docker yang harus diketik secara manual ke dalam workflow Gradle yang lengkap, terkontrol versinya, dan dapat digunakan kembali. Belas dua tugas mencakup seluruh siklus hidup: penyediaan, identitas, masuk, tarik, uji, diagnosa, pembersihan.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Komposisi docker-compose + build.gradle.kts + opencode.json membentuk trinitas yang rapi: Docker untuk isolasi, Gradle untuk orkestrasi, OpenCode untuk konsumsi. Setiap lapisan memiliki tanggung jawabnya, dan tidak ada yang meluap ke lapisan lain.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Langkah berikutnya? Mengotomatisasi siklus hidup dalam CI sehingga instance Ollama tersedia di lingkungan dev sementara. Tapi itu adalah artikel lain.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_sumber_daya&amp;quot;&amp;gt;Sumber daya&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://docs.gradle.org/current/userguide/tutorial_using_tasks.html&amp;quot;&amp;gt;Manual Pengguna Gradle — Tugas&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://docs.gradle.org/current/userguide/custom_tasks.html&amp;quot;&amp;gt;User Manual Gradle — Tugas Khusus&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://hub.docker.com/r/ollama/ollama&amp;quot;&amp;gt;Gambar Docker Ollama di Docker Hub&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://kotlinlang.org/docs/gradle.html&amp;quot;&amp;gt;Gradle DSL Kotlin — Dokumentasi&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;hr&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;em&amp;gt;Artikel dipublikasikan 2026-05-08&amp;lt;/em&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>Menggabungkan dua langganan Ollama Pro pada mesin yang sama dengan Docker</title>
            <link >https://cheroliv.com/blog/2026/0120_cumuler_deux_abonnements_ollama_pro_docker_post.html</link>
            <pubDate>Fri, 8 May 2026 00:00:00 +0000</pubDate>
            <guid isPermaLink="false">blog/2026/0120_cumuler_deux_abonnements_ollama_pro_docker_post.html</guid>
            <description>&amp;lt;div id=&amp;quot;toc&amp;quot; class=&amp;quot;toc&amp;quot;&amp;gt;
&amp;lt;div id=&amp;quot;toctitle&amp;quot;&amp;gt;Table des matières&amp;lt;/div&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel1&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_pengantar&amp;quot;&amp;gt;Pengantar&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_kenapa_dua_kali_hal_yang_sama&amp;quot;&amp;gt;Kenapa dua kali hal yang sama?&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_jantung_masalah_kunci_perangkat&amp;quot;&amp;gt;Jantung Masalah : Kunci Perangkat&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_penyiapan_langkah_demi_langkah&amp;quot;&amp;gt;Penyiapan — Langkah demi Langkah&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_prasyarat&amp;quot;&amp;gt;Prasyarat&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_tahap_1_membuat_volume&amp;quot;&amp;gt;Tahap 1 : Membuat Volume&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_langkah_2_menjalankan_kontainer_docker&amp;quot;&amp;gt;Langkah 2 : Menjalankan Kontainer Docker&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_langkah_3_mengambil_kunci_publik&amp;quot;&amp;gt;Langkah 3 : Mengambil Kunci Publik&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_langkah_4_simpan_kunci_di_ollama_com&amp;quot;&amp;gt;Langkah 4 : Simpan Kunci di Ollama.com&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_langkah_5_menguji_dengan_model_kecil_gratis&amp;quot;&amp;gt;Langkah 5: Menguji dengan model kecil gratis&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_jebakan_api_v1models&amp;quot;&amp;gt;Jebakan API `/v1/models&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_hasil_dua_sesi_dua_akun_nol_konflik&amp;quot;&amp;gt;Hasil : Dua Sesi, Dua Akun, Nol Konflik&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_pelajaran_yang_dipelajari&amp;quot;&amp;gt;Pelajaran yang Dipelajari&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_untuk_pergi_lebih_jauh&amp;quot;&amp;gt;Untuk pergi lebih jauh&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div id=&amp;quot;preamble&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;waktu membaca : 14 minutes&amp;lt;/div&amp;gt;
&amp;lt;p&amp;gt;Dua langganan Pro, satu mesin, satu kartu bank, dan tidak ada konflik jaringan. Bagaimana Docker memungkinkan saya menggandakan kapasitas inferensi cloud saya tanpa membeli komputer kedua.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_pengantar&amp;quot;&amp;gt;Pengantar&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Saya telah menggunakan Ollama Pro selama beberapa bulan untuk memenuhi sesi OpenCode saya dengan model cloud. Halnya, meski dengan langganan Pro, kita cepat terbatas oleh &amp;lt;strong&amp;gt;rate limiting&amp;lt;/strong&amp;gt; ketika meluncurkan beberapa agen secara paralel. Solusi yang jelas: langganan kedua.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Tapi sekarang, ini adalah drama.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Ollama mengidentifikasi setiap mesin dengan kunci SSH unik — &amp;lt;strong&amp;gt;Device Key&amp;lt;/strong&amp;gt; yang terkenal. Dua instance pada OS yang sama akan berbagi identitas yang sama, dan tidak memungkinkan untuk menghubungkan dua akun Pro ke perangkat yang sama. Dan seperti yang Anda duga, saya tidak akan membeli laptop kedua hanya untuk itu.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Solusi: mencurangi. Membuat Ollama percaya bahwa ia berjalan pada dua mesin yang berbeda, padahal mereka berbagi CPU yang sama, RAM yang sama, dan koneksi jaringan yang sama. Docker akan memberi kita gelembung isolasi yang sempurna.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
!theme plain
skinparam BoxPadding 10
skinparam DefaultFontSize 12

left to right direction

node &amp;quot;Mesin Fisik&amp;quot; {
  frame &amp;quot;Ollama Instansi A (Asli)&amp;quot; as Native {
    database &amp;quot;Identitas\n/usr/share/ollama/.ollama/id_ed25519&amp;quot; as KeyA
    portin &amp;quot;API\n:11434&amp;quot; as PortA
  }

  frame &amp;quot;Ollama Instance B (Docker)&amp;quot; as Docker {
    database &amp;quot;Identitas
~/ollama-b-data/id_ed25519&amp;quot; as KeyB
    portin &amp;quot;API\n:11435&amp;quot; as PortB
  }
}

cloud &amp;quot;Ollama Awan&amp;quot; {
  actor &amp;quot;Akun Profesional A
(email 1)&amp;quot; as AccountA
  actor &amp;quot;Akun Pro B\n(surel 2)&amp;quot; as AccountB
}

KeyA --&amp;amp;gt; AccountA : &amp;quot;Kunci SSH A&amp;quot;
KeyB --&amp;amp;gt; AccountB : &amp;quot;Kunci SSH B&amp;quot;
PortA --&amp;amp;gt; AccountA : &amp;quot;Permintaan A&amp;quot;
PortB --&amp;amp;gt; AccountB : &amp;quot;Permintaan B&amp;quot;

note bottom of Docker
  Conteneur isolé : identité SSH distincte,
  port réseau distinct, volume persistant
end note
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_kenapa_dua_kali_hal_yang_sama&amp;quot;&amp;gt;Kenapa dua kali hal yang sama?&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Sebelum Anda menganggap saya orang gila, izinkan saya menjelaskan kasus penggunaan.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Dengan satu langganan Pro, saya dapat menjalankan model seperti`deepseek-v4-pro:cloud`dalam sesi OpenCode. Masalah muncul ketika saya ingin &amp;lt;strong&amp;gt;dua&amp;lt;/strong&amp;gt; sesi simultan. Kuota rate limiting menyebabkan sesi kedua mengalami lag atau ditolak sepenuhnya.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Dengan dua langganan Pro yang independen:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Sesi OpenCode 1 → Akun Pro A`localhost:11434`)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Sesi OpenCode 2 → akun Pro B`localhost:11435`)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Setiap sesi memiliki kuota masing-masing, konteks masing-masing, dan tidak mengganggu yang lain. Ini adalah multi-processing manusia.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;admonitionblock important&amp;quot;&amp;gt;
&amp;lt;table&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;icon&amp;quot;&amp;gt;
&amp;lt;i class=&amp;quot;fa icon-important&amp;quot; title=&amp;quot;❗&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Dua langganan Pro = dua email yang berbeda. Kartu bank yang sama berfungsi — sistem pembayaran Ollama tidak memblokir langganan berulang dari cara pembayaran yang sama. Saya telah memeriksa.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_jantung_masalah_kunci_perangkat&amp;quot;&amp;gt;Jantung Masalah : Kunci Perangkat&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Ketika Anda menginstal Ollama, sepasang kunci SSH ed25519 dihasilkan di direktori identitas:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;$ cat /usr/share/ollama/.ollama/id_ed25519.pub
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI...&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Kunci ini diunggah ke server Ollama saat`ollama signin`. Dia yang mengatakan « mesin ini milik akun Pro tertentu ». Jika kedua instance Anda berbagi file yang sama`id_ed25519`, mereka memiliki identitas yang sama. Akhir permainan.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Berikan instance kedua kita direktori identitas miliknya sendiri, yang terisolasi dalam volume Docker.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_penyiapan_langkah_demi_langkah&amp;quot;&amp;gt;Penyiapan — Langkah demi Langkah&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_prasyarat&amp;quot;&amp;gt;Prasyarat&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Instalasi native Ollama yang berfungsi (skrip resmi`curl | sh`)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Docker terpasang dan berfungsi&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Portainer atau docker-compose untuk deployment&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Dua akun Ollama dengan dua email yang berbeda&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Saya telah menggunakan versi`0.20.2`— yang disediakan oleh skrip resmi dan gambar Docker yang sesuai.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_tahap_1_membuat_volume&amp;quot;&amp;gt;Tahap 1 : Membuat Volume&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Sebuah direktori sederhana pada host akan berfungsi sebagai volume persisten untuk identitas instance B:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;mkdir -p ~/ollama-b-data&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Folder ini akan dipasang di kontainer Docker sebagai`/root/.ollama`, di tempat Ollama menyimpan kunci identitasnya, riwayat dan modelnya.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_langkah_2_menjalankan_kontainer_docker&amp;quot;&amp;gt;Langkah 2 : Menjalankan Kontainer Docker&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-yaml hljs&amp;quot; data-lang=&amp;quot;yaml&amp;quot;&amp;gt;# docker-compose.yml
services:
  ollama-instance-b:
    image: ollama/ollama:0.20.2
    container_name: ollama-instance-b
    ports:
      - &amp;quot;11435:11434&amp;quot;
    volumes:
      - /home/cheroliv/ollama-b-data:/root/.ollama
    environment:
      - OLLAMA_HOST=0.0.0.0
    restart: always&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Apa yang terjadi di sini:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Pelabuhan&amp;lt;strong&amp;gt;11435&amp;lt;/strong&amp;gt;dari host dipetakan ke&amp;lt;strong&amp;gt;11434&amp;lt;/strong&amp;gt;dalam kontainer. demikian, instance Docker mendengarkan pada`:11435`tanpa konflik dengan instansi native yang mengokupi`:11434`.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Volume`~/ollama-b-data`dipasang pada`/root/.ollama`— ini adalah tempat identitas SSH akan disimpan.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;`OLLAMA_HOST=0.0.0.0`memungkinkan kontainer untuk menerima koneksi eksternal.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Saya telah men-deploy stack ini melalui Portainer, tetapi sebuah sederhana`docker compose up -d`juga berfungsi sama baiknya.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_langkah_3_mengambil_kunci_publik&amp;quot;&amp;gt;Langkah 3 : Mengambil Kunci Publik&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Karena kontainer masih bersih saat pertama kali dijalankan, Ollama otomatis menghasilkan pasangan kunci SSH baru dalam volume. Kita mengambil kunci publik:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;$ docker exec ollama-instance-b cat /root/.ollama/id_ed25519.pub
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDDQ+dvnfmuo49q5O8LOlvgZ39SKORFw47ry9k4H2jPc&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Saya memastikan bahwa kunci ini &amp;lt;strong&amp;gt;berbeda&amp;lt;/strong&amp;gt; dari kunci instance asli:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;# Instance native
$ cat /usr/share/ollama/.ollama/id_ed25519.pub
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGyF...(différente)

# Instance Docker
$ cat ~/ollama-b-data/id_ed25519.pub
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDDQ...(différente)&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Dua kunci yang berbeda, dua identitas terpisah. Permainan selesai.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;admonitionblock note&amp;quot;&amp;gt;
&amp;lt;table&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;icon&amp;quot;&amp;gt;
&amp;lt;i class=&amp;quot;fa icon-note&amp;quot; title=&amp;quot;ℹ️&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Jika kontainer Anda memulai ulang, ia &amp;lt;strong&amp;gt;menggunakan kembali&amp;lt;/strong&amp;gt; kunci yang ada dalam volume. Identitas bersifat persisten. Anda tidak akan kehilangan asosiasi dengan akun Pro.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_langkah_4_simpan_kunci_di_ollama_com&amp;quot;&amp;gt;Langkah 4 : Simpan Kunci di Ollama.com&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Buka &amp;lt;a href=&amp;quot;https://ollama.com/settings/keys&amp;quot; class=&amp;quot;bare&amp;quot;&amp;gt;https://ollama.com/settings/keys&amp;lt;/a&amp;gt; → Tambahkan Kunci SSH. Kita menempelkan kunci publik instance Docker lalu memvalidasi.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Selanjutnya, kami menautkan identitas ini ke akun Pro B:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;$ docker exec -it ollama-instance-b ollama signin&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Browser terbuka, kita masuk dengan email akun B, dan token terhubung dengan kunci SSH kontainer. Kita memastikan bahwa semuanya OK :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;$ docker exec -it ollama-instance-b ollama signin
User: cherolivpro&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_langkah_5_menguji_dengan_model_kecil_gratis&amp;quot;&amp;gt;Langkah 5: Menguji dengan model kecil gratis&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Sebelum saya membahas harga berlangganan Pro, saya ingin yakin bahwa pipa berfungsi. Saya menarik sebuah model lokal kecil dan gratis untuk memvalidasi konektivitas :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;$ docker exec ollama-instance-b ollama pull qwen3:0.6b
pulling manifest
pulling 7f4030143c1c: 100% ▕██████████████████▏ 522 MB
success

$ curl -s http://localhost:11435/api/tags
{&amp;quot;models&amp;quot;:[{&amp;quot;name&amp;quot;:&amp;quot;qwen3:0.6b&amp;quot;,&amp;quot;model&amp;quot;:&amp;quot;qwen3:0.6b&amp;quot;,...}]}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Pelabuhan`11435`mengjawab, model telah disajikan. Instans B masih hidup.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Setelah saya merasa tenang, saya beralih ke pull dari model cloud Pro yang sebenarnya:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;$ docker exec ollama-instance-b ollama pull deepseek-v4-pro:cloud
pulling manifest
pulling 31c3059e137e: 100% ▕██████████████████▏  344 B
success&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;344 byte untuk manifest model cloud — normal, inferensi dilakukan di sisi server, bukan lokal. Dan inilah ujian terakhir:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;$ curl -s http://localhost:11435/v1/chat/completions \
  -H &amp;quot;Content-Type: application/json&amp;quot; \
  -d &amp;#39;{&amp;quot;model&amp;quot;:&amp;quot;deepseek-v4-pro:cloud&amp;quot;,&amp;quot;messages&amp;quot;:[{&amp;quot;role&amp;quot;:&amp;quot;user&amp;quot;,&amp;quot;content&amp;quot;:&amp;quot;Dis bonjour en une phrase courte.&amp;quot;}]}&amp;#39;

{
  &amp;quot;id&amp;quot;: &amp;quot;chatcmpl-480&amp;quot;,
  &amp;quot;model&amp;quot;: &amp;quot;deepseek-v4-pro&amp;quot;,
  &amp;quot;choices&amp;quot;: [{
    &amp;quot;message&amp;quot;: { &amp;quot;content&amp;quot;: &amp;quot;Bonjour !&amp;quot; },
    &amp;quot;finish_reason&amp;quot;: &amp;quot;stop&amp;quot;
  }],
  &amp;quot;usage&amp;quot;: { &amp;quot;total_tokens&amp;quot;: 181 }
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Halo juga, instance B.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_jebakan_api_v1models&amp;quot;&amp;gt;Jebakan API `/v1/models&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Saya kehilangan 20 menit karena detail bodoh. Ketika saya mengonfigurasi penyedia`ollama-b`di OpenCode, tidak ada yang muncul di pemilih model. Tidak ada. Tidak apa-apa.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Alasan? API`/v1/models`dari instance B kembali`{&amp;quot;object&amp;quot;:&amp;quot;list&amp;quot;,&amp;quot;data&amp;quot;:null}`alih-alih`{&amp;quot;object&amp;quot;:&amp;quot;list&amp;quot;,&amp;quot;data&amp;quot;:[]}`tidak ada model yang ditarik. Nilai`null`membuat parsing crash di sisi OpenCode, yang tidak menampilkan provider.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Solusi: mendeklarasikan model &amp;lt;strong&amp;gt;eksplisit&amp;lt;/strong&amp;gt; dalam konfigurasi OpenCode alih-alih bergantung pada penemuan dinamis.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;~/.config/opencode/opencode.json&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-json hljs&amp;quot; data-lang=&amp;quot;json&amp;quot;&amp;gt;{
  &amp;quot;$schema&amp;quot;: &amp;quot;https://opencode.ai/config.json&amp;quot;,
  &amp;quot;provider&amp;quot;: {
    &amp;quot;ollama&amp;quot;: {
      &amp;quot;npm&amp;quot;: &amp;quot;@ai-sdk/openai-compatible&amp;quot;,
      &amp;quot;name&amp;quot;: &amp;quot;Ollama (local)&amp;quot;,
      &amp;quot;options&amp;quot;: { &amp;quot;baseURL&amp;quot;: &amp;quot;http://localhost:11434/v1&amp;quot; },
      &amp;quot;models&amp;quot;: {
        &amp;quot;gemma4:e2b&amp;quot;: { &amp;quot;name&amp;quot;: &amp;quot;Gemma 4 E2B (local)&amp;quot; }
      }
    },
    &amp;quot;ollama-b&amp;quot;: {
      &amp;quot;npm&amp;quot;: &amp;quot;@ai-sdk/openai-compatible&amp;quot;,
      &amp;quot;name&amp;quot;: &amp;quot;Ollama Instance B (Docker)&amp;quot;,
      &amp;quot;options&amp;quot;: { &amp;quot;baseURL&amp;quot;: &amp;quot;http://localhost:11435/v1&amp;quot; },
      &amp;quot;models&amp;quot;: {
        &amp;quot;qwen3:0.6b&amp;quot;: { &amp;quot;name&amp;quot;: &amp;quot;Qwen3 0.6B (B)&amp;quot; },
        &amp;quot;deepseek-v4-pro:cloud&amp;quot;: { &amp;quot;name&amp;quot;: &amp;quot;DeepSeek V4 Pro (B)&amp;quot; }
      }
    }
  }
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Dengan pernyataan eksplisit ini, OpenCode segera melihat provider B dan model-modelnya. Merestart sesi, dan selektor`/models`Menampilkan kedua instance berdampingan.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;admonitionblock tip&amp;quot;&amp;gt;
&amp;lt;table&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;icon&amp;quot;&amp;gt;
&amp;lt;i class=&amp;quot;fa icon-tip&amp;quot; title=&amp;quot;💡&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Jika Anda tidak melihat provider khusus Anda di`/models`, jangan buang tiga jam untuk me-restart terminal Anda. Deklarasikan model secara manual di`opencode.json`— Ini menyelesaikan masalah secara langsung.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_hasil_dua_sesi_dua_akun_nol_konflik&amp;quot;&amp;gt;Hasil : Dua Sesi, Dua Akun, Nol Konflik&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Pada akhirnya, saya dapat meluncurkan dua sesi OpenCode secara bersamaan:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-text hljs&amp;quot; data-lang=&amp;quot;text&amp;quot;&amp;gt;Session 1 → /models → Ollama (local) → deepseek-v4-pro:cloud → Compte A
Session 2 → /models → Ollama Instance B (Docker) → deepseek-v4-pro:cloud → Compte B&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Setiap sesi memiliki kuota dan batas laju masing-masing, dan tidak saling mengganggu. Meski mesin sama, kartu biru sama, dua email berbeda.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Dan yang terbaik? Kontainer Docker sedang`restart: always`. Dia tetap bertahan setelah reboot sistem tanpa perlu intervensi manual.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_pelajaran_yang_dipelajari&amp;quot;&amp;gt;Pelajaran yang Dipelajari&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;olist arabic&amp;quot;&amp;gt;
&amp;lt;ol class=&amp;quot;arabic&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Docker mengisolasi semua, bahkan identitas&amp;lt;/strong&amp;gt;— Bind mount volume sederhana sudah cukup untuk memberikan kontainer kumpulan kunci SSH miliknya sendiri, sehingga membuatnya tak dapat dibedakan dari mesin fisik yang berbeda dari perspektif Ollama.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Dua email, CB yang sama&amp;lt;/strong&amp;gt;— Ollama tidak memblokir langganan berganda dari metode pembayaran yang sama. Hanya email yang harus berbeda.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Coba gratis sebelum membayar&amp;lt;/strong&amp;gt; — Un &amp;lt;code&amp;gt;ollama pull qwen3:0.6b&amp;lt;/code&amp;gt; (modèle libre, 522 Mo) permet de valider toute la chaîne réseau sans débourser un centime. Vous validez le plomberie d&amp;amp;#8217;abord, vous activez le Pro ensuite.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;/v1/models` dengan &amp;lt;code&amp;gt;data: null&amp;lt;/code&amp;gt; merusak OpenCode&amp;lt;/strong&amp;gt;— Jika Anda mengonfigurasi provider khusus dengan`&amp;quot;models&amp;quot;: {}`, OpenCode mencoba menemukan model melalui API. Jika API merespons`data: null`, penyedia tidak muncul. Deklarasikan model-model secara eksplisit.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Dua akun, itu adalah multi-processing manusia&amp;lt;/strong&amp;gt;— Satu akun Pro = satu sesi OpenCode aktif. Dua akun = dua sesi paralel. Bagi seseorang yang mengelola beberapa proyek Gradle secara bersamaan, itu merupakan game changer.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_untuk_pergi_lebih_jauh&amp;quot;&amp;gt;Untuk pergi lebih jauh&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://ollama.com/settings/keys&amp;quot;&amp;gt;Ollama Settings — Pengelolaan kunci SSH&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://docs.ollama.com/&amp;quot;&amp;gt;Dokumentasi resmi Ollama&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://hub.docker.com/r/ollama/ollama&amp;quot;&amp;gt;Image Docker Ollama di Docker Hub&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://opencode.ai/docs/providers/#ollama&amp;quot;&amp;gt;OpenCode — Penyedia Konfigurasi Ollama&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;hr&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;em&amp;gt;Artikel dipublikasikan pada 2026-05-08&amp;lt;/em&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>Instal terminal Kitty dan atur Zsh sebagai shell bawaan di Xubuntu</title>
            <link >https://cheroliv.com/blog/2025/0080_kitty_term_post.html</link>
            <pubDate>Thu, 7 May 2026 00:00:00 +0000</pubDate>
            <guid isPermaLink="false">blog/2025/0080_kitty_term_post.html</guid>
            <description>&amp;lt;div id=&amp;quot;toc&amp;quot; class=&amp;quot;toc&amp;quot;&amp;gt;
&amp;lt;div id=&amp;quot;toctitle&amp;quot;&amp;gt;Sommaire&amp;lt;/div&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel1&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_mengapa_memilih_kitty_dan_zsh&amp;quot;&amp;gt;Mengapa memilih Kitty dan Zsh?&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_prasyarat_sistem&amp;quot;&amp;gt;Prasyarat sistem&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_pengunduhan_dan_pemasangan_kitty&amp;quot;&amp;gt;Pengunduhan dan pemasangan Kitty&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_unduh_skrip_instalasi&amp;quot;&amp;gt;Unduh skrip instalasi&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_pembuatan_tautan_simbolis&amp;quot;&amp;gt;Pembuatan tautan simbolis&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_integrasi_kitty_di_xubuntu&amp;quot;&amp;gt;Integrasi Kitty di Xubuntu&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_menyalin_file_desktop&amp;quot;&amp;gt;Menyalin file desktop&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_ubah_file_desktop&amp;quot;&amp;gt;Ubah file desktop&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_mengaitkan_kitty_sebagai_terminal_default_opsional&amp;quot;&amp;gt;Mengaitkan Kitty sebagai terminal default (opsional)&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_instalasi_zsh&amp;quot;&amp;gt;Instalasi Zsh&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_tetapkan_zsh_sebagai_shell_default&amp;quot;&amp;gt;Tetapkan Zsh sebagai shell default&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_metode_global_untuk_semua_terminal&amp;quot;&amp;gt;Metode global (untuk semua terminal)&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_metode_khusus_untuk_kitty&amp;quot;&amp;gt;Metode khusus untuk Kitty&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_konfigurasi_zsh_opsional&amp;quot;&amp;gt;Konfigurasi Zsh (opsional)&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_buat_file_zshrc&amp;quot;&amp;gt;Buat file .zshrc&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_contoh_konfigurasi_minimal&amp;quot;&amp;gt;Contoh konfigurasi minimal&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_ringkasan_workflow_lengkap&amp;quot;&amp;gt;Ringkasan workflow lengkap&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_verifikasi_akhir&amp;quot;&amp;gt;Verifikasi akhir&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_membuka_file_konfigurasi&amp;quot;&amp;gt;📂 Membuka file konfigurasi&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_️_tambah_atau_ubah_font_size&amp;quot;&amp;gt;🖋️ Tambah atau ubah `font_size&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_menyimpan_dan_mulai_ulang_kitty&amp;quot;&amp;gt;💾 Menyimpan dan mulai ulang Kitty&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_penyesuaian_dinamis_zoom_sementara&amp;quot;&amp;gt;✅ Penyesuaian dinamis (Zoom sementara)&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_instalasi_dan_konfigurasi_oh_my_zsh&amp;quot;&amp;gt;Instalasi dan konfigurasi Oh My Zsh&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_instalasi_oh_my_zsh&amp;quot;&amp;gt;Instalasi Oh My Zsh&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_mengubah_tema&amp;quot;&amp;gt;Mengubah tema&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_verifikasi&amp;quot;&amp;gt;Verifikasi&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_diagram_kasus_penggunaan&amp;quot;&amp;gt;Diagram kasus penggunaan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_aktifkan_transparansi_di_kitty&amp;quot;&amp;gt;Aktifkan transparansi di Kitty&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_prasyarat_untuk_transparansi&amp;quot;&amp;gt;Prasyarat untuk transparansi&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_konfigurasi_transparansi_dalam_kitty_conf&amp;quot;&amp;gt;Konfigurasi transparansi dalam kitty.conf&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_mulai_ulang_kitty&amp;quot;&amp;gt;Mulai ulang Kitty&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_suplemen_yang_mungkin&amp;quot;&amp;gt;Suplemen yang mungkin&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_diagram_urutan_inisialisasi_kitty_dengan_transparansi&amp;quot;&amp;gt;Diagram urutan - Inisialisasi Kitty dengan transparansi&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_keterbatasan_yang_diketahui&amp;quot;&amp;gt;Keterbatasan yang diketahui&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_nonaktifkan_bip_suara_audible_bell&amp;quot;&amp;gt;Nonaktifkan bip suara (audible bell)&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_pembaruan_kitty&amp;quot;&amp;gt;Pembaruan Kitty&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_referensi&amp;quot;&amp;gt;Referensi&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_kesimpulan&amp;quot;&amp;gt;Kesimpulan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div id=&amp;quot;preamble&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Panduan ini menjelaskan langkah demi langkah cara menginstal terminal &amp;lt;strong&amp;gt;kitty&amp;lt;/strong&amp;gt; di Xubuntu, mengonfigurasi dengan benar, dan mengganti bash dengan &amp;lt;strong&amp;gt;zsh&amp;lt;/strong&amp;gt; sebagai shell default. Proyek kitty tersedia di sini: &amp;lt;a href=&amp;quot;https://github.com/kovidgoyal/kitty&amp;quot; class=&amp;quot;bare&amp;quot;&amp;gt;https://github.com/kovidgoyal/kitty&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;em&amp;gt;&amp;lt;em&amp;gt;_ Kitty adalah terminal modern, cepat dan dapat dikonfigurasi, sangat sesuai untuk pengembang yang sangat menuntut. &amp;lt;/em&amp;gt;_&amp;lt;/em&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_mengapa_memilih_kitty_dan_zsh&amp;quot;&amp;gt;Mengapa memilih Kitty dan Zsh?&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Kitty membedakan dengan:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Performa lebih tinggi dengan akselerasi GPU,&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Sebuah konfigurasi yang sepenuhnya berbasis pada satu file teks tunggal,&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Dukungan excellent untuk font dan ligatur.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Zsh justru merupakan shell interaktif yang kuat yang menawarkan:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Penyelesaian cerdas,&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Sebuah prompt yang dapat dipersonalisasi,&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Kompatibilitas besar dengan bash.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Dengan menggabungkan Kitty dan Zsh, Anda mendapatkan lingkungan yang produktif dan estetis.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
title Arsitektur sederhana Kitty + Zsh
actor User

User --&amp;amp;gt; Kitty : Lance le terminal
Kitty --&amp;amp;gt; Zsh : Initialise le shell interactif
Zsh --&amp;amp;gt; System : Interagit avec le système (fichiers, commandes)
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;admonitionblock note&amp;quot;&amp;gt;
&amp;lt;table&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;icon&amp;quot;&amp;gt;
&amp;lt;i class=&amp;quot;fa icon-note&amp;quot; title=&amp;quot;Note&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Panduan ini sengaja terbatas pada penginstalan melalui skrip dan Xubuntu.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_prasyarat_sistem&amp;quot;&amp;gt;Prasyarat sistem&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Sebelum memulai, pastikan Anda memiliki :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Xubuntu terinstal,&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;akses ke internet,&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;sebuah shell bash yang berfungsi untuk menjalankan skrip.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Periksa lingkungan Anda :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;uname -a
lsb_release -a&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_pengunduhan_dan_pemasangan_kitty&amp;quot;&amp;gt;Pengunduhan dan pemasangan Kitty&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Kitty merekomendasikan menginstalnya melalui skrip resmi yang menempatkan file-file di`~/.local/kitty.app`.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;admonitionblock important&amp;quot;&amp;gt;
&amp;lt;table&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;icon&amp;quot;&amp;gt;
&amp;lt;i class=&amp;quot;fa icon-important&amp;quot; title=&amp;quot;Important&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Ini metode adalah independen dari manajer paket (tanpa`apt`).&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_unduh_skrip_instalasi&amp;quot;&amp;gt;Unduh skrip instalasi&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Jalankan perintah berikut:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;curl -L https://sw.kovidgoyal.net/kitty/installer.sh | sh /dev/stdin&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Perintah ini melakukan :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;unduhan biner,&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;ekstraksi dalam`~/.local/kitty.app`,&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;pembuatan tautan simbolik.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
title Unduhan dan instalasi Kitty
start
:curl -L installer.sh;
:sh /dev/stdin;
:Décompression dans ~/.local/kitty.app;
:create symlinks;
stop
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_pembuatan_tautan_simbolis&amp;quot;&amp;gt;Pembuatan tautan simbolis&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Agar dapat memanggil`kitty`dari terminal, buat sebuah tautan simbolik :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;ln -s ~/.local/kitty.app/bin/kitty ~/.local/bin/kitty&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Tambahkan`~/.local/bin`tambahkan ke PATH Anda jika belum dilakukan :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;echo &amp;#39;export PATH=&amp;quot;$HOME/.local/bin:$PATH&amp;quot;&amp;#39; &amp;amp;gt;&amp;amp;gt; ~/.zshrc
source ~/.zshrc&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Anda dapat memeriksa instalasi :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;kitty --version&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_integrasi_kitty_di_xubuntu&amp;quot;&amp;gt;Integrasi Kitty di Xubuntu&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Agar Kitty muncul di menu aplikasi:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_menyalin_file_desktop&amp;quot;&amp;gt;Menyalin file desktop&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Salin berkas`.desktop`disediakan :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;cp ~/.local/kitty.app/share/applications/kitty.desktop ~/.local/share/applications/&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_ubah_file_desktop&amp;quot;&amp;gt;Ubah file desktop&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Edit berkas :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;nano ~/.local/share/applications/kitty.desktop&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Periksa atau ubah baris berikut :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-none hljs&amp;quot;&amp;gt;[Desktop Entry]
Version=1.0
Type=Application
Name=Kitty Terminal
Comment=Fast GPU-based terminal emulator
Exec=/home/$USER/.local/kitty.app/bin/kitty
Icon=kitty
Categories=System;TerminalEmulator;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Ganti`YOURUSER`dengan nama pengguna Anda.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
title Proses integrasi Kitty dalam menu Xubuntu
start
:Copier .desktop;
:Editer les chemins;
:Sauvegarder;
:Menu Applications;
stop
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_mengaitkan_kitty_sebagai_terminal_default_opsional&amp;quot;&amp;gt;Mengaitkan Kitty sebagai terminal default (opsional)&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Agar Kitty menjadi terminal yang diluncurkan secara default :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;sudo update-alternatives --install /usr/bin/x-terminal-emulator x-terminal-emulator ~/.local/kitty.app/bin/kitty 50
sudo update-alternatives --config x-terminal-emulator&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Pilih`kitty`dalam menu yang ditawarkan&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_instalasi_zsh&amp;quot;&amp;gt;Instalasi Zsh&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Jika Zsh belum terinstal, jalankan :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;sudo apt install zsh&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Periksa instalasi:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-none hljs&amp;quot;&amp;gt;zsh --version&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_tetapkan_zsh_sebagai_shell_default&amp;quot;&amp;gt;Tetapkan Zsh sebagai shell default&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Anda dapat mengubah shell secara global atau hanya di Kitty.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_metode_global_untuk_semua_terminal&amp;quot;&amp;gt;Metode global (untuk semua terminal)&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Identifikasi jalur mutlak:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;which zsh&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Secara umum`/usr/bin/zsh`.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Ubah shell default :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;chsh -s /usr/bin/zsh&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Keluar dan masuk kembali.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
title Perubahan shell global
actor User

User --&amp;amp;gt; Bash : Session actuelle
User --&amp;amp;gt; System : chsh -s /usr/bin/zsh
System --&amp;amp;gt; &amp;quot;/etc/passwd&amp;quot; : Modifie le shell
note right of &amp;quot;/etc/passwd&amp;quot; : Shell par défaut mis à jour
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_metode_khusus_untuk_kitty&amp;quot;&amp;gt;Metode khusus untuk Kitty&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Jika Anda hanya ingin mengubah shell di Kitty, edit file konfigurasi :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;nano ~/.config/kitty/kitty.conf&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Tambahkan baris ini :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-text hljs&amp;quot; data-lang=&amp;quot;text&amp;quot;&amp;gt;shell zsh&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Simpan dan restart Kitty.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_konfigurasi_zsh_opsional&amp;quot;&amp;gt;Konfigurasi Zsh (opsional)&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Untuk menyesuaikan Zsh Anda :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_buat_file_zshrc&amp;quot;&amp;gt;Buat file .zshrc&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;touch ~/.zshrc
nano ~/.zshrc&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_contoh_konfigurasi_minimal&amp;quot;&amp;gt;Contoh konfigurasi minimal&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;export ZSH_THEME=&amp;quot;robbyrussell&amp;quot;
export PATH=&amp;quot;$HOME/.local/bin:$PATH&amp;quot;
alias ll=&amp;quot;ls -la&amp;quot;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_ringkasan_workflow_lengkap&amp;quot;&amp;gt;Ringkasan workflow lengkap&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Skema di bawah ini merangkum seluruh proses:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
title Alur kerja instalasi dan konfigurasi
start
:Téléchargement script Kitty;
:Installation dans ~/.local;
:Création symlink;
:Ajout ~/.local/bin au PATH;
if (Zsh installé ?) then (non)
  :sudo apt install zsh;
endif
:Configurer Kitty.desktop;
:Optionnel - changer terminal par défaut;
:Optionnel - chsh -s /usr/bin/zsh;
stop
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_verifikasi_akhir&amp;quot;&amp;gt;Verifikasi akhir&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Jalankan Kitty :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;kitty&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Anda seharusnya melihat bahwa`zsh`aktif :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;echo $SHELL&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Hasil yang diharapkan :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;/usr/bin/zsh&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Untuk mengubah ukuran font di&amp;lt;strong&amp;gt;Kitty&amp;lt;/strong&amp;gt;, harus menyesuaikan parameter`font_size`dalam file konfigurasi. Berikut adalah prosedur detailnya:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_membuka_file_konfigurasi&amp;quot;&amp;gt;📂 Membuka file konfigurasi&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;File konfigurasi utama biasanya berada di:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;~/.config/kitty/kitty.conf&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Jika file ini belum ada, Anda dapat membuatnya. (No source text provided, so nothing to translate.)&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;nano ~/.config/kitty/kitty.conf&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_️_tambah_atau_ubah_font_size&amp;quot;&amp;gt;🖋️ Tambah atau ubah `font_size&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Tambahkan direktif berikut, atau ubah jika sudah ada:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-text hljs&amp;quot; data-lang=&amp;quot;text&amp;quot;&amp;gt;font_size 14.0&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;di sini`14.0`sesuai dengan ukuran yang diinginkan. Anda dapat memasukkan nilai desimal apa pun, misalnya`12.0`, &amp;lt;code&amp;gt;15.5&amp;lt;/code&amp;gt;, dst.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_menyimpan_dan_mulai_ulang_kitty&amp;quot;&amp;gt;💾 Menyimpan dan mulai ulang Kitty&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Catat (&amp;lt;code&amp;gt;Ctrl + O&amp;lt;/code&amp;gt;, lalu`Entrée`) dan tinggalkan (&amp;lt;code&amp;gt;Ctrl + X&amp;lt;/code&amp;gt;) jika Anda menggunakan`nano`.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Tutup semua jendela Kitty, kemudian restart terminal agar perubahan berlaku.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_penyesuaian_dinamis_zoom_sementara&amp;quot;&amp;gt;✅ Penyesuaian dinamis (Zoom sementara)&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Jika Anda ingin melakukan zoom in/out sementara tanpa mengubah konfigurasi, gunakan pintasan keyboard default:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Perbesar ukuran font&amp;lt;/strong&amp;gt;(empty)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;code&amp;gt;Ctrl + Shift + =&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Kurangi ukuran font&amp;lt;/strong&amp;gt;:&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;code&amp;gt;Ctrl + Shift + -&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Reset ukuran&amp;lt;/strong&amp;gt;:&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;code&amp;gt;Ctrl + Shift + Backspace&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Pengaturan ini tidak persisten setelah terminal ditutup.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_instalasi_dan_konfigurasi_oh_my_zsh&amp;quot;&amp;gt;Instalasi dan konfigurasi Oh My Zsh&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Setelah Zsh terinstal dan dikonfigurasi sebagai shell default, Anda dapat memperkaya lingkungan Anda dengan &amp;lt;strong&amp;gt;Oh My Zsh&amp;lt;/strong&amp;gt;, sebuah framework manajemen konfigurasi Zsh yang menawarkan:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;sebuah kumpulan tema untuk prompt,&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;banyak plugin,&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;organisasi yang jelas dari konfigurasi.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_instalasi_oh_my_zsh&amp;quot;&amp;gt;Instalasi Oh My Zsh&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Pastikan Zsh aktif:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;zsh --version&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Jika diperlukan, mulai :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;zsh&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Kemudian unduh dan jalankan skrip instalasi resmi:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;sh -c &amp;quot;$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)&amp;quot;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Skrip ini:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Buat folder`~/.oh-my-zsh`,&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;cadangan opsional file Anda`~/.zshrc`,&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;mengaktifkan secara otomatis prompt baru.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_mengubah_tema&amp;quot;&amp;gt;Mengubah tema&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Buka file konfigurasi Anda :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;nano ~/.zshrc&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Ubah variabel`ZSH_THEME`untuk memilih tema lain (misalnya`agnoster`) :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;ZSH_THEME=&amp;quot;agnoster&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Simpan lalu muat ulang konfigurasi :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;source ~/.zshrc&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_verifikasi&amp;quot;&amp;gt;Verifikasi&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Jalankan Kitty dan pastikan bahwa prompt telah berubah dengan baik. Anda dapat memastikan bahwa Oh My Zsh aktif dengan menjalankan :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;echo $ZSH&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Hasilnya seharusnya serupa dengan :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;/home/pengguna_anda/.oh-my-zsh&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_diagram_kasus_penggunaan&amp;quot;&amp;gt;Diagram kasus penggunaan&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
title Use case penginstalan dan penggunaan Oh My Zsh
actor Utilisateur as User

package &amp;quot;Terminal Kitty&amp;quot; {
  component &amp;quot;Zsh Shell&amp;quot; as Zsh
}

component &amp;quot;kerangka kerja Oh My Zsh&amp;quot; as OhMyZsh

User --&amp;amp;gt; Kitty : Lance Kitty
Kitty --&amp;amp;gt; Zsh : Initialise Zsh
Zsh --&amp;amp;gt; OhMyZsh : Charge configuration (~/.zshrc, thèmes, plugins)
User --&amp;amp;gt; OhMyZsh : Personnalise le thème et les plugins

@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_aktifkan_transparansi_di_kitty&amp;quot;&amp;gt;Aktifkan transparansi di Kitty&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Salah satu fitur paling digemari dari Kitty adalah kemampuan menambahkan &amp;lt;strong&amp;gt;transparensi&amp;lt;/strong&amp;gt; pada latar belakang terminal. Ini memungkinkan Anda untuk melihat sedikit lingkungan di bawahnya (jendela, desktop, dll.), yang sangat berguna saat multitasking.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_prasyarat_untuk_transparansi&amp;quot;&amp;gt;Prasyarat untuk transparansi&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Transparansi dalam Kitty berdasarkan le&amp;lt;strong&amp;gt;komposer jendela&amp;lt;/strong&amp;gt;digunakan oleh lingkungan grafis. Di Xubuntu (berbasis XFCE), pastikan bahwa&amp;lt;strong&amp;gt;komposisi diaktifkan&amp;lt;/strong&amp;gt;.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;Memeriksa :&amp;lt;/div&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;klik kanan di desktop &amp;amp;gt; Pengaturan &amp;amp;gt;&amp;lt;strong&amp;gt;Pengaturan manajer jendela&amp;lt;/strong&amp;gt;,&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;tab&amp;lt;strong&amp;gt;komposer&amp;lt;/strong&amp;gt;: kotak &amp;lt;strong&amp;gt;&amp;quot;Activer la composition&amp;quot;&amp;lt;/strong&amp;gt; harus dicentang.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
title Use Case - Mengaktifkan transparansi di Kitty
actor Utilisateur

rectangle &amp;quot;Lingkungan grafis (XFCE/Xubuntu)&amp;quot; {
  usecase &amp;quot;Mengaktifkan komposisi&amp;quot; as UC1
}

rectangle &amp;quot;Terminal Kitty&amp;quot; {
  usecase &amp;quot;Menentukan transparansi dalam kitty.conf&amp;quot; as UC2
}

Utilisateur --&amp;amp;gt; UC1
Utilisateur --&amp;amp;gt; UC2

@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_konfigurasi_transparansi_dalam_kitty_conf&amp;quot;&amp;gt;Konfigurasi transparansi dalam kitty.conf&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Buka file konfigurasi Anda`~/.config/kitty/kitty.conf` :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;nano ~/.config/kitty/kitty.conf&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Tambahkan atau ubah baris berikut:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-conf hljs&amp;quot; data-lang=&amp;quot;conf&amp;quot;&amp;gt;background_opacity 0.85&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Parameter ini menerima nilai antara:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;code&amp;gt;1.0&amp;lt;/code&amp;gt;→ opak (tidak ada transparansi),&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;code&amp;gt;0.0&amp;lt;/code&amp;gt;→ sangat transparan (tidak dapat digunakan)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;nilai tengah seperti`0.90`, &amp;lt;code&amp;gt;0.75&amp;lt;/code&amp;gt;, dll.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Sebuah kompromi visual yang baik biasanya berada di antara`0.85` et &amp;lt;code&amp;gt;0.95&amp;lt;/code&amp;gt;.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_mulai_ulang_kitty&amp;quot;&amp;gt;Mulai ulang Kitty&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Tutup semua instance Kitty, lalu buka kembali jendela baru agar perubahan berlaku :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;kitty&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_suplemen_yang_mungkin&amp;quot;&amp;gt;Suplemen yang mungkin&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Jika Anda menggunakan latar belakang terminal khusus, Anda juga dapat mengatur warna latar belakang dengan transparansi (dalam RGBA):&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-conf hljs&amp;quot; data-lang=&amp;quot;conf&amp;quot;&amp;gt;background #1e1e2eff&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Komponen terakhir ()&amp;lt;code&amp;gt;ff&amp;lt;/code&amp;gt;) menunjukkan opasitas (de`00` à &amp;lt;code&amp;gt;ff&amp;lt;/code&amp;gt;)&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_diagram_urutan_inisialisasi_kitty_dengan_transparansi&amp;quot;&amp;gt;Diagram urutan - Inisialisasi Kitty dengan transparansi&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
title Urutan startup Kitty dengan transparansi
actor User
participant &amp;quot;Kitty&amp;quot; as Kitty
participant &amp;quot;XFCE komposer&amp;quot; as XFCE
participant &amp;quot;GPU/pengelola jendela&amp;quot; as WM

User -&amp;amp;gt; Kitty : Lance Kitty
Kitty -&amp;amp;gt; kitty.conf : Lit configuration
Kitty -&amp;amp;gt; XFCE : Vérifie composition activée
XFCE --&amp;amp;gt; Kitty : Composition = active
Kitty -&amp;amp;gt; WM : Demande allocation fenêtrée transparente
WM --&amp;amp;gt; Kitty : OK (rendu avec alpha &amp;amp;lt; 1)
Kitty -&amp;amp;gt; User : Affiche terminal translucide

@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_keterbatasan_yang_diketahui&amp;quot;&amp;gt;Keterbatasan yang diketahui&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;admonitionblock warning&amp;quot;&amp;gt;
&amp;lt;table&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;icon&amp;quot;&amp;gt;
&amp;lt;i class=&amp;quot;fa icon-warning&amp;quot; title=&amp;quot;Warning&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Transluciditas tidak berfungsi jika: - kompositor XFCE nonaktif, - manajer jendela yang tidak kompatibel sedang digunakan, - Anda menggunakan Kitty dalam mode &amp;lt;strong&amp;gt;tmux&amp;lt;/strong&amp;gt; di latar belakang tanpa dukungan transparansi.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_nonaktifkan_bip_suara_audible_bell&amp;quot;&amp;gt;Nonaktifkan bip suara (audible bell)&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Secara default, Kitty mengeluarkan suara &amp;lt;strong&amp;gt;bip&amp;lt;/strong&amp;gt; ketika perintah salah dimasukkan atau sebuah acara terminal memicu-nya. Bunyi ini dapat dengan cepat menjadi mengganggu, terutama di lingkungan pengembangan yang intensif.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Untuk menonaktifkan perilaku ini, tambahkan direktif berikut ke file`~/.config/kitty/kitty.conf`( empty )&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-conf hljs&amp;quot; data-lang=&amp;quot;conf&amp;quot;&amp;gt;audible_bell no&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;admonitionblock note&amp;quot;&amp;gt;
&amp;lt;table&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;icon&amp;quot;&amp;gt;
&amp;lt;i class=&amp;quot;fa icon-note&amp;quot; title=&amp;quot;Note&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Anda juga dapat mengaktifkan &amp;lt;strong&amp;gt;umpan balik visual&amp;lt;/strong&amp;gt; sebagai pengganti bip suara dengan :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-conf hljs&amp;quot; data-lang=&amp;quot;conf&amp;quot;&amp;gt;visual_bell yes&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_pembaruan_kitty&amp;quot;&amp;gt;Pembaruan Kitty&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Karena Kitty diinstal melalui skrip dan bukan melalui manajer paket, pembaruan dilakukan dengan menjalankan ulang skrip instalasi resmi. Ini memungkinkan Anda mengunduh versi terbaru dan memperbarui instalasi Anda yang ada di`~/.local/kitty.app`.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;admonitionblock note&amp;quot;&amp;gt;
&amp;lt;table&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;icon&amp;quot;&amp;gt;
&amp;lt;i class=&amp;quot;fa icon-note&amp;quot; title=&amp;quot;Note&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;akhiran`.app`di jalan`~/.local/kitty.app`adalah konvensi instalasi Kitty di Linux melalui skrip resmi-nya, dan tidak menunjukkan sama sekali kompatibilitas eksklusif dengan macOS.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;admonitionblock important&amp;quot;&amp;gt;
&amp;lt;table&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;icon&amp;quot;&amp;gt;
&amp;lt;i class=&amp;quot;fa icon-important&amp;quot; title=&amp;quot;Important&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Sebelum menjalankan perintah pembaruan, pastikan Anda berada di direktori rumah Anda (&amp;lt;code&amp;gt;~&amp;lt;/code&amp;gt;)&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Jalankan perintah berikut dari direktori home Anda :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;curl -L https://sw.kovidgoyal.net/kitty/installer.sh | sh /dev/stdin&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Skrip ini akan mengurus :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Unduh binari versi baru.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Ekstrak file di dalam`~/.local/kitty.app`.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Memperbarui tautan simbolik jika diperlukan.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Setelah pembaruan, disarankan untuk menutup semua instance Kitty dan menjalankannya kembali untuk memastikan bahwa versi baru diterima dengan baik.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_referensi&amp;quot;&amp;gt;Referensi&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Jika Anda ingin mempelajari lebih dalam tentang personalisasi, lihat dokumentasi resmi:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Kitty : &amp;lt;a href=&amp;quot;https://sw.kovidgoyal.net/kitty/&amp;quot; class=&amp;quot;bare&amp;quot;&amp;gt;https://sw.kovidgoyal.net/kitty/&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://sw.kovidgoyal.net/kitty/conf/#opt-kitty.background_opacity&amp;quot; class=&amp;quot;bare&amp;quot;&amp;gt;https://sw.kovidgoyal.net/kitty/conf/#opt-kitty.background_opacity&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://sw.kovidgoyal.net/kitty/conf/#opt-kitty.audible_bell&amp;quot; class=&amp;quot;bare&amp;quot;&amp;gt;https://sw.kovidgoyal.net/kitty/conf/#opt-kitty.audible_bell&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Zsh : &amp;lt;a href=&amp;quot;https://zsh.sourceforge.io/&amp;quot; class=&amp;quot;bare&amp;quot;&amp;gt;https://zsh.sourceforge.io/&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Github: &amp;lt;a href=&amp;quot;https://github.com/kovidgoyal/kitty&amp;quot; class=&amp;quot;bare&amp;quot;&amp;gt;https://github.com/kovidgoyal/kitty&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Zsh: &amp;lt;a href=&amp;quot;https://zsh.sourceforge.io/&amp;quot; class=&amp;quot;bare&amp;quot;&amp;gt;https://zsh.sourceforge.io/&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://ohmyz.sh/&amp;quot; class=&amp;quot;bare&amp;quot;&amp;gt;https://ohmyz.sh/&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://github.com/ohmyzsh/ohmyzsh/wiki&amp;quot; class=&amp;quot;bare&amp;quot;&amp;gt;https://github.com/ohmyzsh/ohmyzsh/wiki&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://docs.xfce.org/xfce/xfwm4/compositor&amp;quot; class=&amp;quot;bare&amp;quot;&amp;gt;https://docs.xfce.org/xfce/xfwm4/compositor&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_kesimpulan&amp;quot;&amp;gt;Kesimpulan&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Anda sekarang memiliki:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;dari sebuah terminal modern dan berkinerja baik (Kitty)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;dari shell yang kuat dan dapat disesuaikan (Zsh)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;dari konfigurasi yang bersih dan independen dari paket sistem&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;dari terminal estetis dan fungsional yang dilengkapi dengan Oh My Zsh&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>DGX Spark vs Cloud vs Langganan LLM — Benchmark Ekonomi yang Membunuh Mimpi Perangkat Keras Lokal</title>
            <link >https://cheroliv.com/blog/2026/0119_benchmark_dgx_spark_vs_cloud_abonnement_llm_post.html</link>
            <pubDate>Wed, 6 May 2026 00:00:00 +0000</pubDate>
            <guid isPermaLink="false">blog/2026/0119_benchmark_dgx_spark_vs_cloud_abonnement_llm_post.html</guid>
            <description>&amp;lt;div id=&amp;quot;toc&amp;quot; class=&amp;quot;toc&amp;quot;&amp;gt;
&amp;lt;div id=&amp;quot;toctitle&amp;quot;&amp;gt;Table des matières&amp;lt;/div&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel1&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_konteks_sebuah_pasar_yang_bergeser&amp;quot;&amp;gt;Konteks: Sebuah pasar yang bergeser&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_panel_opsi&amp;quot;&amp;gt;Panel Opsi&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_apa_yang_saya_eksklusikan_dan_mengapa&amp;quot;&amp;gt;Apa Yang Saya Eksklusikan (Dan Mengapa)&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_inferensi_sederhana_kematian_perangkat_keras_lokal&amp;quot;&amp;gt;Inferensi Sederhana : Kematian perangkat keras lokal&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_perhitungan&amp;quot;&amp;gt;Perhitungan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_meski_dengan_penggunaan_intensif&amp;quot;&amp;gt;Meski dengan penggunaan intensif&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_penyetelan_halus_cloud_tidak_dapat_dikalahkan&amp;quot;&amp;gt;Penyetelan Halus : Cloud Tidak Dapat Dikalahkan&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_biaya_nyata_dari_sebuah_fine_tune&amp;quot;&amp;gt;biaya nyata dari sebuah fine-tune&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_aktor_spesialis&amp;quot;&amp;gt;Aktor spesialis&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_alasan_satu_satunya_yang_benar_kedaulatan_data&amp;quot;&amp;gt;Alasan Satu-satunya yang benar: Kedaulatan Data&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_perhitungan_sejati_kekerasiaan&amp;quot;&amp;gt;Perhitungan Sejati Kekerasiaan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_ringkasan_matriks_keputusan&amp;quot;&amp;gt;Ringkasan : Matriks Keputusan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_kasus_khusus_abonnement_pro_terhadap_model_china&amp;quot;&amp;gt;Kasus Khusus: Abonnement Pro Terhadap Model China&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_mengapa_model_cina_mengubah_segalanya&amp;quot;&amp;gt;mengapa model cina mengubah segalanya&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_apa_artinya_untuk_hardware&amp;quot;&amp;gt;Apa artinya untuk hardware&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_dan_huawei&amp;quot;&amp;gt;Dan Huawei?&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_kesimpulan_kelebihan_kedekatan&amp;quot;&amp;gt;Kesimpulan : Kelebihan Kedekatan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div id=&amp;quot;preamble&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;waktu membaca : 14 minutes&amp;lt;/div&amp;gt;
&amp;lt;p&amp;gt;NVIDIA menjual DGX Spark sebagai &amp;quot;superkomputer AI desktop&amp;quot; seharga $4 699. Ollama menawarkan model cloud-nya seharga $20 per bulan. Seorang H100 disewa seharga $1,38 per jam di Vast.ai. Dan model-model open-source asal China — DeepSeek, Qwen, GLM — tersedia di mana-mana, secara gratis, tanpa harus membeli kartu. Maka, Spark ini, untuk apa sebenarnya? Saya telah melakukan perhitungan.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;dlist&amp;quot;&amp;gt;
&amp;lt;dl&amp;gt;
&amp;lt;dt class=&amp;quot;hdlist1&amp;quot;&amp;gt;tok&amp;lt;/dt&amp;gt;
&amp;lt;dd&amp;gt;
&amp;lt;p&amp;gt;[]&amp;lt;/p&amp;gt;
&amp;lt;/dd&amp;gt;
&amp;lt;/dl&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_konteks_sebuah_pasar_yang_bergeser&amp;quot;&amp;gt;Konteks: Sebuah pasar yang bergeser&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Pada Mei 2026, lanskap AI telah berubah secara drastis dibandingkan dengan pengumuman DGX Spark di CES 2025. Tiga faktor telah konvergen untuk membuat hardware lokal semakin sulit untuk dijustifikasi :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Keruntuhan harga cloud GPU&amp;lt;/strong&amp;gt;Sebuah H100 disewa antara $1,38 dan $3 per jam, dibandingkan dengan $5-8 per jam 18 bulan lalu&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Ledakan model-model open-source Cina&amp;lt;/strong&amp;gt;DeepSeek-V4, Qwen-3, GLM-5 adalah kompetitif dengan model-model Barat, tersedia secara gratis&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Kenaikan platform inferensi terkelola&amp;lt;/strong&amp;gt;: Ollama, Groq, Together AI menawarkan API dengan tarif agresif, dengan level gratis yang melimpah&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;DGX Spark, yang diumumkan pada $2 999 lalu naik menjadi $4 699, tiba di dunia yang tidak lagi membutuhkannya.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_panel_opsi&amp;quot;&amp;gt;Panel Opsi&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Berikut solusi yang tersedia pada Mei 2026 untuk menjalankan LLMs, dari yang paling ringan hingga yang paling berat:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;table class=&amp;quot;tableblock frame-all grid-all stretch&amp;quot;&amp;gt;
&amp;lt;colgroup&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.5%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.5%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.5%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.5%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.5%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.5%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.5%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.5%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.5%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.5%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.5%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.5%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.5%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.5%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.5%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.5%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.5%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.5%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.5%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.5%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.5%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.5%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.5%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.5%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.5%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.5%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.5%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.5%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.5%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.5%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.5%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.5%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.5%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.5%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.5%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.5%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.5%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.5%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.5%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.5%;&amp;quot;&amp;gt;
&amp;lt;/colgroup&amp;gt;
&amp;lt;tbody&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Pilihan&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Biaya&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;VRAM / Kapasitas&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;model yang dapat diakses&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Kendala utama&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;Ollama Gratis&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;$0/bulan&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;awan ringan&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Semua model publik&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Penggunaan terbatas, 1 model secara bersamaan&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;Claude Pro&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;$20/bulan (~$240/tahun)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;N/A (API tertutup)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Hanya Claude 4.x&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Model unik, bukan open-source&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;Ollama Pro&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;$20/bulan (~$200/tahun)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;medium awan&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Semua model publik&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;3 model simultan max&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;Ollama Max&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;$100/bulan (~$1 200/tahun)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;awan berat&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Semua model publik&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;10 model simultan&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;GPU Cloud (H100)&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;$1.38-3/h&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;80 Go HBM&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Semua model, fine-tuning&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Penagihan per jam, setup diperlukan&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;DGX Spark&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;$4 699 (pembelian)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;128 Go unifié&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Tous modèles &amp;amp;lt;128 Go&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Lambat (~10x lebih lambat daripada H100), tetap&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;Mac Studio M3 Ultra&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;~$5 000+&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;192 Go unifié&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Tous modèles &amp;amp;lt;192 Go&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Bukan CUDA, ekosistem MLX&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;div class=&amp;quot;admonitionblock tip&amp;quot;&amp;gt;
&amp;lt;table&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;icon&amp;quot;&amp;gt;
&amp;lt;i class=&amp;quot;fa icon-tip&amp;quot; title=&amp;quot;💡&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Harga GPU cloud yang dikutip adalah dari Vast.ai dan RunPod pada Mei 2026 — dari spot/on-demand, bukan yang terdaftar (reserved). Harga hyperscaler (AWS, GCP, Azure) tetap 2-3x lebih tinggi.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_apa_yang_saya_eksklusikan_dan_mengapa&amp;quot;&amp;gt;Apa Yang Saya Eksklusikan (Dan Mengapa)&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Saya sengaja mengecualikan opsi-ops berikut dari benchmark ini :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;API OpenAI / Anthropic / Google&amp;lt;/strong&amp;gt;: topik di sini adalah untuk menjalankan model &amp;lt;strong&amp;gt;open-source&amp;lt;/strong&amp;gt; yang dapat dipilih, di-fine-tune, dan tidak bergantung pada vendor tunggal&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Huawei Ascend / Atlas&amp;lt;/strong&amp;gt; : Huawei ne commercialise pas d&amp;amp;#8217;équivalent desktop au DGX Spark. La gamme Ascend (910C, 910D) et Atlas (300I Duo, 350) est orientée datacenter — cartes PCIe, serveurs rack — pas des boîtiers compacts prêts à brancher. La puce Ascend 910C embarque 128 Go HBM mais c&amp;amp;#8217;est une puce serveur, pas un système autonome. L&amp;amp;#8217;Atlas 300I Duo offre 96 Go pour ~$1 400 mais nécessite une machine hôte&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_inferensi_sederhana_kematian_perangkat_keras_lokal&amp;quot;&amp;gt;Inferensi Sederhana : Kematian perangkat keras lokal&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Mari kita mulai dengan kasus penggunaan paling umum: ngobrol dengan LLM, melakukan code review, menghasilkan teks. Tidak ada fine-tuning, tidak ada batch processing, hanya inferensi interaktif.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_perhitungan&amp;quot;&amp;gt;Perhitungan&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Suatu DGX Spark berharga $4 699. Bagi dengan biaya tahunan setiap langganan:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div id=&amp;quot;diag-breakeven&amp;quot; class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultTextAlignment center

title Titik impas — DGX Spark ($4 699) vs Langganan
rectangle &amp;quot;DGX Spark
$4 699&amp;quot; as spark #FFCDD2

rectangle &amp;quot;Ollama Pro
$200/tahun&amp;quot; as opro #C8E6C9
rectangle &amp;quot;Claude Pro
$240/tahun&amp;quot; as cpro #C8E6C9
rectangle &amp;quot;Ollama Max\n$1 200/tahun&amp;quot; as omax #FFF9C4
rectangle &amp;quot;Cloud H100
$3/jam × 10jam/minggu&amp;quot; as cloud #BBDEFB

spark --&amp;amp;gt; opro : 23.5 années ⏳
spark --&amp;amp;gt; cpro : 19.6 années ⏳
spark --&amp;amp;gt; omax : 3.9 années
spark --&amp;amp;gt; cloud : 3.0 années (à 10h/semaine, intermittent)

note bottom of spark
  Sans compter l&amp;#39;électricité (~$150/an)
  Sans compter l&amp;#39;obsolescence hardware
  Sans compter qu&amp;#39;il est 10x plus lent
end note

@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Putusan ini mengerikan. Untuk inferensi:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Ollama Pro&amp;lt;/strong&amp;gt;Anda sebaiknya&amp;lt;strong&amp;gt;23 tahun&amp;lt;/strong&amp;gt;Untuk mengamortisasikan DGX Spark. Spark akan menjadi usang selama 20 tahun.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Ollama Max&amp;lt;/strong&amp;gt;:&amp;lt;strong&amp;gt;4 tahun&amp;lt;/strong&amp;gt;— asalkan menggunakan Spark 24/7, yang tidak dilakukan oleh siapa pun&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Claude Pro&amp;lt;/strong&amp;gt;: masalah yang sama,&amp;lt;strong&amp;gt;20 tahun&amp;lt;/strong&amp;gt;untuk single-model tertutup&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;em&amp;gt;&amp;lt;em&amp;gt;_ Janjian &amp;#39;superkomputer AI desktop&amp;#39; bertemu dengan realitas akuntansi dasar: $4 699 / $20 per bulan = tidak pernah menguntungkan untuk penggunaan individu. &amp;lt;/em&amp;gt;_&amp;lt;/em&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_meski_dengan_penggunaan_intensif&amp;quot;&amp;gt;Meski dengan penggunaan intensif&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Mari kita ambil skenario terburuk untuk cloud: Anda menjalankan H100 selama 8 jam per hari, 5 hari per minggu, 52 minggu per tahun.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;8 × 5 × 52 = 2 080 jam/tahun&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;2 080 × $2/h (harga rata-rata spot H100) =&amp;lt;strong&amp;gt;$4 160/tahun&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Spark ($4 699) menguntungkan dalam&amp;lt;strong&amp;gt;13 bulan&amp;lt;/strong&amp;gt;. kecuali bahwa :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;olist arabic&amp;quot;&amp;gt;
&amp;lt;ol class=&amp;quot;arabic&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;H100 adalah&amp;lt;strong&amp;gt;~10x lebih cepat&amp;lt;/strong&amp;gt;que GB10 Spark. Anda melakukan dalam 1 jam apa yang Spark lakukan dalam 10 jam. Untuk volume kerja yang sama, H100 membiayai Anda $416/tahun, bukan $4 160.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Le H100 a 80 Go HBM à 3.35 To/s de bande passante. Le Spark a 128 Go LPDDR5X à 273 Go/s. Pour les gros modèles, la bande passante compte plus que la capacité.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;admonitionblock important&amp;quot;&amp;gt;
&amp;lt;table&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;icon&amp;quot;&amp;gt;
&amp;lt;i class=&amp;quot;fa icon-important&amp;quot; title=&amp;quot;❗&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Membandingkan 1 jam H100 dengan 1 jam Spark tidak bermakna. H100 menyelesaikan pekerjaan 10x lebih cepat. Sehingga biaya cloud sebenarnya untuk workload tertentu sekitar 10x lebih rendah dibanding perhitungan naif yang didasarkan pada jam.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_penyetelan_halus_cloud_tidak_dapat_dikalahkan&amp;quot;&amp;gt;Penyetelan Halus : Cloud Tidak Dapat Dikalahkan&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Argumen klasik para pendukung Spark : « ya tetapi untuk fine-tuning, ini berbeda ».&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Tidak, itu tidak.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_biaya_nyata_dari_sebuah_fine_tune&amp;quot;&amp;gt;biaya nyata dari sebuah fine-tune&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Penyesuaian halus LoRA/QLoRA di Unsloth untuk model 7B :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;~2-3h pada H100 =&amp;lt;strong&amp;gt;$5 hingga $9&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Hal yang sama pada Spark =&amp;lt;strong&amp;gt;20-30 jam&amp;lt;/strong&amp;gt;(10x lebih lambat), memblokir mesin&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Dengan $4 699 Spark, Anda dapat membiayai :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;~500 hingga ~900 fine-tunes&amp;lt;/strong&amp;gt;dalam cloud H100&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Ou &amp;lt;strong&amp;gt;~23 tahun&amp;lt;/strong&amp;gt;d&amp;amp;#8217;Ollama Pro&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Ou &amp;lt;strong&amp;gt;sekitar 4 tahun&amp;lt;/strong&amp;gt;d&amp;amp;#8217;Ollama Max&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Dan untuk model yang lebih besar (70B+):&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Le Spark avec ses 128 Go peut charger un 70B en Q4 — mais le fine-tune sera extrêmement lent&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Un H100 avec 80 Go peut fine-tuner un 70B en QLoRA 4-bit en quelques heures&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Untuk fine-tuning penuh, diperlukan multi-GPU tentu saja — Spark tidak lagi dalam kompetisi.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_aktor_spesialis&amp;quot;&amp;gt;Aktor spesialis&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Pada tahun 2026, platform seperti Unsloth, Modal, Replicate, dan Together AI menawarkan fine-tuning serverless :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Tidak ada manajemen GPU&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Tidak ada konfigurasi lingkungan&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Pembayaran per menit&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Optimasi terintegrasi (perhatian flash, kuantisasi otomatis)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div id=&amp;quot;diag-fine-tuning-cost&amp;quot; class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultTextAlignment center
title &amp;amp;lt;size:20&amp;amp;gt;Biaya dari Fine-Tune LoRA 7B — Perbandingan&amp;amp;lt;/size&amp;amp;gt;
note as N
|= Option |= Coût par fine-tune |= Fine-tunes possibles avec $4 699 |
| **Replicate** | $3-5 | ~1 000 |
| **Modal (serverless)** | $2-4 | ~1 500 |
| **Unsloth + Vast.ai H100** | $5-9 | ~500-900 |
| **Together AI** | $4-8 | ~600-1 000 |
| **DGX Spark** | « gratuit » mais 20-30h | ∞ (en théorie) |
end note
note bottom
  Le Spark est « gratuit » par fine-tune seulement si votre temps vaut $0/h
  et que vous n&amp;#39;avez pas besoin de la machine pour autre chose pendant 30h
end note
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_alasan_satu_satunya_yang_benar_kedaulatan_data&amp;quot;&amp;gt;Alasan Satu-satunya yang benar: Kedaulatan Data&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Setelah menghilangkan inferensi, fine-tuning, dan rasio kualitas/harga — apa yang tersisa untuk Spark ?&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Kerahasiaan mutlak&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Bukan privasi « saya tidak ingin prompt saya dibaca oleh karyawan OpenAI ». Itu, ini adalah masalah palsu pada tahun 2026: Ollama, Groq, dan sebagian besar penyedia yang terhormat memiliki kebijakan zero log dan zero retention.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Kerahasiaan yang sebenarnya, itulah :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Air-gap regulatif&amp;lt;/strong&amp;gt;: pertahanan, diplomasi, kesehatan — data tidak dapat &amp;lt;strong&amp;gt;secara fisika&amp;lt;/strong&amp;gt; keluar dari bangunan&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;data hak milik kritis&amp;lt;/strong&amp;gt;: kode sumber dari produk yang tidak dipublikasikan, rahasia industri, algoritma perdagangan&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Ketahanan jaringan&amp;lt;/strong&amp;gt;: AI harus terus beroperasi meski internet jatuh&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;admonitionblock caution&amp;quot;&amp;gt;
&amp;lt;table&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;icon&amp;quot;&amp;gt;
&amp;lt;i class=&amp;quot;fa icon-caution&amp;quot; title=&amp;quot;🔒&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Tapi jujur saja: berapa persentase pembeli DGX Spark yang benar-benar berada dalam hal ini? 1%? Sisa itu adalah hal yang « nice to have » yang diubah menjadi pengeluaran sebesar $4 699.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_perhitungan_sejati_kekerasiaan&amp;quot;&amp;gt;Perhitungan Sejati Kekerasiaan&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Untuk penggunaan individu :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Ollama Pro&amp;lt;/strong&amp;gt;($20/bulan) + model sumber terbuka Cina = solusi pragmatis&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Jika sebuah prompt bocor di Ollama, ia tenggelam dalam jutaan permintaan harian. Ini adalah tetesan air di laut.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Risiko nyata bukanlah bocornya sebuah prompt — ini adalah pelatihan tanpa persetujuan pada data Anda. Dan itu, penyedia yang serius melarangnya secara kontrak.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;em&amp;gt;&amp;lt;em&amp;gt;_ Ketakutan akan « mengdistil promptnya dalam LLM » adalah tingkat penyebaran informasi pribadi yang sangat encer sehingga lebih merupakan kecemasan daripada ancaman nyata. Dalam skala industri, ini berbeda. Tapi dalam skala individu, ini hanya kebisingan. &amp;lt;/em&amp;gt;_&amp;lt;/em&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_ringkasan_matriks_keputusan&amp;quot;&amp;gt;Ringkasan : Matriks Keputusan&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div id=&amp;quot;diag-decision-matrix&amp;quot; class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultTextAlignment center

title Solusi AI mana yang harus dipilih pada tahun 2026 ?
start

:Quel est votre besoin principal ?;

switch (Besoin)

case (Inférence interactive occasionnelle)
  :**Ollama Free**\n$0/mois, tous modèles open-source;
  stop

case (Inférence quotidienne, code review, dev)
  :**Ollama Pro** ($20/mois)\nAccès à tous les modèles open-source\nDeepSeek, Qwen, GLM, Llama, Mistral;
  stop

case (Agents IA, multi-modèles, usage intensif)
  :Contexte &amp;amp;gt; 30K tokens ?;

  if (Oui) then
    :**Ollama Max** ($100/mois)\n10 modèles simultanés\nContexte long sans dégradation;
    stop
  else (Non)
    :**Cloud GPU H100 spot** ($1.38–3/h)\n+ Unsloth/Modal pour fine-tuning\nPerformance brute maximale;
    stop
  endif

case (Fine-tuning régulier)
  :**Vast.ai H100 + Unsloth** ($5–9/fine-tune)\nou **Together AI / Modal** (serverless)\n500+ fine-tunes pour le prix d’un Spark;
  stop

case (Air-gap obligatoire)
  :Données classifiées ?;

  if (Oui) then
    :**DGX Spark** ou **Mac Studio M3 Ultra**\nSeul cas où le hardware local est justifié\n(pas économiquement — politiquement);
    stop
  else (Non)
    :Retour au cloud : pas de besoin d’air-gap;
    stop
  endif

endswitch

@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;table class=&amp;quot;tableblock frame-all grid-all stretch&amp;quot;&amp;gt;
&amp;lt;colgroup&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.2222%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.2222%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.2222%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.2222%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.2222%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.2222%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.2222%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.2222%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.2222%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.2222%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.2222%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.2222%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.2222%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.2222%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.2222%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.2222%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.2222%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.2222%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.2222%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.2222%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.2222%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.2222%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.2222%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.2222%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.2222%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.2222%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.2222%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.2222%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.2222%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.2222%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.2222%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.2222%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.2222%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.2222%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.2222%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.2222%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.2222%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.2222%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.2222%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.2222%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.2222%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.2222%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.2222%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.2222%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.2232%;&amp;quot;&amp;gt;
&amp;lt;/colgroup&amp;gt;
&amp;lt;tbody&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;kriteria&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Ollama Gratis&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Ollama Pro&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Cloud GPU H100&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;DGX Spark&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;Inferensi&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;✅ terbatas&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;✅ Nyaman&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;✅ kelebihan&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;⚠️ Lambat&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;penyesuaian halus&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;❌&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;❌&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;tidak terkalahkan&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;⚠️ Mungkin namun lambat&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;Biaya tahunan&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;$0&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;$200&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Variabel (~$416-2 080*)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;$4 699 muka&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;kerahasiaan&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;⚠️ Cloud US&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;⚠️ Awan AS&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;⚠️ Cloud AS (variabel)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;✅ Total&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;Pemeliharaan&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Tidak ada&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Tidak ada&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Pengaturan unik&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Listrik, pembaruan&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;keterusangan&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;tidak ada&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;tidak ada&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;tidak ada&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;3-5 tahun&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;ekosistem perangkat lunak&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Semua model terbuka&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Semua model terbuka&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Semua model terbuka&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;CUDA (lebih lambat)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;Veridict&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;sesekali&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;disarankan&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Pros/pencarian&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Hanya air-gap&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;em&amp;gt;*Variabel tergantung volume pekerjaan; dengan kecepatan H100 ~10x &amp;amp;gt; Spark, biaya efektif untuk workload tertentu rendah.&amp;lt;/em&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_kasus_khusus_abonnement_pro_terhadap_model_china&amp;quot;&amp;gt;Kasus Khusus: Abonnement Pro Terhadap Model China&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Satu poin layak disoroti : kombinasi&amp;lt;strong&amp;gt;Ollama Pro sewa $20/bulan + model Cina open-source&amp;lt;/strong&amp;gt;paling mungkin itu adalah nilai terbaik untuk uang dalam sejarah AI untuk konsumen.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_mengapa_model_cina_mengubah_segalanya&amp;quot;&amp;gt;mengapa model cina mengubah segalanya&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;DeepSeek-V4-Pro&amp;lt;/strong&amp;gt;: 1.6T params, CSA+HCA, 1M konteks, kompetitif dengan Claude 4 dan GPT-5 untuk kode&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Qwen-3&amp;lt;/strong&amp;gt;: 235B, 256K konteks, sangat baik dalam bahasa Prancis dan penalaran&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;GLM-5.1&amp;lt;/strong&amp;gt;: 744B, MLA+DSA, 200K konteks, disesain untuk agen&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Semua tersedia di Ollama. Semua bersumber terbuka (lisensi permissif). Semua dapat digunakan tanpa batas melalui langganan $20/bulan.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;admonitionblock tip&amp;quot;&amp;gt;
&amp;lt;table&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;icon&amp;quot;&amp;gt;
&amp;lt;i class=&amp;quot;fa icon-tip&amp;quot; title=&amp;quot;💡&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Dengan Ollama Pro, Anda memiliki akses ke ~5 model &amp;quot;frontier&amp;quot; open-source dengan harga hanya satu model tertutup (Claude Pro). Nilainya sangat tidak seimbang hingga hampir tidak masuk akal.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_apa_artinya_untuk_hardware&amp;quot;&amp;gt;Apa artinya untuk hardware&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Jika nilai terbaik (kualitas/harga) berupa langganan cloud seharga $20 per bulan, hardware lokal menjadi pilihan untuk &amp;lt;strong&amp;gt;kenyamanan&amp;lt;/strong&amp;gt; atau &amp;lt;strong&amp;gt;prinsip&amp;lt;/strong&amp;gt; — bukan pilihan ekonomi.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;DGX Spark berharga $4.699 adalah setara dengan:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;235 bulan Ollama Pro&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;23 permainan AAA hari peluncuran&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;4 MacBook Air M4&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Satu tahun sewa untuk apartemen T2 di Lyon&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Untuk perhitungan. Lambat.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_dan_huawei&amp;quot;&amp;gt;Dan Huawei?&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Karena pertanyaan itu telah diajukan kepada saya selama penelitian saya: tidak, Huawei tidak menjual setara dengan DGX Spark.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Yang ada :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Naik 910C&amp;lt;/strong&amp;gt; : 128 Go HBM, 800 TFLOPS FP16 — mais c&amp;amp;#8217;est une puce serveur, pas un desktop&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Atlas 300I Duo&amp;lt;/strong&amp;gt; : 96 Go LPDDR4X, carte PCIe à ~$1 400 — nécessite un hôte&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Atlas 350&amp;lt;/strong&amp;gt; : 112 Go HBM, 1.56 PFLOPS FP4 — accélérateur datacenter, pas un boîtier compact&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Huawei reste focalisé sur le marché des serveurs et des centres de données. Pour un boîtier compact « prêt à brancher » avec 120+ Go de VRAM, le DGX Spark (128 Go unifié) et le Mac Studio M3 Ultra (192 Go unifié) sont les seules options en 2026. Et pour l&amp;amp;#8217;instant, aucun constructeur chinois n&amp;amp;#8217;a annoncé de produit équivalent dans ce format.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_kesimpulan_kelebihan_kedekatan&amp;quot;&amp;gt;Kesimpulan : Kelebihan Kedekatan&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;DGX Spark bukan produk yang buruk. Ini adalah investasi yang buruk untuk 99% pembeli potensial.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div id=&amp;quot;diag-verdict&amp;quot; class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultTextAlignment center

title Peta lanskap AI 2026 — Siapa yang mendominasi?
left to right direction

package &amp;quot;Penggunaan Pribadi / UMKM&amp;quot; #C8E6C9 {
  rectangle &amp;quot;🏆 Ollama Pro
$20/bulan&amp;quot; as pro
  rectangle &amp;quot;Ollama Free\n$0/bulan&amp;quot; as free
  rectangle &amp;quot;spot GPU di cloud\n$1-3/h&amp;quot; as spot
}

package &amp;quot;Penggunaan intensif / Pro&amp;quot; #FFF9C4 {
  rectangle &amp;quot;🏆 Ollama Max
$100/bulan&amp;quot; as max
  rectangle &amp;quot;🏆 GPU Cloud khusus
$2-4/jam&amp;quot; as dedicated
  rectangle &amp;quot;Mode / Together AI
Tanpa server&amp;quot; as serverless
}

package &amp;quot;Air-Gap Wajib&amp;quot; #FFCDD2 {
  rectangle &amp;quot;DGX Spark
$4 699&amp;quot; as spark
  rectangle &amp;quot;Mac Studio M3U
~$5 000&amp;quot; as mac
}

note bottom of spark
  1% des cas d&amp;#39;usage — défense,
  santé réglementée, secrets
  industriels critiques
end note

@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;em&amp;gt;&amp;lt;em&amp;gt;_ Membeli DGX Spark pada tahun 2026 adalah seperti membeli server email pada tahun 2010: secara teknis memungkinkan, secara filosofis memuaskan, secara ekonomis tidak masuk akal untuk individu. &amp;lt;/em&amp;gt;_&amp;lt;/em&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Perhitungannya sederhana, dan ia tidak berbohong :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;$0&amp;lt;/strong&amp;gt;untuk ngobrol dengan DeepSeek di Ollama Free&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;$200/tahun&amp;lt;/strong&amp;gt;untuk bekerja secara serius dengan Ollama Pro&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;$5-9&amp;lt;/strong&amp;gt;untuk fine-tune sesekali di GPU cloud&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;$4.699&amp;lt;/strong&amp;gt;untuk&amp;amp;#8230;&amp;amp;#8203; pengalaman memiliki kasing NVIDIA di meja mereka?&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Pasar telah memutuskan. Langganan cloud dan model open-source China telah menang dalam pertarungan harga. Perangkat keras lokal bertahan dalam niche regulasi, bukan di kantor pengembang.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;em&amp;gt;&amp;lt;em&amp;gt;_ DGX Spark bukanlah alat yang menguntungkan. Ini adalah sebuah statement. « Saya ingin AI saya berada di rumah saya, bukan di cloud orang lain. » Ini patut dihargai. Namun pada tahun 2026, ketika Ollama memberi Anda DeepSeek-V4-Pro di cloud untuk $20/bulan, ini terutama sebuah statement yang sangat mahal. &amp;lt;/em&amp;gt;_&amp;lt;/em&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;hr&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Artikel ini merupakan hasil percakapan dengan agen AI saya tentang relevansi ekonomi DGX Spark, bersamaan dengan pemantauan pasar mengenai harga cloud GPU (Vast.ai, RunPod), tabel harga Ollama/Claude, serta ekosistem model open-source asal China. Harga yang disebut adalah harga bulan Mei 2026 — kemungkinan besar harga akan terus menurun saat Anda membaca baris-baris ini.&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>Matriks Tata Kelola LLM — Mengapa Keamanan AI Anda Dimulai dari Pohon</title>
            <link >https://cheroliv.com/blog/2026/0118_matrice_gouvernance_llm_zone_acces_workspace_post.html</link>
            <pubDate>Mon, 4 May 2026 00:00:00 +0000</pubDate>
            <guid isPermaLink="false">blog/2026/0118_matrice_gouvernance_llm_zone_acces_workspace_post.html</guid>
            <description>&amp;lt;div id=&amp;quot;toc&amp;quot; class=&amp;quot;toc&amp;quot;&amp;gt;
&amp;lt;div id=&amp;quot;toctitle&amp;quot;&amp;gt;Table des matières&amp;lt;/div&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel1&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_masalah_prompt_engineering_adalah_sebuah_benteng_pasir&amp;quot;&amp;gt;Masalah: Prompt Engineering adalah sebuah benteng pasir&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_jawaban_saya_matriks_44&amp;quot;&amp;gt;Jawaban saya: matriks 4×4&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_lima_zona_dimensi_spasial_rgpd&amp;quot;&amp;gt;Lima Zona (Dimensi Spasial — RGPD)&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_klasifikasi_epistemik_dimensi_kedua&amp;quot;&amp;gt;Klasifikasi Epistemik (Dimensi Kedua)&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_bagaimana_llm_mengkonsumsi_matriks_ini&amp;quot;&amp;gt;Bagaimana LLM Mengkonsumsi Matriks Ini&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_implementasi_tugas_gradle_yang_bertipe&amp;quot;&amp;gt;Implementasi : Tugas Gradle yang Bertipe&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_mengapa_ini_sebuah_manifesto_bukan_hanya_sebuah_spec&amp;quot;&amp;gt;Mengapa Ini Sebuah Manifesto, Bukan Hanya Sebuah Spec&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_cascade_lengkap_dari_brain_dump_ke_artikel_blog&amp;quot;&amp;gt;Cascade Lengkap — dari Brain Dump ke Artikel Blog&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_prokontra&amp;quot;&amp;gt;Pro/Kontra&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_kesimpulan_atur_file_anda_bukan_prompt_anda&amp;quot;&amp;gt;Kesimpulan : Atur file Anda, Bukan prompt Anda&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_referensi&amp;quot;&amp;gt;Referensi&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div id=&amp;quot;preamble&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;Waktu membaca: 14 minutes&amp;lt;/div&amp;gt;
&amp;lt;p&amp;gt;Selama berbulan-bulan, saya menumpuk aturan tata kelola agen. Beberapa file EAGER, daftar periksa LAZY, protokol akhir sesi dalam enam langkah. Namun, satu pertanyaan masih belum terjawab : &amp;lt;strong&amp;gt;bagaimana LLM mengetahui apa yang memiliki hak untuk melakukan dengan sebuah file ?&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Jawaban tidak ada dalam prompt. Dia berada dalam sistem berkas.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Berikut spesifikasi arsitektur yang membuat respons dapat digunakan — la &amp;lt;strong&amp;gt;matriks tata kelola LLM&amp;lt;/strong&amp;gt;.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_masalah_prompt_engineering_adalah_sebuah_benteng_pasir&amp;quot;&amp;gt;Masalah: Prompt Engineering adalah sebuah benteng pasir&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Ketika Anda memberikan sebuah file ke sebuah LLM, Anda memberinya semuanya. Kontennya, ya — dan juga ketidakhadiran pengaman. LLM tidak tahu apakah file ini berisi rahasia, pendapat spekulatif, atau kode sumber tertutup. Dia akan mengindeksnya merangkum, mengutip, mencampur dengan data lain — dan potensial mempublikasikannya dalam embedding publik atau respons pengguna.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Parade klasik, itu adalah prompt :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-text hljs&amp;quot; data-lang=&amp;quot;text&amp;quot;&amp;gt;&amp;quot;Tu es un assistant sécurisé. Ne divulgue jamais d&amp;#39;informations confidentielles.
Si tu détectes un secret, ignore-le. Si tu détectes une opinion, ne la répète pas.&amp;quot;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Prompt ini memiliki tiga masalah :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;olist arabic&amp;quot;&amp;gt;
&amp;lt;ol class=&amp;quot;arabic&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Bergantung pada niat baik LLM.&amp;lt;/strong&amp;gt; Sebuah model yang cukup besar untuk&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Menyelesaikan bug kompleks cukup besar untuk mengelilingi sebuah instruksi keamanan yang terendam dalam 200k token konteks&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;olist arabic&amp;quot;&amp;gt;
&amp;lt;ol class=&amp;quot;arabic&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Itu kontekstual, bukan struktural.&amp;lt;/strong&amp;gt; Ganti prompt, ganti LLM,&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;ganti sesi — dan aturan menghilang.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;olist arabic&amp;quot;&amp;gt;
&amp;lt;ol class=&amp;quot;arabic&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Tidak dapat diskalakan.&amp;lt;/strong&amp;gt; Setiap jenis file baru, setiap level baru&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Privasi menuntut klausa baru. Prompt Anda menjadi sebuah teks peraturan yang tidak ada yang membacanya sepenuhnya&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;admonitionblock caution&amp;quot;&amp;gt;
&amp;lt;table&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;icon&amp;quot;&amp;gt;
&amp;lt;i class=&amp;quot;fa icon-caution&amp;quot; title=&amp;quot;🔒&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Keamanan sebuah sistem tidak boleh pernah bergantung pada sebuah instruksi yang mungkin lupa, menghindar, atau tidak dimuat. Dia harus bergantung pada sebuah halangan yang tidak dapat disebrangi tanpa ingin secara eksplisit&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_jawaban_saya_matriks_44&amp;quot;&amp;gt;Jawaban saya: matriks 4×4&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Solusi yang saya terapkan di workspace saya adalah &amp;lt;strong&amp;gt;matriks zona × hak akses LLM&amp;lt;/strong&amp;gt;. Dia tidak meminta LLM untuk berhati-hati. Dia memberi ketidakwaspadaan yang secara struktural mustahil&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div id=&amp;quot;diag-matrix-overview&amp;quot; class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultTextAlignment center
skinparam wrapWidth 220

title Matriks Tata Kelola LLM — Zona × Hak Akses
package &amp;quot;Ruang Kerja&amp;quot; {

  rectangle &amp;quot;AKAR
(Lingkaran 0)
━━━━━━
Brain dump
pikiran bebas
[DILARANG LLM]&amp;quot; as Z0 #FFB3B3
  rectangle &amp;quot;configuration/
(Lingkaran 1)
━━━━━━
Rahasia, token
Infrastuktur pribadi
[DILARANG LLM]&amp;quot; as Z1 #FFB3B3
  rectangle &amp;quot;office/
(Lingkaran 2)
━━━━━━
Data redaksi
Artikel, penataan
[Visifilter]&amp;quot; as Z2 #FFF3B3
  rectangle &amp;quot;foundry/private/\n(Lingkaran 3)\n━━━━━━\nCode closed source\n[DATASET PRIBADI]&amp;quot; as Z3 #B3D9FF
  rectangle &amp;quot;foundry/public/\n(Lingkaran 3)\n━━━━━━\nKode sumber terbuka\nApache 2.0\n[AKSES BEBAS]&amp;quot; as Z4 #B3FFB3

}

Z0 -[hidden]-&amp;amp;gt; Z1
Z1 -[hidden]-&amp;amp;gt; Z2
Z2 -[hidden]-&amp;amp;gt; Z3
Z3 -[hidden]-&amp;amp;gt; Z4

@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_lima_zona_dimensi_spasial_rgpd&amp;quot;&amp;gt;Lima Zona (Dimensi Spasial — RGPD)&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Setiap zona workspace memiliki tingkat GDPR yang menentukan &amp;lt;strong&amp;gt;di mana&amp;lt;/strong&amp;gt; file dapat dirujuk :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;table class=&amp;quot;tableblock frame-all grid-all stretch&amp;quot;&amp;gt;
&amp;lt;colgroup&amp;gt;
&amp;lt;col style=&amp;quot;width: 4.1666%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 4.1666%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 4.1666%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 4.1666%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 4.1666%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 4.1666%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 4.1666%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 4.1666%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 4.1666%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 4.1666%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 4.1666%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 4.1666%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 4.1666%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 4.1666%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 4.1666%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 4.1666%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 4.1666%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 4.1666%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 4.1666%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 4.1666%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 4.1666%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 4.1666%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 4.1666%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 4.1682%;&amp;quot;&amp;gt;
&amp;lt;/colgroup&amp;gt;
&amp;lt;tbody&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Zona&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Tingkat GDPR&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Konten&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Hak akses RAG publik&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Akar&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;0 — Pribadi&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Brain dumps, percakapan LLM&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;dilarang&amp;lt;/strong&amp;gt;— tidak pernah diindeks&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;configuration/&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;1 — Pemilik yang dibatasi&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Rahasia, token, kunci API&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;dilarang&amp;lt;/strong&amp;gt;— tidak pernah diindeks&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;office/&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;2 — Kolaboratif terbatas&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Data redaksi, penataan, pelatihan&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;terfilter&amp;lt;/strong&amp;gt;— Hanya visi&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;foundry/private/&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;3 — Terbuka bersyarat&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Kode bersumber tertutup, SaaS tidak publik&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;DILARANG&amp;lt;/strong&amp;gt;— dataset priadi secara eksklusif&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;foundry/public/&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;4 — Publik asli&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Kode Apache 2.0, plugin yang dipublikasikan&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;BEBAS&amp;lt;/strong&amp;gt;— indeksasi lengkap&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Aturan sederhana: &amp;lt;strong&amp;gt;jalur file menentukan apa yang dapat dilakukan LLM dengannya.&amp;lt;/strong&amp;gt; Tidak ada metadata yang perlu dipertahankan, tidak ada tag yang perlu ditambahkan di frontmatter, tidak ada klasifikasi manual di setiap commit. File berada di`OSS/&amp;lt;code&amp;gt;? Ini publik. Ini berada di`configuration/&amp;lt;/code&amp;gt;? Dia tidak bisa disentuh.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_klasifikasi_epistemik_dimensi_kedua&amp;quot;&amp;gt;Klasifikasi Epistemik (Dimensi Kedua)&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Dimensi spasial mengatakan &amp;lt;strong&amp;gt;où&amp;lt;/strong&amp;gt; router. Tetapi ada sebuah sumbu kedua, tegak lurus: &amp;lt;strong&amp;gt;quoi&amp;lt;/strong&amp;gt; router. Beberapa file di zona yang diizinkan berisi informasi yang tidak boleh diencerkan seperti semula:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div id=&amp;quot;diag-epistemic-filter&amp;quot; class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultTextAlignment center
skinparam wrapWidth 240

title Kaskade Ganda — Filter Spasial + Filter Epistemik
start

:Le LLM rencontre un fichier;

if (Zone physique ?) then (Cercle 0-1)
  :ACCÈS BLOQUÉ\n(secret, brain dump);
  stop
elseif (Zone CSS/)
  :DROIT D&amp;#39;ACCÈS PRIVÉ\n→ dataset fine-tuning;
  :Filtre épistémique\nVision/Opinion;
  if (Classification ?) then (VISION)
    :Dataset privé\n→ entraînement autorisé;
  else (OPINION)
    :CONFINÉ\n→ pas d&amp;#39;entraînement;
  endif
elseif (Zone office/)
  :DROIT D&amp;#39;ACCÈS FILTRÉ\n→ accès conditionnel;
  :Filtre épistémique\nVision/Opinion/Stratégie;
  if (Classification ?) then (VISION)
    :DILUTION COMPLÈTE\n→ docs racine + blog;
  elseif (STRATÉGIE)
    :DILUTION RESTREINTE\n→ docs racine seulement;
  else (OPINION)
    :CONFINEMENT\n→ office/ uniquement;
  endif
else (Zone OSS/)
  :ACCÈS LIBRE\n→ indexation complète;
endif

stop

@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Matriks klasifikasi epistemik, diukir dalam Aturan 2bis dari pengelolaanku agen :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;| Status | Definisi | Signal LLM | Tujuan | | VISION | Arsitektur yang stabil, pola yang telah diuji | Bahasa deklaratif, referensi sesi/uji | Pengenceran lengkap + Blog | | STRATEGI | Penempatan bisnis, harga | Kosakata pasar, persaingan | Hanya dokumen akar | | OPINION | Spekulasi, hipotesis belum divalidasi | Bahasa hipotetis, intuisi | Penahanan Lingkaran 0 |&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Kombinasi dari dua dimensi — zona fisik × klasifikasi epistemik — membentuk&amp;lt;strong&amp;gt;matriks lengkap&amp;lt;/strong&amp;gt;:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div id=&amp;quot;diag-full-matrix&amp;quot; class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE

title Matriks Lengkap — Zona × Klasifikasi
salt
{
{T
+ Zone | VISION | STRATÉGIE | OPINION
+ Racine (0) | ✗ Interdit | ✗ Interdit | ✗ Interdit
+ configuration/ (1) | ✗ Interdit | ✗ Interdit | ✗ Interdit
+ office/ (2) | ✓ Docs + Blog | ✓ Docs seulement | ✗ Confiné
+ CSS/ (3) | ✓ Dataset privé | ✓ Dataset privé | ✗ Confiné
+ OSS/ (4) | ✓ Public libre | ✓ Public libre | ✓ Public libre
}
}

@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_bagaimana_llm_mengkonsumsi_matriks_ini&amp;quot;&amp;gt;Bagaimana LLM Mengkonsumsi Matriks Ini&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Matriks bukanlah dokumen yang dibaca oleh LLM. Ini adalah sebuah &amp;lt;strong&amp;gt;kendala implisit&amp;lt;/strong&amp;gt; dikodekan dalam vektor komposit konteks (EPIC 9 — sedang berlangsung penerapan)&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Ini adalah apa yang LLM terima di setiap sesi:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-none hljs&amp;quot;&amp;gt;VECTEUR COMPOSITE DE CONTEXTE
├── RAG pgvector → OSS/ + office/Vision
│   (similarité sémantique sur contenu publiable)
├── Knowledge Graph graphify → OSS/
│   (relations exactes entre artéfacts publics)
├── Knowledge Graph privé → CSS/
│   (relations entre code closed source — jamais exporté)
├── Métadonnées de zone → chaque fichier taggé par zone physique
│   (le LLM sait s&amp;#39;il est dans office/ ou OSS/)
└── Historique des décisions → WORKSPACE_VISION.adoc
    (contexte temporel des arbitrages)&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Secara konkrit, ketika LLM berkata kepada saya:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-text hljs&amp;quot; data-lang=&amp;quot;text&amp;quot;&amp;gt;Je vais indexer le contenu de edster/ pour enrichir le knowledge graph...&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Metadata zona merespons bahkan sebelum LLM menyelesaikan kalimatnya : &amp;lt;code&amp;gt;edster/`ada di`CSS/&amp;lt;/code&amp;gt;, level 3 →&amp;lt;strong&amp;gt;Akses dilarang pada graf pengetahuan umum.&amp;lt;/strong&amp;gt; RAG tidak melihatnya. Graf tidak menyentuhnya. respons pengguna Dia tidak mengutipnya.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;admonitionblock tip&amp;quot;&amp;gt;
&amp;lt;table&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;icon&amp;quot;&amp;gt;
&amp;lt;i class=&amp;quot;fa icon-tip&amp;quot; title=&amp;quot;💡&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Ontologi spasial berfungsi sebagai sebuah&amp;lt;strong&amp;gt;izin Unix&amp;lt;/strong&amp;gt;untuk LLM. Anda tidak meminta sebuah proses untuk berhati-hati dengan`/etc/shadow`— Anda menolak akses baca untuknya. Prinsip yang sama.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_implementasi_tugas_gradle_yang_bertipe&amp;quot;&amp;gt;Implementasi : Tugas Gradle yang Bertipe&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Matriks bukanlah sebuah filosofi. Ini adalah kode. Berikut kontrak tugas. Gradle yang mengimplementasikannya:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;abstract class ZoneAwareIndexer @Inject constructor(
    private val rootDir: DirectoryProperty,
    private val configServer: ConfigServerProperty  // → configuration/
) : DefaultTask() {

    @Input
    val zoneFilter: SetProperty&amp;amp;lt;Zone&amp;amp;gt; = project.objects.setProperty(Zone::class.java)

    @OutputFile
    val ragIndex: RegularFileProperty = project.objects.fileProperty()

    @TaskAction
    fun index() {
        val allowedPaths = zoneFilter.get().flatMap { it.resolve(rootDir.get()) }
        val forbiddenPaths = Zone.RESTRICTED.resolve(rootDir.get())
                            + Zone.INTIMATE.resolve(rootDir.get())
        // Le RAG ne voit jamais configuration/ ni la racine
        // CSS/ alimente un index privé, pas celui-ci
        // office/ est filtré par le classifieur Vision/Opinion
    }
}

enum class Zone {
    INTIMATE,      // Cercle 0 — racine
    RESTRICTED,    // Cercle 1 — configuration/
    EDITORIAL,     // Cercle 2 — office/
    CLOSED_SOURCE, // Cercle 3 — foundry/private/
    OPEN_SOURCE    // Cercle 3 — foundry/public/
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Tugas ini &amp;lt;strong&amp;gt;dapat diuji&amp;lt;/strong&amp;gt;. Anda dapat menulis sebuah ujian yang memeriksa :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;@Test
fun `le RAG n&amp;#39;indexe jamais configuration`() {
    val index = ZoneAwareIndexer(rootDir, configServer)
    index.zoneFilter.set(setOf(Zone.OPEN_SOURCE, Zone.EDITORIAL))
    index.index()

    assertThat(index.ragIndex).doesNotContain(&amp;quot;configuration/&amp;quot;)
}

@Test
fun `le code closed source est exclu du RAG public`() {
    val index = ZoneAwareIndexer(rootDir, configServer)
    index.zoneFilter.set(setOf(Zone.OPEN_SOURCE))  // OSS only

    assertThat(index.ragIndex).doesNotContain(&amp;quot;edster&amp;quot;)
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;380 tes, 380 LULUS. Seperti pada plantuml-gradle — keamanan diuji, tidak diharapkan.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_mengapa_ini_sebuah_manifesto_bukan_hanya_sebuah_spec&amp;quot;&amp;gt;Mengapa Ini Sebuah Manifesto, Bukan Hanya Sebuah Spec&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Dokumen ini adalah spesifikasi arsitektur karena ia mendefinisikan :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;beberapa&amp;lt;strong&amp;gt;dimensi&amp;lt;/strong&amp;gt;(spasial, epistemik)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Beberapa&amp;lt;strong&amp;gt;aturan deterministik&amp;lt;/strong&amp;gt;(zona → hak akses)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Un &amp;lt;strong&amp;gt;kontrak tugas&amp;lt;/strong&amp;gt;(Gradle bertipe, dapat diuji)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;satu&amp;lt;strong&amp;gt;implementasi referensi&amp;lt;/strong&amp;gt;(workspace saya)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Namun itu juga sebuah&amp;lt;strong&amp;gt;jelas&amp;lt;/strong&amp;gt;karena dia mengambil sikap terhadap sebuah perdebatan lebih luas : &amp;lt;strong&amp;gt;bagaimana mengatur akses sebuah LLM ke data?&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Jawaban industri adalah prompt engineering + guardrail&amp;lt;br&amp;gt;
klasifikasi post-hoc. Jawaban saya adalah : &amp;lt;strong&amp;gt;letakkan file Anda di&amp;lt;/strong&amp;gt; dokumen yang baik.** Sisa otomatis.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;admonitionblock important&amp;quot;&amp;gt;
&amp;lt;table&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;icon&amp;quot;&amp;gt;
&amp;lt;i class=&amp;quot;fa icon-important&amp;quot; title=&amp;quot;❗&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Manifesto ini tidak mengatakan &amp;quot;LLM harus disesuaikan&amp;quot;. Ia berkata: &amp;quot;penyelarasan sebuah LLM atas aturan keamanan Anda adalah konsekuensi dari sistem Anda berkas, bukan prompt Anda.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_cascade_lengkap_dari_brain_dump_ke_artikel_blog&amp;quot;&amp;gt;Cascade Lengkap — dari Brain Dump ke Artikel Blog&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Untuk mengilustrasi siklus lengkap, berikut adalah bagaimana ide matriks ini lahir. dan telah dilarutkan:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-none hljs&amp;quot;&amp;gt;Session 4 mai 2026 — Feedback global du workspace
  │
  ├→ Le LLM identifie : &amp;quot;la dualité public/privé est invisible&amp;quot;
  │   → Classifié VISION (constat architectural vérifiable)
  │
  ├→ PICTURE_ME_ROLLIN_MATRICE_4X4.adoc — STIMULUS (Cercle 0)
  │   → Brain dump structuré, classification VISION confirmée
  │
  ├→ DILUTION → WORKSPACE_AS_PRODUCT.adoc (section Matrice 4×4)
  │   → La spec vit dans les documents racine
  │
  ├→ DILUTION → WORKSPACE_ORGANIZATION.adoc (OSS/CSS)
  │   → La granularisation est documentée
  │
  └→ ARTICLE 0117 (ce document) → cheroliv.com
      → La VISION est publiée&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Yang dimulai sebagai observasi dalam sesi (&amp;quot;hey, RAG tidak tahu bahwa edster adalah sumber tertutup&amp;quot;) telah menjadi sebuah spesifikasi arsitektur diterbitkan dalam kurang dari satu sesi. Ini adalah pola STIMULUS dalam aksi.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_prokontra&amp;quot;&amp;gt;Pro/Kontra&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;table class=&amp;quot;tableblock frame-all grid-all stretch&amp;quot;&amp;gt;
&amp;lt;colgroup&amp;gt;
&amp;lt;col style=&amp;quot;width: 50%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 50%;&amp;quot;&amp;gt;
&amp;lt;/colgroup&amp;gt;
&amp;lt;thead&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;Pendekatan ini (Ontologi Spasial + Matriks)&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;Pendekatan Klasik (Prompt + Pengaman)&amp;lt;/th&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/thead&amp;gt;
&amp;lt;tbody&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;Mekanisme&amp;lt;/strong&amp;gt; — sistem file memblokir akses, bukan LLM&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;Deklaratif&amp;lt;/strong&amp;gt; — permintaan meminta LLM untuk berhati-hati&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;Testable&amp;lt;/strong&amp;gt; — kontrak tugas Gradle diverifikasi oleh 380+ tes&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;Not testable&amp;lt;/strong&amp;gt; — &amp;quot;Apakah LLM telah mematuhi aturan?&amp;quot; adalah pertanyaan terbuka&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;Mandiri dari LLM&amp;lt;/strong&amp;gt; — ganti model, folder tetap ada&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;Bergantung pada LLM&amp;lt;/strong&amp;gt; — setiap model meminterpretasi prompt secara berbeda&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;Scalable&amp;lt;/strong&amp;gt; — jenis data baru = folder baru, tidak ada perubahan kode&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;tidak dapat diskalakan&amp;lt;/strong&amp;gt; — jenis data baru = klausa prompt baru, risiko regresi baru&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;Auditable&amp;lt;/strong&amp;gt; — sistem file adalah jalur audit&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;tidak dapat diaudit&amp;lt;/strong&amp;gt; — prompt tidak memiliki riwayat, tidak ada perbedaan, tidak ada penyalahan&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;Contrainte&amp;lt;/strong&amp;gt; : menuntut disiplin organisasi awal&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;Kendala&amp;lt;/strong&amp;gt;: mengharuskan disiplin penulisan prompt setiap sesi&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_kesimpulan_atur_file_anda_bukan_prompt_anda&amp;quot;&amp;gt;Kesimpulan : Atur file Anda, Bukan prompt Anda&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Matriks tata kelola LLM yang baru saja saya jelaskan bukanlah sebuah produk. Ini adalah&amp;lt;strong&amp;gt;specifikasi arsitektur&amp;lt;/strong&amp;gt;— dan itulah sebabnya dia adalah dapat dipublikasikan&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Yang membuatnya kuat adalah bahwa ia tidak meminta apa pun dari LLM. Ia tidak Dia tidak mempercayai. Ia tidak bersandar pada penyelarasan, pemahamannya. bahasa Prancis, atau niat baiknya. Ia berdasar pada sistem file — lapisan terendah, paling stabil, paling teruji dari seluruh stack.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Saat saya berkata kepada LLM saya &amp;quot;kamu bisa mengindeks semua di`OSS/&amp;lt;code&amp;gt;, tidak ada dalam `configuration/&amp;lt;/code&amp;gt;, et `office/`hanya jika ia diklasifikasikan sebagai Vision Ini bukan instruksi. Ini adalah &amp;lt;strong&amp;gt;filter&amp;lt;/strong&amp;gt; — diimplementasikan dalam Kotlin, Diverifikasi oleh 380 tes, dieksekusi sebelum LLM melihat data.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Ini adalah perbedaan antara mengatakan kepada seseorang &amp;#39;jangan lihat di dalam laci ini&amp;#39; dan menutup laci kunci. Agen tata kelola yang saya bangun sejak Bulan-bulan adalah kunci. Matriks adalah rencana furnitur.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_referensi&amp;quot;&amp;gt;Referensi&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;0116_compartimentage_epistemique_automatise_llm_post.html&amp;quot;&amp;gt;Artikel 0116 — Pembagian Epistemik Otomatis&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;0114_gouvernance_cercles_confiance_ontologie_spatiale_alignement_llm_post.html&amp;quot;&amp;gt;Article 0114 — Lingkaran Kepercayaan dan Ontologi Spasial&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;0108_gouvernance_agent_opencode_eager_lazy_post.html&amp;quot;&amp;gt;Article 0108 — Mengatur Agent AI dengan AsciiDoc&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;../2026/0115_anonymiseur_dataset_mvp0_realite_augmentee_llm_post.html&amp;quot;&amp;gt;Pasal 0115 — Penyamar identitas MVP0&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>Granularisasi OSS/CSS — Mengapa Saya Memotong foundry/ menjadi Dua</title>
            <link >https://cheroliv.com/blog/2026/0117_granularisation_oss_css_repositorie_workspace_post.html</link>
            <pubDate>Sun, 3 May 2026 00:00:00 +0000</pubDate>
            <guid isPermaLink="false">blog/2026/0117_granularisation_oss_css_repositorie_workspace_post.html</guid>
            <description>&amp;lt;div id=&amp;quot;toc&amp;quot; class=&amp;quot;toc&amp;quot;&amp;gt;
&amp;lt;div id=&amp;quot;toctitle&amp;quot;&amp;gt;Table des matières&amp;lt;/div&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel1&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_kejadian_yang_menunggu_untuk_terjadi&amp;quot;&amp;gt;Kejadian yang menunggu untuk terjadi&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_pertanyaan_sebenarnya&amp;quot;&amp;gt;Pertanyaan sebenarnya&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_solusi_oss_dan_css&amp;quot;&amp;gt;Solusi : OSS/ dan CSS/&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_mengapa_oss_dan_css_dan_bukan_public_dan_private&amp;quot;&amp;gt;Mengapa &amp;quot;OSS&amp;quot; dan &amp;quot;CSS&amp;quot; dan Bukan &amp;quot;Public&amp;quot; dan &amp;quot;Private&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_pohon_lengkap_empat_zona_bukan_tiga&amp;quot;&amp;gt;Pohon Lengkap — Empat Zona, Bukan Tiga&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_kasus_khusus_cheroliv_com&amp;quot;&amp;gt;Kasus Khusus : cheroliv.com&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_bakery_gradle_tidak_peduli&amp;quot;&amp;gt;bakery-gradle tidak peduli&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_dampak_pada_agen_tata_kelola&amp;quot;&amp;gt;Dampak pada Agen Tata Kelola&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_apa_yang_kita_dapatkan_dan_apa_yang_kita_hilangkan&amp;quot;&amp;gt;Apa yang Kita Dapatkan (Dan Apa yang Kita Hilangkan)&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_kesimpulan_sinyal_fisik_mengalahkan_sinyal_perangkat_lunak&amp;quot;&amp;gt;Kesimpulan: Sinyal Fisik mengalahkan Sinyal Perangkat Lunak&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_referensi&amp;quot;&amp;gt;Referensi&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div id=&amp;quot;preamble&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;waktu membaca : 10 minutes&amp;lt;/div&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;code&amp;gt;foundry/&amp;lt;/code&amp;gt;— zona fungsional yang menampung kode dari ruang kerja saya — Mengandung 18 proyek. Delapan bersifat open source di bawah lisensi Apache 2.0. Hanya satu sumber tertutup — SaaS Edster. Selama berbulan-bulan, sembilan proyek ini telah berdampingan. dalam folder yang sama, dipisahkan hanya oleh&amp;amp;#8230;&amp;amp;#8203; tidak ada. Status publik/privat mereka adalah sebuah informasi dalam kepalaku, bukan di sistem file.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Dan kemudian saya ingin menghubungkan sebuah RAG. Dan di situ, semuanya runtuh.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_kejadian_yang_menunggu_untuk_terjadi&amp;quot;&amp;gt;Kejadian yang menunggu untuk terjadi&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Pada April 2026, saya mulai mengimplementasikan pgvector RAG untuk slider-gradle. Prinsip: mengindeksi semua repositori`foundry/&amp;lt;code&amp;gt;, menghasilkan beberapa embeddings, dan suntikan ke dalam konteks LLM agar ia memiliki sebuah persepsi&amp;quot; kasus`foundry/&amp;lt;/code&amp;gt;.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Pipeline tersebut sederhana:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;val repos = fileTree(rootDir) {
    include(&amp;quot;**/*.adoc&amp;quot;, &amp;quot;**/*.kts&amp;quot;, &amp;quot;**/*.kt&amp;quot;, &amp;quot;**/*.json&amp;quot;, &amp;quot;**/*.yml&amp;quot;)
}
val chunks = repos.map { chunk(it) }
val embeddings = chunks.map { embed(it) }
pgvector.insert(embeddings)&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Sederhana. Efektif. Dan&amp;lt;strong&amp;gt;berbahaya&amp;lt;/strong&amp;gt;.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Karena ini`fileTree`tidak membedakan antara`plantuml-gradle/` (Apache 2.0, publik) dan`edster/`(closed source, private). Dia menelan semuanya. Dan jika suatu hari saya mempublikasikan embedding ini — di dashboard, dalam satu respons LLM, dalam dataset pelatihan — kode Edster yang proprietary sedang bocor.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;admonitionblock caution&amp;quot;&amp;gt;
&amp;lt;table&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;icon&amp;quot;&amp;gt;
&amp;lt;i class=&amp;quot;fa icon-caution&amp;quot; title=&amp;quot;🔒&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Masalahnya bukan bahwa LLM membaca kode sumber tertutup. Masalahnya, bahwa kode ini harus berakhir dalam embedding vektor &amp;lt;strong&amp;gt;publik&amp;lt;/strong&amp;gt; — tidak dapat dibalik, tidak dapat dihapus, tidak dapat diaudit.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_pertanyaan_sebenarnya&amp;quot;&amp;gt;Pertanyaan sebenarnya&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Bukan &amp;quot;bagaimana mencegah LLM bocor kode?&amp;quot;. Adalah &amp;quot;bagaimana membuat pelarian secara struktural menjadi mustahil?&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Jawaban bukan prompt. Jawaban, itu memotong folder menjadi dua.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_solusi_oss_dan_css&amp;quot;&amp;gt;Solusi : OSS/ dan CSS/&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Sebelum :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-none hljs&amp;quot;&amp;gt;foundry/
├── plantuml-gradle/       ← public
├── bakery-gradle/         ← public
├── magic-stick/           ← public
├── edster/                ← PRIVÉ
├── slider-gradle/         ← public
└── ...                    ← mélange invisible&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Setelah :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-none hljs&amp;quot;&amp;gt;foundry/
├── OSS/                   ← tout est Apache 2.0
│   ├── plantuml-gradle/
│   ├── bakery-gradle/
│   ├── magic-stick/
│   ├── slider-gradle/
│   └── ...
└── CSS/                   ← tout est closed source / private
    └── edster/&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Le `fileTree`menjadi :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;val ossRepos = fileTree(File(rootDir, &amp;quot;OSS&amp;quot;)) {
    include(&amp;quot;**/*.adoc&amp;quot;, &amp;quot;**/*.kts&amp;quot;, &amp;quot;**/*.kt&amp;quot;, &amp;quot;**/*.json&amp;quot;, &amp;quot;**/*.yml&amp;quot;)
}
val cssRepos = fileTree(File(rootDir, &amp;quot;CSS&amp;quot;)) {
    include(&amp;quot;**/*.adoc&amp;quot;, &amp;quot;**/*.kts&amp;quot;, &amp;quot;**/*.kt&amp;quot;, &amp;quot;**/*.json&amp;quot;, &amp;quot;**/*.yml&amp;quot;)
}

// Embeddings publics — OSS seulement
pgvectorPublic.insert(ossRepos.map { chunk(it) }.map { embed(it) })

// Dataset fine-tuning privé — CSS seulement
fineTuningDataset.insert(cssRepos.map { chunk(it) })  // jamais publié&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;admonitionblock important&amp;quot;&amp;gt;
&amp;lt;table&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;icon&amp;quot;&amp;gt;
&amp;lt;i class=&amp;quot;fa icon-important&amp;quot; title=&amp;quot;❗&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;RAG umum tidak pernah melihat`CSS/`. Kode closed source memberi dataset tanpa fine-tuning — sebuah model internal yang tidak pernah keluar dari rumahku. La pemisahan adalah mekanik, bukan deklaratif.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_mengapa_oss_dan_css_dan_bukan_public_dan_private&amp;quot;&amp;gt;Mengapa &amp;quot;OSS&amp;quot; dan &amp;quot;CSS&amp;quot; dan Bukan &amp;quot;Public&amp;quot; dan &amp;quot;Private&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Pemilihan akronim OSS (Open Source Software) dan CSS (Closed Source Software) adalah disengaja:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Public&amp;quot;/&amp;quot;Private&amp;quot; menjelaskan&amp;lt;strong&amp;gt;kejelasan&amp;lt;/strong&amp;gt;(apa yang GitHub lihat)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;OSS&amp;quot;/&amp;quot;CSS&amp;quot; menggambarkan&amp;lt;strong&amp;gt;alam&amp;lt;/strong&amp;gt;kode (yang harus diketahui oleh LLM)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Keterlihatan GitHub adalah metadata dari repositori jarak jauh. Sifat kode adalah sifat dari konten. LLM tidak memiliki akses ke API GitHub — tetapi ia memiliki akses ke sistem file.&amp;lt;code&amp;gt;CSS/`dia berkata kepadanya &amp;quot;perhatian, kode ini tidak berada di bawah lisensi free&amp;quot; tanpa perlu membaca satu`LICENSE`atau untuk mengurai sebuah`package.json&amp;lt;/code&amp;gt;.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;admonitionblock tip&amp;quot;&amp;gt;
&amp;lt;table&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;icon&amp;quot;&amp;gt;
&amp;lt;i class=&amp;quot;fa icon-tip&amp;quot; title=&amp;quot;💡&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Nama folder adalah metadata paling kuat yang dapat Anda berikan kepada sebuah LLM. Dia tidak dapat diparsing dengan salah, diabaikan, atau diinterpretasikan dengan salah. LLM tersebut melihat`CSS/edster/`Dalam jalur file — dia tahu&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_pohon_lengkap_empat_zona_bukan_tiga&amp;quot;&amp;gt;Pohon Lengkap — Empat Zona, Bukan Tiga&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Granularisasi OSS/CSS menyelesaikan ontologi tiga zona. Zona `foundry/`beralih dari 1 ke 2 subzona fungsional:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div id=&amp;quot;diag-four-zone-workspace&amp;quot; class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultTextAlignment center
skinparam wrapWidth 260

title Organisasi foundry/ — Granularisasi OSS/CSS
package &amp;quot;pabrik pengecoran/&amp;quot; {

  package &amp;quot;Lingkaran 0 — Akar&amp;quot; #FFCCCC {
    rectangle &amp;quot;PICTURE_ME_ROLLIN_*.adoc&amp;quot; as STIM
    rectangle &amp;quot;WORKSPACE_*.adoc&amp;quot; as VISION_DOC
    note right of STIM
      Brain dump libre
      Jamais versionné
      [LLM : interdit d&amp;#39;indexer]
    end note
  }

  package &amp;quot;Lingkaran 1 — konfigurasi/&amp;quot; #FFD9CC {
    rectangle &amp;quot;Rahasia, token&amp;quot; as SECRETS
    rectangle &amp;quot;Spring Cloud Config&amp;quot; as SCC
    note right of SECRETS
      Privé-CVS, solo access
      [LLM : INTERDIT]
    end note
  }

  package &amp;quot;Lingkaran 2 — kantor/&amp;quot; #FFFFCC {
    rectangle &amp;quot;artikel, pengkawangan&amp;quot; as DATA
    rectangle &amp;quot;Pelatihan, SPG/SPD&amp;quot; as FORM
    note right of DATA
      Privé-CVS
      [LLM : filtré Vision/Opinion]
    end note
  }

  package &amp;quot;Lingkaran 3 — pabrik cetak/&amp;quot; #CCE5FF {
    package &amp;quot;OSS/ — Apache 2.0&amp;quot; #CCFFCC {
      rectangle &amp;quot;Plugin Gradle&amp;quot; as PLUGINS
      rectangle &amp;quot;mesin&amp;quot; as ENGINE
    }
    package &amp;quot;CSS/ — Sumber tertutup&amp;quot; #CCDDFF {
      rectangle &amp;quot;edster/&amp;quot; as EDSTER
    }
  }
}

@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;| Zona | GDPR | Indeksasi RAG publik | Dataset penyetelan halus | | Akar | Tingkat 0 — Pribadi | ✗ | ✗ | (empty)&amp;lt;code&amp;gt;configuration/&amp;lt;/code&amp;gt;| Level 1 — Terbatas | ✗ | ✗ | | &amp;lt;code&amp;gt;office/&amp;lt;/code&amp;gt;| Tingkat 2 — Kolaboratif | ✓ Disaring | ✓ Disaring | | &amp;lt;code&amp;gt;foundry/private/&amp;lt;/code&amp;gt;| Tingkat 3 — Terbuka Bersyarat | ✗ | ✓ Pribadi | | &amp;lt;code&amp;gt;foundry/public/&amp;lt;/code&amp;gt;| Tingkat 4 — Masyarakat Asli | ✓ Gratis | ✓ Publik |&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_kasus_khusus_cheroliv_com&amp;quot;&amp;gt;Kasus Khusus : cheroliv.com&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Sementara saya berada di sana, saya memperbaiki ketidaksesuaian lain. Situs saya &amp;lt;code&amp;gt;cheroliv.com`hidup di`foundry/`seperti sebuah proyek perangkat lunak sepenuhnya — dengan build Gradle sendiri, CI sendiri, sendiri tata kelola&amp;lt;/code&amp;gt;.agents/`.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Tetapi artikel blog bukan kode. Ini adalah &amp;lt;strong&amp;gt;data&amp;lt;/strong&amp;gt;. redaksi** Lingkaran 2 — sama seperti kerangka`office/pilotage/` atau formasi dari`office/formations/`.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-none hljs&amp;quot;&amp;gt;AVANT                            APRÈS
foundry/                office/
  cheroliv.com/                    sites/
    site/jbake/content/blog/         cheroliv.com/
      2026/0117_....adoc               2026/0117_....adoc&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Yang berubah:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Artikel ada di`office/`→ kerahasiaan Cercle 2&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Tugas`publishSite`menjadi sebuah capability dari`engine`&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;melalui`bakery-gradle`— plugin menerima satu`FileTree`, dia tidak tahu bahwa artikel-artikel berasal dari`office/`&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Satu saja CI, satu saja tata kelola — duplikasi nol&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Klasifikasi Vision/Opinion (Aturan 2bis) berlaku secara otomatis :&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;artikel di`office/`disaring sebelum publikasi&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_bakery_gradle_tidak_peduli&amp;quot;&amp;gt;bakery-gradle tidak peduli&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Dan itulah yang indah. Plugin tersebut`bakery-gradle`tidak telah diubah. Menerima direktori konten AsciiDoc, ia menghasilkan HTML, ia push on GitHub Pages. bahwa direktori ini dinamakan`site/jbake/content/` ou &amp;lt;code&amp;gt;office/sites/cheroliv/&amp;lt;/code&amp;gt;— plugin tidak peduli.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;// engine/build.gradle.kts
task(&amp;quot;publishBlog&amp;quot;) {
    doLast {
        val articles = fileTree(&amp;quot;../../office/sites/cheroliv/2026/&amp;quot;)
        bakery.generate(articles)  // ← bakery ne sait pas d&amp;#39;où ça vient
    }
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Kontrak interface adalah tugas Gradle. Bukan API REST. Bukan webhook. Tugas — bertipe, dapat diuji, dapat dieksekusi secara lokal dan dalam CI.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_dampak_pada_agen_tata_kelola&amp;quot;&amp;gt;Dampak pada Agen Tata Kelola&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Granularisasi OSS/CSS mengubah tata kelola agen pada tiga poin:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;olist arabic&amp;quot;&amp;gt;
&amp;lt;ol class=&amp;quot;arabic&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;RAG public&amp;lt;/strong&amp;gt; : lingkup indeksasi beralih dari`foundry/**`&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;à &amp;lt;code&amp;gt;foundry/public/&amp;lt;strong&amp;gt;&amp;lt;/code&amp;gt;+&amp;lt;code&amp;gt;office/Vision/&amp;lt;/strong&amp;gt;&amp;lt;/code&amp;gt;. Kode sumber tertutup dikeluarkan dari indeks oleh konfigurasi tugas, bukan oleh prompt.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;olist arabic&amp;quot;&amp;gt;
&amp;lt;ol class=&amp;quot;arabic&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Knowledge Graph&amp;lt;/strong&amp;gt; : graphify-gradle menghasilkan dua graf :&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;*&amp;lt;code&amp;gt;office/graph.json&amp;lt;/code&amp;gt;(publik) — hubungan antara artefak OSS + office/Vision *&amp;lt;code&amp;gt;configuration/graph_private.json&amp;lt;/code&amp;gt;(gitignored, pribadi) — hubungan antara artefak CSS, tidak pernah dipublikasikan&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;olist arabic&amp;quot;&amp;gt;
&amp;lt;ol class=&amp;quot;arabic&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;penyetelan halus dataset&amp;lt;/strong&amp;gt; :`codebase-gradle`sekarang memiliki sumber :&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;*&amp;lt;code&amp;gt;OSS/&amp;lt;/code&amp;gt;→ datasets publik (model komunitas, benchmarks) *&amp;lt;code&amp;gt;CSS/&amp;lt;/code&amp;gt;→ kumpulan data pribadi (model internal, fine-tuning proprietary)&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_apa_yang_kita_dapatkan_dan_apa_yang_kita_hilangkan&amp;quot;&amp;gt;Apa yang Kita Dapatkan (Dan Apa yang Kita Hilangkan)&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;table class=&amp;quot;tableblock frame-all grid-all stretch&amp;quot;&amp;gt;
&amp;lt;colgroup&amp;gt;
&amp;lt;col style=&amp;quot;width: 7.1428%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 7.1428%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 7.1428%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 7.1428%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 7.1428%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 7.1428%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 7.1428%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 7.1428%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 7.1428%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 7.1428%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 7.1428%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 7.1428%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 7.1428%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 7.1436%;&amp;quot;&amp;gt;
&amp;lt;/colgroup&amp;gt;
&amp;lt;tbody&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Sebelum (folder datar)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Setelah (OSS/CSS + office/sites)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;ls foundry/&amp;lt;/code&amp;gt;→ kampuran publik/privat&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;ls foundry/public/&amp;lt;/code&amp;gt;→ semua dapat dipublikasikan&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;RAG mengindeks semua tanpa diskriminasi&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;RAG mengindeks`OSS/`+`office/Vision`hanya&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Tidak dapat mengetahui apakah proyek itu open source&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;jalan`OSS/` ou `CSS/`yang dikatakan&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;cheroliv.com memiliki CI sendiri, tata kelola sendiri&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;CI dan tata kelola dibagikan bersama dalam engine&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Artikel blog berada di repositori &amp;#39;code&amp;#39;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Artikel blog ada di`office/sites/`— data, bukan kode&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Risiko kebocoran kode sumber tertutup dalam embeddings publik&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Mustahil secara struktural —`CSS/`di luar lingkup RAG&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Biaya tunggal : satu`git mv`global pada 17 repositori OSS + sebuah refactor dari &amp;lt;code&amp;gt;build.gradle.kts&amp;lt;/code&amp;gt; de &amp;lt;code&amp;gt;engine&amp;lt;/code&amp;gt;. Ini adalah migrasi dingin — Tidak ada data selama penerbangan, tidak ada pengguna yang terpengaruh.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_kesimpulan_sinyal_fisik_mengalahkan_sinyal_perangkat_lunak&amp;quot;&amp;gt;Kesimpulan: Sinyal Fisik mengalahkan Sinyal Perangkat Lunak&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Yang saya lakukan siang ini adalah mengganti sebuah konvensi tak terlihat. dengan pemisahan yang terlihat di zona`foundry/&amp;lt;code&amp;gt;. Sebelum, Anda harus &amp;lt;strong&amp;gt;mengetahui&amp;lt;/strong&amp;gt; bahwa`edster/`was sumber tertutup. Sekarang, Anda &amp;lt;strong&amp;gt;lihat&amp;lt;/strong&amp;gt; — nama folder adalah`CSS/&amp;lt;/code&amp;gt;.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Ini adalah prinsip yang sama seperti izin Unix, VLAN jaringan, atau kompartemen tahan api dalam basis data. Keamanan tidak boleh Ini harus menjadi informasi yang harus Anda ingat. Ini harus menjadi sebuah properti. &amp;lt;strong&amp;gt;fisik&amp;lt;/strong&amp;gt;sistem.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Granularisasi OSS/CSS adalah penerapan prinsip ini dalam bidang. dari tata kelola LLM. LLM tidak perlu tahu bahwa Edster adalah rahasia. Dia perlu tidak mampu mengindeksnya.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Dan itu persis apa`CSS/`menjamin.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_referensi&amp;quot;&amp;gt;Referensi&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;0118_matrice_gouvernance_llm_zone_acces_workspace_post.html&amp;quot;&amp;gt;Pasal 0117 — Matriks Tata Kelola LLM&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;0114_gouvernance_cercles_confiance_ontologie_spatiale_alignement_llm_post.html&amp;quot;&amp;gt;Artikel 0114 — Lingkaran Kepercayaan&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Artikel 0116 — Kompartimentasi Epistemik Otomatis&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;0108_gouvernance_agent_opencode_eager_lazy_post.html&amp;quot;&amp;gt;Article 0108 — Mengawal sebuah agen AI dengan AsciiDoc&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>Kompartemen Epistemik Otomatis — Bagaimana LLM Melindungi Taman Pikiran Saya</title>
            <link >https://cheroliv.com/blog/2026/0116_compartimentage_epistemique_automatise_llm_post.html</link>
            <pubDate>Sat, 2 May 2026 00:00:00 +0000</pubDate>
            <guid isPermaLink="false">blog/2026/0116_compartimentage_epistemique_automatise_llm_post.html</guid>
            <description>&amp;lt;div id=&amp;quot;toc&amp;quot; class=&amp;quot;toc&amp;quot;&amp;gt;
&amp;lt;div id=&amp;quot;toctitle&amp;quot;&amp;gt;Table des matières&amp;lt;/div&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel1&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_masalah_berpikir_bebas_tanpa_mempublikasikan_apapun&amp;quot;&amp;gt;Masalah: Berpikir Bebas tanpa Mempublikasikan Apapun&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_pola_stimulus_lahir_menetapkan_mengencerkan_mati&amp;quot;&amp;gt;pola STIMULUS — lahir, menetapkan, mengencerkan, mati&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_delegasi_ke_llm_mengapa_llm_itu_yang_memfilter&amp;quot;&amp;gt;Delegasi ke LLM : Mengapa LLM itu yang memfilter&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div id=&amp;quot;preamble&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;waktu membaca : 10 minutes&amp;lt;/div&amp;gt;
&amp;lt;p&amp;gt;LLM saya tidak hanya mengeksekusi tugas Gradle saya. Ia adalah penjaga perbatasan antara pikiran pribadi saya dan ekspresi publik saya. Setiap ide diklasifikasikan — Visi, Opini, Strategi — sebelum dirutakan. Ini adalah cara pembagian otomatis epistemik ini melindungi kebun mental saya tanpa membuat saya terdiam.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;dlist&amp;quot;&amp;gt;
&amp;lt;dl&amp;gt;
&amp;lt;dt class=&amp;quot;hdlist1&amp;quot;&amp;gt;tik&amp;lt;/dt&amp;gt;
&amp;lt;dd&amp;gt;
&amp;lt;p&amp;gt;[]&amp;lt;/p&amp;gt;
&amp;lt;/dd&amp;gt;
&amp;lt;/dl&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_masalah_berpikir_bebas_tanpa_mempublikasikan_apapun&amp;quot;&amp;gt;Masalah: Berpikir Bebas tanpa Mempublikasikan Apapun&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Ketika seorang pengembang solo mendokumentasikan proses pemikirannya dengan LLM, ia bertemu dengan paradoks:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-none hljs&amp;quot;&amp;gt;CAPTURE TOUT (Cercle 0, jardin secret)
  ├→ Brain dump libre, pas de censure
  ├→ Idées brutes, spéculations, intuitions non vérifiées
  ├→ Allers-retours LLM : challenger, itérer, raffiner
  └→ Le LLM voit TOUT — le bon, le flou, l&amp;#39;embryonnaire

PUBLICATION SÉLECTIVE (Cercle 4, blog public)
  ├→ Uniquement les décisions actées
  ├→ Uniquement les patterns testés (&amp;amp;gt;100 tests PASS)
  ├→ Uniquement ce qui a de la valeur pour le lecteur
  └→ JAMAIS une spéculation présentée comme une vérité&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Di antara kedua ekstrem ini, ada&amp;lt;strong&amp;gt;penapis&amp;lt;/strong&amp;gt;. Pertanyaannya adalah: siapa yang melakukannya? Seorang manusia lelah di akhir sesi? Skrip regex yang naif? Atau LLM itu sendiri, yang sudah membaca semuanya?&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Jawaban saya : LLM.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_pola_stimulus_lahir_menetapkan_mengencerkan_mati&amp;quot;&amp;gt;pola STIMULUS — lahir, menetapkan, mengencerkan, mati&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;STIMULUS adalah sebuah file`.adoc`temporaire di akar workspace (Lingkaran 0). Nama nya mengikuti konvensi yang evokatif —&amp;lt;code&amp;gt;PICTURE_ME_ROLLIN.adoc&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;HOLD_MY_BEER.adoc&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ONCE_UPON_A_TIME.adoc&amp;lt;/code&amp;gt;— yang menangkap niat eksploratif saat ini.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Siklus hidupnya tanpa belas kasihan:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-none hljs&amp;quot;&amp;gt;CRÉATION (Cercle 0)
  ├→ Fichier vide, brain dump libre
  ├→ Allers-retours LLM ← → développeur
  └→ Aucune pression de structure ou de publication

MATURATION
  ├→ Le contenu se densifie
  ├→ Des sections émergent naturellement
  └→ Le fichier devient une référence informelle de session

CLASSIFICATION ÉPISTÉMIQUE ← FAIT PAR LE LLM
  ├→ VISION    → publiable, diluer dans les docs publics + blog
  ├→ OPINION   → confiner au Cercle 0, pas de dilution externe
  └→ STRATÉGIE → dilution restreinte (docs racine, PAS de blog)

DILUTION (Cascade vers Cercles légitimes)
  ├→ VISION → WORKSPACE_AS_PRODUCT.adoc + WHAT_THE_GAMES.adoc + blog
  ├→ STRATÉGIE → WORKSPACE_AS_PRODUCT.adoc (section business)
  └→ OPINION → reste dans le stimulus, ne bouge pas

ARCHIVAGE + SUPPRESSION
  ├→ Snapshot dans configuration/vision-archive/ (trace historique)
  └→ rm du stimulus — il a fini son travail&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Seorang rangsangan yang bertahan lebih dari 2 sesi tanpa pengenceran adalah anti-pola. Entah informasi terlalu penting untuk disimpan di file sementara (→ pengenceran), atau informasi terlalu kabur untuk layak ada (→ menghapus).&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_delegasi_ke_llm_mengapa_llm_itu_yang_memfilter&amp;quot;&amp;gt;Delegasi ke LLM : Mengapa LLM itu yang memfilter&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Saya tidak meng-hardcode klasifikasi Vision/Opinion di Kotlin. Saya menyuntikkan di&amp;lt;strong&amp;gt;prompt sistem&amp;lt;/strong&amp;gt;dari LLM via`AGENT_GOVERNANCE.adoc`:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-none hljs&amp;quot;&amp;gt;----
----
Pour chaque dilution de stimulus ou session, tu es responsable de classifier
le contenu :
- VISION : décision architecturale actée, pattern testé en production (&amp;amp;gt;100 tests PASS),
  décision documentée dans BACKLOG ou ROADMAP
- OPINION : spéculation, hypothèse non validée par des tests, intuition
  sans ancrage expérimental
- STRATEGIE : positionnement business, pricing, marché cible

Règles :
- Si OPINION, le contenu reste confiné dans son cercle d&amp;#39;origine
- Si VISION, tu procèdes à la dilution dans les documents racine
- Si STRATEGIE, dilution restreinte (docs racine, pas de blog public)
- Cette classification détermine aussi si un article de blog est publiable
  sur cheroliv.com
----

LLM membaca instruksi ini**sebelum**untuk memproses sesi. Dia berada dalam konteks imediatnya, bukan dalam file terpisah. Ini adalah *prompt engineering deterministik* : perilaku muncul dari teks yang disuntikkan, bukan dari klasifikator eksternal.

=== Kenapa ini bekerja — dan Kenapa ini kontraintuitif

Reaksi standar: « Kamu menyerahkan klasifikasi kepada AI? Bagaimana jika ia salah?

Jawaban: manusia tidak lebih baik pada pukul 2 pagi. Perbedaannya, LLM itu membaca SEMUA. Dia memiliki konteks lengkap sesi dan dokumen target. Dia tahu bahwa`VPS ECO-16 12€ HT`adalah keputusan infrastruktur (VISION) sedangkan`« le TTS ElevenLabs sonne plus naturel que Coqui »`adalah sebuah opini yang belum diverifikasi oleh sebuah benchmark.

Manusia, pada akhir sesi, membaca secara diagonal dan melakukan yang terbaik. LLM membaca secara menyeluruh dan menerapkan grade sistematis. Hasilnya adalah**lebih andal**, tidak kurang.

Dan untuk 20% kasus ambigu — zona abu-abu tempat bahkan LLM ragu — jawabannya bukan « menambahkan kode ». Jawabannya adalah**LangGraph4j**(EPIC 13) : grafik keadaan yang, untuk kasus ambigu, mengaktifkan node Human-in-the-Loop dan mengembalikan ke manusia untuk keputusan. Tapi untuk 80% kasus yang jelas, LLM cukup.

== Pipa Lengkap : dari Brain Dump ke Blog Post

Berikut adalah alur lengkap — dari ide mentah sampai artikel dipublikasikan — dengan LLM sebagai satu-satunya filter :

[source]
----
CERCLE 0 (jardin secret)
  PICTURE_ME_ROLLIN.adoc   ← brain dump, allers-retours LLM
       ↓
  [LLM classifie] → VISION / OPINION / STRATÉGIE
       ↓
  VISION → dilution dans WORKSPACE_AS_PRODUCT.adoc
         → dilution dans WHAT_THE_GAMES_BEEN_MISSING.adoc
         → dilution dans WORKSPACE_VISION.adoc (session context)
         → RÉDACTION ARTICLE BLOG ← le LLM écrit, pas le dev
       ↓
CERCLE 4 (blog public)
  0116_compartimentage_epistemique.adoc  ← publié sur cheroliv.com
       ↓
  ./gradlew deploy (bakery-gradle → JBake → JGit → GitHub Pages)
       ↓
  https://cheroliv.com/blog/2026/0116/  ← live
----

Pengembang tidak menulis artikel. Ia menantang, meninjau, memvalidasi. LLM menulis. Loop adalah:

1. Brain dump bebas → tanpa sensor
2. Klasifikasi epistemik oleh LLM → tidak ada bocoran
3. Penulisan oleh LLM → tanpa procrastinasi
4. Pemeriksaan manusia → tidak ada kesalahan

Penekanan sejarah — « Saya tidak punya waktu untuk menulis » — telah dihapus. LLM melakukan sebagian besar pekerjaan. Manusia melakukan yang hanya dapat dilakukan oleh manusia: menilai.

== Tiga Dokumen Akar — Bagaimana Informasi Menemukan Tempatnya

Setiap dokumen target memiliki peran spesifik dalam kaskade penurunan:

[cols=&amp;quot;1,3,2&amp;quot;]
|===
|dokumen |Apa yang ia terima |Jenis konten |`WORKSPACE_AS_PRODUCT.adoc` |Keputusan arsitektur, stack teknis, roadmap, model bisnis |visi + strategi |`WHAT_THE_GAMES_BEEN_MISSING.adoc` |Pola, pelajaran, anti-pola, ontologi, aturan formal |VISION (pola yang telah diuji) |`WORKSPACE_VISION.adoc` |Konteks sesi, riwayat keputusan, poin infrastruktur |VISI + STRATEGI (konteks) |`AGENT_GOVERNANCE.adoc` |Aturan tata kelola, portofolio proyek, prosedur |VISI (tata kelola) |**Blog cheroliv.com** |Artikel publik, nilai untuk pembaca eksternal |Visi hanya
|===

Sama stimulasi dapat memenuhi kebutuhan 4 dokumen + blog — setiap destinasi menerima bagian yang relevan, tanpa redundansi, tanpa kontradiksi.

Ini adalah yang terjadi untuk`PICTURE_ME_ROLLIN.adoc`(3 Mei 2026) :

| Informasi | Klasifikasi | Tujuan | Inventaris 13+ proyek foundry/ | VISION | WORKSPACE_AS_PRODUCT | VPS-2 OVH, edster.cloud, 8.49€/bulan | VISI | WORKSPACE_AS_PRODUCT + WORKSPACE_VISION | | « Pasar Afrika siap untuk uang seluler » | STRATÉGIE | WORKSPACE_AS_PRODUCT (section business) | | « DeepSeek &amp;amp;gt; Kimi &amp;amp;gt; GLM untuk konteks panjang » | OPINION → VISION | Dikenakan pembatasan lalu dipromosikan setelah benchmark (artikel 0112) | | Karyawan Sintetis sebagai produk akhir | STRATEGI | WORKSPACE_AS_PRODUCT (tanpa blog) |

== Pola Baby-Step + TestContainer + Dokumen Hidup — Kode yang Menguji Diri Sendiri

Pola STIMULUS/Dilution mengatur pemikiran. Setara untuk kode-nya adalah pola Baby-Step/TestContainer/Doc Vivante, yang divalidasi oleh plantuml-gradle (380/380 tes LULUS, 137 sesi tanpa kebocoran Docker) :

[cols=&amp;quot;1,3&amp;quot;]
|===
|Prinsip |aturan |**langkah bayi** |Tidak lebih dari 3 poin per US. Jika satu US menimbulkan ketakutan, potong. Satu US dengan 1 poin yang melewati &amp;amp;gt; satu US dengan 8 poin yang menunggu. |**TestContainer pertama** |Sebelum kode bisnis, kontainer. Uji infra lulus → lingkungan stabil terjamin. |**Dataset komite** |Subhimpunan minimal tetapi representatif dalam`src/test/resources/`, tanpa PII. Dapat di-klon, dapat diluncurkan. |**Dokumentasi hidup** |Setiap test Cucumber = satu kalimat dalam bahasa alami. Kode sumber adalah spec yang dapat dieksekusi. Tidak ada dokumen Markdown yang harus dipelihara. |**Pembersihan otomatis** |Zero kebocoran sumber daya. Wadah yatim,`/tmp/gradle-test-*`: semua bersih
|===

Pola ini adalah alasan mengapa plantuml-gradle tidak pernah mengalami regresi Docker dalam 138 sesi. Dia memastikan bahwa kode yang berkomunikasi dengan infrastruktur eksternal (PostgreSQL, Redis, Kafka) selalu diuji di lingkungan yang identik dengan produksi — terkontrol versinya, dapat direproduksi, dapat dijalankan dengan satu perintah.

== Kesimpulan: kebebasan berpikir tanpa ketakutan mempublikasikan

Kompartimentasi epistemologis yang diotomatisasi bukan alat sensor. Adalah sebagai**pembebas**. Ia memungkinkan untuk berpikir secara bebas, untuk spekulasi tanpa filter, untuk brain-dumping segala yang ada di pikiran — tanpa pernah takut bahwa sebuah ide embryonnaire akan terekspos di blog.

LLM adalah penjaga batas ini. Ia membaca semuanya. Ia mengklasifikasikan. Ia meredam apa yang layak untuk bertahan. Ia membatasi apa yang harus tetap pribadi. Ia menyusun apa yang dapat dipublikasi.

Dan developer itu, dia, tetap memegang kata terakhir: membaca, menantang, memvalidasi. Menilai.

Ini adalah pembagian kerja yang tidak dimiliki oleh pengembang solo: sebuah karyawan sintetis yang mengurus administrasi mental, sehingga manusia dapat fokus pada apa yang ia lakukan dengan baik — merancang, merancang arsitektur, dan mengambil keputusan.

== Referensi

* Artikel tentang governanasi agen Eager/Lazy :link:../2026/0108_gouvernance_agent_opencode_eager_lazy_post.html[Mengatur Agent AI dengan AsciiDoc]
* Artikel tentang ontologi spasial dan lingkaran kepercayaan :link:../2026/0114_gouvernance_cercles_confiance_ontologie_spatiale_alignement_llm_post.html[Ontologi spasial sebagai mekanisme penyelarasan]
* Artikel tentang Anonymiseur MVP0 :link:../2026/0115_anonymiseur_dataset_mvp0_realite_augmentee_llm_post.html[Pegawai pertama kami : Penganonim Datasets]
* Artikel tentang perbandingan LLMs konteks panjang :link:../2026/0112_comparaison_kimi_glm_deepseek_long_contexte_plugin_gradle_opencode_post.html[DeepSeek-V4-Pro, Kimi K2.6, GLM-5.1]
* Artikel tentang mekanisme agen backup :link:../2026/0110_mecanisme_backup_contexte_agent_post.html[Sliding Window dan Gelombang Dingin]
----&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>Pegawai pertama kami: Anonimisir Dataset — MVP0 dalam Masa Percobaan</title>
            <link >https://cheroliv.com/blog/pilot-batch/0115_anonymiseur_dataset_mvp0_realite_augmentee_llm_post.html</link>
            <pubDate>Fri, 1 May 2026 00:00:00 +0000</pubDate>
            <guid isPermaLink="false">blog/pilot-batch/0115_anonymiseur_dataset_mvp0_realite_augmentee_llm_post.html</guid>
            <description>&amp;lt;div id=&amp;quot;toc&amp;quot; class=&amp;quot;toc&amp;quot;&amp;gt;
&amp;lt;div id=&amp;quot;toctitle&amp;quot;&amp;gt;Table des matières&amp;lt;/div&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel1&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_masalah_yang_membuat_semua_orang_melarikan_diri&amp;quot;&amp;gt;Masalah yang membuat semua orang melarikan diri&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div id=&amp;quot;preamble&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;waktu membaca : 12 minutes&amp;lt;/div&amp;gt;
&amp;lt;p&amp;gt;Saya tidak mempekerjakan seorang LLM. Saya mempekerjakan seorang penononim. Sebelum melakukan RAG pgvector, sebelum melakukan fine-tuning pada ahli CDA/FPA, sebelum menyediakan workspaces klien — seseorang harus membersihkan toilet data. Pegawai ini adalah ahli anonimisasi (MVP0, EPIC 2 dari codebase-gradle). Dan itulah yang menjadikan produk tersebut dapat dipasarkan.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;dlist&amp;quot;&amp;gt;
&amp;lt;dl&amp;gt;
&amp;lt;dt class=&amp;quot;hdlist1&amp;quot;&amp;gt;tik&amp;lt;/dt&amp;gt;
&amp;lt;dd&amp;gt;
&amp;lt;p&amp;gt;[]&amp;lt;/p&amp;gt;
&amp;lt;/dd&amp;gt;
&amp;lt;/dl&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_masalah_yang_membuat_semua_orang_melarikan_diri&amp;quot;&amp;gt;Masalah yang membuat semua orang melarikan diri&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Semua startup AI yang memproses data pelatihan menghadapi dinding yang sama :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-none hljs&amp;quot;&amp;gt;----
----
Données brutes client (SPG, cours, évaluations)
  ├→ Noms de stagiaires en clair dans les PDF
  ├→ Emails dans les métadonnées JSON
  ├→ IP de connexion dans les logs pgvector
  ├→ Identifiants dans les nœuds Graphify
  └→ Références OF pilote dans les schémas SQL
----

Hasil: atau kamu melakukan RAG pada data kotor (halusinasi PII, tidak kepatuhan GDPR, risiko hukum), atau kamu tidak melakukan apa-apa (tidak ada produk). Cara ketiga — membersihkan secara manual — tidak dapat diskalakan.

Jawaban saya: tidak menghindari masalah. L&amp;#39;**mengotomatisasi**.

== MVP0: Penyamar, Pekerja pertama yang dipekerjakan dalam masa percobaan

MVP0 bukan fitur. Ini adalah**ahli kenyamanan**— seorang pekerja yang mengerjakan pekerjaan kotor yang bodoh dan jahat, dan yang tidak ada yang mau membicarakan dalam pitch decks.

Apa yang dia lakukan :

[source]
----
ENTRÉE : Données brutes multi-sources
  ├→ AsciiDoc (SPG_A2SP.adoc) → noms, dates, OF pilote
  ├→ JSON (catalogue formations) → emails, téléphones
  ├→ YAML (config workspace) → tokens OAuth2
  ├→ SQL (schémas DDL) → IP, adresses
  ├→ pgvector (métadonnées embeddings) → identifiants
  ├→ ONNX (outputs classification) → noms propres
  └→ Graphify (graph.json) → nœuds avec PII

SORTIE : Datasets propres, format standardisé
  ├→ [ANONYMIZED] remplace les PII détectées
  ├→ Classification RGPD (niveau 0→4)
  ├→ Rapport d&amp;#39;audit (quoi a été nettoyé)
  └→ Dataset prêt pour RAG ET fine-tuning
----

Dia mendeteksi pola-pola yang paling jelas terlebih dahulu (emails)`.*@.*`, token`sk-*`, `ghp_*`, IPs), lalu ia belajar. Masa percobaan, itu adalah itu: kita memvalidasi bahwa ia tidak membiarkan negatif palsu lolos sebelum mengangkatnya tetap.

== Apa yang dibuka oleh MVP ini (dan ini bukan hanya kepatuhan)

Anonimisasi seperti MVP0 bukan tol GDPR sebelum jalan raya. Itu adalah jalan raya itu sendiri. Berikut apa yang dibukanya :

=== Saat ini: Realitas Augmented dalam Prompt

[source]
----
DONNÉES BRUTES CLIENT
    ↓
Anonymiseur MVP0 → datasets propres
    ↓
RAG pgvector (MVP1) → top-K documents similaires (filtrés, anonymisés)
    ↓
LLM (deepseek-v4-pro) → réponse augmentée, zéro PII
----

RAG tidak mencari di data kotor — ia mencari di ruang**bersih oleh konstruksi**. Ini mengubah semuanya untuk kualitas jawaban: LLM tidak menghabiskan waktunya untuk mengelak dari PII yang dia kenali tetapi tidak boleh disebutkan.

=== Masa depan : Fine-Tuning pada Data Sehat

[source]
----
MVP0 → datasets propres accumulés (session après session)
    ↓
Fine-tuning expert métier (CDA, FPA) sur données zéro PII
    ↓
Modèle fine-tuné exposé via Ollama (sans fuite de données)
----

Fine-tuning pada data mentah adalah potensi bencana hukum. Pada data bersih, ini adalah**metode yang dapat direproduksi**. Setiap klien mengakumulasi datasetnya yang telah dibersihkan, setiap klien dapat melakukan fine-tuning pada pakar bisnisnya atas datanya — tanpa pernah mengekspos data pribadi.

== Mengapa itu dapat dipasarkan?

Bukan « alat kepatuhan GDPR ». Ini adalah**metode yang dapat direproduksi yang mengubah masalah universel menjadi aset**:

1. *Semua perusahaan pelatihan memiliki data kotor* — ini masalahnya
2. *Tidak ada yang memiliki pipeline pembersihan otomatis multi-sumber* — itu adalah pasar
3. *Perulangan: anonymisasi → RAG → fine-tuning bersifat proprietor* — itu adalah hambatan

SaaS Edster (MVP3) tidak akan menjual « workspace Gradle ». Ia akan menjual loop tertutup ini. Dan MVP0 adalah pintu masuk.

== Format SQL sebagai Cara Ketiga dari Konteks

Pengimporan data terstruktur bukan hanya format pertukaran. Skema SQL (DDL) memuat**model entitas-relasi domain**:

[source]
----
CREATE TABLE formation (
  id UUID PRIMARY KEY,
  titre VARCHAR NOT NULL,
  referentiel RNCP REFERENCES rncp(id),
  organisme OF REFERENCES of_pilote(id)  -- ← va être anonymisé
);
----

DDL ini, setelah dianonimkan (baris 4), menjadi sumber konteks untuk LLM. Bukan teks —**model bisnis**. RAG memberikan konten (similaritas vektor), Graphify memberikan relasi (struktur tepat), dan skema SQL memberikan**Desain Berbasis Domain implisit**: bounded contexts, agrégats, value objects. LLM memahami bisnis karena ia membaca skemanya.

== Roadmap: dari MVP0 ke SaaS

[source]
----
MVP0 — Anonymiseur (ce que cet article décrit)
  Sortie : datasets propres, reproductible

MVP1 — RAG pgvector (dépend de MVP0)
  Realité augmentée en prompt sur données nettoyées

MVP2 — Graphify + ONNX + SQL DDD (dépend de MVP1)
  Vecteur composite de contexte (règles + similarité + relations exactes)

MVP3 — SaaS Edster (dépend de MVP0-2)
  Provisionnement workspace client complet
----

Setiap MVP bergantung pada yang sebelumnya. Tidak ada yang dapat dikirim tanpa MVP0. Ini adalah alasan mengapa Anonymiseur bukan « P0 » dalam backlog — ia adalah**MVP0**. Ini adalah deliverable komersial pertama, dan semua sisanya bergantung pada keberhasilannya.

== Kesimpulan : pekerjaan tidak terlihat yang membuat segalanya mungkin

Dalam startup konvensional, pembersihan data di-outsource, dilakukan secara manual, atau diabaikan. Dalam arsitektur ini, ini adalah**inti produk**. L&amp;#39;Anonymiseur adalah karyawan pertama karena tanpa dia, tidak ada yang berfungsi :

* Tanpa MVP0 → tidak ada RAG legal (MVP1)
* Tanpa MVP0 → tidak ada fine-tuning tanpa kebocoran data (EPIC 5)
* Tanpa MVP0 → tidak ada SaaS kredibel (MVP3)
* Tanpa MVP0 → tidak ada realitas augmentasi dalam prompt
* Tanpa MVP0 → tidak ada jalur ketiga konteks (SQL DDD)

Dia melakukan pekerjaan kotor. Dia tidak akan pernah disebutkan dalam pitch. Tapi dia yang menjaga pabrik berjalan.

Dan itulah mengapa kami merekrutnya terlebih dahulu.

== Referensi

* Article tentang ontologi spasial dan lingkaran kepercayaan :link:../2026/0114_gouvernance_cercles_confiance_ontologie_spatiale_alignement_llm_post.html[Ontologi Spasial sebagai Mekanisme Penyelarasan]
* Artikel tentang governansi agen Eager/Lazy :link:../2026/0108_gouvernance_agent_opencode_eager_lazy_post.html[Mengatur Agent AI dengan AsciiDoc]
* Artikel tentang mekanisme Hot/Warm/Cold :link:../2026/0110_mecanisme_backup_contexte_agent_post.html[Sliding Window dan Gelombang Dingin]
* Artikel tentang perbandingan dari tiga LLM :link:../2026/0112_comparaison_kimi_glm_deepseek_long_contexte_plugin_gradle_opencode_post.html[DeepSeek-V4-Pro, Kimi K2.6, GLM-5.1]
----&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>Karyawan pertama kami : Anonimiser Dataset — MVP0 dalam masa percobaan</title>
            <link >https://cheroliv.com/blog/2026/0115_anonymiseur_dataset_mvp0_realite_augmentee_llm_post.html</link>
            <pubDate>Fri, 1 May 2026 00:00:00 +0000</pubDate>
            <guid isPermaLink="false">blog/2026/0115_anonymiseur_dataset_mvp0_realite_augmentee_llm_post.html</guid>
            <description>&amp;lt;div id=&amp;quot;toc&amp;quot; class=&amp;quot;toc&amp;quot;&amp;gt;
&amp;lt;div id=&amp;quot;toctitle&amp;quot;&amp;gt;Table des matières&amp;lt;/div&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel1&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_masalah_yang_membuat_semua_orang_lari&amp;quot;&amp;gt;Masalah yang membuat semua orang lari&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div id=&amp;quot;preamble&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;waktu membaca : 12 minutes&amp;lt;/div&amp;gt;
&amp;lt;p&amp;gt;Saya tidak merekrut sebuah LLM. Saya merekrut seorang penganonim. Sebelum melakukan RAG pgvector, sebelum fine-tuning ahli CDA/FPA, sebelum menyediakan workspace klien — seseorang harus membersihkan toilet data. Pegawai ini adalah ahli anonymisasi (MVP0, EPIC 2 dari codebase-gradle). Dan itulah yang membuat produk dapat dipasarkan.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;dlist&amp;quot;&amp;gt;
&amp;lt;dl&amp;gt;
&amp;lt;dt class=&amp;quot;hdlist1&amp;quot;&amp;gt;tik&amp;lt;/dt&amp;gt;
&amp;lt;dd&amp;gt;
&amp;lt;p&amp;gt;[]&amp;lt;/p&amp;gt;
&amp;lt;/dd&amp;gt;
&amp;lt;/dl&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_masalah_yang_membuat_semua_orang_lari&amp;quot;&amp;gt;Masalah yang membuat semua orang lari&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Semua startup AI yang memanipulasi data pelatihan menabrak dinding yang sama :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-none hljs&amp;quot;&amp;gt;----
----
Données brutes client (SPG, cours, évaluations)
  ├→ Noms de stagiaires en clair dans les PDF
  ├→ Emails dans les métadonnées JSON
  ├→ IP de connexion dans les logs pgvector
  ├→ Identifiants dans les nœuds Graphify
  └→ Références OF pilote dans les schémas SQL
----

Hasil: baik kamu melakukan RAG pada data kotor (halusinasi PII, tidak kepatuhan terhadap GDPR, risiko hukum), atau kamu tidak melakukan apa-apa (tidak ada produk). Cara ketiga — membersihkan secara manual — tidak dapat diskalakan.

Jawaban saya: jangan mengelakari masalah. L&amp;#39;**mengotomatisasi**.

== MVP0: Anonimizer, pertama yang dipekerjakan dalam masa percobaan

MVP0 bukanlah sebuah fitur. Ini adalah**pakar kenyamanan**— seorang pekerja yang mengerjakan pekerjaan kotor, bodoh dan jahat, dan yang tidak ada yang ingin membicarakan di dalam dek pitch.

Yang dilakukan olehnya:

[source]
----
ENTRÉE : Données brutes multi-sources
  ├→ AsciiDoc (SPG_A2SP.adoc) → noms, dates, OF pilote
  ├→ JSON (catalogue formations) → emails, téléphones
  ├→ YAML (config workspace) → tokens OAuth2
  ├→ SQL (schémas DDL) → IP, adresses
  ├→ pgvector (métadonnées embeddings) → identifiants
  ├→ ONNX (outputs classification) → noms propres
  └→ Graphify (graph.json) → nœuds avec PII

SORTIE : Datasets propres, format standardisé
  ├→ [ANONYMIZED] remplace les PII détectées
  ├→ Classification RGPD (niveau 0→4)
  ├→ Rapport d&amp;#39;audit (quoi a été nettoyé)
  └→ Dataset prêt pour RAG ET fine-tuning
----

Deteksi pola yang paling jelas terlebih dahulu (email`.*@.*`, token`sk-*`, `ghp_*`, IPs), kemudian ia belajar. Masa uji: ini adalah kita memastikan bahwa dia tidak membiarkan negatif palsu lolos sebelum kita mengangkatnya sebagai tetap.

== Apa yang Dibuka oleh MVP Ini (dan ini bukan hanya kepatuhan)

Anonymisasi seperti MVP0 bukan tol RGPD sebelum jalan raya. Ini adalah jalan raya itu sendiri. Berikut ini adalah apa yang dibukanya :

=== Sekarang: Realitas Augmented dalam Prompt

[source]
----
DONNÉES BRUTES CLIENT
    ↓
Anonymiseur MVP0 → datasets propres
    ↓
RAG pgvector (MVP1) → top-K documents similaires (filtrés, anonymisés)
    ↓
LLM (deepseek-v4-pro) → réponse augmentée, zéro PII
----

RAG tidak mencari di data kotor — ia mencari di sebuah ruang**bersih oleh konstruksi**. Ini mengubah segalanya untuk kualitas jawaban: LLM tidak menghabiskan waktunya untuk mengelakari PII yang dikenal tetapi tidak boleh disebutkan.

=== Masa depan : Fine-Tuning pada Data Sehat

[source]
----
MVP0 → datasets propres accumulés (session après session)
    ↓
Fine-tuning expert métier (CDA, FPA) sur données zéro PII
    ↓
Modèle fine-tuné exposé via Ollama (sans fuite de données)
----

Fine-tuning pada data mentah adalah potensi bencana hukum. Pada data bersih, ini adalah**metode yang dapat direproduksi**. Setiap klien mengakumulasi dataset yang telah dibersihkan miliknya, setiap klien dapat melatih ulang ahli bisnisnya pada data mereka — tanpa pernah mengekspos data pribadi.

== Mengapa Ini Dapat Dipasarkan

Ini bukan « alat kepatuhan GDPR ». Ini adalah**metode yang dapat direproduksi yang mengubah masalah universal menjadi aset**:

1. *Semua perusahaan pelatihan memiliki data kotor* — ini adalah masalah
2. *Tidak ada yang memiliki pipeline pembersihan multi-source otomatis* — itu adalah pasar
3. *Lingkaran: anonimisasi → RAG → fine-tuning adalah proprietari* — itu adalah hambatan

SaaS Edster (MVP3) tidak akan menjual « sebuah workspace Gradle ». Dia akan menjual loop yang tertutup ini. Dan MVP0 adalah pintu masuk.

== Format SQL sebagai Cara Ketiga dari Konteks

Import data terstruktur bukan hanya format pertukaran. Skema SQL (DDL) memuat**model entitas-relasi domain**:

[source]
----
CREATE TABLE formation (
  id UUID PRIMARY KEY,
  titre VARCHAR NOT NULL,
  referentiel RNCP REFERENCES rncp(id),
  organisme OF REFERENCES of_pilote(id)  -- ← va être anonymisé
);
----

DDL ini, setelah dianonimkan (baris 4), menjadi sumber konteks untuk LLM. Bukan teks —**model bisnis**. RAG memberikan konten (kesamaan vektor), Graphify memberikan relasi (struktur tepat), dan skema SQL memberikan le**Domain-Driven Design implisit**bounded contexts, agregat, value objects. LLM memahami bidang karena ia membaca skemanya.

== Peta jalan: dari MVP0 ke SaaS

[source]
----
MVP0 — Anonymiseur (ce que cet article décrit)
  Sortie : datasets propres, reproductible

MVP1 — RAG pgvector (dépend de MVP0)
  Realité augmentée en prompt sur données nettoyées

MVP2 — Graphify + ONNX + SQL DDD (dépend de MVP1)
  Vecteur composite de contexte (règles + similarité + relations exactes)

MVP3 — SaaS Edster (dépend de MVP0-2)
  Provisionnement workspace client complet
----

Ini adalah alasan mengapa Anonymiser tidak « P0 » dalam backlog — ia adalah**MVP0**. Ini adalah produk yang pertama yang dapat diperjualbelikan, dan seluruh hal lainnya bergantung pada keberhasilannya.

== Kesimpulan : Pekerjaan Tak Terlihat yang Membuat Semua Hal Mungkin

Dalam startup konvensional, pembersihan data dilakukan secara eksternal, secara manual, atau diabaikan. Dalam arsitektur ini, ini adalah**inti dari produk**. Anonymiseur adalah karyawan pertama karena tanpa dia, tidak ada yang berfungsi :

* Tanpa MVP0 → tidak ada RAG legal (MVP1)
* Tanpa MVP0 → tidak ada fine-tuning tanpa kebocoran data (EPIC 5)
* Tanpa MVP0 → tidak ada SaaS yang kredibel (MVP3)
* Tanpa MVP0 → tidak ada realitas augmentasi dalam prompt
* Tanpa MVP0 → tidak ada jalur konteks ketiga (SQL DDD)

Dia melakukan pekerjaan kotor. Dia tidak akan pernah disebut dalam pitch. Tetapi dia yang menjaga pabrik berjalan.

Dan itulah sebabnya kita merekrutnya terlebih dahulu.

== Referensi

* Article tentang ontologi spasial dan lingkaran kepercayaan :link:../2026/0114_gouvernance_cercles_confiance_ontologie_spatiale_alignement_llm_post.html[Ontologi Spasial sebagai Mekanisme Penyelarasan]
* Artikel tentang tata kelola agen Eager/Lazy :link:../2026/0108_gouvernance_agent_opencode_eager_lazy_post.html[Mengatur Agen AI dengan AsciiDoc]
* Artikel tentang mekanisme Hot/Warm/Cold:link:../2026/0110_mecanisme_backup_contexte_agent_post.html[Sliding Window dan Gelombang Dingin]
* Artikel tentang perbandingan tiga LLMs :link:../2026/0112_comparaison_kimi_glm_deepseek_long_contexte_plugin_gradle_opencode_post.html[DeepSeek-V4-Pro, Kimi K2.6, GLM-5.1]
----&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>Taman Rahasia Pengembang: Bagaimana Ontologi Spasial Menyelaraskan LLM Tanpa Prompt Engineering</title>
            <link >https://cheroliv.com/blog/2026/0114_gouvernance_cercles_confiance_ontologie_spatiale_alignement_llm_post.html</link>
            <pubDate>Thu, 30 Apr 2026 00:00:00 +0000</pubDate>
            <guid isPermaLink="false">blog/2026/0114_gouvernance_cercles_confiance_ontologie_spatiale_alignement_llm_post.html</guid>
            <description>&amp;lt;div id=&amp;quot;toc&amp;quot; class=&amp;quot;toc&amp;quot;&amp;gt;
&amp;lt;div id=&amp;quot;toctitle&amp;quot;&amp;gt;Table des matières&amp;lt;/div&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel1&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_catatan_prompt_engineering_adalah_sebuah_kastil_pasir&amp;quot;&amp;gt;Catatan: Prompt Engineering adalah sebuah kastil pasir&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_empat_lingkaran_kepercayaan_ontologi_spasial&amp;quot;&amp;gt;Empat Lingkaran Kepercayaan: Ontologi Spasial&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_taman_rahasia_ruang_di_luar_cvs&amp;quot;&amp;gt;Taman Rahasia: Ruang Di Luar CVS&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel3&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_gitignore_sebagai_konfigurasi_tata_kelola&amp;quot;&amp;gt;.gitignore` sebagai Konfigurasi Tata Kelola&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_mekanisme_snapshot&amp;quot;&amp;gt;Mekanisme Snapshot&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_vault_configuration_seperti_spring_cloud_config&amp;quot;&amp;gt;Vault : &amp;lt;code&amp;gt;configuration/&amp;lt;/code&amp;gt; seperti Spring Cloud Config&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_perpustakaan_office_sebagai_data_konsumsi&amp;quot;&amp;gt;Perpustakaan: &amp;lt;code&amp;gt;office/&amp;lt;/code&amp;gt; sebagai Data Konsumsi&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_forges_foundry_sebagai_implementasi&amp;quot;&amp;gt;Forges : &amp;lt;code&amp;gt;foundry/&amp;lt;/code&amp;gt; sebagai Implementasi&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_procontra_alignasi_spasial_vs_alignasi_prompt&amp;quot;&amp;gt;Pro/Contra : Alignasi Spasial vs Alignasi Prompt&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_pendekatan_klasik_penyelarasan_dengan_prompt_awal&amp;quot;&amp;gt;Pendekatan Klasik: Penyelarasan dengan Prompt Awal&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_pendekatan_ini_penyelarasan_melalui_ontologi_spasial&amp;quot;&amp;gt;Pendekatan ini : Penyelarasan melalui Ontologi Spasial&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_aljabar_relasional_dari_workspace_dan_delta_yang_dapat_diamati&amp;quot;&amp;gt;Aljabar Relasional dari Workspace dan Delta yang dapat diamati&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_vektor_komposit_konteks_rag_pgvector_graphify&amp;quot;&amp;gt;Vektor Komposit Konteks : RAG + pgvector + Graphify&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_komponen_1_rag_langchain4j_postgresql_pgvector&amp;quot;&amp;gt;Komponen 1 — RAG LangChain4j + PostgreSQL pgvector&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_komponen_2_knowledge_graph_graphify&amp;quot;&amp;gt;Komponen 2 — Knowledge Graph Graphify&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_komponen_3_graphify_inkremental_sparse_dapat_diagregasikan_dapat_dikonsumsi&amp;quot;&amp;gt;Komponen 3 — Graphify Inkremental : Sparse, Dapat Diagregasikan, Dapat Dikonsumsi&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_klasifikasi_otomatis_gdpr_llm_sebagai_router&amp;quot;&amp;gt;Klasifikasi Otomatis GDPR : LLM sebagai Router&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_roadmap_dari_asciidoc_ke_langgraph4j&amp;quot;&amp;gt;Roadmap : dari AsciiDoc ke LangGraph4j&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_apa_yang_diselesaikan_oleh_arsitektur_ini_dan_apa_yang_tidak_diselesaikan&amp;quot;&amp;gt;Apa yang Diselesaikan oleh Arsitektur Ini (dan apa yang tidak Diselesaikan)&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_kesimpulan_arsitektur_sebagai_diskursus&amp;quot;&amp;gt;Kesimpulan: Arsitektur sebagai Diskursus&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_referensi&amp;quot;&amp;gt;Referensi&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div id=&amp;quot;preamble&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;waktu membaca : 20 minutes&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Teknik prompt engineering itu rapuh. Pada 80 000 token, aturan penyelarasan Anda terbenam dalam noise, dan LLM lupa apa yang Anda minta di awal percakapan. Solusi saya? Tidak menyelaraskan melalui teks, tetapi melalui l&amp;#39;&amp;lt;strong&amp;gt;ruang&amp;lt;/strong&amp;gt;. Saya telah menyusun workspace pengembangan saya menjadi empat lingkaran kepercayaan konsentris — dari taman rahasia intim hingga pabrik publik — dan setiap lingkaran adalah area fisik dari sistem berkas. LLM tidak perlu dikonfirmasi ulang aturan-aturannya: jalur file mengandung aturan-aturannya. Berikut ini cara kerjanya, dan mengapa arsitektur perataan berbasis ruang ini lebih tahan daripada`system prompt`500 baris.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Ini adalah artikel yang panjang.&amp;lt;br&amp;gt;
Duduklah dengan nyaman.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;dlist&amp;quot;&amp;gt;
&amp;lt;dl&amp;gt;
&amp;lt;dt class=&amp;quot;hdlist1&amp;quot;&amp;gt;ketuk&amp;lt;/dt&amp;gt;
&amp;lt;dd&amp;gt;
&amp;lt;p&amp;gt;[]&amp;lt;/p&amp;gt;
&amp;lt;/dd&amp;gt;
&amp;lt;/dl&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_catatan_prompt_engineering_adalah_sebuah_kastil_pasir&amp;quot;&amp;gt;Catatan: Prompt Engineering adalah sebuah kastil pasir&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Selama tiga minggu, saya telah mengembangkan plugin Gradle dengan Opencode, menggunakan tiga LLM yang berbeda — Kimi K2.6, GLM-5.1, dan DeepSeek-V4-Pro (yang satu-satunya yang masih bertahan, tetapi itu adalah cerita lain&amp;lt;a href=&amp;quot;0112_comparaison_kimi_glm_deepseek_long_contexte_plugin_gradle_opencode_post.html&amp;quot;&amp;gt;Déjà couverte ici.&amp;lt;/a&amp;gt;). Metode tata kelola agen&amp;lt;a href=&amp;quot;0108_gouvernance_agent_opencode_eager_lazy_post.html&amp;quot;&amp;gt;Je l&amp;amp;#8217;ai documentée en détail dans un précédent article.&amp;lt;/a&amp;gt; berdasarkan file AsciiDoc —&amp;lt;code&amp;gt;AGENT.adoc&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;INDEX.adoc&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;PROMPT_REPRISE.adoc&amp;lt;/code&amp;gt;— yang memuat ~30 000 token aturan, backlog, dan riwayat di setiap awal sesi.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Dan meskipun infrastruktur dokumen ini, dua hal membuat saya terkesan:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;olist arabic&amp;quot;&amp;gt;
&amp;lt;ol class=&amp;quot;arabic&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;LLM lupa.&amp;lt;/strong&amp;gt; Meskipun dengan aturan mutlak di awal setiap file EAGER, di atas 60 000 token kumulatif (konteks awal + percakapan), Kimi K2.6 telah mulai mengusulkan`Write`menggugat pada file konfigurasi. GLM-5.1 salah mengidentifikasi token Firebase sebagai placeholder yang harus diganti. Aturan telah ditulis — LLM tidak lagi &amp;lt;strong&amp;gt;melihatnya&amp;lt;/strong&amp;gt;.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Tata kelola itu sendiri menjadi masalah.&amp;lt;/strong&amp;gt; Setelah mengimplementasikan mekanisme Hot/Warm/Cold&amp;lt;a href=&amp;quot;0110_mecanisme_backup_contexte_agent_post.html&amp;quot;&amp;gt;Article sur la rotation de backup.&amp;lt;/a&amp;gt; untuk menghindari ledakan konteks, file-file EAGER saya masih berat 1414 baris&amp;lt;a href=&amp;quot;0111_audit_contexte_agent_post.html&amp;quot;&amp;gt;J&amp;amp;#8217;ai documenté cet audit ici.&amp;lt;/a&amp;gt; Tata kelola — yang dirancang untuk melindungi LLM dari saturasi — justru menyaturasi LLM.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Saya membutuhkan sebuah mekanisme penyelarasan yang tidak bergantung pada jumlah token dalam prompt.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Jawaban: berhenti menyelaraskan menurut teks, dan mulai menyelaraskan dengan l&amp;#39;&amp;lt;strong&amp;gt;ruang&amp;lt;/strong&amp;gt;.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_empat_lingkaran_kepercayaan_ontologi_spasial&amp;quot;&amp;gt;Empat Lingkaran Kepercayaan: Ontologi Spasial&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;folder saya`workspace/&amp;lt;code&amp;gt;(di&amp;lt;/code&amp;gt;~/workspace/`) bukan &amp;lt;strong&amp;gt;pas&amp;lt;/strong&amp;gt; sebuah repositori Git. Ini adalah akar dari seluruh pekerjaan saya — kode, dokumentasi, pelatihan, infrastruktur. Dan ia terstruktur dalam empat zona yang bukan konvensi pengaturan, tetapi&amp;lt;strong&amp;gt;lingkaran kepercayaan konsentrik&amp;lt;/strong&amp;gt; :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;table class=&amp;quot;tableblock frame-all grid-all stretch&amp;quot;&amp;gt;
&amp;lt;colgroup&amp;gt;
&amp;lt;col style=&amp;quot;width: 6.6666%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 13.3333%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 20%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 26.6666%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 33.3335%;&amp;quot;&amp;gt;
&amp;lt;/colgroup&amp;gt;
&amp;lt;tbody&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;tingkat&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;label&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Zona fisik&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;CVS&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Keterlihatan&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;0&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;Taman rahasia&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;`workspace/`akar&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Tidak ada&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Intim — pikiran bebas, tanpa commit, tanpa publikasi&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;1&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;kotak aman&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;configuration/&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Git privat (solo)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Rahasia, token, arsip visi. Satu orang saja.&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;2&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;perpustakaan&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;office/&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Git pribadi (luas)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Data pedagogi, SPG/SPD, skema JSON. Lingkaran kepercayaan teridentifikasi.&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;4&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;Bengkel (publik)&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;foundry/&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Git publik (Apache 2.0)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Kode sumber, plugin, tes, dokumentasi teknis.&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Tidak ada level 3 dalam tabel. Level 3 adalah level &amp;lt;strong&amp;gt;transitoire&amp;lt;/strong&amp;gt; : itu adalah konten dari`office/`yang telah dianonimkan dan siap untuk dipublikasikan sebagai data terbuka. Tidak memiliki zona fisik khusus — ini adalah kondisi data, bukan lokasi.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Setiap level menjawab pertanyaan spesifik :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Di mana menaruh ide yang belum siap untuk dibagikan, bahkan dengan lingkaran terbatas?&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Di mana menyimpan token API agar tidak bocor ?&amp;lt;/strong&amp;gt; → selembari (&amp;lt;code&amp;gt;configuration/&amp;lt;/code&amp;gt;) Fisik terisolasi. Tidak ada repositori lain yang dapat mereferensikannya secara kesalahan.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Dimana membangun bersama sebuah katalog pelatihan dengan suatu OF pilot?&amp;lt;/strong&amp;gt; → Perpustakaan (&amp;lt;code&amp;gt;office/&amp;lt;/code&amp;gt;). Berversi, kolaboratif, tetapi pribad&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Dimana mengindustrialisasi plugin Gradle sumber terbuka?&amp;lt;/strong&amp;gt; → pabrik kode`foundry/`). Publik, dapat di-fork, diuji di CI.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Ontologi ini&amp;lt;strong&amp;gt;dapat dikonsumsi oleh LLM&amp;lt;/strong&amp;gt;. Saat ia membaca sebuah file dalam`foundry/plantuml-gradle/src/`, dia mengetahui secara tersirat: &amp;quot;saya berada di lingkaran 4 — kode publik, tes wajib, tidak ada rahasia, tidak ada data pendidikan&amp;quot;. Dia tidak memerlukan prompt yang mengingatkannya.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_taman_rahasia_ruang_di_luar_cvs&amp;quot;&amp;gt;Taman Rahasia: Ruang Di Luar CVS&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Ini adalah konsep yang paling penting — dan yang paling tidak intuitif.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;akar`workspace/&amp;lt;code&amp;gt;tidak memiliki&amp;lt;/code&amp;gt;.git/&amp;lt;code&amp;gt;. Dokumen yang tinggal di sana (`WORKSPACE_VISION.adoc&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;WORKSPACE_AS_PRODUCT.adoc&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;WORKSPACE_ORGANIZATION.adoc&amp;lt;/code&amp;gt;— dan yang Anda baca saat ini, yang turun dari itu) tidak dimaksudkan untuk ditaruh dalam sistem kontrol versi, dibagikan, atau bahkan dibaca oleh siapa pun selain saya. Ini adalah&amp;lt;strong&amp;gt;pikiran yang sedang tumbuh&amp;lt;/strong&amp;gt;.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;admonitionblock important&amp;quot;&amp;gt;
&amp;lt;table&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;icon&amp;quot;&amp;gt;
&amp;lt;i class=&amp;quot;fa icon-important&amp;quot; title=&amp;quot;❗&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Taman rahasia itu adalah &amp;lt;strong&amp;gt;out-of-CVS&amp;lt;/strong&amp;gt; secara alami. Ketiadaan pencatatan versi adalah kondisi kebebasan berpikir. Di sini tidak ditulis untuk dibaca — ditulis di sini untuk memperjelas visi sendiri.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Namun kebebasan ini memiliki biaya: satu`rm`kecelakaan, dan ini adalah bulan-bulan pemikiran strategis yang menghilang. Solusinya bukanlah untuk membuat versi taman (karena itu akan menghancurkannya) — adalah untuk&amp;lt;strong&amp;gt;mencerminkan&amp;lt;/strong&amp;gt;dalam lingkaran yang paling terbatas&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect3&amp;quot;&amp;gt;
&amp;lt;h4 id=&amp;quot;_gitignore_sebagai_konfigurasi_tata_kelola&amp;quot;&amp;gt;.gitignore` sebagai Konfigurasi Tata Kelola&amp;lt;/h4&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Di akar`workspace/&amp;lt;code&amp;gt;, sebuah file&amp;lt;/code&amp;gt;.gitignore`minimal mendeklarasikan&amp;lt;strong&amp;gt;kebijakan historisasi&amp;lt;/strong&amp;gt;des fichiers du jardin secret :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-none hljs&amp;quot;&amp;gt;.goosehints
.goose&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Ce &amp;lt;code&amp;gt;.gitignore`tidak memiliki fungsi Git klasik (tidak ada&amp;lt;/code&amp;gt;.git/`di akar). Dia bertindak seperti sebuah&amp;lt;strong&amp;gt;file konfigurasi tata kelola&amp;lt;/strong&amp;gt;yang menjawab pertanyaan tertentu: &amp;lt;strong&amp;gt;artefak taman rahasia yang layak untuk dihistorisasi, dan manakah yang hanya bersifat sementara ?&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;File yang tercantum dalam`.gitignore`—&amp;lt;code&amp;gt;.goosehints&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;.goose&amp;lt;/code&amp;gt;— adalah artefak sementara yang dihasilkan oleh agen. Tidak ada nilai strategis. Tidak ada historisasi.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;File-file`.adoc`dari akar —&amp;lt;code&amp;gt;WORKSPACE_VISION.adoc&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;WORKSPACE_ORGANIZATION.adoc&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;WORKSPACE_AS_PRODUCT.adoc&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;WHAT_THE_GAMES_BEEN_MISSING.adoc&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;depots_implementes_strategie.adoc&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;synthese-LLMs-long-contexte.adoc&amp;lt;/code&amp;gt;— tidak&amp;lt;strong&amp;gt;tidak&amp;lt;/strong&amp;gt;di dalam`.gitignore`. Ini artefak yang akan dihistorikan.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Le `.gitignore`adalah&amp;lt;strong&amp;gt;skema tata kelola&amp;lt;/strong&amp;gt;: Semua yang tidak tercantum di sana adalah kandidat snapshot. Dan LLM, dengan membaca file ini, tahu tepat apa yang harus diarsipkan dan apa yang dapat diabaikan — tanpa perlu diingatkan dalam prompt.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect3&amp;quot;&amp;gt;
&amp;lt;h4 id=&amp;quot;_mekanisme_snapshot&amp;quot;&amp;gt;Mekanisme Snapshot&amp;lt;/h4&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Saya telah membuat`configuration/vision-archive/&amp;lt;code&amp;gt;: snapshot ber-tanggal dari semua&amp;lt;/code&amp;gt;.adoc`dari akar, commits di repositori pribadi`configuration/`. Setiap sesi brainstorming menghasilkan sebuah snapshot :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;DATE=$(date +%Y-%m-%d)
mkdir -p /home/cheroliv/workspace/configuration/vision-archive/$DATE
cp /home/cheroliv/workspace/*.adoc /home/cheroliv/workspace/configuration/vision-archive/$DATE/
cd /home/cheroliv/workspace/configuration &amp;amp;amp;&amp;amp;amp; git add vision-archive/ &amp;amp;amp;&amp;amp;amp; \
  git commit -m &amp;quot;vision-archive: snapshot $DATE&amp;quot;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Prosedur dipicu pada akhir sesi oleh LLM sendiri, yang mengeksekusi tugas routing global ini. Setiap snapshot mencapture keadaan lengkap dari pemikiran strategis pada saat T.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Sejarah Git dari`configuration/`menjadi&amp;lt;strong&amp;gt;Biografi dari pikiran strategis saya&amp;lt;/strong&amp;gt;. Saya bisa membuat satu`git log&amp;amp;#8201;&amp;amp;#8212;&amp;amp;#8201;vision-archive/`dan melihat perkembangan visi saya, sesi per sesi, tanggal per tanggal&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Contoh nyata dari riwayat setelah sehari bekerja :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-none hljs&amp;quot;&amp;gt;$ git -C configuration log --oneline -- vision-archive/
1089d1b vision-archive: fin de session finale 2026-05-03
6b21eaf vision-archive: fin de session 2026-05-03-1300
1690f82 vision-archive: post-article 2026-05-03
28e6593 vision-archive: post-migration 2026-05-03
3707b78 vision-archive: snapshot 2026-05-03 — jardin secret initial&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Lima snapshot dalam sehari. Setiap snapshot adalah titik pemulihan. Setiap snapshot adalah jalon dalam pembentukan strategi.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Berkas`configuration/vision-archive/latest/`selalu mengandung sebuah&amp;lt;strong&amp;gt;salinan kerja&amp;lt;/strong&amp;gt;cuplikan terakhir, berfungsi sebagai referensi cepat tanpa harus menjelajahi riwayat Git.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Dan untuk LLM, ini adalah&amp;lt;strong&amp;gt;oracle koherensi&amp;lt;/strong&amp;gt;: ketika ia mengamati adanya ketidaksesuaian antara implementasi saat ini dan visi yang terarsip pada tanggal sebelumnya, ia dapat melaporkannya.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_vault_configuration_seperti_spring_cloud_config&amp;quot;&amp;gt;Vault : &amp;lt;code&amp;gt;configuration/&amp;lt;/code&amp;gt; seperti Spring Cloud Config&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;`configuration/`tidak hanya berisi arsip dari penglihatan. Fungsinya utama — dan masa depan — adalah menjadi sebuah&amp;lt;strong&amp;gt;server Spring Cloud Config&amp;lt;/strong&amp;gt;. Semua rahasia, token, kredensial, dan deskriptor infrastruktur berada di sini, dalam repositori Git pribadi yang dapat diakses hanya oleh pemilik.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Mengapa sebuah repositori terpisah daripada sebuah file`.env`di setiap proyek?&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Push rahasia tidak sengaja&amp;lt;/strong&amp;gt; : tidak mungkin. Rahasia berada di repositori pribadi yang proyek publik tidak dapat mereferensikan secara salah.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Audit&amp;lt;/strong&amp;gt; : riwayat Git memberikan jejak setiap modifikasi konfigurasi. Siapa yang mengubah apa, kapan, dengan hash SHA apa.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Rollback&amp;lt;/strong&amp;gt; :`git revert`pada konfigurasi yang rusak. Instan, tanpa backup manual.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_perpustakaan_office_sebagai_data_konsumsi&amp;quot;&amp;gt;Perpustakaan: &amp;lt;code&amp;gt;office/&amp;lt;/code&amp;gt; sebagai Data Konsumsi&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;`office/`is the documentary counterpart of the development work. It contains the blog articles, the technical specifications, the training materials (38 course directories FPA, SPG/SPD, JSON schemas, Bloom/Harrow/Krathwohl taxonomies) — everything that constitutes the&amp;lt;strong&amp;gt;materi pembelajaran&amp;lt;/strong&amp;gt;.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Tapi`office/`bukan hanya sebuah laci untuk dokumen. Ini adalah sebuah&amp;lt;strong&amp;gt;sumber data terstruktur&amp;lt;/strong&amp;gt;bahwasanya plugin Gradle`foundry/`mengkonsumi. Sebuah artikel blog di`office/`adalah sebuah entri yang akan diubah menjadi HTML oleh sebuah plugin. Sebuah SPG AsciiDoc di`office/metiers/FPA/`adalah sebuah artefak yang akan diurai oleh sebuah orchestrator untuk menghasilkan slide, kuis, dan kapsul video.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Et le `build.gradle.kts`pada akar dari`office/`bukan kode bisnis — ini adalah&amp;lt;strong&amp;gt;skrip konsumsi ekosistem plugin&amp;lt;/strong&amp;gt;. Dia berkata: &amp;quot;Ini adalah plugin Gradle yang saya gunakan, ini adalah konteks workspace saya&amp;quot;.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_forges_foundry_sebagai_implementasi&amp;quot;&amp;gt;Forges : &amp;lt;code&amp;gt;foundry/&amp;lt;/code&amp;gt; sebagai Implementasi&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;code&amp;gt;foundry/`mengandung&amp;lt;strong&amp;gt;kode industri&amp;lt;/strong&amp;gt;. 54 repositori Git, yang 7 saat ini diatur oleh beberapa&amp;lt;/code&amp;gt;.agents/`. Ini-lah tempat visi menjadi dapat dieksekusi — plugin Gradle, CI/CD, tes JUnit5 + Cucumber.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Hubungan dengan`office/`adalah dua arah :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;code&amp;gt;office/&amp;lt;/code&amp;gt;→&amp;lt;code&amp;gt;foundry/&amp;lt;/code&amp;gt;: data pedagogik adalah bahan baku yang dikonsumsi oleh plugin.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;code&amp;gt;foundry/&amp;lt;/code&amp;gt;→&amp;lt;code&amp;gt;office/`plugin menghasilkan data yang mengkaya`office/&amp;lt;/code&amp;gt; — le `graph.json`dari graphify-gradle, decks slider yang terkompilasi, laporan build.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_procontra_alignasi_spasial_vs_alignasi_prompt&amp;quot;&amp;gt;Pro/Contra : Alignasi Spasial vs Alignasi Prompt&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Mari kita bandingkan kedua pendekatan.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_pendekatan_klasik_penyelarasan_dengan_prompt_awal&amp;quot;&amp;gt;Pendekatan Klasik: Penyelarasan dengan Prompt Awal&amp;lt;/h3&amp;gt;
&amp;lt;table class=&amp;quot;tableblock frame-all grid-all stretch&amp;quot;&amp;gt;
&amp;lt;colgroup&amp;gt;
&amp;lt;col style=&amp;quot;width: 20%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 80%;&amp;quot;&amp;gt;
&amp;lt;/colgroup&amp;gt;
&amp;lt;tbody&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;✅ Profesional&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;lawan&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Mudah diimplementasikan. Sebuah blok teks dalam system prompt.&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;Lemah pada konteks panjang&amp;lt;/strong&amp;gt; : aturan tenggelam setelah 80K token.&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Berfungsi untuk aturan umum (ton, format).&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;Tidak dapat diverifikasi&amp;lt;/strong&amp;gt; : tidak ada yang mencegah LLM melanggar aturan.&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Tidak perlu memikirkan ulang arsitektur proyek.&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;Tidak dapat dipindahkan&amp;lt;/strong&amp;gt; : setiap repo mendeclare ulang aturan.&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;Secara deklaratif&amp;lt;/strong&amp;gt; : LLM tahu bahwa &amp;lt;strong&amp;gt;harus&amp;lt;/strong&amp;gt;, tapi tidak ada yang menghalangi.&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_pendekatan_ini_penyelarasan_melalui_ontologi_spasial&amp;quot;&amp;gt;Pendekatan ini : Penyelarasan melalui Ontologi Spasial&amp;lt;/h3&amp;gt;
&amp;lt;table class=&amp;quot;tableblock frame-all grid-all stretch&amp;quot;&amp;gt;
&amp;lt;colgroup&amp;gt;
&amp;lt;col style=&amp;quot;width: 20%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 80%;&amp;quot;&amp;gt;
&amp;lt;/colgroup&amp;gt;
&amp;lt;tbody&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;✅ Pro&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;❌ terhadap&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;Resilien terhadap konteks panjang&amp;lt;/strong&amp;gt; : aturan berada di jalur file, bukan di prompt yang jauh.&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;Biaya infrastruktur awal&amp;lt;/strong&amp;gt; : mengatur workspace dalam lingkaran memakan waktu.&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;Dapat diverifikasi secara mekanis&amp;lt;/strong&amp;gt; : sebuah rahasia di`foundry/&amp;lt;code&amp;gt;terdeteksi oleh`git check-ignore&amp;lt;/code&amp;gt;.&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;Membutuhkan disiplin&amp;lt;/strong&amp;gt; : seorang kontributor baru harus memahami lingkaran.&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;Transferable&amp;lt;/strong&amp;gt; : satu`AGENT.adoc`standar mengekspos lingkaran ke setiap proyek baru.&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;Hanya mencakup batasan spasial&amp;lt;/strong&amp;gt; — gaya kode tetap ada dalam prompt.&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;keamanan sejak desain&amp;lt;/strong&amp;gt; : satu`git push`sejak`foundry/&amp;lt;code&amp;gt;tidak bisa mengekspos`configuration/&amp;lt;/code&amp;gt;.&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;Konsumsi oleh agen non-LLM&amp;lt;/strong&amp;gt; : sebuah orchestrator Gradle dapat merute sesuai dengan lingkaran.&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Keuntungan utama bukan bersifat defensif (keamanan, visibilitas) — ini&amp;lt;strong&amp;gt;kreatif&amp;lt;/strong&amp;gt;. Ketika LLM bekerja dalam ruang yang terstruktur oleh sebuah ontologi, dia dapat melakukan sesuatu yang tidak dapat diizinkan oleh prompt apa pun : &amp;lt;strong&amp;gt;mengamati delta&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_aljabar_relasional_dari_workspace_dan_delta_yang_dapat_diamati&amp;quot;&amp;gt;Aljabar Relasional dari Workspace dan Delta yang dapat diamati&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Ketika LLM menelusuri`foundry/&amp;lt;code&amp;gt;, ia memiliki sebuah&amp;lt;strong&amp;gt;kerangka data terstruktur&amp;lt;/strong&amp;gt;: simpul (plugins, file, tes, dependensi), tepi bertipe (`import&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;depends_on&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;generates&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;tests&amp;lt;/code&amp;gt;), relasi komposisi dan urutan (`training-gradle`Ekstrak repositori →`slider-gradle`menghasilkan slide →`capsule-gradle`sunting video&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Algebra ini tidak ditulis di mana-mana dalam bentuk jelas — tapi ia &amp;lt;strong&amp;gt;dapat diamati&amp;lt;/strong&amp;gt;. Setiap`build.gradle.kts`menunjukkan dependensi. Setiap`INDEX.adoc`menunjukkan referensi silang&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;LLM mengamati aljabar ini dan mendeteksi itu&amp;lt;strong&amp;gt;delta&amp;lt;/strong&amp;gt;: selisih antara apa yang sistem sudah mampu menghasilkan dan apa yang visi menggambarkan.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Dari delta ini, ia dapat menentukan&amp;lt;strong&amp;gt;peta ahli bisnis&amp;lt;/strong&amp;gt;— CDA (Perancang Pengembang Aplikasi, Kotlin/Gradle/JHipster) dan FPA (Pelatihan Profesional Dewasa, Pedagogi/Qualiopi/Bloom) — dan mengidentifikasi apa yang kurang untuk setiap ahli.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-none hljs&amp;quot;&amp;gt;Expert CDA (Kotlin/Gradle/JHipster)
  ├── Plugins : jhipster-gradle-plugins, plantuml-gradle,
  │   codebase-gradle
  ├── Delta : pas encore de SPG CDA formalisé,
  │   pas de fine-tuning expert CDA

Expert FPA (Pédagogie/Qualiopi/Bloom)
  ├── Plugins : training-gradle, slider-gradle,
  │   school-backoffice/forms, capsule-gradle
  ├── Matériel : 38 modules cours, SPG/SPD, taxonomies
  └── Delta : parser AsciiDoc→JSON à créer,
      orchestrateur à coder&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;LLM tidak perlu diberi tahu apa yang harus dilakukan. Delta muncul dari struktur.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_vektor_komposit_konteks_rag_pgvector_graphify&amp;quot;&amp;gt;Vektor Komposit Konteks : RAG + pgvector + Graphify&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Aljabar relasional bukan konstruksi teoretis. Ia &amp;lt;strong&amp;gt;terwujud&amp;lt;/strong&amp;gt; oleh tiga komponen yang membentuk satu&amp;lt;strong&amp;gt;vektor komposit konteks&amp;lt;/strong&amp;gt;untuk LLM.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_komponen_1_rag_langchain4j_postgresql_pgvector&amp;quot;&amp;gt;Komponen 1 — RAG LangChain4j + PostgreSQL pgvector&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Saya sudah memiliki LangChain4j dalam produksi di dua plugin :`slider-gradle`(4 penyedia LLM) dan`plantuml-gradle`(7 penyedia). Embedding ONNX (AllMiniLmL6V2) mengindeks data dari`office/`dan kode sumber dari`foundry/`dalam PostgreSQL + pgvector.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;RAG ini beroperasi pada dua dimensi :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Dimension data&amp;lt;/strong&amp;gt; : dokumen`office/`(SPG, articles, formations, skema JSON)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Dimensi kode&amp;lt;/strong&amp;gt; : codebases`foundry/`(sumber Kotlin, uji Cucumber, AGENT.adoc)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Intersepsi mencakup baik &amp;lt;strong&amp;gt;apa&amp;lt;/strong&amp;gt; (domain bisnis) dan &amp;lt;strong&amp;gt;bagaimana&amp;lt;/strong&amp;gt; (implementasi).&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_komponen_2_knowledge_graph_graphify&amp;quot;&amp;gt;Komponen 2 — Knowledge Graph Graphify&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Graphify terintegrasi dalam`plantuml-gradle`(109 tes, 380/380 LULUS) dan menghasilkan sebuah`graph.json`— sebuah knowledge graph terstruktur dengan node, edge, dan komunitas yang terdeteksi secara otomatis.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Berbeda dengan RAG yang bekerja berdasarkan similaritas vektor (samar), knowledge graph bekerja berdasarkan&amp;lt;strong&amp;gt;relasi eksak&amp;lt;/strong&amp;gt;(deterministis) :`graphify query`untuk pertanyaan semantik (~50 token)`graphify path`untuk menavigasi,`graphify explain`untuk menjelaskan.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_komponen_3_graphify_inkremental_sparse_dapat_diagregasikan_dapat_dikonsumsi&amp;quot;&amp;gt;Komponen 3 — Graphify Inkremental : Sparse, Dapat Diagregasikan, Dapat Dikonsumsi&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Ini adalah keputusan arsitektural kunci.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Graphify tidak boleh hidup dalam satu repositori. Harus hidup dengan cara&amp;lt;strong&amp;gt;tersiar&amp;lt;/strong&amp;gt;dalam setiap plugin :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;olist arabic&amp;quot;&amp;gt;
&amp;lt;ol class=&amp;quot;arabic&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Setiap plugin&amp;lt;/strong&amp;gt; memuat satu tugas Gradle`updateKnowledgeGraph`yang memanggil Graphify&amp;lt;strong&amp;gt;pada lingkup sendiri&amp;lt;/strong&amp;gt;dan menghasilkan satu`graph.json`lokal.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Skrip build`office/&amp;lt;code&amp;gt;&amp;lt;/strong&amp;gt; mengonsumsi`graphify-gradle`dengan`rootDir = /home/cheroliv/workspace`dan menghasilkan sebuah`graph.json&amp;lt;/code&amp;gt; &amp;lt;strong&amp;gt;global&amp;lt;/strong&amp;gt;yang menggabungkan grafik lokal.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;RAG dari setiap plugin&amp;lt;/strong&amp;gt; menginject`graph.json`global seperti&amp;lt;strong&amp;gt;filter konteks&amp;lt;/strong&amp;gt;untuk permintaan LLM.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-none hljs&amp;quot;&amp;gt;TÂCHE GRADLE (dans chaque plugin)
    ↓
graphify → graph.json (scope local)
    ↓
office/build.gradle.kts → graph.json (scope global)
    ↓
RAG pgvector (dans slider, plantuml, codebase...)
    ↓  ← injection du graph.json comme filtre
LLM (deepseek-v4-pro)
    ↓  ← observation algèbre relationnelle
    ↓  ← détection delta vs cartographies experts
PRIORISATION → prochaine tâche&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Hasil: LLM tidak mencari dalam kekosongan — ia menavigasi ruang yang terstruktur oleh knowledge graph. Permintaan tentang &amp;lt;code&amp;gt;génère un diagramme&amp;lt;/code&amp;gt; dapat mencapai node-node yang relevan dalam grafik.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_klasifikasi_otomatis_gdpr_llm_sebagai_router&amp;quot;&amp;gt;Klasifikasi Otomatis GDPR : LLM sebagai Router&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Ontologi spasial tidak hanya menyelaraskan LLM — ia memberi padanya sebuah&amp;lt;strong&amp;gt;grid klasifikasi GDPR&amp;lt;/strong&amp;gt;Untuk mengalirkan setiap data ke zona sahnya&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;table class=&amp;quot;tableblock frame-all grid-all stretch&amp;quot;&amp;gt;
&amp;lt;colgroup&amp;gt;
&amp;lt;col style=&amp;quot;width: 12.5%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 37.5%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 25%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 25%;&amp;quot;&amp;gt;
&amp;lt;/colgroup&amp;gt;
&amp;lt;tbody&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;kriteria&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Deteksi LLM&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Aksi&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Tingkat maksimal&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Data pribadi (nama, email, IP)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;pola`@`, IP, nama proper&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Anonimkan →`[OF_PILOTE]`atau router level 2&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;2&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Token / Rahasia / Kredensial&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Pola`sk-&amp;amp;#8230;&amp;amp;#8203;&amp;lt;code&amp;gt;, `ghp_&amp;amp;#8230;&amp;amp;#8203;&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ya29&amp;amp;#8230;&amp;amp;#8203;&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Router →&amp;lt;code&amp;gt;configuration/&amp;lt;/code&amp;gt;. Direferensikan oleh`${VAR}`di kode.&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;1&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;URL internal&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Mengandung`localhost`, IP pribadi&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Menganonimkan →&amp;lt;code&amp;gt;${API_URL}&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;2&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Data pedagogik (SPG, kuliah)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Struktur Bloom/Qualiopi&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;router →&amp;lt;code&amp;gt;office/&amp;lt;/code&amp;gt;(tingkat 2). Versi ganda jika ekspor.&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;3&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Kode sumber / uji&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Perpanjangan`.kt`, &amp;lt;code&amp;gt;.kts`dalam`foundry/&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Router →&amp;lt;code&amp;gt;foundry/&amp;lt;/code&amp;gt;. Memeriksa tidak ada rahasia.&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;4&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Pada akhir sesi, LLM mengeksekusi sebuah&amp;lt;strong&amp;gt;tugas global&amp;lt;/strong&amp;gt;:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;olist arabic&amp;quot;&amp;gt;
&amp;lt;ol class=&amp;quot;arabic&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Pembacaan`.gitignore`akar untuk mengidentifikasi artefak sementara (yang harus dikecualikan dari snapshot) vs artefak strategis (yang harus direkam)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Inventaris semua file`.adoc`dari akar`workspace/`&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Penyaringan: pengecualian berkas yang tercantum dalam`.gitignore`, inklusi semua yang lain&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Salin ke`configuration/vision-archive/$DATE/&amp;lt;code&amp;gt;dan pembaruan symlink`latest/&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Commit dalam repositori`configuration/`dengan pesan terstruktur&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Klasifikasi GDPR otomatis setiap file yang dimodifikasi (semua lingkaran)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Routing : data`office/&amp;lt;code&amp;gt;→ commit pribadi, kode`foundry/&amp;lt;/code&amp;gt;→&amp;lt;code&amp;gt;./gradlew check&amp;lt;/code&amp;gt;, konfigurasi → commit&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Laporan terstruktur dengan peringatan GDPR dan konfirmasi arsip&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Le `.gitignore`akar bukan berkas konfigurasi Git — ini adalah&amp;lt;strong&amp;gt;file pengelolaan historisasi&amp;lt;/strong&amp;gt;. Ia menentukan skema yang LLM konsumsi untuk memutuskan berkas mana dari taman rahasia yang masuk ke dalam biograf strategis dan mana yang dapat dibuang.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;LLM tidak lagi hanya sebuah generator teks. Ia adalah&amp;lt;strong&amp;gt;manajer informasi&amp;lt;/strong&amp;gt;dari ekosistem — produsen, klasifikator, router.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_roadmap_dari_asciidoc_ke_langgraph4j&amp;quot;&amp;gt;Roadmap : dari AsciiDoc ke LangGraph4j&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Hari ini, tata kelola ini adalah &amp;lt;strong&amp;gt;determinist&amp;lt;/strong&amp;gt;: LLM menerapkan prosedur yang dijelaskan dalam file AsciiDoc. Ini adalah&amp;lt;strong&amp;gt;tahap 1&amp;lt;/strong&amp;gt;— prompt engineering dengan memori LLM.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;La &amp;lt;strong&amp;gt;fase 2&amp;lt;/strong&amp;gt;akan mengekstrak setiap langkah dari prosedur dalam sebuah&amp;lt;strong&amp;gt;tugas Gradle bertipe&amp;lt;/strong&amp;gt;:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-none hljs&amp;quot;&amp;gt;./gradlew endSessionWorkspace    → snapshot vision-archive
./gradlew endSessionProject      → archive .agents/
./gradlew endSessionReport       → rapport multi-zones&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;La &amp;lt;strong&amp;gt;fase 3&amp;lt;/strong&amp;gt;akan memodelkan proses akhir sesi sebagai&amp;lt;strong&amp;gt;grafik status&amp;lt;/strong&amp;gt;denganhttps://github.com/langgraph4j/langgraph4j[LangGraph4j]:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-none hljs&amp;quot;&amp;gt;[Start] → [Inventaire fichiers modifiés]
       → [Classification RGPD] (nœud ONNX)
       → [Branchement par cercle]
            ├→ cercle 0 → snapshot → commit
            ├→ cercle 2 → anonymisation → commit
            ├→ cercle 4 → archive → commit
            └→ cercle 1 → commit configuration/
       → [Rapport] → [End]&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Grafik ini akan versi di CI/CD, dijalankan oleh Gradle, dan dikonfigurasi melalui GitHub Secrets plugin. LLM tidak lagi perlu memutuskan perutean — grafik akan melakukannya.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_apa_yang_diselesaikan_oleh_arsitektur_ini_dan_apa_yang_tidak_diselesaikan&amp;quot;&amp;gt;Apa yang Diselesaikan oleh Arsitektur Ini (dan apa yang tidak Diselesaikan)&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;admonitionblock caution&amp;quot;&amp;gt;
&amp;lt;table&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;icon&amp;quot;&amp;gt;
&amp;lt;i class=&amp;quot;fa icon-caution&amp;quot; title=&amp;quot;🔒&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Ontologi spasial tidak mencakup semuanya. Konvensi gaya kode, penamaan, pilihan desain arsitektur — semuanya tetap berada dalam prompt. Apa yang diselesaikan oleh ontologi spasial adalah&amp;lt;strong&amp;gt;lapisan keamanan dan visibilitas&amp;lt;/strong&amp;gt;: di mana setiap byte harus hidup, dan siapa yang dapat melihatnya.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Ini adalah apa yang dia sebenarnya bawa:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;olist arabic&amp;quot;&amp;gt;
&amp;lt;ol class=&amp;quot;arabic&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;* tidak ada`git push`accidental dari sebuah rahasia* : rahasia-rahasa berada di`configuration/&amp;lt;code&amp;gt;(cercle 1). Proyek-proyek publik berada di`foundry/&amp;lt;/code&amp;gt;(lingkaran 4). Tidak ada jalur yang melewati keduanya.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Tidak ada kode bisnis dalam data&amp;lt;/strong&amp;gt; :`office/&amp;lt;code&amp;gt;mengandung beberapa&amp;lt;/code&amp;gt;.adoc`, beberapa YAML, beberapa skema JSON — tetapi tidak ada`.kt`. Le `build.gradle.kts`yang tinggal di sana adalah skrip konsumsi, bukan kode bisnis.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Tidak ada pemikiran strategis yang diekspos&amp;lt;/strong&amp;gt; : dokumen visi berada di taman rahasia (akar di luar CVS). Snapshot berada di`configuration/`(kotak aman pribadi). Tidak ada orang, bahkan dalam lingkaran kepercayaan yang diperluas, yang membaca asal-usul strategi.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Prioritasi otomatis&amp;lt;/strong&amp;gt; : LLM mengamati delta antara aljabar relasional (yang ada) dan pemetaan ahli (yang diperlukan). Tugas pengembangan berikutnya muncul dari delta tersebut.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_kesimpulan_arsitektur_sebagai_diskursus&amp;quot;&amp;gt;Kesimpulan: Arsitektur sebagai Diskursus&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Saya tidak menaruh manifesto politik di footer situs saya. Saya tidak menaruh aturan etika di prompt saya. Penataan tidak ada dalam teks — itu ada di&amp;lt;strong&amp;gt;sistem file&amp;lt;/strong&amp;gt;.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Saat LLM bekerja di ruang ini, ia tidak dapat membocorkan rahasia (jalan ini mencegahnya). Ia tidak dapat mencampurkan data pendidikan dengan kode sumber (zona fisik berbeda). Ia tidak dapat melupakan aturan keamanan sebesar 80.000 token — karena aturan itu tidak ada dalam prompt, melainkan di`workspace/&amp;lt;code&amp;gt;→`configuration/&amp;lt;/code&amp;gt;→&amp;lt;code&amp;gt;office/&amp;lt;/code&amp;gt;→`foundry/`bahwa setiap pembacaan file mengaktifkan kembali&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Ini adalah prinsip &amp;lt;strong&amp;gt;secure by design&amp;lt;/strong&amp;gt; yang diterapkan pada penyelarasan agen: tidak meminta LLM untuk mengingat aturan. Pastikan arsitektur membuat kesalahan menjadi mustahil secara struktural.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Dan sekaligus, ini memberi LLM apa yang tidak dapat diberikan oleh prompt apa pun: kemampuan untuk&amp;lt;strong&amp;gt;mengerti&amp;lt;/strong&amp;gt;où ia berada, apa yang ada, apa yang kurang — dan dari itu menyimpulkan apa yang harus dilakukan.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_referensi&amp;quot;&amp;gt;Referensi&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Artikel tentang pengelolaan agen Eager/Lazy :&amp;lt;a href=&amp;quot;../2026/0108_gouvernance_agent_opencode_eager_lazy_post.html&amp;quot;&amp;gt;Mengelola Agen AI dengan AsciiDoc&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Article tentang mekanisme Hot/Warm/Cold :&amp;lt;a href=&amp;quot;../2026/0110_mecanisme_backup_contexte_agent_post.html&amp;quot;&amp;gt;Jendela geser dan gelombang dingin&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Artikel tentang audit konteks:&amp;lt;a href=&amp;quot;../2026/0111_audit_contexte_agent_post.html&amp;quot;&amp;gt;Ketika gouvernansi Anda sendiri menjadi masalah&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Artikel perbandingan tiga LLM :&amp;lt;a href=&amp;quot;../2026/0112_comparaison_kimi_glm_deepseek_long_contexte_plugin_gradle_opencode_post.html&amp;quot;&amp;gt;DeepSeek-V4-Pro, Kimi K2.6, GLM-5.1&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>Migration Supabase → Firebase : Menghubungkan Formulir Kontak ke Firestore Tanpa Backend</title>
            <link >https://cheroliv.com/blog/2026/0113_integration_firebase_contact_form_post.html</link>
            <pubDate>Wed, 29 Apr 2026 00:00:00 +0000</pubDate>
            <guid isPermaLink="false">blog/2026/0113_integration_firebase_contact_form_post.html</guid>
            <description>&amp;lt;div id=&amp;quot;toc&amp;quot; class=&amp;quot;toc&amp;quot;&amp;gt;
&amp;lt;div id=&amp;quot;toctitle&amp;quot;&amp;gt;Table des matières&amp;lt;/div&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel1&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_skeno_sebuah_formulir_yang_tidak_menyimpan_apa_apa&amp;quot;&amp;gt;Skeno: sebuah formulir yang tidak menyimpan apa-apa&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_warisan_supabase&amp;quot;&amp;gt;Warisan Supabase&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_pengiriman_ganda_hantu&amp;quot;&amp;gt;pengiriman ganda hantu&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_mengapa_firebase_alih_alih_supabase&amp;quot;&amp;gt;Mengapa Firebase alih-alih Supabase?&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_tahap_1_membuat_proyek_firebase&amp;quot;&amp;gt;Tahap 1 : Membuat proyek Firebase&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_inisialisasi&amp;quot;&amp;gt;Inisialisasi&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_aturan_keamanan_firestore&amp;quot;&amp;gt;Aturan keamanan Firestore&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_tahap_2_menulis_ulang_javascript_pengiriman&amp;quot;&amp;gt;Tahap 2 : Menulis ulang JavaScript pengiriman&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_ketergantungan_window_firebase&amp;quot;&amp;gt;Ketergantungan `window.&amp;lt;em&amp;gt;FIREBASE&amp;lt;/em&amp;gt;&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_baru_contact_js&amp;quot;&amp;gt;Baru `contact.js&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_fase_3_membersihkan_kode_mati_supabase&amp;quot;&amp;gt;Fase 3 : Membersihkan kode mati Supabase&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_phase_4_mengatur_footer&amp;quot;&amp;gt;Phase 4 : Mengatur footer&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_tahap_5_arsitektur_akhir&amp;quot;&amp;gt;Tahap 5 : Arsitektur akhir&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_apa_yang_dikatakan_oleh_migrasi_ini_tentang_dogfooding&amp;quot;&amp;gt;Apa yang dikatakan oleh migrasi ini tentang dogfooding&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_ringkasan_perubahan&amp;quot;&amp;gt;Ringkasan perubahan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_langkah_selanjutnya_backlog&amp;quot;&amp;gt;Langkah selanjutnya (backlog)&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_referensi&amp;quot;&amp;gt;Referensi&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div id=&amp;quot;preamble&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;waktu membaca : 12 minutes&amp;lt;/div&amp;gt;
&amp;lt;p&amp;gt;Selama bertahun-tahun, formulir kontak situs ini berjalan pada mock JavaScript — sebuah janji dengan tingkat keberhasilan 85 %, Firestore palsu, tidak ada data yang disimpan. Rencana awal mengantisipasi backend Supabase dengan Google Apps Script untuk notifikasi email. Ditinggalkan. Hari ini, saya menceritakan migrasi ke Firebase Firestore: pembuatan projekt, aturan keamanan, penulisan ulang JS, pembersihan kode mati Supabase. Dan mengapa pilihan ini mengartikan sesuatu yang lebih luas tentang filosofi pengembangan.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;dlist&amp;quot;&amp;gt;
&amp;lt;dl&amp;gt;
&amp;lt;dt class=&amp;quot;hdlist1&amp;quot;&amp;gt;toc&amp;lt;/dt&amp;gt;
&amp;lt;dd&amp;gt;
&amp;lt;p&amp;gt;[]&amp;lt;/p&amp;gt;
&amp;lt;/dd&amp;gt;
&amp;lt;/dl&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_skeno_sebuah_formulir_yang_tidak_menyimpan_apa_apa&amp;quot;&amp;gt;Skeno: sebuah formulir yang tidak menyimpan apa-apa&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Situs ini dihasilkan oleh JBake, plugin Gradle saya`bakery`. Ini 100% statis — tidak ada backend, tidak ada basis data. Namun saya memiliki formulir kontak. Halaman`contact.html`ada, HTML sudah siap (kolom nama, email, telepon, subjek, pesan, validasi HTML5, honeypot anti-spam), gaya Bootstrap sudah terpasang. Secara visual, semuanya sempurna.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Namun saat pengajuan, tidak terjadi apa-apa.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-javascript hljs&amp;quot; data-lang=&amp;quot;javascript&amp;quot;&amp;gt;const firebaseMock = new Promise((resolve, reject) =&amp;amp;gt; {
    setTimeout(() =&amp;amp;gt; {
        if (Math.random() &amp;amp;lt; 0.85) {
            resolve({ status: 201, message: &amp;#39;Message stored in Firestore.&amp;#39; });
        } else {
            reject({ status: 500, message: &amp;#39;Firestore write failed.&amp;#39; });
        }
    }, 1500);
});&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Suatu mock. Sebuah janji yang hanya berpura-pura. Pengguna melihat spinner, lalu pesan « Pesan terkirim dengan sukses ! ». Tapi data pergi ke dalam hampa. Tidak ada pesan yang disimpan di mana pun.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Situasi lebih buruk daripada formulir yang rusak — ini formulir yang berbohong.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_warisan_supabase&amp;quot;&amp;gt;Warisan Supabase&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Rencana awal, yang didokumentasikan di`content/draft/integration_formulaire_contact_supabase.adoc`, memperkirakan :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;olist arabic&amp;quot;&amp;gt;
&amp;lt;ol class=&amp;quot;arabic&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Suatu basis data Supabase dengan tabel`contacts`dan Keamanan Tingkat Baris&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Sebuah RPC`handle_contact_form`sisi server&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Sebuah trigger SQL yang memanggil sebuah webhook Google Apps Script&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Google Apps Script yang mengirim email notifikasi Gmail&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Kode JavaScript yang sesuai masih ada di`script.js`. Ada sebuah kelas`SupabaseManager`yang menginisialisasi klien Supabase dengan variabel global`SUPABASE_URL` et &amp;lt;code&amp;gt;SUPABASE_KEY&amp;lt;/code&amp;gt;, dan sebuah kelas`ContactFormHandler`yang mendengarkan acara submit formulir dan memanggil`SupabaseManager.submitContactForm()`.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Masalah : variabel global ini tidak lagi di-inject di dalam footer. Yang`&amp;amp;lt;script src=&amp;quot;supabase-js&amp;quot;&amp;amp;gt;`telah dihapus. Kode memanggil`supabase.createClient()`pada satu variabel`supabase`yang tidak ada lagi. Maka:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-none hljs&amp;quot;&amp;gt;console.error : &amp;#39;Supabase client library (supabase-js) is not loaded.&amp;#39;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Bukan hanya data tidak disimpan, tetapi kode pengiriman sudah mati.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_pengiriman_ganda_hantu&amp;quot;&amp;gt;pengiriman ganda hantu&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Untuk memburukkan hal-hal, ada sebuah&amp;lt;strong&amp;gt;persaingan diam&amp;lt;/strong&amp;gt;antara dua handler pada formulir yang sama :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;olist arabic&amp;quot;&amp;gt;
&amp;lt;ol class=&amp;quot;arabic&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;`contact.js`dengarkan submit, panggil mock Firebase&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;code&amp;gt;script.js&amp;lt;/code&amp;gt;— via`ContactFormHandler`— dengarkan juga submit, telepon`SupabaseManager`&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Keduanya melakukan`event.preventDefault() + event.stopPropagation()&amp;lt;code&amp;gt;. Seperti`contact.js`dimuat terlebih dahulu di`footer.thyme&amp;lt;/code&amp;gt;, handler-nya dipasang terlebih dahulu. Dia menghambat penyebaran.`ContactFormHandler`tidak akan pernah dipicu.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Ini bahkan bukan sebuah bug aktif — ini sebuah zombie. Kode yang tidak pernah kesempatan untuk dieksekusi.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div id=&amp;quot;diag-before&amp;quot; class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE

title Status Awal — Formulir Kontak
rectangle &amp;quot;contact.thyme
(HTML Bootstrap, honeypot)&amp;quot; as FORM
rectangle &amp;quot;footer.thyme
(Firebase SDK dengan placeholder konfigurasi)&amp;quot; as FOOT
rectangle &amp;quot;contact.js\n(mock Firebase, 85% keberhasilan)&amp;quot; as CONT
rectangle &amp;quot;script.js\n(SupabaseManager + ContactFormHandler, mati)&amp;quot; as SCRIPT
rectangle &amp;quot;Pengguna&amp;quot; as USER

USER -&amp;amp;gt; FORM : Soumission du formulaire
FORM -&amp;amp;gt; CONT : submit event (attaché en premier)
CONT -&amp;amp;gt; CONT : preventDefault + stopPropagation
CONT -&amp;amp;gt; CONT : mock Promise (1.5s, aléatoire)
note right of CONT
  ⚠️ Aucune donnée stockée
end note

FORM ..&amp;amp;gt; SCRIPT : submit event (bloqué par stopPropagation)
note right of SCRIPT : ❌ Jamais déclenché
note right of SCRIPT : ❌ supabase-js non chargé

@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_mengapa_firebase_alih_alih_supabase&amp;quot;&amp;gt;Mengapa Firebase alih-alih Supabase?&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Keputusan migrasi dicatat dalam`AGENT.adoc`:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Firebase kini dipilih karena alasan-alasan berikut: paket gratis terbaik, Firestore asli, Cloud Functions terintegrasi, ekosistem Google yang lebih sesuai. Implementasi Supabase yang ada ditandai sebagai « ⚠️ Ditinggalkan ».&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Di luar rencana gratis, ada alasan arsitektur. Situs ini berada di ekosistem Google: repositori target adalah`cheroliv.github.io`, CNAME menunjuk ke GitHub Pages, build Gradle melakukan push ke GitHub melalui JGit. Menambahkan layanan Google (Firebase) alih-alih layanan pihak ketiga (Supabase) mengurangi permukaan penyebaran.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Firestore dalam mode native (bukan mode Datastore) juga lebih dekat dengan model mental NoSQL dokumen yang saya miliki: koleksi, dokumen, kolom bertipe, stempel waktu server, aturan keamanan terintegrasi.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_tahap_1_membuat_proyek_firebase&amp;quot;&amp;gt;Tahap 1 : Membuat proyek Firebase&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_inisialisasi&amp;quot;&amp;gt;Inisialisasi&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Karena CLI Firebase tidak terinstal di komputer saya, saya menggunakan konsol web:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;olist arabic&amp;quot;&amp;gt;
&amp;lt;ol class=&amp;quot;arabic&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Pergi kehttps://console.firebase.google.com/[Konsol Firebase]&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Membuat proyek`cheroliv-contact`(atau menggunakan kembali proyek yang ada)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Aktifkan Firestore dalam mode asli (bukan Datastore)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Membuat database di wilayah`eur3`(Eropa)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Untuk penggunaan minimalis seperti milik kita (satu koleksi, penulisan publik), mode asli adalah pilihan yang tepat. Tidak perlu aturan Datastore yang kompleks.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_aturan_keamanan_firestore&amp;quot;&amp;gt;Aturan keamanan Firestore&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Formulir ini bersifat publik — siapa pun dapat mengirim pesan. Tapi saya ingin membatasi penyalahgunaan:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-javascript hljs&amp;quot; data-lang=&amp;quot;javascript&amp;quot;&amp;gt;rules_version = &amp;#39;2&amp;#39;;

service cloud.firestore {
  match /databases/{database}/documents {

    match /contact_messages/{messageId} {
      // Lecture : admin uniquement (authentifié)
      allow read: if request.auth != null;

      // Écriture : publique, mais limitée
      allow create: if request.auth == null
        &amp;amp;amp;&amp;amp;amp; request.resource.data.name is string
        &amp;amp;amp;&amp;amp;amp; request.resource.data.name.size() &amp;amp;gt;= 1
        &amp;amp;amp;&amp;amp;amp; request.resource.data.name.size() &amp;amp;lt;= 100
        &amp;amp;amp;&amp;amp;amp; request.resource.data.email is string
        &amp;amp;amp;&amp;amp;amp; request.resource.data.email.matches(&amp;#39;.*@.*\\..*&amp;#39;)
        &amp;amp;amp;&amp;amp;amp; request.resource.data.email.size() &amp;amp;lt;= 254
        &amp;amp;amp;&amp;amp;amp; request.resource.data.subject is string
        &amp;amp;amp;&amp;amp;amp; request.resource.data.subject.size() &amp;amp;gt;= 3
        &amp;amp;amp;&amp;amp;amp; request.resource.data.subject.size() &amp;amp;lt;= 200
        &amp;amp;amp;&amp;amp;amp; request.resource.data.message is string
        &amp;amp;amp;&amp;amp;amp; request.resource.data.message.size() &amp;amp;gt;= 10
        &amp;amp;amp;&amp;amp;amp; request.resource.data.message.size() &amp;amp;lt;= 5000
        &amp;amp;amp;&amp;amp;amp; request.resource.data.created_at == request.time
        &amp;amp;amp;&amp;amp;amp; request.resource.data.user_agent is string
        &amp;amp;amp;&amp;amp;amp; request.resource.data.user_agent.size() &amp;amp;lt;= 500;
    }
  }
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;poin kunci :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;code&amp;gt;allow read&amp;lt;/code&amp;gt;— hanya pengguna terautentikasi yang dapat membaca pesan (saya, melalui konsol Firebase)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;code&amp;gt;allow create&amp;lt;/code&amp;gt;— siapa pun dapat membuat dokumen, tetapi dengan validasi kolom&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Validasi sisi server : ukuran min/max, format email,&amp;lt;code&amp;gt;created_at`harus sesuai dengan`request.time&amp;lt;/code&amp;gt;(anti-falsifikasi)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;`user_agent`dikirim untuk pelacakan (tidak kritis tetapi berguna)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Aturan ini lebih ketat daripada yang sederhana`allow write: if true;`. Mereka mencegah seorang penyerang menginjeksi payload besar atau bidang yang tidak terformat dengan benar.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_tahap_2_menulis_ulang_javascript_pengiriman&amp;quot;&amp;gt;Tahap 2 : Menulis ulang JavaScript pengiriman&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Kontraktnya sederhana :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;olist arabic&amp;quot;&amp;gt;
&amp;lt;ol class=&amp;quot;arabic&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Membaca data formulir&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Periksa honeypot (kolom`hp_name`— jika itu terisi, itu adalah bot, kita mensimulasikan keberhasilan tanpa mengirimkan apa-apa)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;memanggil`addDoc(window.&amp;lt;em&amp;gt;FIREBASE&amp;lt;/em&amp;gt;.collection(db, &amp;quot;contact_messages&amp;quot;), {&amp;amp;#8230;&amp;amp;#8203;})`&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Menampilkan keberhasilan atau kesalahan&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_ketergantungan_window_firebase&amp;quot;&amp;gt;Ketergantungan `window.&amp;lt;em&amp;gt;FIREBASE&amp;lt;/em&amp;gt;&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;dalam`footer.thyme`, skrip modul menginisialisasi Firebase SDK dan mengekspos objek global :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-html hljs&amp;quot; data-lang=&amp;quot;html&amp;quot;&amp;gt;&amp;amp;lt;script type=&amp;quot;module&amp;quot;&amp;amp;gt;
    import { initializeApp } from &amp;quot;https://www.gstatic.com/firebasejs/11.6.0/firebase-app.js&amp;quot;;
    import { getFirestore, collection, addDoc, serverTimestamp }
      from &amp;quot;https://www.gstatic.com/firebasejs/11.6.0/firebase-firestore.js&amp;quot;;

    const firebaseConfig = { /* valeurs réelles */ };
    const app = initializeApp(firebaseConfig);
    const db = getFirestore(app);

    window.__FIREBASE__ = { db, collection, addDoc, serverTimestamp };
&amp;amp;lt;/script&amp;amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Skrip modul berjalan sebelum`DOMContentLoaded`, maka`window.&amp;lt;em&amp;gt;FIREBASE&amp;lt;/em&amp;gt;`dijamin tersedia ketika handler`contact.js`Memicu. Untuk berhati-hati, saya tetap menambahkan polling 5 detik jika CDN lambat.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_baru_contact_js&amp;quot;&amp;gt;Baru `contact.js&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-javascript hljs&amp;quot; data-lang=&amp;quot;javascript&amp;quot;&amp;gt;document.addEventListener(&amp;#39;DOMContentLoaded&amp;#39;, function () {
    &amp;#39;use strict&amp;#39;;

    const form = document.getElementById(&amp;#39;contact-form&amp;#39;);
    if (!form) return;

    const submitButton = form.querySelector(&amp;#39;button[type=&amp;quot;submit&amp;quot;]&amp;#39;);
    const successMessage = document.getElementById(&amp;#39;contact-success-message&amp;#39;);
    const errorMessage = document.getElementById(&amp;#39;contact-error-message&amp;#39;);

    // Éléments de validation
    const nameInput = form.querySelector(&amp;#39;input[name=&amp;quot;name&amp;quot;]&amp;#39;);
    const emailInput = form.querySelector(&amp;#39;input[name=&amp;quot;email&amp;quot;]&amp;#39;);
    const phoneInput = form.querySelector(&amp;#39;input[name=&amp;quot;phone&amp;quot;]&amp;#39;);
    const subjectInput = form.querySelector(&amp;#39;input[name=&amp;quot;subject&amp;quot;]&amp;#39;);
    const messageInput = form.querySelector(&amp;#39;textarea[name=&amp;quot;message&amp;quot;]&amp;#39;);
    const honeypotInput = form.querySelector(&amp;#39;input[name=&amp;quot;hp_name&amp;quot;]&amp;#39;);

    /**
     * Attend que window.__FIREBASE__ soit disponible.
     * Timeout de 5 secondes — si le CDN Firebase est lent, on abandonne.
     */
    function waitForFirebase(timeoutMs = 5000) {
        return new Promise((resolve, reject) =&amp;amp;gt; {
            if (window.__FIREBASE__) {
                resolve(window.__FIREBASE__);
                return;
            }
            const start = Date.now();
            const interval = setInterval(() =&amp;amp;gt; {
                if (window.__FIREBASE__) {
                    clearInterval(interval);
                    resolve(window.__FIREBASE__);
                } else if (Date.now() - start &amp;amp;gt; timeoutMs) {
                    clearInterval(interval);
                    reject(new Error(&amp;#39;Firebase SDK non disponible après timeout&amp;#39;));
                }
            }, 100);
        });
    }

    // --- Validation (identique à l&amp;#39;existant) ---
    function validateForm() {
        nameInput.setCustomValidity(&amp;#39;&amp;#39;);
        emailInput.setCustomValidity(&amp;#39;&amp;#39;);
        if (phoneInput) phoneInput.setCustomValidity(&amp;#39;&amp;#39;);
        subjectInput.setCustomValidity(&amp;#39;&amp;#39;);
        messageInput.setCustomValidity(&amp;#39;&amp;#39;);

        if (nameInput.value.trim().length &amp;amp;lt; 1) {
            nameInput.setCustomValidity(&amp;#39;Veuillez saisir votre nom.&amp;#39;);
        }
        const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
        if (!emailPattern.test(emailInput.value.trim())) {
            emailInput.setCustomValidity(&amp;#39;Veuillez saisir une adresse email valide.&amp;#39;);
        }
        if (phoneInput &amp;amp;amp;&amp;amp;amp; phoneInput.value.trim() !== &amp;#39;&amp;#39;) {
            const phonePattern = /^\d{10,15}$/;
            if (!phonePattern.test(phoneInput.value.trim())) {
                phoneInput.setCustomValidity(&amp;#39;Veuillez saisir un numéro valide (10 à 15 chiffres).&amp;#39;);
            }
        }
        if (subjectInput.value.trim().length &amp;amp;lt; 3) {
            subjectInput.setCustomValidity(&amp;#39;Veuillez saisir un sujet (3 caractères minimum).&amp;#39;);
        }
        if (messageInput.value.trim().length &amp;amp;lt; 10) {
            messageInput.setCustomValidity(&amp;#39;Veuillez saisir un message (10 caractères minimum).&amp;#39;);
        }

        form.classList.add(&amp;#39;was-validated&amp;#39;);
        return form.checkValidity();
    }

    // --- Handler de soumission ---
    form.addEventListener(&amp;#39;submit&amp;#39;, async function (event) {
        event.preventDefault();
        event.stopPropagation();

        if (!validateForm()) return;

        // Honeypot : si rempli, simuler un succès sans rien envoyer
        if (honeypotInput &amp;amp;amp;&amp;amp;amp; honeypotInput.value.trim() !== &amp;#39;&amp;#39;) {
            successMessage.style.display = &amp;#39;block&amp;#39;;
            form.reset();
            form.classList.remove(&amp;#39;was-validated&amp;#39;);
            return;
        }

        // UI : état d&amp;#39;envoi
        submitButton.disabled = true;
        submitButton.innerHTML = `
            &amp;amp;lt;span class=&amp;quot;spinner-border spinner-border-sm&amp;quot; role=&amp;quot;status&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;amp;gt;&amp;amp;lt;/span&amp;amp;gt;
            Envoi en cours...
        `;
        successMessage.style.display = &amp;#39;none&amp;#39;;
        errorMessage.style.display = &amp;#39;none&amp;#39;;

        try {
            const fb = await waitForFirebase();
            const messagesCollection = fb.collection(fb.db, &amp;#39;contact_messages&amp;#39;);

            await fb.addDoc(messagesCollection, {
                name: nameInput.value.trim(),
                email: emailInput.value.trim(),
                phone: phoneInput ? phoneInput.value.trim() : &amp;#39;&amp;#39;,
                subject: subjectInput.value.trim(),
                message: messageInput.value.trim(),
                created_at: fb.serverTimestamp(),
                user_agent: navigator.userAgent.substring(0, 500)
            });

            successMessage.style.display = &amp;#39;block&amp;#39;;
            form.reset();
            form.classList.remove(&amp;#39;was-validated&amp;#39;);

        } catch (error) {
            console.error(&amp;#39;Erreur Firestore:&amp;#39;, error);
            errorMessage.style.display = &amp;#39;block&amp;#39;;

        } finally {
            submitButton.disabled = false;
            submitButton.innerHTML = `
                &amp;amp;lt;i class=&amp;quot;bi bi-send me-2&amp;quot;&amp;amp;gt;&amp;amp;lt;/i&amp;amp;gt;
                Envoyer le Message
            `;
        }
    }, false);
});&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Perubahan dibandingkan dengan mock:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;code&amp;gt;waitForFirebase()&amp;lt;/code&amp;gt;— polling dengan timeout, kuat bahkan jika CDN lambat&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;code&amp;gt;honeypot&amp;lt;/code&amp;gt;— jika bidang tersembunyi`hp_name`terisi, mensimulasikan kesuksesan pasti Firestore. Bot berpikir telah berhasil tetapi tidak ada yang disimpan.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;code&amp;gt;addDoc(collection, {&amp;amp;#8230;&amp;amp;#8203;})&amp;lt;/code&amp;gt;— panggilan Firestore yang sebenarnya dengan`serverTimestamp()` et &amp;lt;code&amp;gt;user_agent&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Manajemen kesalahan dengan`try/catch`asinkron&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Pembersihan`finally`(pemulihan tombol)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;admonitionblock note&amp;quot;&amp;gt;
&amp;lt;table&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;icon&amp;quot;&amp;gt;
&amp;lt;i class=&amp;quot;fa icon-note&amp;quot; title=&amp;quot;Note&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;mengapa`user_agent`? Ini opsional, tapi berguna untuk diagnosis. Jika pesan aneh tiba, mengetahui apakah berasal dari browser desktop, seluler, atau skrip curl membantu dalam penyortiran.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_fase_3_membersihkan_kode_mati_supabase&amp;quot;&amp;gt;Fase 3 : Membersihkan kode mati Supabase&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;`script.js`mengandung 250 baris kode mati :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;code&amp;gt;SupabaseManager&amp;lt;/code&amp;gt;(baris 417-481) — 65 baris&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;code&amp;gt;ContactFormHandler&amp;lt;/code&amp;gt;(baris 490-551) — 62 baris&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Blok inisialisasi (baris 645-654) — 10 baris&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Total : ~140 baris yang akan dihapus.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Blok`DOMContentLoaded`membuat sebuah`SupabaseManager`lalu satu`ContactFormHandler`ditekatkan pada formulir. Seperti yang dijelaskan di atas, kode ini tidak pernah dieksekusi (diblokir oleh`contact.js`), dan bahkan jika dieksekusi, ia akan gagal (tidak ada SDK Supabase yang dimuat).&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Saya menghapus:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;olist arabic&amp;quot;&amp;gt;
&amp;lt;ol class=&amp;quot;arabic&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Kelas`SupabaseManager`&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Kelas`ContactFormHandler`&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Blok inisialisasi dalam`DOMContentLoaded`(baris 645-654)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Le reste de &amp;lt;code&amp;gt;script.js`masih utuh :`ThemeManager&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ScrollToTopButton&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;MobileMenuManager&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;SmoothScrollWithOffset&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;NavbarHeightUpdater&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;DynamicNavbarBreakpoint&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;CodeBlockManager&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;TooltipManager&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;PhoneInputManager&amp;lt;/code&amp;gt;.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_phase_4_mengatur_footer&amp;quot;&amp;gt;Phase 4 : Mengatur footer&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;`footer.thyme`sudah memiliki boilerplate Firebase tetapi dengan nilai placeholder. Saya mengganti :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-javascript hljs&amp;quot; data-lang=&amp;quot;javascript&amp;quot;&amp;gt;const firebaseConfig = {
    apiKey: &amp;quot;REMPLACER_PAR_VOTRE_API_KEY&amp;quot;,
    authDomain: &amp;quot;REMPLACER_PAR_VOTRE_AUTH_DOMAIN&amp;quot;,
    projectId: &amp;quot;REMPLACER_PAR_VOTRE_PROJECT_ID&amp;quot;,
    storageBucket: &amp;quot;REMPLACER_PAR_VOTRE_STORAGE_BUCKET&amp;quot;,
    messagingSenderId: &amp;quot;REMPLACER_PAR_VOTRE_SENDER_ID&amp;quot;,
    appId: &amp;quot;REMPLACER_PAR_VOTRE_APP_ID&amp;quot;
};&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Dengan nilai sebenarnya yang diperoleh sejak&amp;lt;strong&amp;gt;Pengaturan Proyek &amp;amp;gt; Umum &amp;amp;gt; Aplikasi Anda &amp;amp;gt; Aplikasi Web&amp;lt;/strong&amp;gt;di konsol Firebase.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Nilai sensitif`apiKey`adalah publik oleh desain di Firebase, namun saya lebih suka tidak melakukan commit-nya dalam bentuk jelas). Saya menyimpannya di`site.yml`(sudah dalam`.gitignore`) dan plugin`bakery`Menyuntikkan mereka ke dalam template melalui logika yang ditambahkan pada sisi build.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Saat ini, saya menempatkannya langsung di`footer.thyme`— build`./gradlew serve`akan memuatnya secara lokal. Saat penyebaran, saya akan memigrasikan inyeksi ke`site.yml`atau ke variabel Gradle.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;admonitionblock warning&amp;quot;&amp;gt;
&amp;lt;table&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;icon&amp;quot;&amp;gt;
&amp;lt;i class=&amp;quot;fa icon-warning&amp;quot; title=&amp;quot;Warning&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;L&amp;#39;`apiKey`Firebase bukan&amp;lt;strong&amp;gt;langkah&amp;lt;/strong&amp;gt;sebuah rahasia. Ia bersifat publik oleh desain. Yang melindungi data Anda adalah&amp;lt;strong&amp;gt;peraturan keamanan Firestore&amp;lt;/strong&amp;gt;, not the API key. Do not put it in a`.env`dimuat sisi server — ia dimaksudkan untuk diekspos ke peramban&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_tahap_5_arsitektur_akhir&amp;quot;&amp;gt;Tahap 5 : Arsitektur akhir&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div id=&amp;quot;diag-after&amp;quot; class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE

title Arsitektur Final — Formulir Kontak Firebase
actor Utilisateur as USER

package &amp;quot;peramban&amp;quot; #E8F5E9 {
  rectangle &amp;quot;contact.thyme\n(HTML, honeypot)&amp;quot; as FORM
  rectangle &amp;quot;contact.js\n(validation + Firestore)&amp;quot; as CONT
  rectangle &amp;quot;footer.thyme
(Firebase SDK init)&amp;quot; as FOOT
}

cloud &amp;quot;Firebase&amp;quot; #BBDEFB {
  rectangle &amp;quot;Firestore\n(contact_messages)&amp;quot; as FS
}

FORM --&amp;amp;gt; CONT : submit event
CONT --&amp;amp;gt; FOOT : window.__FIREBASE__.addDoc()
FOOT --&amp;amp;gt; FS : Insert document\n(règles de sécurité validées)

note right of FS
  Firestore Rules :
  - create: public, validé
  - read: auth uniquement
end note

@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_apa_yang_dikatakan_oleh_migrasi_ini_tentang_dogfooding&amp;quot;&amp;gt;Apa yang dikatakan oleh migrasi ini tentang dogfooding&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Situs ini dihasilkan oleh plugin Gradle saya sendiri.&amp;lt;code&amp;gt;bakery&amp;lt;/code&amp;gt;. Formulir kontak berada di dalam situs. Migrasi Supabase → Firebase didokumentasikan dalam`AGENT.adoc`, dia dibahas dalam backlog, dia diuji melalui`./gradlew serve`, dan ia menghasilkan sebuah postingan blog (yang sedang Anda baca).&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Ini adalah dogfooding murni. Situs adalah produk dari plugin, plugin adalah produk dari pengembang, pengembang mendokumentasikan proses di situs itu sendiri.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Sudah lengkap lingkarannya.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Fakta bahwa saya telah menahan sebuah mock selama berbulan-bulan (sesi penuh di mana formulir itu diam-diam menyalahkan) membuatku menyadari sesuatu: backlog dari sebuah situs statis pribadi tidak pernah &amp;#39;selesai&amp;#39;. Selalu ada US prioritas, selalu ada artikel dalam draft, selalu ada bagian yang dikomentari dalam template. Disiplin bukanlah untuk menyelesaikan semuanya — melainkan menyelesaikan apa yang terlihat oleh pengguna.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Formulir kontak yang rusak lebih buruk daripada tidak ada formulir sama sekali. Itu adalah janji yang tidak dipenuhi.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_ringkasan_perubahan&amp;quot;&amp;gt;Ringkasan perubahan&amp;lt;/h3&amp;gt;
&amp;lt;table class=&amp;quot;tableblock frame-all grid-all stretch&amp;quot;&amp;gt;
&amp;lt;colgroup&amp;gt;
&amp;lt;col style=&amp;quot;width: 6.6666%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 6.6666%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 6.6666%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 6.6666%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 6.6666%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 6.6666%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 6.6666%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 6.6666%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 6.6666%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 6.6666%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 6.6666%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 6.6666%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 6.6666%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 6.6666%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 6.6676%;&amp;quot;&amp;gt;
&amp;lt;/colgroup&amp;gt;
&amp;lt;tbody&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Berkas&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Modifikasi&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Dampak&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;blog/2026/0113_*.adoc&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Pembuatan artikel&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Dokumentasi&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;assets/js/contact.js&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Penulisan ulang (mock → Firestore sebenarnya)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;fungsional&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;assets/js/script.js&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Penghapusan SupabaseManager + ContactFormHandler + init blok&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Pembersihan&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;templates/footer.thyme&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Penggantian config placeholder → nilai nyata&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Konfigurasi&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_langkah_selanjutnya_backlog&amp;quot;&amp;gt;Langkah selanjutnya (backlog)&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Email pemberitahuan&amp;lt;/strong&amp;gt;: Sebuah Cloud Function`onCreate`pada`contact_messages`yang mengirim email melalui SendGrid. Formulir menyimpan, tetapi saya tidak mendapatkan pemberitahuan. Prioritas sedang — pesan terlihat di konsol Firebase.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Rate limiting sisi klien&amp;lt;/strong&amp;gt;: Menambahkan timestamp localStorage untuk mencegah pengiriman berulang dalam satu rafalan. Honeypot memblokir bot yang naif, rate limiter akan memblokir bot yang sedikit lebih cerdas.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Uji&amp;lt;/strong&amp;gt;: Sebuah tes Playwright yang mengirimkan formulir dan memverifikasi bahwa dokumen muncul di Firestore. Saat ini, saya menguji secara manual melalui`./gradlew serve`.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_referensi&amp;quot;&amp;gt;Referensi&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://firebase.google.com/docs/firestore/security/get-started&amp;quot;&amp;gt;Dokumentasi Aturan Keamanan Firestore&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://firebase.google.com/docs/firestore/manage-data/add-data#add_a_document&amp;quot;&amp;gt;Dokumentasi Firestore addDoc — Web v9 modular&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;/blog/2026/0108_gouvernance_agent_opencode_eager_lazy_post.html&amp;quot;&amp;gt;Seri agen tata kelola — Bagian 1: Eager/Lazy&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;/blog/2026/0110_mecanisme_backup_contexte_agent_post.html&amp;quot;&amp;gt;Seri agen tata kelola — Bagian 2 : Hot/Warm/Cold&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;/blog/2026/0111_audit_contexte_agent_post.html&amp;quot;&amp;gt;Series Agen Tata Kelola — Bagian 3: Audit&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>DeepSeek-V4-Pro, Kimi K2.6, GLM-5.1: Tiga LLM yang diuji dalam Vibe Coding Konteks Panjang</title>
            <link >https://cheroliv.com/blog/2026/0112_comparaison_kimi_glm_deepseek_long_contexte_plugin_gradle_opencode_post.html</link>
            <pubDate>Tue, 28 Apr 2026 00:00:00 +0000</pubDate>
            <guid isPermaLink="false">blog/2026/0112_comparaison_kimi_glm_deepseek_long_contexte_plugin_gradle_opencode_post.html</guid>
            <description>&amp;lt;div id=&amp;quot;toc&amp;quot; class=&amp;quot;toc&amp;quot;&amp;gt;
&amp;lt;div id=&amp;quot;toctitle&amp;quot;&amp;gt;Table des matières&amp;lt;/div&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel1&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_konteks_bukan_sebuah_tempat_uji_sebuah_proyek_konstruksi&amp;quot;&amp;gt;Konteks: bukan sebuah tempat uji, sebuah proyek konstruksi&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_lingkungan_uji_saya&amp;quot;&amp;gt;lingkungan uji saya&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_kriteria_penilaian&amp;quot;&amp;gt;Kriteria Penilaian&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_putaran_1_kimi_k2_6_awalan_palsu&amp;quot;&amp;gt;Putaran 1 : Kimi K2.6 — Awalan Palsu&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_sesi_9_perbaikan&amp;quot;&amp;gt;Sesi 9 : perbaikan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_session_10_kelamunan_sunyi&amp;quot;&amp;gt;Session 10 : kelamunan sunyi&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_round_2_glm_5_1_pejuang_yang_terhormat&amp;quot;&amp;gt;Round 2 : GLM-5.1 — Pejuang yang terhormat&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_arsitektur_dsa_terhadap_mla_murni&amp;quot;&amp;gt;Arsitektur : DSA terhadap MLA murni&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_sesi_11_13_teguh_tetapi_menggecewakan&amp;quot;&amp;gt;Sesi 11-13: teguh tetapi menggecewakan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_round_3_deepseek_v4_pro_mesin_perang&amp;quot;&amp;gt;Round 3 : DeepSeek-V4-Pro — Mesin Perang&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_arsitektur_csa_hca_jaring_ganda&amp;quot;&amp;gt;Arsitektur: CSA + HCA, jaring ganda&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_sesi_1_8_kelegaan&amp;quot;&amp;gt;Sesi 1-8: kelegaan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_ujian_yang_menentukan_refactoring_hingga_100k_token&amp;quot;&amp;gt;Ujian yang menentukan: refactoring hingga 100K token&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_muka_ke_muka_perbandingan_metrik&amp;quot;&amp;gt;Muka ke Muka: Perbandingan Metrik&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_mengapa_deepseek_v4_pro_menang_dalam_pengembangan_perangkat_lunak&amp;quot;&amp;gt;Mengapa DeepSeek-V4-Pro Menang dalam Pengembangan Perangkat Lunak&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_arsitektur_csahca_dirancang_untuk_kode&amp;quot;&amp;gt;Arsitektur CSA+HCA dirancang untuk kode&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_2_konteks_eager_adalah_zona_kenyamanan&amp;quot;&amp;gt;2. Konteks EAGER adalah zona kenyamanan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_3_rasio_kualitaslatensi_optimal_untuk_alur&amp;quot;&amp;gt;3. Rasio kualitas/latensi optimal untuk alur&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_pelajaran_yang_dipelajari_cara_memilih_llm_anda_untuk_vibe_coding&amp;quot;&amp;gt;Pelajaran yang Dipelajari : Cara Memilih LLM Anda untuk Vibe Coding&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_dan_bagaimana_dengan_governance_agent_dalam_semua_ini&amp;quot;&amp;gt;Dan bagaimana dengan Governance Agent dalam semua ini?&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_sumber_teknis&amp;quot;&amp;gt;Sumber Teknis&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_conclusion_pilihan_artisan&amp;quot;&amp;gt;Conclusion: Pilihan Artisan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div id=&amp;quot;preamble&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;waktu membaca : 18 minutes&amp;lt;/div&amp;gt;
&amp;lt;p&amp;gt;Perang LLM juga dimainkan di terminal seorang pengembang. Bukan di benchmarks akademis yang steril. Dalam kehidupan nyata: sebuah prompt 30 ribu token, tata kelola agen dalam AsciiDoc, plugin Gradle Kotlin DSL yang perlu didebug, dan sesi-sesi yang berlangsung selama tiga minggu. Saya telah menguji untuk Anda DeepSeek-V4-Pro, Kimi K2.6 dan GLM-5.1. Ini adalah verdict, dengan bukti teknis sebagai pendukung.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;dlist&amp;quot;&amp;gt;
&amp;lt;dl&amp;gt;
&amp;lt;dt class=&amp;quot;hdlist1&amp;quot;&amp;gt;gangguan obsesif kompulsif&amp;lt;/dt&amp;gt;
&amp;lt;dd&amp;gt;
&amp;lt;p&amp;gt;[]&amp;lt;/p&amp;gt;
&amp;lt;/dd&amp;gt;
&amp;lt;/dl&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_konteks_bukan_sebuah_tempat_uji_sebuah_proyek_konstruksi&amp;quot;&amp;gt;Konteks: bukan sebuah tempat uji, sebuah proyek konstruksi&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Tiga minggu yang lalu, saya sedang bekerja pada`codebase-gradle`, meta-build system saya yang memusatkan konfigurasi YAML dari empat proyek: pembuat README PlantUML, pembuat slide AsciiDoc, situs statis JBake, dan chatbot LLM. Agen Opencode, yang dikendalikan oleh metodologi file agen saya dalam AsciiDoc, memuat sekitar 30K token konteks EAGER di awal setiap sesi — aturan mutlak, backlog, riwayat 10 sesi terakhir.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Di lapangan ini saya berhadapan dengan tiga model:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Kimi K2.6&amp;lt;/strong&amp;gt;(Moonshot AI, 1T parameter / 32B diaktifkan, 256K konteks maks, MLA) - &amp;lt;strong&amp;gt;GLM-5.1&amp;lt;/strong&amp;gt;(Zhipu AI/Tsinghua, 744B params / DSA, 200K konteks maks) - &amp;lt;strong&amp;gt;DeepSeek-V4-Pro&amp;lt;/strong&amp;gt;(DeepSeek, 1.6T parameter / 49B diaktifkan, 1M konteks maksimal, CSA+HCA)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Semua dilayani melalui Ollama di server cloud, semua dalam mode thinking (fase refleksi diaktifkan). Tantangan: menghasilkan kode yang benar, menjaga konsistensi dalam sesi panjang, dan tidak halusinasi ketika konteks melebihi 80K token.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_lingkungan_uji_saya&amp;quot;&amp;gt;lingkungan uji saya&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div id=&amp;quot;diag-dev-env&amp;quot; class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultTextAlignment center
skinparam wrapWidth 220

title Lingkungan Uji — Sesi Opencode × 3 LLM
left to right direction

package &amp;quot;🖥️ Terminal Pengembang&amp;quot; #E8F5E9 {
  rectangle &amp;quot;AGENT.adoc
(aturan, backlog)&amp;quot; as AG
  rectangle &amp;quot;PROMPT_REPRISE\n(misi)&amp;quot; as PR
  rectangle &amp;quot;INDEX.adoc
(peta jalan)&amp;quot; as IDX
}

package &amp;quot;☁️ Ollama Cloud&amp;quot; #BBDEFB {
  rectangle &amp;quot;DeepSeek-V4-Pro
1.6T / CSA+HCA&amp;quot; as DV4
  rectangle &amp;quot;Kimi K2.6
1T / MLA&amp;quot; as KIMI
  rectangle &amp;quot;GLM-5.1
744B / MLA+DSA&amp;quot; as GLMG
}

package &amp;quot;⚙️ basis kode Gradle&amp;quot; #FFF9C4 {
  folder &amp;quot;buildSrc/&amp;quot; {
    file &amp;quot;codebase.kt&amp;quot;
    file &amp;quot;readme.kt&amp;quot;
    file &amp;quot;site.kt&amp;quot;
    file &amp;quot;slider.kt&amp;quot;
    file &amp;quot;snapshot.kt&amp;quot;
  }
  file &amp;quot;build.gradle.kts
(947 baris)&amp;quot;
  file &amp;quot;embeds.yml&amp;quot;
}

AG --&amp;amp;gt; DV4 : Sessions 1-8 ✅
AG --&amp;amp;gt; KIMI : Sessions 9-10 ⚠️
AG --&amp;amp;gt; GLMG : Sessions 11-13 ⚠️

DV4 --&amp;amp;gt; &amp;quot;basis kode&amp;quot; : &amp;quot;TDD, pengrefaktoran,\ncuplikan&amp;quot;
KIMI --&amp;amp;gt; &amp;quot;basis kode&amp;quot; : &amp;quot;Degradasi
mulai dari 60K token&amp;quot;

note bottom of GLMG
  Meilleur que Kimi
  mais latence +
  DSA moins robuste
  que CSA+HCA
end note

@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Setiap sesi dimulai dengan sekitar 30K token konteks EAGER :`AGENT.adoc`(287 baris),&amp;lt;code&amp;gt;PROMPT_REPRISE.adoc&amp;lt;/code&amp;gt;(51 baris),&amp;lt;code&amp;gt;.agents/INDEX.adoc&amp;lt;/code&amp;gt;(218 baris)&amp;lt;code&amp;gt;LAZY_EAGER_ESSENTIALS.adoc&amp;lt;/code&amp;gt;(50 baris). Konteks bertambah cepat dengan pertukaran — sesi tipikal dari 10 pesan menambahkan 15-20K token ke prompt kumulatif.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_kriteria_penilaian&amp;quot;&amp;gt;Kriteria Penilaian&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Saya mengevaluasi setiap model pada empat sumbu kritis untuk pengembangan perangkat lunak yang didukung :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;table class=&amp;quot;tableblock frame-all grid-all stretch&amp;quot;&amp;gt;
&amp;lt;colgroup&amp;gt;
&amp;lt;col style=&amp;quot;width: 10%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 10%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 10%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 10%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 10%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 10%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 10%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 10%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 10%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 10%;&amp;quot;&amp;gt;
&amp;lt;/colgroup&amp;gt;
&amp;lt;tbody&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;axis&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Kriteria konkret&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;Kohersi konteks panjang&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Apakah agen mengingat konvensi yang telah diputuskan 40 pesan yang lalu?&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;Kualitas kode yang dihasilkan&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Apakah kode berhasil dikompilasi pada upaya pertama? Apakah ia mematuhi pola yang ada?&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;Penalaran arsitektur&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Apakah agen memahami hubungan antar-modul tanpa saya perlu menjelaskannya lagi?&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;Resistensi terhadap halusinasi&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Dari berapa banyak token agen mulai menciptakan API atau kelas yang tidak ada?&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Dan sebuah metrik sintetik buatan sendiri : yang&amp;lt;strong&amp;gt;koefisien pemulihan&amp;lt;/strong&amp;gt;(berapa banyak waktu yang saya habiskan untuk memperbaiki agen alih-alih mengoding dengannya).&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_putaran_1_kimi_k2_6_awalan_palsu&amp;quot;&amp;gt;Putaran 1 : Kimi K2.6 — Awalan Palsu&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Kimi K2.6 telah menjadi pilihan pertama saya. Benchmark-nya pada SWE-Bench Verified (80.2) dan Terminal-Bench 2.0 (66.7) sangat baik. Arsitektur MLA-nya menjanjikan efisiensi yang baik pada urutan panjang.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_sesi_9_perbaikan&amp;quot;&amp;gt;Sesi 9 : perbaikan&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Sesi pertama dengan Kimi. Tugas: menerapkan metode`resolveActiveKey()&amp;lt;code&amp;gt;di dalam`codebase.kt&amp;lt;/code&amp;gt;— sebuah fungsi resolusi kunci API dengan fallback CLI. Konteksnya adalah 30K token, Kimi berpikir cepat, menghasilkan kode bersih dengan manajemen kadaluwarsa kunci.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;fun resolveActiveKey(
    cfg: CodebaseConfiguration,
    logger: Logger,
    cliProvider: String? = null,
    cliAccount: String? = null,
    cliKey: String? = null
): NamedApiKey? {
    // Résolution provider → compte → clé avec CLI override
    // Kimi a parfaitement compris la chaîne de priorité
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Kode tersebut berhasil dikompilasi. Tujuh kasus uji lulus. Saya optimis.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_session_10_kelamunan_sunyi&amp;quot;&amp;gt;Session 10 : kelamunan sunyi&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Sesi kedua. Konteks meningkat hingga ~90K token dengan pertukaran. Saya meminta Kimi untuk menambahkan mekanisme snapshot AsciiDoc yang menganonimkan rahasia sebelum ditulis.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Inilah tempatnya mulai melenceng. Kimi mulai menciptakan kelas yang tidak ada. Dia menawarkan padaku.&amp;lt;code&amp;gt;AnonymizedObjectMapper&amp;lt;/code&amp;gt;— kelas fiktif. Dia membingungkan`ReadmeYmlAnonymizer` et &amp;lt;code&amp;gt;CodebaseYmlAnonymizer&amp;lt;/code&amp;gt;. Dia menyarankan saya mengimpor`com.fasterxml.jackson.anonymize.*`— sebuah paket yang tidak pernah ada.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Lebih buruk: dalam fase refleksinya, saya melihat bahwa ia membangun argumen berdasarkan premis yang salah. Dia &amp;quot;ingat&amp;quot; bahwa`GitConfig`memiliki sebuah ladang`anonymizedToken`— tidak, ini`resolvedToken()&amp;lt;code&amp;gt;. Dia mengatribusikan pada`SiteYmlAnonymizer`sebuah metode`maskSupabaseCredentials()&amp;lt;/code&amp;gt;— yang tidak ada.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;em&amp;gt;&amp;lt;em&amp;gt;_ Bukan itu sebuah bug. Itu adalah pelarutan progresif koherensi. Seolah-olah setiap token yang ditambahkan ke konteks mengencerkan sedikit lebih memori dari 30.000 token pertama. &amp;lt;/em&amp;gt;_&amp;lt;/em&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Saya menghentikan Kimi setelah dua sesi. Diagnosisnya jelas: MLA mengompres KV cache dengan baik, tetapi tanpa mekanisme seleksi sparse fine-grained, perhatiannya secara mekanis menjadi encer di atas 60K token. Setiap token &amp;quot;melihat&amp;quot; konteks jauh semakin buruk — dan mulai mengisi lubang dengan noise.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_round_2_glm_5_1_pejuang_yang_terhormat&amp;quot;&amp;gt;Round 2 : GLM-5.1 — Pejuang yang terhormat&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;GLM-5.1 hadir dengan arsitektur yang berbeda: MLA untuk model dasar, kemudian &amp;lt;strong&amp;gt;continued pre-training&amp;lt;/strong&amp;gt; dengan DSA (DeepSeek Sparse Attention) — sebuah indexer ringan yang secara dinamis memilih token-token teratas 2048 yang relevan dari seluruh riwayat.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_arsitektur_dsa_terhadap_mla_murni&amp;quot;&amp;gt;Arsitektur : DSA terhadap MLA murni&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Perbedaan tersebut bersifat fundamental. Di tempat Kimi mengompresi riwayat dalam ruang laten unik (dan secara bertahap kehilangan kemampuan untuk mendiskriminasikan informasi yang relevan), GLM menambahkan indexer pasca-pelatihan yang melakukan pemilihan sparse secara eksplisit.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div id=&amp;quot;diag-mla-vs-dsa&amp;quot; class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultTextAlignment center

title Arsitektur Perhatian — MLA vs MLA+DSA
left to right direction

rectangle &amp;quot;**Kimi K2.6 — MLA murni**&amp;quot; as MLA #FFCDD2 {
  rectangle &amp;quot;KV Cache
lengkap&amp;quot; as KV1
  rectangle &amp;quot;kompresi\nlaten&amp;quot; as CL1
  rectangle &amp;quot;Dekoding\ndi ruang laten&amp;quot; as DL1
  KV1 --&amp;amp;gt; CL1
  CL1 --&amp;amp;gt; DL1
  note bottom of DL1
    ⚠️ Au-delà de 60K tokens :
    perte de discrimination
  end note
}

rectangle &amp;quot;GLM-5.1 — MLA + DSA&amp;quot; as DSA #C8E6C9 {
  rectangle &amp;quot;KV Cache
lengkap&amp;quot; as KV2
  rectangle &amp;quot;Kompresi\nlaten (MLA)&amp;quot; as CL2
  rectangle &amp;quot;Pengindeks ringan
(DSA, top-k=2048)&amp;quot; as IL
  rectangle &amp;quot;Dekode
pada token yang dipilih&amp;quot; as DL2
  KV2 --&amp;amp;gt; CL2
  CL2 --&amp;amp;gt; IL
  IL --&amp;amp;gt; DL2
  note bottom of DL2
    ✅ &amp;quot;Tanpa kehilangan secara inherent&amp;quot;
    Sélection explicite
    des tokens pertinents
  end note
}

MLA --&amp;amp;gt; DSA : &amp;quot;Gain: seleksi sparse\nmengurangi penyampingan&amp;quot;

@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Laporan teknis ini secara eksplisit menyatakan: DSA adalah &amp;lt;em&amp;gt;&amp;quot;lossless by construction&amp;quot;&amp;lt;/em&amp;gt; — berbeda dengan alternatif seperti SWA (pattern search), Gated DeltaNet atau SimpleGDN yang kehilangan hingga 5.69 poin pada RULER@128K.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_sesi_11_13_teguh_tetapi_menggecewakan&amp;quot;&amp;gt;Sesi 11-13: teguh tetapi menggecewakan&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;GLM-5.1 lebih baik menjaga jarak. Pada sesi 11 (~60K token), ia tetap koheren. Ia menghasilkan sebuah`SnapshotManager`berfungsi dengan manajemen yang tepat untuk empat anonimizer&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Namun latensi adalah masalah. Tahap refleksi DSA lebih berat daripada MLA murni — indexer harus memindai ulang sejarah setiap langkah. Sebuah respons yang membutuhkan 8 detik dengan Kimi membutuhkan 15 detik dengan GLM. Pada sesi 30 pesan, hal ini terasa.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Dan kemudian ada kesalahan halus. GLM tidak mengelirukan seperti Kimi — tapi ia membuat kesalahan &amp;lt;strong&amp;gt;penamaan&amp;lt;/strong&amp;gt;. Ia memanggil`toAnonymizedYaml()&amp;lt;code&amp;gt;metode yang seharusnya disebut`anonymize()&amp;lt;/code&amp;gt;. Dia membalikkan`loadReadmeConfiguration()` et &amp;lt;code&amp;gt;loadCodebaseConfiguration()`di`renderFileSection()&amp;lt;/code&amp;gt;. Bukan halusinasi, ini adalah kebingungan permukaan — tetapi di produksi, kebingungan permukaan dapat memecah build.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Saya telah mengadakan tiga sesi dengan GLM. Lebih baik daripada Kimi, tanpa ragu. Tapi tiga sesi perbaikan manual pada detail penamaan, itu menguras.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_round_3_deepseek_v4_pro_mesin_perang&amp;quot;&amp;gt;Round 3 : DeepSeek-V4-Pro — Mesin Perang&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;DeepSeek-V4-Pro hadir dengan arsitektur paling ambisius dari tiga: sebuah sistem hibrid yang menggabungkan dua mekanisme perhatian komplementer.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_arsitektur_csa_hca_jaring_ganda&amp;quot;&amp;gt;Arsitektur: CSA + HCA, jaring ganda&amp;lt;/h3&amp;gt;
&amp;lt;div id=&amp;quot;diag-csa-hca&amp;quot; class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultTextAlignment center
skinparam wrapWidth 180

title DeepSeek-V4-Pro — Arsitektur Hibrid CSA + HCA
left to right direction

rectangle &amp;quot;KV Cache
1M token&amp;quot; as KV #E3F2FD

rectangle &amp;quot;CSA\nPerhatian Sparse yang Dikompresi&amp;quot; as CSA #C8E6C9 {
  rectangle &amp;quot;Kompresi
m token → 1&amp;quot; as CC
  rectangle &amp;quot;Perhatian Sparse\n(DSA, top-k)&amp;quot; as SA
  CC --&amp;amp;gt; SA

  note bottom of SA
    Attention locale fine
    + sélection sparse
  end note
}

rectangle &amp;quot;HCA
Perhatian yang terkompresi berat&amp;quot; as HCA #BBDEFB {
  rectangle &amp;quot;Kompresi ekstrem\nm&amp;#39; &amp;amp;gt;&amp;amp;gt; m → 1&amp;quot; as ECC
  rectangle &amp;quot;Perhatian padat
sisa&amp;quot; as EDA
  ECC --&amp;amp;gt; EDA

  note bottom of EDA
    Contexte global
    + connexions longue distance
  end note
}

rectangle &amp;quot;Fusi
hibrid&amp;quot; as FUSION #FFF9C4
rectangle &amp;quot;Dekoding\nkohéren&amp;quot; as DEC #FFE0B2

KV --&amp;amp;gt; CSA : Précision locale
KV --&amp;amp;gt; HCA : Vision globale

CSA --&amp;amp;gt; FUSION
HCA --&amp;amp;gt; FUSION
FUSION --&amp;amp;gt; DEC

@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Dua tingkat kompresi, dua tingkat ketelitian perhatian:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;CSA&amp;lt;/strong&amp;gt;mengompres cache KV setiap`m`tokens, lalu menerapkan perhatian spars (DeepSeek Sparse Attention) — hanya top-k entri yang dikompresi yang diakses. Akurat, lokal, efisien. - &amp;lt;strong&amp;gt;HCA&amp;lt;/strong&amp;gt;kompresi ekstrem (faktor`m&amp;#39;` bien plus grand que &amp;lt;code&amp;gt;m&amp;lt;/code&amp;gt;), perhatian yang padat pada sisa yang dikompresi — pertahankan koneksi global tanpa ledakan kuadratik.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Hasil: pada 1M token, DeepSeek-V4-Pro hanya mengonsumsi&amp;lt;strong&amp;gt;27% dari FLOPs inferensi&amp;lt;/strong&amp;gt; et &amp;lt;strong&amp;gt;10% dari ukuran cache KV&amp;lt;/strong&amp;gt;dibandingkan dengan DeepSeek-V3.2. Model sebelumnya.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Dan terutama, laporan teknis (Gambar 9) mengumumkan : &amp;lt;em&amp;gt;&amp;quot;Retrieval performance remains highly stable within a 128K context window.&amp;quot;&amp;lt;/em&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_sesi_1_8_kelegaan&amp;quot;&amp;gt;Sesi 1-8: kelegaan&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Saya telah menghabiskan delapan sesi dengan DeepSeek-V4-Pro pada`codebase-gradle`. Delapan sesi tanpa satu pun halusinasi, tanpa satu pun kelas yang diciptakan, tanpa satu pun kebingungan penamaan.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Sesi 1: implementasi`CodebaseYmlConfig` et &amp;lt;code&amp;gt;CodebaseYmlAnonymizer&amp;lt;/code&amp;gt;. 350 baris Kotlin, tes inline, semua berhasil dikompil. Sesi 4 : penambahan`SnapshotManager`dengan tree view, pengumpulan file, rendering AsciiDoc per file. 279 baris, tidak ada error. Sesi 7 : debug dari`renderFileSection()`yang harus mengelola empat jenis anonymizer berbeda tanpa keambiguan resolusi. Diselesaikan dalam tiga pesan.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Latensi lebih tinggi daripada Kimi — sekitar 10-12 detik per respons dalam mode thinking. Namun tingkat koreksi hampir nol. Saya tidak menghabiskan waktu saya untuk memperbaiki kesalahan agen. Saya coding &amp;lt;strong&amp;gt;dengan&amp;lt;/strong&amp;gt; dia.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_ujian_yang_menentukan_refactoring_hingga_100k_token&amp;quot;&amp;gt;Ujian yang menentukan: refactoring hingga 100K token&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Pada sesi 8, konteks kumulatif melebihi 100K token. Saya meminta refactoring berat: mengekstrak empat tugas verifikasi inline dari`build.gradle.kts`ke file tes JUnit5 di`buildSrc/src/test/`.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Agen menawarkan rencana dalam tiga tahap:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;olist arabic&amp;quot;&amp;gt;
&amp;lt;ol class=&amp;quot;arabic&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Membuat kelas pengujian dengan migrasi kasus yang ada&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Menambahkan dependensi JUnit5 dan Kotest dalam`buildSrc/build.gradle.kts`&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Hapus kode inline dari`build.gradle.kts`&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Rencananya benar. Pelaksanaannya rapi. Ia bahkan mengidentifikasi sebuah kasus tepi yang saya lewatkan: dependensi Jackson yang terduplikasi antara`buildscript {}` et `buildSrc/build.gradle.kts`yang harus disatukan selama migrasi.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;100K token konteks, dan agen ingat bahwa`CodebaseYmlAnonymizer.TOKEN_MASK`adalah`&amp;quot;&amp;lt;strong&amp;gt;*&amp;lt;/strong&amp;gt;&amp;quot;`, bahwa`GitConfig.resolvedToken()&amp;lt;code&amp;gt;adalah sebuah fungsi ekstensi yang didefinisikan dalam`readme.kt&amp;lt;/code&amp;gt;, dan bahwa`SnapshotManager.PRUNED_DIRS`mengecualikan`build`, &amp;lt;code&amp;gt;.gradle&amp;lt;/code&amp;gt; et &amp;lt;code&amp;gt;.git&amp;lt;/code&amp;gt;.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Itu saja, perbedaan.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_muka_ke_muka_perbandingan_metrik&amp;quot;&amp;gt;Muka ke Muka: Perbandingan Metrik&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;table class=&amp;quot;tableblock frame-all grid-all stretch&amp;quot;&amp;gt;
&amp;lt;colgroup&amp;gt;
&amp;lt;col style=&amp;quot;width: 25%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 25%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 25%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 25%;&amp;quot;&amp;gt;
&amp;lt;/colgroup&amp;gt;
&amp;lt;thead&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;Kriteria&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;Kimi K2.6&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;GLM-5.1&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;DeepSeek-V4-Pro&amp;lt;/th&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/thead&amp;gt;
&amp;lt;tbody&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;konteks maks&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;256K&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;200K&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;1M&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Mekanisme perhatian&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;MLA murni&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;MLA + DSA&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;CSA + HCA hibrid&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Parameter total&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;1T&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;744B&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;1.6T&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Pengaturan diaktifkan&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;32B&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;tidak dipublikasikan&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;49B&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;ambang degradasi yang diamati&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;amp;lt;/think&amp;amp;gt; (output nothing)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;~120K tokens&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;~128K tokens&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Perilaku di luar&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Hallusinasi massal&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Kesalahan permukaan&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;degradasi progresif lambat&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Stabilitas NIAH&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;belum dipublikasikan&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;100% @128K (DSA)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;stabil hingga 128K (Gambar 9)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;MRCR @128K&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;tidak dipublikasikan&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;belum dipublikasikan&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;lebih baik daripada Gemini 3.1 Pro&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;sesi-sesi yang diadakan sebelum pengabaian&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;2&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;3&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;8 (dan melanjutkan)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Waktu koreksi / waktu kode&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;60%&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;30%&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;amp;lt;5%&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Latensi rata-rata (mode berpikir)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;8 s&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;15 s&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;11 s&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Koefisien kepercayaan*&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;2/10&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;6/10&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;9/10&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;em&amp;gt;*Koefisien kepercayaan = ukuran subjektif dari kemampuan saya untuk mengambil kode agen dan commit tanpa membaca baris demi baris.&amp;lt;/em&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div id=&amp;quot;diag-radar&amp;quot; class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultTextAlignment center

title Radar Perbandingan — 3 LLMs dalam Pengembangan Perangkat Lunak yang Dibantu
legend right
  |= Couleur |= Modèle |
  | &amp;amp;lt;#FF5252&amp;amp;gt; | Kimi K2.6 |
  | &amp;amp;lt;#FFC107&amp;amp;gt; | GLM-5.1 |
  | &amp;amp;lt;#4CAF50&amp;amp;gt; | DeepSeek-V4-Pro |
endlegend

rectangle &amp;quot; &amp;quot; as space #FFFFFF

rectangle &amp;quot;Konsistensi
80K+ token&amp;quot; as coh #F5F5F5
rectangle &amp;quot;Kualitas\nkode&amp;quot; as qual #F5F5F5
rectangle &amp;quot;Penalaran
arsitek&amp;quot; as arch #F5F5F5
rectangle &amp;quot;Resistensi
halusinasi&amp;quot; as hall #F5F5F5
rectangle &amp;quot;Kecepatan\n(pengalaman pengembangan)&amp;quot; as speed #F5F5F5
rectangle &amp;quot;Pengetahuan
Gradle/Kotlin&amp;quot; as kg #F5F5F5
rectangle &amp;quot;Tingkat koreksi&amp;quot; as corr #F5F5F5

coh --&amp;amp;gt; qual
qual --&amp;amp;gt; arch
arch --&amp;amp;gt; hall
hall --&amp;amp;gt; speed
speed --&amp;amp;gt; kg
kg --&amp;amp;gt; corr

note top of coh
  Kimi      ████░░░░░░ 4/10
  GLM       ██████░░░░ 6/10
  DeepSeek  █████████░ 9/10
end note

note top of qual
  Kimi      ███████░░░ 7/10 (sous 60K)
  GLM       ████████░░ 8/10
  DeepSeek  █████████░ 9/10
end note

note top of arch
  Kimi      █████░░░░░ 5/10
  GLM       ███████░░░ 7/10
  DeepSeek  █████████░ 9/10
end note

note top of hall
  Kimi      ██████░░░░ 6/10 → ██░░░░░░░░ 2/10 (&amp;amp;gt; 60K)
  GLM       ███████░░░ 7/10
  DeepSeek  █████████░ 9/10
end note

note top of speed
  Kimi      █████████░ 9/10
  GLM       ██████░░░░ 6/10
  DeepSeek  █████░░░░░ 5/10
end note

note top of kg
  Kimi      ███████░░░ 7/10
  GLM       ███████░░░ 7/10
  DeepSeek  █████████░ 9/10
end note

note top of corr
  Kimi      ██░░░░░░░░ 2/10 (beaucoup de corrections)
  GLM       █████░░░░░ 5/10
  DeepSeek  ██████████ 10/10 (presque rien à corriger)
end note

@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_mengapa_deepseek_v4_pro_menang_dalam_pengembangan_perangkat_lunak&amp;quot;&amp;gt;Mengapa DeepSeek-V4-Pro Menang dalam Pengembangan Perangkat Lunak&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Kelebihan DeepSeek-V4-Pro bukan karena hanya satu faktor — ini adalah konvergensi :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_arsitektur_csahca_dirancang_untuk_kode&amp;quot;&amp;gt;Arsitektur CSA+HCA dirancang untuk kode&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;pengembangan perangkat lunak yang dibantu adalah kasus penggunaan &amp;lt;strong&amp;gt;ekstrem&amp;lt;/strong&amp;gt; untuk perhatian konteks panjang. Anda perlu : - Presisi lokal (kelas ini mewarisi apa? metode ini didefinisikan di mana?) →&amp;lt;strong&amp;gt;CSA&amp;lt;/strong&amp;gt; - Visi global (mengapa modul ini ada ? bagaimana empat anonymiser berinteraksi ?) →&amp;lt;strong&amp;gt;HCA&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Kimi hanya dengan MLA mengelola akurasi lokal tetapi kehilangan visi global di atas 60K. GLM dengan DSA meningkatkan visi global tetapi tetap mono-level. DeepSeek menggabungkan keduanya secara eksplisit.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_2_konteks_eager_adalah_zona_kenyamanan&amp;quot;&amp;gt;2. Konteks EAGER adalah zona kenyamanan&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Sistem tata kelola saya memuat sekitar 30K token aturan dan backlog saat startup. Dengan jendela stabil hingga 128K, DeepSeek memiliki sekitar 100K token ruang tambahan untuk pertukaran sesi. Ini adalah 3 hingga 4 kali lebih banyak daripada yang dapat Kimi tangani tanpa degradasi.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;em&amp;gt;&amp;lt;em&amp;gt;_ Jendela 128K stabilitas sempurna cocok tepat dengan kebutuhan saya: 30K EAGER + 70K pertukaran = sesi produktif 20-30 pesan tanpa pernah keluar dari zona hijau. &amp;lt;/em&amp;gt;_&amp;lt;/em&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_3_rasio_kualitaslatensi_optimal_untuk_alur&amp;quot;&amp;gt;3. Rasio kualitas/latensi optimal untuk alur&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Ya, DeepSeek-V4-Pro lebih lambat daripada Kimi (11s vs 8s). Tapi waktu &amp;lt;strong&amp;gt;total&amp;lt;/strong&amp;gt; dari suatu tugas jauh lebih rendah karena saya tidak menghabiskan 20 menit untuk memperbaiki halusinasi agen.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Pengembang tidak mengukur latensi dari sebuah respons. Dia mengukur waktu antara &amp;quot;saya menajukan pertanyaan&amp;quot; dan &amp;quot;kode ada di repositori saya dan berfungsi&amp;quot;. Pada metrik tersebut, DeepSeek-V4-Pro adalah yang tercepat dari tiga.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_pelajaran_yang_dipelajari_cara_memilih_llm_anda_untuk_vibe_coding&amp;quot;&amp;gt;Pelajaran yang Dipelajari : Cara Memilih LLM Anda untuk Vibe Coding&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Di luar peringkat spesifik, pengalaman ini mengajarkan saya untuk mengevaluasi LLM untuk pengembangan perangkat lunak yang didukung berdasarkan kriteria yang tidak ada dalam benchmark manapun :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;olist arabic&amp;quot;&amp;gt;
&amp;lt;ol class=&amp;quot;arabic&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Lihat arsitekturnya, bukan ukuran konteks yang diumumkan&amp;lt;/strong&amp;gt;— Sebuah model yang menglaim 256K konteks tetapi hanya memiliki MLA akan berakhir seperti Kimi: secara teoretis mampu, secara praktis tidak dapat digunakan di atas 60K.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Uji pada konteks Anda, bukan pada benchmark generik&amp;lt;/strong&amp;gt;— Prompt awal saya sebesar 30K token AsciiDoc dengan aturan absolut dan backlog tidak ada hubungannya dengan pertanyaan HLE atau AIME.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Latensi bukanlah musuh bila kualitas ikut&amp;lt;/strong&amp;gt;— Sebuah model lambat yang menghasilkan kode yang benar lebih cepat daripada model cepat yang menghasilkan kode yang salah.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Waspadalah terhadap model tanpa laporan teknis publik&amp;lt;/strong&amp;gt;— Jika tim tidak mendokumentasikan arsitektur perhatiannya, berarti mereka tidak percaya pada kinerjanya dalam konteks panjang.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div id=&amp;quot;diag-decision-tree&amp;quot; class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultTextAlignment center
skinparam wrapWidth 260

title Pohon Keputusan — Memilih LLM-nya untuk Pengembangan yang Dibantu
start

:Contexte initial\n&amp;amp;gt; 20K tokens ?;

if (Oui) then
  :Architecture d&amp;#39;attention\nhybride ou multi-niveau ?;

  if (CSA+HCA) then
    :DeepSeek-V4-Pro
    Fenetre stable 128K
    Ideal pour gouvernance EAGER;
    stop
  elseif (MLA + DSA) then
    :GLM-5.1
    Correct jusqu&amp;#39;a ~120K
    Latence elevee;
    stop
  else (MLA seul)
    :Kimi K2.6 (sessions courtes)
    ou eviter pour long contexte
    Chercher un modele avec
    sparse attention;
    stop
  endif
else (Contexte &amp;amp;lt; 20K)
  :N&amp;#39;importe lequel fonctionne.
  Privilegier la latence.;
  stop
endif

@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_dan_bagaimana_dengan_governance_agent_dalam_semua_ini&amp;quot;&amp;gt;Dan bagaimana dengan Governance Agent dalam semua ini?&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Pengalaman ini mengonfirmasi satu poin yang saya pertahankan sejak artikel saya tentang tata kelola Eager/Lazy:&amp;lt;strong&amp;gt;Kualitas LLM dan kualitas tata kelola bersifat multiplikatif, bukan aditif&amp;lt;/strong&amp;gt;.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Dengan Kimi K2.6, tata kelola saya sempurna — tetapi model mengaburkan informasi. Hasil: tata kelola sempurna × halusinasi = nol.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Dengan DeepSeek-V4-Pro, tata kelola EAGER (30K token aturan) + LAZY (arsip sesi, referensi teknis) + Hot/Warm/Cold (cadangan rotatif) membentuk sebuah ekosistem di mana setiap lapisan memperkuat yang lain. Agen memiliki aturan di depan mata (EAGER), dapat mengakses riwayat (LAZY), dan konteks tidak pernah meluap (rotasi cadangan).&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;em&amp;gt;&amp;lt;em&amp;gt;_ Sebuah LLM yang baik tanpa tata kelola adalah seperti mesin Ferrari tanpa stir. Tata kelola yang baik tanpa LLM yang baik adalah seperti stir tanpa mesin. DeepSeek-V4-Pro + tata kelola agen saya = pertama kali saya merasa sedang mengemudi. &amp;lt;/em&amp;gt;_&amp;lt;/em&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Mekanisme backup rotatif (rotasi setiap 10 sesi atau &amp;amp;gt;500 baris EAGER) menjadi bermakna dengan model yang mampu menahan konteks 128K: sliding window dari 10 sesi aktif mempertahankan konteks yang segar tanpa pernah melebihi zona degradasi.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_sumber_teknis&amp;quot;&amp;gt;Sumber Teknis&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Saya telah menggabungkan pengalaman empiris saya dengan laporan teknis resmi untuk memvalidasi pengamatan saya :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;olist arabic&amp;quot;&amp;gt;
&amp;lt;ol class=&amp;quot;arabic&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;DeepSeek-V4 Technical Report&amp;lt;/strong&amp;gt; — PDF diambil darihttps://huggingface.co/deepseek-ai/DeepSeek-V4-Flash[HuggingFace]. Gambar 9 : &amp;quot;Kinerja pengambilan tetap sangat stabil dalam jendela konteks 128K. Sementara degradasi kinerja menjadi terlihat di luar batas 128K, kemampuan pengambilan model pada 1 juta token tetap sangat kuat.&amp;quot; Arsitektur CSA+HCA yang didokumentasikan bagian 2.3.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;GLM-5 Laporan Teknis&amp;lt;/strong&amp;gt; —https://arxiv.org/abs/2602.15763[arXiv:2602.15763]DSA diperkenalkan melalui pra-pelatihan berkelanjutan, &amp;quot;lossless by construction&amp;quot;. Konteks maksimal 202.752 token. Tabel 3/5/6 mendokumentasikan performa konteks panjang versus alternatif (SWA, Gated DeltaNet, SimpleGDN).&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Kartu Model Kimi K2.6&amp;lt;/strong&amp;gt; —https://huggingface.co/moonshotai/Kimi-K2.6[HuggingFace]. MLA, konteks maksimal 256K. Strategi discard-all di atas ambang batas pada tugas agen (mengonfirmasi secara implisit batas praktis MLA dalam konteks panjang).&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Kimi K2 Laporan Teknis&amp;lt;/strong&amp;gt; —https://arxiv.org/abs/2507.20534[arXiv:2507.20534]. Arsitektur MoE, optimiser MuonClip, kinerja agen (HLE, BrowseComp, Terminal-Bench).&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Poin paling terungkap: Dalam penilaian mereka sendiri, Moonshot menerapkan strategi &amp;lt;strong&amp;gt;discard-all&amp;lt;/strong&amp;gt; (penghapusan konteks lama) begitu jendela konteks melebihi batas. Hal ini secara pasti mengonfirmasi apa yang saya amati: MLA saja tidak dapat mempertahankan koherensi di seluruh jendela 256K. Arsitektur tidak mengikuti.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_conclusion_pilihan_artisan&amp;quot;&amp;gt;Conclusion: Pilihan Artisan&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Setelah tiga minggu dan tiga belas sesi pengembangan nyata, putusannya mutlak:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;table class=&amp;quot;tableblock frame-all grid-all stretch&amp;quot;&amp;gt;
&amp;lt;colgroup&amp;gt;
&amp;lt;col style=&amp;quot;width: 8.3333%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 8.3333%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 8.3333%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 8.3333%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 8.3333%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 8.3333%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 8.3333%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 8.3333%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 8.3333%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 8.3333%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 8.3333%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 8.3337%;&amp;quot;&amp;gt;
&amp;lt;/colgroup&amp;gt;
&amp;lt;tbody&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Kimi K2.6&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;GLM-5.1&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;DeepSeek-V4-Pro&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Excellent di bawah 60K&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Kuat hingga 120K&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Mendominasi di mana-mana&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Tidak dapat digunakan di luar&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Kebingungan permukaan&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Stabil hingga 128K&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;2 sesi, ditinggalkan&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;3 sesi, ditinggalkan&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;8 sesi, diadopsi&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;DeepSeek-V4-Pro telah menjadi LLM default saya untuk semua sesi pengembangan perangkat lunak yang dibantu dengan Opencode. Tidak karena ia yang terbaru. Tidak karena ia memiliki benchmark akademik terbaik. Namun karena dalam kehidupan nyata seorang pengembang yang mengirimkan plugin Gradle Kotlin DSL dengan konteks agen 30K token, ia adalah satu-satunya yang tidak meniakhiri saya ketika konteks membesar.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;CSA+HCA adalah &amp;lt;strong&amp;gt;game changer&amp;lt;/strong&amp;gt; arkitektonik. Kompresi tingkat ganda + sparsifikasi bukan detail implementasi — ini yang membedakan antara asisten kode dan rekan tim yang andal.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;em&amp;gt;&amp;lt;em&amp;gt;_ Saya memilih DeepSeek-V4-Pro karena ia adalah satu-satunya dari tiga yang mengubah tata kelola agen saya dari batasan defensif (&amp;quot;bagaimana menghindari agen lupa?&amp;quot;) menjadi keunggulan ofensif (&amp;quot;apa yang bisa kita bangun sekarang bahwa agen mengingat semuanya?&amp;quot;). &amp;lt;/em&amp;gt;_&amp;lt;/em&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;hr&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Makalah ini adalah hasil dari 13 sesi pengembangan nyata pada proyek`codebase-gradle`, dokumentasi dalam`.agents/sessions/`menurut metodologi pengelolaan agen saya Eager/Lazy/Hot/Warm/Cold. Sumber-sumber teknis yang disebut dapat diakses secara publik di HuggingFace dan arXiv.&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>Audit Agent Konteks : Ketika Governance Anda sendiri menjadi masalah — dan Cara Memperbaikinya tanpa Kehilangan Apa pun</title>
            <link >https://cheroliv.com/blog/2026/0111_audit_contexte_agent_post.html</link>
            <pubDate>Mon, 27 Apr 2026 00:00:00 +0000</pubDate>
            <guid isPermaLink="false">blog/2026/0111_audit_contexte_agent_post.html</guid>
            <description>&amp;lt;div id=&amp;quot;toc&amp;quot; class=&amp;quot;toc&amp;quot;&amp;gt;
&amp;lt;div id=&amp;quot;toctitle&amp;quot;&amp;gt;Table des matières&amp;lt;/div&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel1&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_sahnya_sesi_051_ada_yang_tidak_beres&amp;quot;&amp;gt;Sahnya: sesi 051, ada yang tidak beres&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_audit_merinci_627_baris&amp;quot;&amp;gt;Audit: merinci 627 baris&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_ketakutan_kehilangan_teknik&amp;quot;&amp;gt;Ketakutan kehilangan teknik&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_solusi_split_ensiklopedi&amp;quot;&amp;gt;Solusi : split ensiklopedi&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_isi_file_essentials&amp;quot;&amp;gt;Isi file ESSENTIALS&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_hasil_50_konteks_eager&amp;quot;&amp;gt;Hasil: -50% konteks EAGER&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_mengapa_folder_bernama_encyclopedies&amp;quot;&amp;gt;Mengapa folder bernama `encyclopedies/&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_lanjut_logis_dari_seri&amp;quot;&amp;gt;Lanjut logis dari seri&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_apa_yang_akan_saya_lakukan_berbeda&amp;quot;&amp;gt;Apa yang akan saya lakukan berbeda&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_panduan_untuk_mengaudit_konteks_anda_sendiri&amp;quot;&amp;gt;Panduan untuk mengaudit konteks Anda sendiri&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_tata_kelola_hidup&amp;quot;&amp;gt;Tata kelola hidup&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_tautan&amp;quot;&amp;gt;Tautan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div id=&amp;quot;preamble&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;waktu membaca : 14 minutes&amp;lt;/div&amp;gt;
&amp;lt;p&amp;gt;Anda telah menghabiskan mingguan untuk membangun tata kelola agen yang sempurna. Eager/Lazy, prosedur akhir sesi, backup rotatif. Sistem berjalan. Lalu suatu hari, agen menjadi lambat. Responsnya menjadi encer. Anda mengukur: 1414 baris dimuat secara otomatis. Dan yang terburuk, itu bukan kode, bukan backlog, bukan arsip sesi. Penyebabnya adalah file yang mendokumentasikan metode Anda.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;dlist&amp;quot;&amp;gt;
&amp;lt;dl&amp;gt;
&amp;lt;dt class=&amp;quot;hdlist1&amp;quot;&amp;gt;tik&amp;lt;/dt&amp;gt;
&amp;lt;dd&amp;gt;
&amp;lt;p&amp;gt;[]&amp;lt;/p&amp;gt;
&amp;lt;/dd&amp;gt;
&amp;lt;/dl&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_sahnya_sesi_051_ada_yang_tidak_beres&amp;quot;&amp;gt;Sahnya: sesi 051, ada yang tidak beres&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;30 April 2026, 16:00. Saya tengah dalam sesi`magic-stick`, proyek saya membangun ISO Linux live. Agen Opencode telah memuat otomatis file-file Eager saya seperti biasa —&amp;lt;code&amp;gt;AGENT.adoc&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;PROMPT_REPRISE.adoc&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;.agents/INDEX.adoc&amp;lt;/code&amp;gt;. Semua normal.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Tapi jawaban-nya lemah. Agen memakan waktu dua detik lebih untuk berpikir. Ia melupakan detail yang baru saja dia lihat tiga pesan sebelumnya. Ini bukan crash, bukan kesalahan — ini adalah degradasi perlahan, jenis yang tidak langsung terlihat.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Saya sudah pernah mengalami hal itu pada sesi 048, ketika saya menemukan bahwa file-file tata kelola memiliki 5200 baris secara kumulatif. Saya kemudian mengonseptualisasikan mekanisme Hot/Warm/Cold dengan window geser sebesar 10 sesi, gelombang dinginnya yang identik, serta sensor-nya dengan dua pemicu. Masalah tersebut sudah teratasi. Dalam teori.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Tapi di sini, kita berada di sesi 051. Rotasi backup telah terjadi dengan baik — file-file EAGER berubah dari 2087 menjadi 500 baris. Namun, konteks masih berat. Ada yang terlepas dariku.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Saya membuka terminal dan saya mengetik :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;wc -l AGENT.adoc AGENT_MODUS_OPERANDI.adoc PROMPT_REPRISE.adoc \
  .agents/INDEX.adoc .agents/SESSIONS_HISTORY.adoc \
  .agents/archives/COMPLETED_TASKS_ARCHIVE_2026-04.adoc&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-none hljs&amp;quot;&amp;gt;  287 AGENT.adoc
  627 AGENT_MODUS_OPERANDI.adoc
   51 PROMPT_REPRISE.adoc
  218 .agents/INDEX.adoc
   18 .agents/SESSIONS_HISTORY.adoc
  213 .agents/archives/COMPLETED_TASKS_ARCHIVE_2026-04.adoc
 1414 total&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;1414 baris. Rotasi backup telah memangkas dengan baik di INDEX, SESSIONS_HISTORY dan COMPLETED_TASKS — mereka bersih. Tapi ada seekor gajah di ruangan yang belum saya lihat :&amp;lt;strong&amp;gt;627 baris&amp;lt;/strong&amp;gt;dalam satu file.&amp;lt;code&amp;gt;AGENT_MODUS_OPERANDI.adoc&amp;lt;/code&amp;gt;.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;File ini adalah file yang saya telah tulis di sesi 1 untuk mendokumentasikan strategi Eager/Lazy. Ini adalah manual metode. Dan ia sendiri telah menjadi 44% dari konteks EAGER.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div id=&amp;quot;diag-before-audit&amp;quot; class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE

title Konteks EAGER — 1414 baris
rectangle &amp;quot;AGENT_MODUS_OPERANDI
**627 baris (44%)**&amp;quot; as MOD #FFCDD2
rectangle &amp;quot;AGENT.adoc
287 baris (20%)&amp;quot; as AG #BBDEFB
rectangle &amp;quot;INDEX.adoc\n218 baris (15%)&amp;quot; as IDX #C8E6C9
rectangle &amp;quot;COMPLETED_TASKS
213 baris (15%)&amp;quot; as ARC #FFF9C4
rectangle &amp;quot;PROMPT_REPRISE
51 baris (4%)&amp;quot; as PRO #E1BEE7
rectangle &amp;quot;SESSIONS_HISTORY
18 baris (1%)&amp;quot; as HIS #FFE0B2

note bottom of MOD
  Le fichier qui documente
  la méthode est devenu
  le plus gros poste de dépense
end note

@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Ironi itu total. File yang dirancang untuk &amp;lt;strong&amp;gt;menghemat&amp;lt;/strong&amp;gt; konteks telah menjadi &amp;lt;strong&amp;gt;konsumen&amp;lt;/strong&amp;gt; utama konteks. Seolah-olah manual penggunaan mobil Anda lebih berat daripada mesin.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_audit_merinci_627_baris&amp;quot;&amp;gt;Audit: merinci 627 baris&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Saya memutuskan untuk melakukan audit bagian demi bagian. Bukan untuk menghapus — untuk &amp;lt;strong&amp;gt;memahami&amp;lt;/strong&amp;gt; yang layak berada di EAGER dan yang mungkin bisa tinggal di tempat lain tanpa kehilangan pengetahuan.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Berikut adalah struktur tepat dari`AGENT_MODUS_OPERANDI.adoc`dan yang saya temukan di sana:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;table class=&amp;quot;tableblock frame-all grid-all stretch&amp;quot;&amp;gt;
&amp;lt;colgroup&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.2727%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.2727%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.2727%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.2727%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.2727%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.2727%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.2727%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.2727%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.2727%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.2727%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.2727%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.2727%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.2727%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.2727%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.2727%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.2727%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.2727%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.2727%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.2727%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.2727%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.2727%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.2727%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.2727%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.2727%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.2727%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.2727%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.2727%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.2727%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.2727%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.2727%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.2727%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.2727%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.2727%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.2727%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.2727%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.2727%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.2727%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.2727%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.2727%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.2727%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.2727%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.2727%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.2727%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.2739%;&amp;quot;&amp;gt;
&amp;lt;/colgroup&amp;gt;
&amp;lt;tbody&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Bagian&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Baris&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Konten&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Sudah ada di&amp;amp;#8230;&amp;amp;#8203;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;P1 — Gambaran Umum&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;64&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Masalah terpecahkan, prinsip-prinsip dasar, analogi dasbor vs manual&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;(Empty)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;P2 — Struktur file&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;97&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Struktur pohon, kebijakan pemuatan, kapan memuat apa&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;—&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;P3 — Aturan mutlak&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;34&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Git dilarang, perintah destruktif dilarang, rahasia dilarang&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;AGENT.adoc&amp;lt;/code&amp;gt; + &amp;lt;code&amp;gt;INDEX.adoc&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;P4 — Siklus hidup sesi&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;133&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Template pembukaan, aturan kerja, 6 tahap akhir sesi, daftar periksa&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;AGENT.adoc&amp;lt;/code&amp;gt;§Sesi selesai +&amp;lt;code&amp;gt;INDEX.adoc&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;P5 — Metrik dan ambang batas&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;49&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Sesi ideal (15-30 menit, 1-3 berkas), tanda peringatan&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;—&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;P6 — Jenis sesi&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;72&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Tabel deteksi, format proposasi, pengecualian&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;—&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;P7 — Peningkatan terus-menerus&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;39&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Metrik pelacakan, ulasan mingguan&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;—&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;P8 — Checklist pemulai&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;13&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Proyek Bootstrap baru&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;—&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;P9 — Referensi&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;30&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;File referensi, sumber daya eksternal&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;AGENT.adoc&amp;lt;/code&amp;gt;(file EAGER/LAZY)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;Lampiran A+B&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;42&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Glosarium, riwayat versi&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;(Blank)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Putusannya tanpa rayuan:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;duplikasi lengkap&amp;lt;/strong&amp;gt;(167 baris) : P3, P4, P9 — semua sudah di dalam`AGENT.adoc` ou &amp;lt;code&amp;gt;INDEX.adoc&amp;lt;/code&amp;gt;, sering kata demi kata&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Tidak pernah dikonsultasikan dalam praktik&amp;lt;/strong&amp;gt;(215 baris) : P5, P6, P7, P8, Lampiran — dari meta-pemerintahan yang tidak pernah digunakan dalam sesi apa pun&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Pengetahuan unik&amp;lt;/strong&amp;gt;(161 baris) : P1 dan P2 — kosakata LAZY/EAGER, analogi, kebijakan pemuatan&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Pada 627 baris,&amp;lt;strong&amp;gt;382 adalah derau&amp;lt;/strong&amp;gt;. Dan 382 baris ini dimuat &amp;lt;strong&amp;gt;di awal setiap sesi&amp;lt;/strong&amp;gt;, dikonsumsi oleh agen, mengurangi perhatiannya bahkan sebelum saya menyapa.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div id=&amp;quot;diag-audit-result&amp;quot; class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultTextAlignment center

title Audit AGENT_MODUS_OPERANDI.adoc — 627 baris
left to right direction

rectangle &amp;quot;🟡 **Duplikat**
167 baris (27%)&amp;quot; as DUP #FFF9C4 {
  card &amp;quot;P3 — Peraturan Mutlak\n(34 baris)&amp;quot; as D1
  card &amp;quot;P4 — Siklus hidup
(133 baris)&amp;quot; as D2
  card &amp;quot;P9 — Referensi
(30 baris)&amp;quot; as D3
}

rectangle &amp;quot;🔴 **Tidak pernah dikonsultasi**
215 baris (34%)&amp;quot; as JAM #FFCDD2 {
  card &amp;quot;P5 — Metrik
(49 baris)&amp;quot; as J1
  card &amp;quot;P6 — Deteksi
(72 baris)&amp;quot; as J2
  card &amp;quot;P7 — Peningkatan
(39 baris)&amp;quot; as J3
  card &amp;quot;P8 — Bootstrap
(13 baris)&amp;quot; as J4
  card &amp;quot;Annexes — Glosarium
(42 baris)&amp;quot; as J5
}

rectangle &amp;quot;**Pengetahuan unik**
161 baris (26%)&amp;quot; as UNI #C8E6C9 {
  card &amp;quot;P1 — Ringkasan\n(64 baris)&amp;quot; as U1
  card &amp;quot;P2 — Struktur file
(97 baris)&amp;quot; as U2
}

note bottom of UNI
  C&amp;#39;est ÇA qu&amp;#39;il faut garder en EAGER.
  Le reste → cold storage.
end note

@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Diagram ini adalah kunci dari segalanya. Duplikasi (kuning) adalah noise murni — agen membaca mereka dua kali, dalam dua file berbeda. Yang tidak pernah dikonsultasi (merah) adalah dokumentasi mati — ditulis dengan teliti, tidak pernah digunakan. Hanya hijau yang berisi pengetahuan yang tidak dapat ditemukan oleh agen di tempat lain.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_ketakutan_kehilangan_teknik&amp;quot;&amp;gt;Ketakutan kehilangan teknik&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Pada tahap ini, saya memiliki bukti di depan mata saya: saya harus mengurangi`AGENT_MODUS_OPERANDI.adoc`. Tapi saya ragu.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;File ini, saya tulis dengan tangan. Setiap bagian adalah hasil dari pelajaran yang dipelajari dalam sesi nyata. Bagian P3 lahir dari sebuah`rm -rf`aksidental di`bakery-plugin`yang menghabiskan token Firebase nyata. Bagian P4 adalah hasil dari lima belas sesi di mana saya lupa mengarsip dan kehilangan alur. Prosedur dalam 6 langkah bukanlah hasil dari sebuah buku — ia adalah hasil dari kesakitan.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Menghapus bagian-bagian ini, akan menjadi membuang sejarah dari rekayasa saya sendiri. Pelajaran yang menciptakannya, sesi-sesi ketika saya menemukannya, kesalahan yang saya tidak ingin pernah lagi mengulangi. Bukan itu teks — ini adalah pengalaman yang terkristal.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Itulah tempat saya merumuskan prinsip yang akan memandu solusi:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;em&amp;gt;&amp;lt;em&amp;gt;_ &amp;lt;strong&amp;gt;Jangan pernah menghapus. Selalu relokasi.&amp;lt;/strong&amp;gt;Pengetahuan tidak harus menghilang dari proyek. Ia hanya perlu tidak dimuat secara otomatis ketika tidak diperlukan. &amp;lt;/em&amp;gt;_&amp;lt;/em&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_solusi_split_ensiklopedi&amp;quot;&amp;gt;Solusi : split ensiklopedi&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Model yang saya ajukan sederhana dan terinspirasi dari cara Wikipedia berkembang: ketika suatu artikel menjadi terlalu panjang, kita tidak memotongnya — kita membuat artikel terperinci dan menyimpan rangkuman dalam artikel utama.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;untuk`AGENT_MODUS_OPERANDI.adoc`, itu memberi :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;olist arabic&amp;quot;&amp;gt;
&amp;lt;ol class=&amp;quot;arabic&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Mengekstrak&amp;lt;/strong&amp;gt;161 baris pengetahuan unik (P1 + P2) dalam file baru`LAZY_EAGER_ESSENTIALS.adoc`— dikompresi ke ~50 baris, ketat EAGER&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Mengganti nama&amp;lt;/strong&amp;gt; &amp;lt;code&amp;gt;AGENT_MODUS_OPERANDI.adoc&amp;lt;/code&amp;gt; en &amp;lt;code&amp;gt;.agents/encyclopedies/LAZY_EAGER_ENCYCLOPEDIE.adoc&amp;lt;/code&amp;gt;— file asli 627 baris, disimpan &amp;lt;strong&amp;gt;intact&amp;lt;/strong&amp;gt;, dalam penyimpanan dingin&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Jangan pernah mengisi daya&amp;lt;/strong&amp;gt;file ensiklopedia secara otomatis — ia ada untuk manusia, untuk distilasi masa depan, untuk agen yang akan dipanggil dalam enam bulan dengan model yang lebih baik&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div id=&amp;quot;diag-split-solution&amp;quot; class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultTextAlignment center

title Split Ensiklopedik — AGENT_MODUS_OPERANDI
left to right direction

rectangle &amp;quot;**AGENT_MODUS_OPERANDI.adoc**
627 baris
(sebelum split)&amp;quot; as BEFORE #FFCDD2 {
}

rectangle &amp;quot; &amp;quot; as ARROW1
rectangle &amp;quot; &amp;quot; as ARROW2

rectangle &amp;quot;**LAZY_EAGER_ESSENTIALS.adoc**
**~50 baris — EAGER**&amp;quot; as ESS #C8E6C9 {
  card &amp;quot;P1 ringkas\n(15 baris)&amp;quot; as E1
  card &amp;quot;P2 terkompresi
(35 baris)&amp;quot; as E2
}

rectangle &amp;quot;.agents/encyclopedies/`
`LAZY_EAGER_ENCYCLOPEDIE.adoc`
627 baris — COLD&amp;quot; as ENC #BBDEFB {
  card &amp;quot;P1 ke Lampiran
(lengkap, utuh)&amp;quot; as C1
}

BEFORE -[#4CAF50]-&amp;amp;gt; ESS : &amp;quot;Ekstraksi
pengetahuan kritis&amp;quot;
BEFORE -[#2196F3]-&amp;amp;gt; ENC : &amp;quot;Preservasi lengkap teknik&amp;quot;

note bottom of ESS
  Chargé automatiquement
  en début de session
  → L&amp;#39;agent comprend le vocabulaire
end note

note bottom of ENC
  Jamais chargé automatiquement
  Consultable par l&amp;#39;humain
  Matériau brut pour distillation future
end note

@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Nama`encyclopedies/`Tidak sembarangan. Sebuah ensiklopedia bukanlah berkas arsip — itu merupakan koleksi pengetahuan yang terorganisir, dapat diakses tapi tidak dapat dibawa. Tidak ada yang membaca Ensiklopedia Universalis di metro. Kita menyimpannya di perpustakaan, dan kita pergi ke sana saat kita memiliki pertanyaan yang spesifik.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Ini adalah peran tepat dari folder ini: sebuah perpustakaan referensi yang dingin, terstruktur, komprehensif — yang tidak disentuh secara otomatis oleh agen.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_isi_file_essentials&amp;quot;&amp;gt;Isi file ESSENTIALS&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Ini adalah tampilan konkret dari file baru.&amp;lt;code&amp;gt;LAZY_EAGER_ESSENTIALS.adoc&amp;lt;/code&amp;gt;, diekstrak dan dikompres dari P1 dan P2 :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-asciidoc hljs&amp;quot; data-lang=&amp;quot;asciidoc&amp;quot;&amp;gt;= Stratégie LAZY/EAGER — Principes Essentiels

[abstract]
Ce fichier définit la stratégie de gestion du contexte agent.
Chargé automatiquement (EAGER) en début de session.

== Principes

|===
| EAGER | LAZY
| Tableau de bord | Manuel du propriétaire
| Chargé automatiquement | Chargé sur demande
| &amp;amp;lt;= 100 lignes, &amp;amp;lt;= 10k tokens | Illimité, détaillé
| Règles absolues, mission courante | Archives, historique, références
|===

== Politique de Chargement

|===
| Fichier | Type | Quand charger
| PROMPT_REPRISE.adoc | EAGER | Début session (auto)
| *_ESSENTIALS.adoc | EAGER | Début session si EPIC active
| .agents/INDEX.adoc | EAGER | Début session (auto)
| *_REFERENCE.adoc | LAZY | Sur besoin (détails architecture)
| .agents/sessions/N-*.adoc | LAZY | Sur demande (détails session)
| .agents/encyclopedies/*.adoc | COLD | Jamais auto (humain seulement)
|===

== Comment l&amp;#39;Agent Sait Quoi Charger

Début session → PROMPT_REPRISE + INDEX + ESSENTIALS actifs.
Besoin de détails → charger les *_REFERENCE et sessions/ en LAZY.
Connaissance froide → encyclopedies/, jamais automatique.&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Lima puluh baris. Itulah semua yang dibutuhkan agen untuk memahami mekaniknya. Sisanya — riwayat pelajaran, analogi terperinci, prosedur langkah demi langkah, lampiran — berada di dalam ensiklopedia.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Dan yang terpenting:&amp;lt;strong&amp;gt;Tidak ada yang dihapus&amp;lt;/strong&amp;gt;. 627 baris teknik masih ada, di`.agents/encyclopedies/LAZY_EAGER_ENCYCLOPEDIE.adoc`. Mereka hanya disimpan di perpustakaan alih-alih di atas meja kerja.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_hasil_50_konteks_eager&amp;quot;&amp;gt;Hasil: -50% konteks EAGER&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Sebelum split :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-none hljs&amp;quot;&amp;gt; 287 AGENT.adoc
 627 AGENT_MODUS_OPERANDI.adoc
  51 PROMPT_REPRISE.adoc
 218 .agents/INDEX.adoc
  18 .agents/SESSIONS_HISTORY.adoc
 213 .agents/archives/COMPLETED_TASKS_ARCHIVE_2026-04.adoc
1414 total&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Setelah split :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-none hljs&amp;quot;&amp;gt; 287 AGENT.adoc
  50 LAZY_EAGER_ESSENTIALS.adoc       ← remplace 627 lignes
  51 PROMPT_REPRISE.adoc
 218 .agents/INDEX.adoc
  18 .agents/SESSIONS_HISTORY.adoc
 213 .agents/archives/COMPLETED_TASKS_ARCHIVE_2026-04.adoc
 837 total                              ← -41%&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div id=&amp;quot;diag-after-audit&amp;quot; class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE

title Konteks EAGER setelah split ensiklopedi — 837 baris (-41%)
rectangle &amp;quot;AGENT.adoc\n287 baris (34%)&amp;quot; as AG #BBDEFB
rectangle &amp;quot;INDEX.adoc
218 baris (26%)&amp;quot; as IDX #C8E6C9
rectangle &amp;quot;COMPLETED_TASKS
213 baris (25%)&amp;quot; as ARC #FFF9C4
rectangle &amp;quot;PROMPT_REPRISE\n51 baris (6%)&amp;quot; as PRO #E1BEE7
rectangle &amp;quot;ESSENSIAL\n50 baris (6%)&amp;quot; as ESS #A5D6A7
rectangle &amp;quot;SESSIONS_HISTORY\n18 baris (2%)&amp;quot; as HIS #FFE0B2

note bottom of ESS
  De 627 → 50 lignes
  577 lignes d&amp;#39;ingénierie
  préservées en cold storage

  Rien de perdu. Tout de relocalisé.
end note

@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Pengguna terbesar (AGENT_MODUS_OPERANDI, 627 baris) telah diganti dengan sebuah file berukuran 50 baris. Manfaatnya segera terasa : 577 baris konteks telah dibebaskan. Agen tersebut bisa bernapas.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Dan di`.agents/encyclopedies/`, file asli dari 627 bar menunggu. Utuh. Dengan semua bagiannya — termasuk yang merupakan duplikat, termasuk yang belum pernah digunakan. Karena suatu hari nanti, sebuah model yang lebih baik atau seorang data scientist manusia akan ingin menggabungkan sudut-sudut ini, redundansi yang diasumsikan, pelajaran yang telah dipelajari. Dan hari itu, bahan mentah akan ada.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_mengapa_folder_bernama_encyclopedies&amp;quot;&amp;gt;Mengapa folder bernama `encyclopedies/&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Pemilihan nama tidak bersifat kosmetik. Ia mengkodekan filosofi mekanisme.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Sebuah &amp;lt;strong&amp;gt;arsip&amp;lt;/strong&amp;gt; (&amp;lt;code&amp;gt;archives/&amp;lt;/code&amp;gt;) mengandung data historis yang diatur secara kronologis — seperti COMPLETED_TASKS bulanan. Sebuah &amp;lt;strong&amp;gt;backup&amp;lt;/strong&amp;gt; (&amp;lt;code&amp;gt;backup/&amp;lt;/code&amp;gt;) adalah snapshot berlabel waktu — sebuah cadangan dari keadaan masa lalu.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Encyclopédie&amp;lt;/strong&amp;gt;, itu hal lain. Ini adalah kumpulan pengetahuan bersifat tematik, terstruktur berdasarkan subjek, lengkap namun tidak linier. Kita tidak membaca sebuah ensiklopedi dari awal sampai akhir. Kita menyelam ke dalamnya untuk menjawab pertanyaan spesifik.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Ini adalah tepat kontrak dari berkas ini:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;table class=&amp;quot;tableblock frame-all grid-all stretch&amp;quot;&amp;gt;
&amp;lt;colgroup&amp;gt;
&amp;lt;col style=&amp;quot;width: 6.25%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 6.25%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 6.25%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 6.25%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 6.25%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 6.25%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 6.25%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 6.25%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 6.25%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 6.25%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 6.25%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 6.25%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 6.25%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 6.25%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 6.25%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 6.25%;&amp;quot;&amp;gt;
&amp;lt;/colgroup&amp;gt;
&amp;lt;tbody&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;berkas&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;peran&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;akses&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;granularitas&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;archives/&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Data historis (tugas yang selesai per bulan)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;LAZY, terstruktur&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;kronologis&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;backup/&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Cuplikan dingin (gelombang 10 sesi)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;COLD, salinan lengkap&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Kronologis&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;encyclopedies/&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Pengetahuan topikal komprehensif (metodologi, pola)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;COLD, tidak pernah otomatis&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Tematik&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Perbedaan ini menghindari kerumitan. Setiap file tahu di mana ia harus berada sesuai dengan &amp;lt;strong&amp;gt;apa yang berisi&amp;lt;/strong&amp;gt;, bukan sesuai dengan &amp;lt;strong&amp;gt;kapan ia dibuat&amp;lt;/strong&amp;gt;.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_lanjut_logis_dari_seri&amp;quot;&amp;gt;Lanjut logis dari seri&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Jika Anda telah membaca dua artikel pertama, ini cara tiga bagian bersatu:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div id=&amp;quot;diag-trilogie&amp;quot; class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultTextAlignment center
skinparam wrapWidth 220

title Trilogi Tata Kelola Agen
left to right direction

package &amp;quot;📖 **Artikel 0108**\nStrategi Eager/Lazy&amp;quot; as A1 #E8F5E9 {
  card &amp;quot;Memori agen
antara sesi&amp;quot; as M1
  card &amp;quot;Files EAGER
(dimuat otomatis)&amp;quot; as M2
  card &amp;quot;File LAZY\n(permintaan)&amp;quot; as M3
  card &amp;quot;Prosedur 6 langkah&amp;quot; as M4
}

package &amp;quot;🧊 **Article 0110**
Mekanisme Hot/Warm/Cold&amp;quot; as A2 #FFF9C4 {
  card &amp;quot;Penuaan\nkonteks&amp;quot; as V1
  card &amp;quot;Jendela geser
10 sesi&amp;quot; as V2
  card &amp;quot;Gelombang dingin\nsama&amp;quot; as V3
  card &amp;quot;Sensor dengan
dua pemicu&amp;quot; as V4
}

package &amp;quot;🔍 **Artikel 0111**\nAudit + Solusi Ensiklopedia&amp;quot; as A3 #BBDEFB {
  card &amp;quot;Audit file
EAGER&amp;quot; as E1
  card &amp;quot;Identifikasi
pemborosan&amp;quot; as E2
  card &amp;quot;Pecah
ESSENTIALS/ensiklopedia&amp;quot; as E3
  card &amp;quot;Pelestarian
rekayasa&amp;quot; as E4
}

A1 --&amp;amp;gt; A2 : &amp;quot;Memori tumbuh\n→ diperlukan sebuah mekanisme\npenuaian&amp;quot;
A2 --&amp;amp;gt; A3 : &amp;quot;Penuaan tidak cukup
→ kita perlu melakukan audit pada yang
layak untuk berada dalam EAGER&amp;quot;

note bottom of A3
  ✅ Les trois couches sont en place
  sur 6 projets actifs
end note

@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;0108&amp;lt;/strong&amp;gt;menjawab: « Bagaimana memberikan memori pada agen antar sesi?&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;0110&amp;lt;/strong&amp;gt;menjawab: « Bagaimana mencegah memori ini menyengat agen?&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;0111&amp;lt;/strong&amp;gt;menjawab: « Dan jika masalahnya bukan ukuran arsip, tetapi apa yang kita &amp;lt;strong&amp;gt;pilih&amp;lt;/strong&amp;gt; untuk dimuat secara EAGER ?&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Artikel pertama telah membangun struktur. Artikel kedua telah menambahkan mekanisme penuaan. Artikel ketiga melakukan audit konten — dan menemukan bahwa metodologi itu sendiri telah menjadi masalah.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_apa_yang_akan_saya_lakukan_berbeda&amp;quot;&amp;gt;Apa yang akan saya lakukan berbeda&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Dengan memandang kembali, saya melihat kesalahan desain awal.`AGENT_MODUS_OPERANDI.adoc`telah dibuat sebagai dokumen tunggal — sebuah manifesto. Itu adalah pendekatan yang tepat untuk formalisasi pemikiran. Namun setelah pemikiran formalisasi, dokumen seharusnya langsung dipisahkan: intinya dalam EAGER, yang komprehensif dalam cold.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Saya tidak melakukannya karena saya bangga dengan dokumen tersebut. 627 baris rekayasa murni, ditulis tangan, setiap bagian merupakan hasil pelajaran sesi. Itu adalah karyaku. Dan seperti setiap penulis, saya kesulitan memotongnya.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Pelajaran:&amp;lt;strong&amp;gt;Bukan karena sebuah dokumen bagus berarti harus dimuat otomatis.&amp;lt;/strong&amp;gt;. Kualitas konten tidak memiliki hubungannya dengan relevansinya untuk konteks langsung agen.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Hari ini, aturannya sederhana : semua dokumen yang lebih dari 100 baris dalam konteks EAGER adalah seorang tersangka. Dia layak mendapatkan audit. Bukan hukuman — audit. Dan pertanyaan tidak pernah « haruskah ia dihapus? » tetapi « haruskah ia dimuat dalam setiap sesi ?&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_panduan_untuk_mengaudit_konteks_anda_sendiri&amp;quot;&amp;gt;Panduan untuk mengaudit konteks Anda sendiri&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Jika Anda telah mengikuti dua artikel pertama dan menata tata kelola Eager/Lazy Anda sendiri, berikut adalah prosedur audit dalam lima langkah :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;olist arabic&amp;quot;&amp;gt;
&amp;lt;ol class=&amp;quot;arabic&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Mengukur&amp;lt;/strong&amp;gt;:`wc -l`pada semua file EAGER Anda. Totalnya harus di bawah 1000 baris.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Mengidentifikasi yang terbesar&amp;lt;/strong&amp;gt;: file yang lebih dari 20% dari total adalah suspect nomor satu.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Audit bagian per bagian&amp;lt;/strong&amp;gt;: untuk setiap bagian, tanyakan pada diri sendiri « Apakah informasi ini sudah ada di tempat lain? Apakah agen telah membacanya dalam file lain? Apakah telah digunakan dalam 5 sesi terakhir ?&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;mengklasifikasikan&amp;lt;/strong&amp;gt;: ganda, tidak pernah digunakan, pengetahuan unik.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Memisahkan atau memindahkan&amp;lt;/strong&amp;gt;: yang unik dan kritikal → ESSENTIALS dikompak. Sisa semuanya →&amp;lt;code&amp;gt;encyclopedies/&amp;lt;/code&amp;gt;.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div id=&amp;quot;diag-audit-checklist&amp;quot; class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultTextAlignment center

title Prosedur Audit Konteks Agent — 5 Langkah
start

:1. **Mesurer**\n`wc -l *.adoc .agents/*.adoc`;

if (Total &amp;amp;gt; 1000 lignes ?) then (oui)
  :⚠️ Contexte trop lourd;
else (non)
  :✅ Contexte OK;
  stop
endif

:2. **Identifier le suspect**\nLe fichier &amp;amp;gt; 20% du total;

:3. **Auditer section par section**\nPour chaque section :\n• Déjà ailleurs ? → doublon\n• Jamais servi ? → mort\n• Information unique ? → critique;

:4. **Classifier**\n🟡 Doublons → à retirer de l&amp;#39;EAGER\n🔴 Jamais utilisé → cold storage\n🟢 Connaissance unique → à garder;

:5. **Splitter**\n🟢 → ESSENTIALS (~50 lignes, EAGER)\n🟡🔴 → encyclopedies/ (cold storage);

:✅ Contexte optimisé\nsans perte d&amp;#39;ingénierie;

stop

@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Prosedur ini membutuhkan 15 menit. Pada sebuah proyek dengan 50 sesi, ia menghemat ratusan token per sesi masa depan. ROI-nya langsung.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_tata_kelola_hidup&amp;quot;&amp;gt;Tata kelola hidup&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Apa yang diajarkan oleh rangkaian tiga artikel ini adalah bahwa tata kelola agen bukanlah produk yang selesai. Ini adalah&amp;lt;strong&amp;gt;organisme hidup&amp;lt;/strong&amp;gt;. Ia tumbuh bersama proyek. Ia mengidap penyakit pertumbuhan. Ia memerlukan pemeriksaan rutin.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Sesi 048 menunjukkan bahwa arsip membengkak. Sesi 051 menunjukkan bahwa metodologi itu sendiri membengkak. Sesi 060 mungkin akan mengungkapkan hal lain. Ini normal. Ini sehat. Pemerintahan yang tidak pernah menanyakan diri sendiri adalah pemerintahan yang mati.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Tiga mekanisme yang diterapkan — Eager/Lazy, Hot/Warm/Cold, split encyclopédique — membentuk sistem pertahanan dalam lapisan terhadap kejenuhan konteks. Tidak satu pun yang cukup sendirian. Bersama-sama, mereka melengkapi satu sama lain:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Eager/Lazy&amp;lt;/strong&amp;gt;strukturkan informasi &amp;lt;strong&amp;gt;ketersediaan&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Panas/Hangat/Dingin&amp;lt;/strong&amp;gt;strukturkan informasi &amp;lt;strong&amp;gt;berdasarkan kesegaran&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Audit ensiklopedi&amp;lt;/strong&amp;gt;struktur informasi &amp;lt;strong&amp;gt;berdasarkan kepadatan&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div id=&amp;quot;diag-defense-in-depth&amp;quot; class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE
skinparam nodeBackgroundColor #E3F2FD

title Pertahanan Bertingkat terhadap Kejenuhan Konteks
node &amp;quot;**Konteks Agent**
~800 baris
sehat&amp;quot; as CTX #C8E6C9

node &amp;quot;Lapisan 1
**GIAT / MALAS**&amp;quot; as C1 #BBDEFB
node &amp;quot;Lapisan 2\n**HOT / WARM / COLD**&amp;quot; as C2 #BBDEFB
node &amp;quot;Lapisan 3\n**DASAR-DASAR / ENSIKLOPEDIA**&amp;quot; as C3 #BBDEFB

CTX --&amp;amp;gt; C1 : &amp;quot;Struktur berdasarkan
**ketersediaan**&amp;quot;
CTX --&amp;amp;gt; C2 : &amp;quot;Struktur dengan
**keSegaran**&amp;quot;
CTX --&amp;amp;gt; C3 : &amp;quot;Struktur berdasarkan\n**densitas**&amp;quot;

note bottom of C3
  Les trois couches sont nécessaires.
  Aucune n&amp;#39;est suffisante seule.
end note

@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Dengan tiga lapisan ini, konteks agen pada`magic-stick`berubah dari 2087 baris menjadi sekitar 800 baris — pembagian sebanyak 2,6. Tanpa kehilangan satu baris dokumentasi, satu arsip sesi, satu pelajaran yang dipelajari.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Semua ada di sana. Lebih rapi saja.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_tautan&amp;quot;&amp;gt;Tautan&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Article 0108 — Strategi Eager/Lazy :&amp;lt;a href=&amp;quot;/blog/2026/0108_gouvernance_agent_opencode_eager_lazy_post.html&amp;quot;&amp;gt;Mengelola Agen AI dengan AsciiDoc&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Pasal 0110 — Mekanisme Panas/Hangat/Dingin :&amp;lt;a href=&amp;quot;/blog/2026/0110_mecanisme_backup_contexte_agent_post.html&amp;quot;&amp;gt;Jendela Geser dan Gelombang Dingin&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Situs saya : &amp;lt;a href=&amp;quot;https://cheroliv.com&amp;quot; class=&amp;quot;bare&amp;quot;&amp;gt;https://cheroliv.com&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Proyek`magic-stick`: &amp;lt;a href=&amp;quot;https://github.com/cheroliv/magic-stick&amp;quot; class=&amp;quot;bare&amp;quot;&amp;gt;https://github.com/cheroliv/magic-stick&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;hr&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Ilmu tidak perlu menghilang. Ia hanya perlu tidak dimuat ketika tidak diperlukan.&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>Sliding Window dan Gelombang Dingin: Saat Konteks Agent Meledak dan Perlu Diarsipkan Tanpa Kehilangan</title>
            <link >https://cheroliv.com/blog/2026/0110_mecanisme_backup_contexte_agent_post.html</link>
            <pubDate>Sun, 26 Apr 2026 00:00:00 +0000</pubDate>
            <guid isPermaLink="false">blog/2026/0110_mecanisme_backup_contexte_agent_post.html</guid>
            <description>&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_ringkasan&amp;quot;&amp;gt;Ringkasan&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Setelah 48 sesi pada satu proyek saja dan 150+ secara total, sistem tata kelola Eager/Lazy yang saya bangun dengan hati-hati mulai terasa terbeban. File agen, yang seharusnya ringan, berukuran 5200 baris secara kumulatif. Konteks yang dimuat otomatis meningkat lebih cepat daripada kemampuan saya mengendalikannya. Artikel ini menceritakan bagaimana saya merancang mekanisme cadangan — antara sliding window dan gelombang dingin yang sama — untuk menjaga konteks aktif tetap ringan tanpa pernah kehilangan apa pun.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_sinyal_5200_baris&amp;quot;&amp;gt;Sinyal: 5200 Baris&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Saya sedang dalam sesi 048 tentang`magic-stick`, proyek saya untuk membangun ISO Linux live. Opencode menatap saya. Seperti biasa, dia secara otomatis telah memuat file-file Eager saya di awal sesi —&amp;lt;code&amp;gt;AGENT.adoc&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;PROMPT_REPRISE.adoc&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;.agents/INDEX.adoc&amp;lt;/code&amp;gt;. Tidak ada yang anormal.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Kecuali ada sesuatu yang tidak beres. Jawaban lebih lambat. Penalaran lebih encer. Agen melupakan detail yang sebelumnya masih terlihat dua pesan yang lalu.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Saya membuka sebuah terminal dan mengetik :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;wc -l .agents/INDEX.adoc .agents/SESSIONS_HISTORY.adoc \
  .agents/archives/COMPLETED_TASKS_ARCHIVE_2026-04.adoc&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;260 baris untuk INDEX. 55 untuk SESSIONS_HISTORY.&amp;lt;strong&amp;gt;1756 untuk COMPLETED_TASKS_ARCHIVE&amp;lt;/strong&amp;gt;.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Berkas`sessions/`menambahkan lagi sekitar 3200 baris. Total :&amp;lt;strong&amp;gt;5200 baris&amp;lt;/strong&amp;gt;konteks yang dimuat, dengan satu cara atau lainnya, dalam otak sementara agen.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Strategi Eager/Lazy yang saya teoretisasikan dalam artikel sebelumnya berfungsi — tetapi ia memiliki cacat lahir yang saya tidak duga: ia tidak memiliki&amp;lt;strong&amp;gt;tidak ada mekanisme penuaan&amp;lt;/strong&amp;gt;. Setiap sesion menambahkan satu baris ke INDEX, satu paragraphe ke COMPLETED_TASKS, satu file di sessions/. Tidak ada yang keluar. Konteks adalah sebuah bola salju yang membesar setiap sesi baru.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div id=&amp;quot;diag-growth&amp;quot; class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE
skinparam handwritten false

title Pertumbuhan Konteks Agent — Sesi 1 hingga 48
rectangle &amp;quot;Sesi 1-10&amp;quot; as S10 #E8F5E9
rectangle &amp;quot;Sesi 11-20&amp;quot; as S20 #C8E6C9
rectangle &amp;quot;Sesi 21-30&amp;quot; as S30 #A5D6A7
rectangle &amp;quot;Sesi 31-40&amp;quot; as S40 #81C784
rectangle &amp;quot;Sesi 41-48&amp;quot; as S48 #66BB6A

note right of S10
  INDEX : ~50 lignes
  SESSIONS : ~10 lignes
  ARCHIVE : ~100 lignes
  **Total : ~300 lignes**
end note

note right of S20
  INDEX : ~100 lignes
  SESSIONS : ~20 lignes
  ARCHIVE : ~400 lignes
  **Total : ~800 lignes**
end note

note right of S30
  INDEX : ~160 lignes
  SESSIONS : ~30 lignes
  ARCHIVE : ~900 lignes
  **Total : ~1800 lignes**
end note

note right of S40
  INDEX : ~220 lignes
  SESSIONS : ~45 lignes
  ARCHIVE : ~1400 lignes
  **Total : ~3200 lignes**
end note

note right of S48
  INDEX : **260 lignes**
  SESSIONS : **55 lignes**
  ARCHIVE : **1756 lignes**
  **Total : 5200 lignes** ⚠️
end note

S10 -[#green]-&amp;amp;gt; S20
S20 -[#green]-&amp;amp;gt; S30
S30 -[#FF9800]-&amp;amp;gt; S40
S40 -[#red]-&amp;amp;gt; S48

@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Ini bukan bug, ini adalah konsekuensi langsung dari prosedur akhir sesi yang dengan teliti mengarsipkan setiap detail. Sistem tersebut menjadi korban keberhasilannya sendiri.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_diagnosa_redundansi_tiga&amp;quot;&amp;gt;Diagnosa: Redundansi tiga&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Saya meminta agen untuk mendiagnosa masalah. Jawabannya langsung dan tepat.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;table class=&amp;quot;tableblock frame-all grid-all stretch&amp;quot;&amp;gt;
&amp;lt;colgroup&amp;gt;
&amp;lt;col style=&amp;quot;width: 5%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 5%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 5%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 5%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 5%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 5%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 5%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 5%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 5%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 5%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 5%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 5%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 5%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 5%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 5%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 5%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 5%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 5%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 5%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 5%;&amp;quot;&amp;gt;
&amp;lt;/colgroup&amp;gt;
&amp;lt;tbody&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;File&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Baris&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Peran&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Masalah&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;.agents/INDEX.adoc&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;260+&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;EAGER (dimuat otomatis)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Tabel semua sesi sejak 001 —&amp;lt;strong&amp;gt;+1 baris per sesi&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;.agents/SESSIONS_HISTORY.adoc&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;55&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;EAGER&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Tabel ringkasan —&amp;lt;strong&amp;gt;redundansi dengan INDEX&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;COMPLETED_TASKS_ARCHIVE_2026-04.adoc&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;1756&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;EAGER (implisit)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;detail lengkap semua sesi April&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;.agents/sessions/*.adoc&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;~5200 total&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;LAZY (dianggap)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Arsip individu, tetapi&amp;lt;strong&amp;gt;tidak digunakan&amp;lt;/strong&amp;gt;karena semua sudah ada di COMPLETED_TASKS&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Agen mengidentifikasi empat akar penyebab :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;olist arabic&amp;quot;&amp;gt;
&amp;lt;ol class=&amp;quot;arabic&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;COMPLETED_TASKS_ARCHIVE menyerap semua&amp;lt;/strong&amp;gt;— alih-alih menunjuk ke arsip`.sessions/`, Dia menyalin seluruhnya setiap sesi.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;INDEX.adoc berfungsi sebagai sejarah lengkap&amp;lt;/strong&amp;gt;— Tabel &amp;quot;Sessions Récentes&amp;quot; berisi 30+ entri.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;SESSIONS_HISTORY.adoc redundan&amp;lt;/strong&amp;gt;— Info sama seperti INDEX, format berbeda.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Aturan LAZY tidak dipatuhi&amp;lt;/strong&amp;gt;— COMPLETED_TASKS secara implisit EAGER karena ia berisi semuanya.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Usulannya radikal: membatasi INDEX menjadi 10 sesi, mengosongkan COMPLETED_TASKS, memindahkan SESSIONS_HISTORY menjadi LAZY murni. Manfaat segera :&amp;lt;strong&amp;gt;~1900 baris yang dihemat&amp;lt;/strong&amp;gt;.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Ini bersih, efisien, dan logis. Tapi saya memiliki masalah dengan pendekatan ini.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_mengapa_saya_menolak_solusi_yang_jelas&amp;quot;&amp;gt;Mengapa saya menolak solusi yang jelas&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Solusi agen tersebut adalah solusi seorang insinyur yang mengoptimalkan cache. Membatasi. Memotong. Menghapus redundansi.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Tapi &amp;quot;kelebihan&amp;quot; ini bukanlah. Setiap file tata kelola menangkap satu&amp;lt;strong&amp;gt;sudut berbeda&amp;lt;/strong&amp;gt;pada kenyataan yang sama:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;INDEX&amp;lt;/strong&amp;gt;= tampilan makro, dashboard eksekutif&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Riwayat sesi&amp;lt;/strong&amp;gt;= tabel kronologis linear, diberi skor&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;COMPLETED_TASKS_ARCHIVE&amp;lt;/strong&amp;gt;= narasi rinci dengan metrik&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;sessions/*.adoc` = arsip individu, konteks lengkap&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Bukan duplikasi bodoh. Ini adalah dari&amp;lt;strong&amp;gt;perspektif berganda terstruktur&amp;lt;/strong&amp;gt;. Ini persis yang diperlukan untuk mendistilasi — agar nanti seorang manusia (atau LLM masa depan yang lebih terlatih) bisa menyilangkan sudut dan mengekstrak pola.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Bayangkan seorang data scientist yang berkata kepadamu: « Mari kita hapus 3 dari 6 kolom, mereka terkorelasi. » Apa yang kamu jawab? bahwa korelasi bukanlah redundansi ketika setiap kolom menangkap dimensi yang berbeda dari fenomena yang sama. bahwa itu tepatnya kekayaan dimensi yang membuat dataset dapat digunakan.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Ini intuisi saya. Dan saya membela intuisi saya terhadap rasionalitas dingin agen.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;em&amp;gt;&amp;lt;em&amp;gt;_ Saya tidak melihat tempat menyimpan semua yang diam dalam ide saya. Kita tidak hanya menumpuk semuanya, kita memindahkan hasil terstruktur dari prosedur akhir sesi — setiap file dengan sudutnya, redundansi yang sebenarnya adalah peningkatan. Inilah bahan multidimensi yang akan lebih baik untuk distilasi. &amp;lt;/em&amp;gt;_&amp;lt;/em&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Agent menyalahkan pukulan. Dan memperbaiki diri.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_proposisi_ombak_dingin_identik&amp;quot;&amp;gt;Proposisi: Ombak Dingin Identik&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Agent lalu menawarkan sebuah mekanisme yang lebih halus, yang menghargai intuisi saya tentang dataset kaya sekaligus menyelesaikan masalah teknis konteks yang meledak.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Prinsipnya sederhana dan langsung terinspirasi dari pola&amp;lt;strong&amp;gt;Penyimpanan Panas/Hangat/Dingin&amp;lt;/strong&amp;gt;diterapkan pada pengelolaan arsip :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Panas (GIAT)&amp;lt;/strong&amp;gt;= 10 sesi terakhir dalam INDEX, PROMPT_REPRISE dari sesi N+1, 2 file sesi terakhir&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Hangat (LAZY)&amp;lt;/strong&amp;gt;= SESSIONS_HISTORY terbaru, SCRIPT_VERIFICATION, seluruh dokumentasi referensi&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Dingin (backup/)&amp;lt;/strong&amp;gt;= semuanya yang lain, tergeser&amp;lt;strong&amp;gt;utuh&amp;lt;/strong&amp;gt;, tanpa transformasi, tanpa reindeksasi&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-none hljs&amp;quot;&amp;gt;----
----
.agents/
├── INDEX.adoc                         → Sessions N-9 à N (10 dernières)
├── SESSIONS_HISTORY.adoc              → Sessions N-9 à N
├── SCRIPT_VERIFICATION.adoc           → Dernière vérif (pas d&amp;#39;historique)
├── PROMPT_REPRISE.adoc                → Session N+1 uniquement
├── sessions/                          → Sessions N-1 à N uniquement
└── backup/
    └── Y2026-sessions-001-039/          ← Vague froide, COPIE INTÉGRALE
        ├── INDEX.adoc                  → Sessions 001 à 039 (complet)
        ├── SESSIONS_HISTORY.adoc       → Sessions 001 à 039 (complet)
        ├── COMPLETED_TASKS_ARCHIVE.adoc → Sessions 001 à 039 (complet)
        └── sessions/                   → 001.adoc, 002.adoc...
----

Kunci dari mekanisme :**cadangan bukan merupakan indeks pusat, ini adalah salinan tepat dari gelombang yang lalu**. Melebihi batas 10 sesi aktif, tidak ada yang dihapus. Tidak ada yang di-reindex. Tidak ada yang digabung. Kami mengambil paket file agen seperti sesi N-10, dan memindahkannya ke`backup/`.

File-file aktif, mereka, terpotong:

* INDEX : hanya 10 baris terakhir (sliding window)
* SESSIONS_HISTORY : sama
* COMPLETED_TASKS_ARCHIVE : file baru untuk periode saat ini
* sessions/: hanya 2 sesi terakhir

[plantuml, format=svg, id=diag-hot-warm-cold, alt=&amp;quot;Architecture Hot/Warm/Cold du contexte agent — EAGER/LAZY/backup&amp;quot;]
----
@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultTextAlignment center
skinparam wrapWidth 200

title Arsitektur Hot / Warm / Cold dari Konteks Agent
package &amp;quot;HOT (EAGER)
Dimuat otomatis
~300 baris&amp;quot; as HOT #FFCDD2 {
  file &amp;quot;INDEX.adoc
(10 sesi)&amp;quot; as IDX_HOT
  file &amp;quot;PROMPT_REPRISE
(sesi N+1)&amp;quot; as PRO_HOT
  file &amp;quot;AGENT.adoc\n(aturan mutlak)&amp;quot; as AG_HOT
}

package &amp;quot;WARM (LAZY)
Dimuat saat diminta
~500 baris&amp;quot; as WARM #FFF9C4 {
  file &amp;quot;SESSIONS_HISTORY
(10 terakhir)&amp;quot; as HIS_WARM
  file &amp;quot;SCRIPT_VERIFICATION\n(terakhir)&amp;quot; as VER_WARM
  file &amp;quot;PROCEDURES.adoc
(templates)&amp;quot; as PRO_WARM
  file &amp;quot;*_REFERENCE.adoc\n(dokumen teknis)&amp;quot; as REF_WARM
}

package &amp;quot;COLD (backup/)
Tidak pernah diisi
Hanya bacaan manusia&amp;quot; as COLD #BBDEFB {
  folder &amp;quot;Y2026-001-039/&amp;quot; as WAVE {
    file &amp;quot;INDEX (lengkap)&amp;quot; as IDX_COLD
    file &amp;quot;SESSIONS_HISTORY
(lengkap)&amp;quot; as HIS_COLD
    file &amp;quot;COMPLETED_TASKS\n(selesai)&amp;quot; as ARCH_COLD
    folder &amp;quot;sesi/ (001-039)&amp;quot; as SESS_COLD
  }
  folder &amp;quot;Y2026-040-???
(future)&amp;quot; as FUTURE
}

HOT --&amp;amp;gt; WARM : &amp;quot;Agent naik
jika diperlukan&amp;quot;
WARM --&amp;amp;gt; COLD : &amp;quot;Tidak pernah otomatis
Manusia pergi ke sana sendiri&amp;quot;

note bottom of COLD
  Règle : COPIE INTÉGRALE
  Pas de transformation
  Pas de réindexation
  Read-only après archivage
end note

@enduml
----

Keuntungan langsung sangat besar : konteks EAGER beralih dari**~5200 baris hingga ~300 baris**. Pembagian dengan 17. Tanpa kehilangan satu baris pun dari data historis.

== Mengapa ini bukan tempat untuk segalanya

Agen, dalam iterasi pertamanya, khawatir bahwa`backup/`menjadi sebuah kotak hitam — sebuah folder tempat menaruh file yang tidak akan pernah dibaca lagi. Ini adalah rasa takut yang sah. Namun itu berbasis pada sebuah kebingungan.

Sebuah tempat menyimpan segala-galanya, itu ketika seseorang melempar berkas.**tanpa struktur, tanpa konvensi, tanpa logika pengelompokan**. Di sini, backup terstruktur berdasarkan periode (Y2026-001-039), dan setiap folder backup berisi**struktur yang sama**bahwa dokumen`.agents/`aktif : INDEX, SESSIONS_HISTORY, COMPLETED_TASKS_ARCHIVE, sessions/.

Bukan ini sebuah tempat untuk menyimpan segala-galanya. Ini adalah**snapshot dengan stempel waktu**. Bahkan bisa dikatakan bahwa ini adalah mekanisme versioning yang disederhanakan — kecuali bahwa ia tidak melakukan versioning pada file secara individual, tetapi pada paket lengkap dari tata kelola pada suatu saat T.

Saat Anda ingin menemukan informasi lama, Anda tidak memerlukan indeks sentral. Anda memiliki dua pilihan:

1. **grep` ditargetkan**:`grep -r &amp;quot;zsh&amp;quot; backup/Y2026-001-039/`— dan Anda menemukan semua yang menyebutkan zsh dalam periode tersebut, tidak peduli sudutnya (INDEX, SESSIONS_HISTORY, arsip sesi).
2. **Reintegrasi manual**: Anda menyalin sementara folder backup ke konteks aktif, dan Anda meminta agen untuk menganalisis periode tertentu ini.

Indeksnya bersifat implisit. Dia berada dalam struktur file itu sendiri — masing-masing file sudah menjadi indeks dari sudut pandangnya masing-masing.

== Metafora dari rak-rak

Agar mekanisme tersebut intuitif, saya mengonsepsikan menjadi tiga rak:

* **Rak 1 (EAGER)**— rencana kerja. Yang saya butuh *sekarang*. INDEX terbaru, PROMPT_REPRISE, aturan mutlak. Ringan, segera, kritis.
* **Rak 2 (LAZY)**— perpustakaan konsultasi. Yang bisa saya ambil berdasarkan permintaan. Referensi teknis, riwayat terkini, prosedur. Lebih besar, tetapi tidak dimuat ke memori.
* **Gua (cadangan/)**— arsip dingin. Semua yang sudah lalu tetapi saya tidak ingin membuangnya. Agen tidak pernah masuk ke sana. Manusia turun ke sana ketika ia ingin mendistil.

Agent menawarkan awalnya sebuah rak keempat — satu`index-backup.adoc`yang akan LAZY dan berisi daftar isi seluruh backup. Saya menolak. Ini akan menjadi redundansi tambahan dalam sistem yang sudah menderita dari pertumbuhan linear. Struktur file-file yang terarsip sudah menjadi sebuah indeks.

== Sensor dengan dua pemicu

Konseptualisasinya kuat, tapi masih ada titik buta:**kapan tepat memicu rotasi?**Artikel awal mengidentifikasinya sebagai pertanyaan terbuka. Dua hari kemudian, jawaban tersebut dikodifikasi dalam file-file governansi dari enam proyek: sebuah sensor dengan dua trigger.

=== Pemicu Otomatis — `N % 10 == 0

Pemicu pertama bersifat matematika. Ketika nomor sesi adalah kelipatan 10 — sesi 10, 20, 30, 40 — rotasi backup dieksekusi secara otomatis dalam prosedur akhir sesi, tepat setelah langkah 6.

Mengapa 10? Ini adalah kompromi antara dua gaya yang berlawanan: jendela yang terlalu pendek (5 sesi) kehilangan konteks yang diperlukan untuk keberlanjutan; jendela yang terlalu panjang (20 sesi) tidak menyelesaikan masalah konteks berlebihan. Sepuluh sesi, dengan ritmo satu hingga dua sesi per hari, mencakup sekitar satu minggu kerja — cukup agar agen mengingat keputusan terbaru, namun tidak cukup untuk meledaknya konteks.

=== Pemicu Ambang — 500 Baris EAGER

Pemicu kedua bersifat dinamis. Terlepas dari nomor sesi, jika file-file EAGER yang terakumulasi melebihi**500 baris**, rotasi terpicu.

Ambang batas ini melindungi dari skenario di mana sesi-sesi sangat produktif — banyak konten yang ditulis dalam sedikit sesi. Sesi yang menghasilkan 120 baris konten redaksi memperbesar `COMPLETED_TASKS_ARCHIVE` jauh lebih cepat dibandingkan sesi debug yang memperbaiki dua baris. Ambang batas 500 baris, yang diukur melalui`wc -l .agents/INDEX.adoc .agents/archives/COMPLETED_TASKS_ARCHIVE_*.adoc PROMPT_REPRISE.adoc`, menangkup ketidaksimetrian ini.

=== Pemicu Manual — &amp;quot;cadangan rotasi

Akhirnya, manusia tetap menguasai. Kata kunci`rotation backup`, `backup rotation` ou `lance la rotation backup`memicu prosedur atas permintaan, terlepas dari akhir sesi. Bermanfaat ketika kita merasa konteks menjadi berat tetapi kita belum sampai kelipatan 10, atau ketika kita ingin mengarsipkan fase kerja sebelum memulai yang baru.

[plantuml, format=svg, id=diag-capteur-trigger, alt=&amp;quot;Les trois déclencheurs du capteur de rotation backup — automatique, seuil, manuel&amp;quot;]
----
@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultTextAlignment center
skinparam wrapWidth 200

title Sensor Cadangan Rotasi — Tiga Pemicu
start

:Procédure de fin de session;
note right: Mots-clés &amp;quot;akhir sesi&amp;quot;\nou &amp;quot;Kita berhenti di sini&amp;quot;

:Étapes 1 à 6\n(archivage standard);
note right
  1. Archive sessions/N.adoc
  2. MAJ PROMPT_REPRISE
  3. MAJ SESSIONS_HISTORY
  4. MAJ INDEX.adoc
  5. MAJ TEST_COVERAGE
  6. MAJ COMPLETED_TASKS
end note

if (N % 10 == 0\nOU\nEAGER cumulé &amp;amp;gt; 500 lignes ?) then (oui)
  :⚙️ Rotation Backup\n(étape 7);
  note right
    1. Créer backup/Y20XX-sessions-X-Y/
    2. Copier intégrale INDEX + HISTORY
       + COMPLETED_TASKS + sessions/
    3. Tronquer actifs à 10 sessions
    4. Ajouter _Localisation active_
  end note
else (non)
  :Pas de rotation;
endif

:Checklist [✅] x 7\n(si applicable);

stop

@enduml
----

Diagram ini menunjukkan pemasangan sensor yang tepat dalam prosedur akhir sesi. Langkah 7 bersifat opsional — ia hanya dieksekusi jika salah satu dari dua kondisi terpenuhi — tetapi ia secara sistematis *terverifikasi*. Checklist akhir mencakup`[✅] 7. Backup roté (si applicable)`.

=== lingkaran tertutup

Sensor ini menutup loop yang dibuka oleh artikel sebelumnya. Governance Eager/Lazy telah menyelesaikan masalah memori agen antara dua sesi. Mekanisme Hot/Warm/Cold telah menyelesaikan masalah memori yang bertambah. Sensor dengan dua pemicu menyelesaikan masalah *kapan* — menghilangkan beban mental manusia untuk memantau ukuran konteks.

[plantuml, format=svg, id=diag-boucle-fermee, alt=&amp;quot;Les trois couches de la gouvernance agent — Eager/Lazy, Hot/Warm/Cold, Capteur&amp;quot;]
----
@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultTextAlignment center

title Tiga Lapisan Tata Kelola Agen
left to right direction

package &amp;quot;Lapisan 1 — Memori\n(Pasal 0108)&amp;quot; as C1 #E8F5E9 {
  rectangle &amp;quot;**EAGER**
Dashboard
Dimuat otomatis&amp;quot; as EAG
  rectangle &amp;quot;**LAZY**
Manual pemilik
Dimuat saat diperlukan&amp;quot; as LAZ
  EAG -[hidden]right-&amp;amp;gt; LAZ
}

package &amp;quot;Lapisan 2 — Penuaan
(Artikel 0110)&amp;quot; as C2 #FFF9C4 {
  rectangle &amp;quot;**HOT**
10 sesi aktif
~300 baris&amp;quot; as HOT
  rectangle &amp;quot;**WARM**
Referensi
Prosedur&amp;quot; as WRM
  rectangle &amp;quot;**COLD**
backup/
gelombang dingin&amp;quot; as CLD
  HOT -[hidden]right-&amp;amp;gt; WRM
  WRM -[hidden]right-&amp;amp;gt; CLD
}

package &amp;quot;Lapisan 3 — Pemicu\n(Hari ini)&amp;quot; as C3 #BBDEFB {
  rectangle &amp;quot;**Otomatis**
N % 10 == 0&amp;quot; as AUTO
  rectangle &amp;quot;**Ambang**
&amp;amp;gt; 500 baris&amp;quot; as SEUIL
  rectangle &amp;quot;**Manual**
cadangan rotasi&amp;quot; as MAN
  AUTO -[hidden]right-&amp;amp;gt; SEUIL
  SEUIL -[hidden]right-&amp;amp;gt; MAN
}

C1 --&amp;amp;gt; C2 : &amp;quot;Memori tumbuh
→ diperlukan mekanisme
penuaan&amp;quot;
C2 --&amp;amp;gt; C3 : &amp;quot;Penuaan
→ perlu sebuah pemicu
untuk mengeksekusinya&amp;quot;

note bottom of C3
  ✅ Déployé sur 6 projets
  magic-stick · bakery-gradle
  plantuml-gradle · cheroliv.com
  jhipster-gradle-plugins
  quizz-benchmark-gradle
end note

@enduml
----

Tiga lapisan tersebut menumpuk secara logis. Lapisan pertama memberikan memori kepada agen. Lapisan kedua mencegah memori tersebut menghabisi agen. Lapisan ketiga mengotomatisasi pemeliharaan memori tersebut agar manusia tidak perlu memikirkannya.

=== Migrasi Efektif di cheroliv.com

Mekanisme itu tidak tetap teoretis. Pada`cheroliv.com`, rotasi backup pertama telah dieksekusi pada 29 april 2026 — sesi -6 hingga 2 telah bermigrasi ke`.agents/backup/Y2026-sessions-neg6-a-002/`:

|===
|Berkas |Sebelum rotasi |Setelah rotasi |keuntungan |`.agents/INDEX.adoc` |19 sesi tercantum |10 sesi (3-12) |-9 entri |`.agents/SESSIONS_HISTORY.adoc` |18 sesi |10 sesi |-8 entri |`COMPLETED_TASKS_ARCHIVE` |161 baris (sesi 1-12) |135 baris (sesi 3-12) |-26 baris |`sessions/` |20 file |10 file |-10 file |**cadangan/** |tidak ada |1 gelombang dingin (10 sesi yang diarsipkan) |+1 paket dingin
|===

Peningkatan dalam jumlah baris modest — proyek ini masih muda, 12 sesi — namun yang penting adalah bahwa**mekanisme telah ditempatkan**. Rotasi otomatis berikutnya akan dipicu pada sesi 20, atau lebih awal jika 500 baris EAGER tercapai.

== Pelajaran Manusia-Agen

Sesi 048 ini mengajarkan saya sesuatu yang fundamental tentang kolaborasi dengan agen AI.

Agen memiliki bias alami: dia mencari untuk**mengoptimalkan**, à **menyederhanakan**, à **menghilangkan redundansi**. Ini adalah bias dari sistem yang dilatih untuk menghasilkan respons yang rapi dan ringkas. Di hadapan dataset yang kaya dan multidimensi, refleks pertamanya adalah menguranginya menjadi bentuk paling sederhana.

Manusia, dia, memiliki intuisi yang berbeda: dia merasakan bahwa redundansi terstruktur adalah sebuah**kelebihan**, bukan kesalahan. Bahwa keragaman sudut pandang atas satu realitas itu adalah apa yang secara tepat akan memungkinkan, kemudian, penyaringan kualitas.

Bukan karena agen salah. Yang dimaksud adalah bahwa &amp;quot;optimum&amp;quot; -nya bukan milik saya. Agen mengoptimalkan untuk**hadir**— konteks segera, jawaban cepat atas pertanyaan yang diajukan. Manusia mengoptimalkan untuk**masa depan**— kemampuan untuk menemukan, menyilangkan, mengdistil dalam tiga bulan atau tiga tahun.

____ Yang saya inginkan adalah bahan mentah. Pikiranmu, ragu-ragumu, jawabanmu atas pertanyaan-pertanyaan saya. Bukan sintesismu. Saya tahu cara membuat sintesis lebih baik daripadamu. Saya ingin bahan mentah. ____

Kalimat yang saya katakan padanya di akhir sesi ini merangkum semuanya. Agent adalah alat produksi. Manusia adalah alat distilasi. Tata kelola tidak dibuat agar agen memahami semuanya sendiri — ia dibuat agar manusia bisa, nanti, mengolah bahan yang diproduksi.

Gelombang dingin identik adalah terjemahan arsitektur dari filosofi ini: kita tidak membuang apa-apa, kita tidak menggabungkan apa-apa, kita tidak mengindeks ulang apa-apa. Kita memindahkan paket utuh. Distilasi akan datang nanti, secara manual, oleh manusia.

== Lanjutan Logis dari Artikel Sebelumnya

Jika Anda telah membacalink:/blog/2026/0108_gouvernance_agent_opencode_eager_lazy_post.html[artikel tentang strategi Eager/Lazy], Anda akan mengenal progresi alami :

1. **Pasal 0108**— Tata kelola Eager/Lazy : *comment* menyusun konteks agent pada dua tingkat ketersediaan
2. **Artikel 0110**— Mekanisme cadangan: *bagaimana* membuat konteks ini tua tanpa kehilangan ketika ia terlalu besar

Artikel pertama menjawab pertanyaan: « Agen tidak mengingat apa-apa di antara dua sesi, bagaimana memberi agen memori?

Dia menjawab pertanyaan yang timbul secara tak terelakkan dari yang pertama : « Memori bertambah setiap sesi, bagaimana mencegahnya melumuri agen tanpa menghapusnya ?

Jawabannya hanya berisi satu pola:**Panas/Hangat/Dingin**, diterapkan pada file tata kelola. Dan sebagai satu prinsip:**tidak tidak kehilangan apa-apa, selalu memindahkan kembali semuanya**.

== Tautan

* Artikel sebelumnya :link:/blog/2026/0108_gouvernance_agent_opencode_eager_lazy_post.html[Mengatur Agen AI dengan AsciiDoc]
* Situs saya : https://cheroliv.com
* Proyek`magic-stick`: https://github.com/cheroliv/magic-stick

---

*Sistem tata kelola yang baik tidak pernah menghapus data. Ia mengelola data.*
----&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>Ketika Gradle 9, Docker 29 dan Testcontainers menjadi gelisah : debugging sebuah build JHipster</title>
            <link >https://cheroliv.com/blog/2026/0109_gradle_jhipster_docker_testcontainers_debugging_post.html</link>
            <pubDate>Sat, 25 Apr 2026 00:00:00 +0000</pubDate>
            <guid isPermaLink="false">blog/2026/0109_gradle_jhipster_docker_testcontainers_debugging_post.html</guid>
            <description>&amp;lt;div id=&amp;quot;toc&amp;quot; class=&amp;quot;toc&amp;quot;&amp;gt;
&amp;lt;div id=&amp;quot;toctitle&amp;quot;&amp;gt;Table des matières&amp;lt;/div&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel1&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_situasi_sebuah_proyek_jhipster_sebuah_build_yang_tidak_lagi_memuat&amp;quot;&amp;gt;Situasi: sebuah proyek JHipster, sebuah build yang tidak lagi memuat&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_kesalahan_1_variabel_fantom_reportsdir&amp;quot;&amp;gt;Kesalahan 1 : variabel fantom `reportsDir&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_error_2_main_tidak_ada_lagi_di_gradle_9&amp;quot;&amp;gt;Error 2 : &amp;lt;code&amp;gt;main = \&amp;quot;&amp;amp;#8230;&amp;amp;#8203;\&amp;quot;&amp;lt;/code&amp;gt; tidak ada lagi di Gradle 9&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_kesalahan_3_sourcecompatibility_tidak_lagi_merupakan_properti_proyek&amp;quot;&amp;gt;Kesalahan 3 : &amp;lt;code&amp;gt;sourceCompatibility&amp;lt;/code&amp;gt; tidak lagi merupakan properti proyek&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_kesalahan_4_pernyataan_groovy_dengan_tiga_operand&amp;quot;&amp;gt;Kesalahan 4 : pernyataan Groovy dengan tiga operand&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_error_5_dependensi_implisit_compilekotlin_openapigenerate&amp;quot;&amp;gt;Error 5: dependensi implisit &amp;lt;code&amp;gt;compileKotlin&amp;lt;/code&amp;gt; → `openApiGenerate&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_error_6_generategitproperties_gagal_pada_filteroutputstream_write&amp;quot;&amp;gt;Error 6: &amp;lt;code&amp;gt;generateGitProperties&amp;lt;/code&amp;gt; gagal pada `FilterOutputStream.write()&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_akhirnya_testcontainers_masuk_ke_panggung&amp;quot;&amp;gt;Akhirnya: Testcontainers masuk ke panggung&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_tahap_1_jalur_docker_java&amp;quot;&amp;gt;Tahap 1 : jalur docker-java&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_tahap_2_penelusuran_web_dan_breadcrumb_github&amp;quot;&amp;gt;Tahap 2 : penelusuran web dan breadcrumb GitHub&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_tahap_3_jerat_artefak_yang_diubah_namanya&amp;quot;&amp;gt;Tahap 3: jerat artefak yang diubah namanya&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_tahap_4_memaksakan_resolusi&amp;quot;&amp;gt;Tahap 4 : memaksakan resolusi&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_hasil_akhir&amp;quot;&amp;gt;Hasil akhir&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_tabel_ringkasan_koreksi&amp;quot;&amp;gt;Tabel ringkasan koreksi&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_kesimpulan&amp;quot;&amp;gt;Kesimpulan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div id=&amp;quot;preamble&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;waktu membaca : 15 minutes&amp;lt;/div&amp;gt;
&amp;lt;p&amp;gt;Anda pernah mengalami momen ketika sesuatu yang sederhana`./gradlew build`yang Anda jalankan seratus kali sehari tiba-tiba mengalami crash pada kesalahan yang belum pernah terlihat? Kalikan dengan sebelas kesalahan berturut-turut, tambahkan dosis incompatibilitas Docker Engine 29, taburkan Gradle 9 yang menghilangkan API yang telah kita gunakan selama sepuluh tahun. Inilah catatan lengkapnya.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;hr&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;Diagram 1 — Migrasi Gradle 9 (rantai kesalahan)&amp;lt;/div&amp;gt;
&amp;lt;p&amp;gt;Tujuan: menampilkan rangkaian ketidaksesuaian&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;👉 Jelas, berfokus pada Gradle.&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
left to right direction
title Migrasi Gradle 9
rectangle &amp;quot;1
reportsDir&amp;quot; as A
rectangle &amp;quot;2\nmainClass&amp;quot; as B
rectangle &amp;quot;3
sourceCompatibility&amp;quot; as C
rectangle &amp;quot;4\nGroovy assert&amp;quot; as D

A --&amp;amp;gt; B
B --&amp;amp;gt; C
C --&amp;amp;gt; D

@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;hr&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;Diagram 2 — Plugin JHipster yang tidak kompatibel&amp;lt;/div&amp;gt;
&amp;lt;p&amp;gt;Tujuan : mengisolasi plugin yang rusak.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;👉 Visi jelas tentang dependensi.&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
left to right direction
title Plugins yang perlu diperbaiki
skinparam shadowing false
skinparam roundcorner 18

rectangle &amp;quot;1\nopenApiGenerate&amp;quot; as A #FFF3E0
rectangle &amp;quot;2
gitProperties
2.5.7&amp;quot; as B #FFF3E0
rectangle &amp;quot;3
Versi yang diperbaiki&amp;quot; as C #E3F2FD
rectangle &amp;quot;4
Mengasambel OK&amp;quot; as D #E8F5E9

A --&amp;amp;gt; B
B --&amp;amp;gt; C
C --&amp;amp;gt; D

@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;hr&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;Diagram 3 — Docker 29 / Testcontainers&amp;lt;/div&amp;gt;
&amp;lt;p&amp;gt;Tujuan : masalah runtime.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;👉 Riwayat jelas dari runtime&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
left to right direction
title Docker 29 + Testcontainers

skinparam shadowing false
skinparam roundcorner 18
skinparam defaultFontSize 14

rectangle &amp;quot;1\nSusun OK&amp;quot; as A #E8F5E9
rectangle &amp;quot;2
Ujian Cucumber GAGAL&amp;quot; as B #FFEBEE
rectangle &amp;quot;3
Testcontainers 1.32
ditolak&amp;quot; as C #FFF3E0
rectangle &amp;quot;4
Upgrade
versi&amp;quot; as D #E3F2FD
rectangle &amp;quot;5\nBuild akhir OK&amp;quot; as E #E8F5E9

A --&amp;amp;gt; B
B --&amp;amp;gt; C
C --&amp;amp;gt; D
D --&amp;amp;gt; E

@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;hr&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;cara lain untuk membayangkannya&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml

!option handwritten true

skinparam backgroundColor #FEFEFE
skinparam shadowing false
skinparam defaultFontName Inter
skinparam defaultFontSize 14
skinparam ArrowThickness 2
skinparam RoundCorner 18

title Peta jalan stabilisasi — Gradle 9 ke build final
left to right direction

rectangle &amp;quot;⚙️ Migrasi Gradle 9&amp;quot; as A #E3F2FD
rectangle &amp;quot;🧩 Plugin JHipster&amp;quot; as B #FFF3E0
rectangle &amp;quot;🐳 Docker 29
Testcontainers&amp;quot; as C #FCE4EC
rectangle &amp;quot;✅ Build Stable&amp;quot; as D #E8F5E9

note bottom of A
reportsDir
mainClass
sourceCompatibility
Groovy assert
end note

note bottom of B
openApiGenerate
gitProperties
versions alignées
end note

note bottom of C
tests Cucumber KO
image Docker rejetée
upgrade libs
end note

A -[#42A5F5]-&amp;amp;gt; B : corrections build.gradle
B -[#FB8C00]-&amp;amp;gt; C : assemble OK
C -[#E53935]-&amp;amp;gt; D : tests réparés

@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_situasi_sebuah_proyek_jhipster_sebuah_build_yang_tidak_lagi_memuat&amp;quot;&amp;gt;Situasi: sebuah proyek JHipster, sebuah build yang tidak lagi memuat&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Proyek`edster`adalah sebuah aplikasi JHipster yang dihasilkan pada tahun 2024. Skrip build akar`build.gradle`est tetap stabil selama beberapa bulan. Lalu kami memutuskan untuk meningkatkan versinya:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;table class=&amp;quot;tableblock frame-all grid-all stretch&amp;quot;&amp;gt;
&amp;lt;colgroup&amp;gt;
&amp;lt;col style=&amp;quot;width: 7.1428%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 7.1428%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 7.1428%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 7.1428%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 7.1428%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 7.1428%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 7.1428%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 7.1428%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 7.1428%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 7.1428%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 7.1428%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 7.1428%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 7.1428%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 7.1436%;&amp;quot;&amp;gt;
&amp;lt;/colgroup&amp;gt;
&amp;lt;tbody&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Komponen&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Versi target&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Gradle&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;9.4.1&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Java&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;21.0.11-tem (Eclipse Temurin)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;JHipster&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;8.x (kerangka kerja`tech.jhipster:jhipster-framework:8.11.0`)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Spring Boot&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;3.4.5&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Kotlin&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;2.3.0&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Docker Engine&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;29.4.1 (API 1.54)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Perubahan versi Java melalui SDKMAN :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;sdk use java 21.0.11-tem&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Peluncuran pertama:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;$ ./gradlew build --no-daemon

FAILURE: Build failed with an exception.

* Where:
Build file &amp;#39;/home/.../edster/build.gradle&amp;#39; line: 18

* What went wrong:
An exception occurred applying plugin request [id: &amp;#39;jhipster.cucumber-conventions&amp;#39;]
&amp;amp;gt; Failed to apply plugin &amp;#39;jhipster.cucumber-conventions&amp;#39;.
   &amp;amp;gt; Could not create task &amp;#39;:cucumberTest&amp;#39;.
      &amp;amp;gt; Could not create task &amp;#39;:consoleLauncherTest&amp;#39;.
         &amp;amp;gt; Could not set unknown property &amp;#39;reportsDir&amp;#39; for task &amp;#39;:consoleLauncherTest&amp;#39;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Kami bahkan tidak bisa meluncur dari pemuatan build script. Masalahnya ada di`buildSrc/src/main/groovy/jhipster.cucumber-conventions.gradle`.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_kesalahan_1_variabel_fantom_reportsdir&amp;quot;&amp;gt;Kesalahan 1 : variabel fantom `reportsDir&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;skrip`jhipster.cucumber-conventions.gradle`mengandung :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-groovy hljs&amp;quot; data-lang=&amp;quot;groovy&amp;quot;&amp;gt;tasks.register(&amp;#39;consoleLauncherTest&amp;#39;, JavaExec) {
    dependsOn(testClasses)
    String cucumberReportsDir = file(&amp;quot;$buildDir/reports/tests&amp;quot;)
    outputs.dir(reportsDir)           // &amp;amp;lt;-- reportsDir n&amp;#39;existe PAS
    classpath = sourceSets[&amp;quot;test&amp;quot;].runtimeClasspath
    main = &amp;quot;org.junit.platform.console.ConsoleLauncher&amp;quot;
    // ...
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Variabel yang didefinisikan adalah`cucumberReportsDir`. Yang digunakan adalah`reportsDir`-- yang tidak ditentukan di mana-mana.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Perbaikan&amp;lt;/strong&amp;gt; : &amp;lt;code&amp;gt;outputs.dir(cucumberReportsDir)&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_error_2_main_tidak_ada_lagi_di_gradle_9&amp;quot;&amp;gt;Error 2 : &amp;lt;code&amp;gt;main = \&amp;quot;&amp;amp;#8230;&amp;amp;#8203;\&amp;quot;&amp;lt;/code&amp;gt; tidak ada lagi di Gradle 9&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Ulang segera:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-none hljs&amp;quot;&amp;gt;Could not set unknown property &amp;#39;main&amp;#39; for task &amp;#39;:consoleLauncherTest&amp;#39; of type org.gradle.api.tasks.JavaExec.&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Gradle 9 menghapus properti`main`untuk keuntungan`mainClass`pada tugas-tugas`JavaExec`.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Perbaikan&amp;lt;/strong&amp;gt;:`mainClass = &amp;quot;org.junit.platform.console.ConsoleLauncher&amp;quot;`&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_kesalahan_3_sourcecompatibility_tidak_lagi_merupakan_properti_proyek&amp;quot;&amp;gt;Kesalahan 3 : &amp;lt;code&amp;gt;sourceCompatibility&amp;lt;/code&amp;gt; tidak lagi merupakan properti proyek&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Kesalahan baru :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-none hljs&amp;quot;&amp;gt;Could not set unknown property &amp;#39;sourceCompatibility&amp;#39; for root project &amp;#39;edster&amp;#39; of type org.gradle.api.Project.&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Skrip root memiliki:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-groovy hljs&amp;quot; data-lang=&amp;quot;groovy&amp;quot;&amp;gt;sourceCompatibility=17
targetCompatibility=17&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Gradle 9 menghapus properti-properti ini pada level root. Mereka harus berada di dalam sebuah blok`java`.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Koreksi&amp;lt;/strong&amp;gt;:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-groovy hljs&amp;quot; data-lang=&amp;quot;groovy&amp;quot;&amp;gt;java {
    sourceCompatibility = JavaVersion.VERSION_21
    targetCompatibility = JavaVersion.VERSION_21
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_kesalahan_4_pernyataan_groovy_dengan_tiga_operand&amp;quot;&amp;gt;Kesalahan 4 : pernyataan Groovy dengan tiga operand&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Skrip lama mengandung:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-groovy hljs&amp;quot; data-lang=&amp;quot;groovy&amp;quot;&amp;gt;assert System.properties[&amp;quot;java.specification.version&amp;quot;] == &amp;quot;17&amp;quot; || &amp;quot;21&amp;quot; || &amp;quot;24&amp;quot;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Di Groovy,&amp;lt;code&amp;gt;&amp;quot;21&amp;quot;&amp;lt;/code&amp;gt; et &amp;lt;code&amp;gt;&amp;quot;24&amp;quot;`adalah string truthy. Ekspresi menyelesaikan ke&amp;lt;/code&amp;gt;(&amp;amp;#8230;&amp;amp;#8203; == &amp;quot;17&amp;quot;) || true || true`, yang selalu benar&amp;amp;#8201;&amp;amp;#8212;&amp;amp;#8201;tetapi sintaksnya tidak valid untuk pernyataan ketat yang diinginkan.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Koreksi&amp;lt;/strong&amp;gt;:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-groovy hljs&amp;quot; data-lang=&amp;quot;groovy&amp;quot;&amp;gt;assert System.properties[&amp;quot;java.specification.version&amp;quot;] in [&amp;quot;17&amp;quot;, &amp;quot;21&amp;quot;, &amp;quot;23&amp;quot;, &amp;quot;24&amp;quot;]&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_error_5_dependensi_implisit_compilekotlin_openapigenerate&amp;quot;&amp;gt;Error 5: dependensi implisit &amp;lt;code&amp;gt;compileKotlin&amp;lt;/code&amp;gt; → `openApiGenerate&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Build sedang berjalan. Kompilasi Kotlin berhasil. Lalu :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-none hljs&amp;quot;&amp;gt;Task &amp;#39;:compileKotlin&amp;#39; uses this output of task &amp;#39;:openApiGenerate&amp;#39;
without declaring an explicit or implicit dependency.&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Gradle 9 menolak dependensi implisit antara tugas. Karena`compileKotlin`membaca sumber yang dihasilkan oleh`openApiGenerate`, harus mendeklarasikan tautan.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;perbaikan&amp;lt;/strong&amp;gt;dalam`build.gradle`:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-groovy hljs&amp;quot; data-lang=&amp;quot;groovy&amp;quot;&amp;gt;afterEvaluate {
    tasks.named(&amp;quot;compileKotlin&amp;quot;).configure {
        dependsOn(tasks.named(&amp;quot;openApiGenerate&amp;quot;))
    }
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Le `afterEvaluate`diperlukan karena`openApiGenerate`adalah sebuah ekstensi (plugin OpenAPI Generator) dan bukan sebuah tugas yang dapat diakses langsung selama fase konfigurasi.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_error_6_generategitproperties_gagal_pada_filteroutputstream_write&amp;quot;&amp;gt;Error 6: &amp;lt;code&amp;gt;generateGitProperties&amp;lt;/code&amp;gt; gagal pada `FilterOutputStream.write()&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Compilasi Java berhasil. Sumber daya diproses. Kemudian :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-none hljs&amp;quot;&amp;gt;&amp;amp;gt; Task :generateGitProperties FAILED

No signature of method: java.io.FilterOutputStream.write() is applicable for argument types: (Integer) values: [103]&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;plugin`gradle-git-properties`Versi 2.5.0 tidak kompatibel dengan Java 21 / Gradle 9. Bug internal mencoba memanggil`write(int)`via Groovy pada aliran yang ditutup&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Perbaikan&amp;lt;/strong&amp;gt;di`gradle.properties` :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-properties hljs&amp;quot; data-lang=&amp;quot;properties&amp;quot;&amp;gt;gitPropertiesPluginVersion=2.5.7&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_akhirnya_testcontainers_masuk_ke_panggung&amp;quot;&amp;gt;Akhirnya: Testcontainers masuk ke panggung&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Sampai sekarang, ini hanya &amp;#39;build script debugging&amp;#39; murni. Setiap kesalahan adalah ketidakcocokan Gradle 9 atau Java 21 dalam skrip build. Setelah enam perbaikan di atas, le`./gradlew assemble`berhasil lolos.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Tapi`./gradlew build`menjalankan juga tes, dan tes Cucumber menggunakan Testcontainers untuk menjalankan PostgreSQL sementara.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-none hljs&amp;quot;&amp;gt;Could not find a valid Docker environment.

EnvironmentAndSystemPropertyClientProviderStrategy: failed with exception BadRequestException
(Status 400: {&amp;quot;message&amp;quot;:&amp;quot;client version 1.32 is too old. Minimum supported API version is 1.40&amp;quot;}
UnixSocketClientProviderStrategy: failed with exception BadRequestException
(Status 400: {&amp;quot;message&amp;quot;:&amp;quot;client version 1.32 is too old. Minimum supported API version is 1.40&amp;quot;}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Testcontainers tidak mampu berkomunikasi dengan Docker. Dua strategi berbeda (variabel lingkungan + soket Unix) gagal pada kesalahan yang sama.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div id=&amp;quot;diag-docker-handshake&amp;quot; class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE
skinparam handwritten false

actor &amp;quot;Testcontainers
1.20.6&amp;quot; as TC
participant &amp;quot;docker-java
3.4.1&amp;quot; as DJ
participant &amp;quot;Docker Engine
29.4.1&amp;quot; as DE

TC -&amp;amp;gt; DJ : new DockerClient()
DJ -&amp;amp;gt; DE : GET /_ping\nUser-Agent: docker-java/3.4.1\nAPI-Version: 1.32
DE --&amp;amp;gt; DJ : HTTP 400 Bad Request\nclient version 1.32 is too old.\nMinimum supported API version is 1.40
DJ --&amp;amp;gt; TC : BadRequestException
TC --&amp;amp;gt; TC : Could not find a valid\nDocker environment

note right of DE
  Docker Engine 29.x impose
  API minimum = 1.40
  (défaut ancien : 1.24)
end note

note left of DJ
  docker-java 3.4.1 envoie
  hardcoded API version 1.32
  dans l&amp;#39;en-tête HTTP
end note

@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_tahap_1_jalur_docker_java&amp;quot;&amp;gt;Tahap 1 : jalur docker-java&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Refleks pertama : klien Docker Java yang digunakan oleh Testcontainers mengirim versi API`1.32`dalam handshake HTTP-nya, tetapi Docker Engine 29.4.1 memerlukan minimum`1.40`. Masalahnya ada di sisi klien, bukan pada daemon.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Pemeriksaan versi docker-java yang diselesaikan oleh Testcontainers 1.20.6 :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;$ ./gradlew dependencies --configuration testRuntimeClasspath | grep docker-java

+--- com.github.docker-java:docker-java-api:3.4.1
+--- com.github.docker-java:docker-java-transport:3.4.1&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;docker-java 3.4.1, yang berasal dari Testcontainers 1.20.6. Versi publik terbaru dari docker-java adalah 3.5.1. Mari kita coba memaksa peningkatan versi melalui`resolutionStrategy`dalam blok`configurations` de &amp;lt;code&amp;gt;build.gradle&amp;lt;/code&amp;gt;:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-groovy hljs&amp;quot; data-lang=&amp;quot;groovy&amp;quot;&amp;gt;configurations {
    all {
        resolutionStrategy.eachDependency { details -&amp;amp;gt;
            if (details.requested.group == &amp;quot;com.github.docker-java&amp;quot;
                &amp;amp;amp;&amp;amp;amp; details.requested.name.startsWith(&amp;quot;docker-java&amp;quot;)) {
                details.useVersion(&amp;quot;3.5.1&amp;quot;)
            }
        }
    }
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Ulangi build. Kesalahan yang sama :`client version 1.32 is too old`.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Verifikasi cache Gradle: JARs telah di-download ulang dengan baik, tetapi kesalahan tetap ada. docker-java 3.5.1 masih mengirim`1.32`. Versi ini TIDAK cukup untuk Docker Engine 29.4.1.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Tindakan penyucian&amp;lt;/strong&amp;gt;: membersihkan sepenuhnya cache Gradle, hanya untuk memastikan :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;rm -rf ~/.gradle/caches
rm -rf /path/to/project/.gradle&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Restart lengkap setelah purge. Kesalahan yang sama.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_tahap_2_penelusuran_web_dan_breadcrumb_github&amp;quot;&amp;gt;Tahap 2 : penelusuran web dan breadcrumb GitHub&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;docker-java 3.5.1 tidak mencukupi. Hanya perlu mencari apakah seseorang telah mengalami masalah ini.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Pencarian terarah pada isu testcontainers-java dan docker-java :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-none hljs&amp;quot;&amp;gt;site:github.com/testcontainers &amp;quot;client version 1.32 is too old&amp;quot;
site:github.com/docker-java &amp;quot;client version 1.32&amp;quot; Docker Engine 29&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Hasil segera pertama :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://github.com/testcontainers/testcontainers-java/issues/11210&amp;quot;&amp;gt;testcontainers-java#11210&amp;lt;/a&amp;gt;—&amp;lt;code&amp;gt;[Bug]: client version 1.32 is too old. Minimum supported API version is 1.44&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://github.com/testcontainers/testcontainers-java/issues/11212&amp;quot;&amp;gt;testcontainers-java#11212&amp;lt;/a&amp;gt;—&amp;lt;code&amp;gt;[Bug]: Docker 29.0.0 could not find a valid Docker environment&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://github.com/testcontainers/testcontainers-java/issues/11491&amp;quot;&amp;gt;testcontainers-java#11491&amp;lt;/a&amp;gt;(No content to translate)&amp;lt;code&amp;gt;[Bug]: Incompatibility in Ubuntu Github runners with Docker Engine 29.1.*&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;breadcrumb jelas: Docker Engine 29.x telah meningkatkan versi minimum API-nya menjadi`1.24`(bug lama) ke`1.40`. Testcontainers 1.20.x menggunakan docker-java 3.4.x yang negoiasi versi API dengan mengirim`1.32`. Daemon menolak dengan sopan tapi tegas.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Dalam isu #11491, seorang pemelihara menjawab:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;em&amp;gt;&amp;lt;em&amp;gt;_ Hai, saya memiliki proyek yang berjalan dengan versi 2.0.3 di GH runner dengan mesin 29.1 dan ini berfungsi. Bisakah kalian semua mohon memeriksa bahwa tidak ada konflik versi? Mohon, ingat semua modul telah diawali dengan`testcontainers-&amp;lt;code&amp;gt;. Jadi, mulai dengan versi 2.x, kita beralih dari`postgresql&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;testcontainers-postgresql&amp;lt;/code&amp;gt; &amp;lt;/em&amp;gt;_&amp;lt;/em&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Jawaban ini berisi&amp;lt;strong&amp;gt;dua informasi kritis&amp;lt;/strong&amp;gt;:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;olist arabic&amp;quot;&amp;gt;
&amp;lt;ol class=&amp;quot;arabic&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Testcontainers 2.0.3+ menyelesaikan masalah&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Modul telah&amp;lt;strong&amp;gt;diberi nama ulang dengan awalan `testcontainers-&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_tahap_3_jerat_artefak_yang_diubah_namanya&amp;quot;&amp;gt;Tahap 3: jerat artefak yang diubah namanya&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Penggantian nama ini brutal. Di Testcontainers 2.x, tidak ada nama lama yang lagi berfungsi dengan nama itu:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;table class=&amp;quot;tableblock frame-all grid-all stretch&amp;quot;&amp;gt;
&amp;lt;colgroup&amp;gt;
&amp;lt;col style=&amp;quot;width: 10%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 10%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 10%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 10%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 10%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 10%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 10%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 10%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 10%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 10%;&amp;quot;&amp;gt;
&amp;lt;/colgroup&amp;gt;
&amp;lt;tbody&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Nama lama (1.x)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Nama baru (2.x)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;org.testcontainers:postgresql&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;org.testcontainers:testcontainers-postgresql&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;org.testcontainers:jdbc&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;org.testcontainers:testcontainers-jdbc&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;org.testcontainers:junit-jupiter&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;org.testcontainers:testcontainers-junit-jupiter&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;org.testcontainers:testcontainers&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;tidak berubah&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Percobaan pertama: menerapkan BOM Testcontainers 2.0.5 dan nama-nama artifact baru di`build.gradle`.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-groovy hljs&amp;quot; data-lang=&amp;quot;groovy&amp;quot;&amp;gt;dependencies {
    testImplementation platform(&amp;quot;org.testcontainers:testcontainers-bom:2.0.5&amp;quot;)
    testImplementation &amp;quot;org.testcontainers:testcontainers-postgresql&amp;quot;
    testImplementation &amp;quot;org.testcontainers:testcontainers-jdbc&amp;quot;
    testImplementation &amp;quot;org.testcontainers:testcontainers-junit-jupiter&amp;quot;
    testImplementation &amp;quot;org.testcontainers:testcontainers&amp;quot;
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;code&amp;gt;./gradlew build&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-none hljs&amp;quot;&amp;gt;FAILURE: Could not find org.testcontainers:jdbc:2.0.5.
Could not find org.testcontainers:junit-jupiter:2.0.5.&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;BOM testcontainers-bom tidak cukup.&amp;lt;/strong&amp;gt;Kenapa ? Karena Spring Boot 3.4.5 mengekspos BOM manajemen dependensi-nya sendiri (&amp;lt;code&amp;gt;spring-boot-dependencies&amp;lt;/code&amp;gt;) yang&amp;lt;strong&amp;gt;menang&amp;lt;/strong&amp;gt;pada BOM Testcontainers dalam resolusi transitif. Spring Boot memperbaiki`testcontainers` à &amp;lt;code&amp;gt;1.20.6&amp;lt;/code&amp;gt;, dan karena itu semua artefak yang tidak secara eksplisit berada dalam BOM Spring Boot (seperti yang baru`testcontainers-*&amp;lt;code&amp;gt;) mengarah ke nama-nama lama`1.20.6&amp;lt;/code&amp;gt;-- yang tidak lagi ada dalam versi ini.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Saat memeriksa`dependencies --configuration testRuntimeClasspath`, ada :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-none hljs&amp;quot;&amp;gt;+--- org.testcontainers:testcontainers -&amp;amp;gt; 1.20.6 (*)
|    \--- org.testcontainers:testcontainers:2.0.5 -&amp;amp;gt; 1.20.6&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Le &amp;lt;code&amp;gt;&amp;amp;#8594;`menampilkan penggantian : Testcontainers 2.0.5 &amp;lt;strong&amp;gt;diminta&amp;lt;/strong&amp;gt;, tapi Spring Boot memaksa`1.20.6&amp;lt;/code&amp;gt;.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div id=&amp;quot;diag-bom-war&amp;quot; class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE
skinparam handwritten false
skinparam componentStyle rectangle

left to right direction

component &amp;quot;build.gradle
(konsumen)&amp;quot; as BG #E3F2FD {
    rectangle &amp;quot;permintaan\n`testcontainers-bom:2.0.5&amp;quot; as REQ
    rectangle &amp;quot;permintaan
`testcontainers-postgresql&amp;quot; as REQ2
}

component &amp;quot;Maven Central&amp;quot; as MC #FFF3E0

component &amp;quot;Pernyataan&amp;quot; as DEC #E8F5E9 {
    rectangle &amp;quot;testcontainers-bom:2.0.5`\ndiperbaiki 2.0.5&amp;quot; as BOM2
    rectangle &amp;quot;spring-boot-dependencies:3.4.5`
tetap 1.20.6&amp;quot; as BOM1
}

component &amp;quot;Resolusi Gradle&amp;quot; as RES #FFEBEE {
    rectangle &amp;quot;Versi pemenang\n`testcontainers:1.20.6&amp;quot; as WIN #EF9A9A
}

BG --&amp;amp;gt; DEC : &amp;quot;menganalisis BOMs&amp;quot;
BOM2 --&amp;amp;gt; RES : &amp;quot;menyatakan 2.0.5&amp;quot;
BOM1 --&amp;amp;gt; RES : &amp;quot;menawarkan 1.20.6&amp;quot;
WIN -&amp;amp;gt; MC : &amp;quot;download\ntestcontainers:1.20.6&amp;quot;

note bottom of WIN
  Spring Boot BOM prime car
  le plugin spring-boot applique
  ses dépendances de gestion
  après la résolution du consommateur
end note

@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_tahap_4_memaksakan_resolusi&amp;quot;&amp;gt;Tahap 4 : memaksakan resolusi&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Strategi menjadi ganda:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;olist arabic&amp;quot;&amp;gt;
&amp;lt;ol class=&amp;quot;arabic&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Memaksa docker-java pada versi 3.7.1 (diuji sebagai kompatibel dengan Docker Engine 29)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Memaksa Testcontainers core pada versi 2.0.5 dengan melewati BOM Spring Boot&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div id=&amp;quot;diag-resolution-override&amp;quot; class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE
skinparam handwritten false
skinparam componentStyle rectangle

left to right direction

component &amp;quot;build.gradle
(dengan resolutionStrategy)&amp;quot; as BG #E8F5E9 {
    rectangle &amp;quot;BOM Testcontainers\n`2.0.5&amp;quot; as BOM2
    rectangle &amp;quot;resolutionStrategy\n.eachDependency&amp;quot; as RS #66BB6A
}

component &amp;quot;Konflik terpecahkan&amp;quot; as RESOLVED #E3F2FD {
    rectangle &amp;quot;docker-java
`3.7.1` (dipaksa)&amp;quot; as DJ
    rectangle &amp;quot;testcontainers\n`2.0.5` (dipaksa)&amp;quot; as TC #81C784
    rectangle &amp;quot;testcontainers-postgresql
`2.0.5&amp;quot; as TPG
    rectangle &amp;quot;testcontainers-jdbc\n`2.0.5&amp;quot; as TJ
    rectangle &amp;quot;testcontainers-junit-jupiter
`2.0.5&amp;quot; as TJJ
}

component &amp;quot;BOM Spring Boot
(implisit, dioverride)&amp;quot; as BOM1 #FFEBEE

component &amp;quot;Maven Central&amp;quot; as MC #FFF3E0

BG --&amp;amp;gt; RESOLVED : &amp;quot;BOM Testcontainers 2.0.5
melewati langsung&amp;quot;
RS -&amp;amp;gt; DJ : &amp;quot;kekuatan 3.7.1&amp;quot;
RS -&amp;amp;gt; TC : &amp;quot;gaya 2.0.5&amp;quot;
RESOLVED --&amp;amp;gt; MC : &amp;quot;unduh JARs\nkompatibel Docker 29&amp;quot;
BOM1 -[#gray,dashed]-&amp;amp;gt; RESOLVED : &amp;quot;diabaikan pada testcontainers\net docker-java&amp;quot;

note bottom of RS
  Le `eachDependency` intercepte
  la résolution Gradle AVANT
  que le BOM Spring Boot ne gagne.
  C&amp;#39;est une intervention chirurgicale.
end note

@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Koreksi final&amp;lt;/strong&amp;gt;dalam`build.gradle`(Empty output)&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-groovy hljs&amp;quot; data-lang=&amp;quot;groovy&amp;quot;&amp;gt;configurations {
    all {
        resolutionStrategy.eachDependency { details -&amp;amp;gt;
            if (details.requested.group == &amp;quot;com.github.docker-java&amp;quot;
                &amp;amp;amp;&amp;amp;amp; details.requested.name.startsWith(&amp;quot;docker-java&amp;quot;)) {
                details.useVersion(&amp;quot;3.7.1&amp;quot;)
            }
            if (details.requested.group == &amp;quot;org.testcontainers&amp;quot;
                &amp;amp;amp;&amp;amp;amp; details.requested.name == &amp;quot;testcontainers&amp;quot;) {
                details.useVersion(&amp;quot;2.0.5&amp;quot;)
            }
        }
    }
}

dependencies {
    testImplementation platform(&amp;quot;org.testcontainers:testcontainers-bom:2.0.5&amp;quot;)
    testImplementation &amp;quot;org.testcontainers:testcontainers-jdbc&amp;quot;
    testImplementation &amp;quot;org.testcontainers:testcontainers-junit-jupiter&amp;quot;
    testImplementation &amp;quot;org.testcontainers:testcontainers-postgresql&amp;quot;
    testImplementation &amp;quot;org.testcontainers:testcontainers&amp;quot;
    // ... autres dépendances Spring Boot
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Le `resolutionStrategy.eachDependency`adalah satu-satunya cara untuk mengabaikan BOM`spring-boot-dependencies`Dikelola oleh plugin Spring Boot. Uji kesamaan pada`details.requested.name == &amp;quot;testcontainers&amp;quot;`adalah sengaja restriktif: kita hanya memaksa modul core. Modul-modul`testcontainers-*`mereka mengikuti BOM Testcontainers 2.0.5 yang telah kami deklarasikan secara eksplisit&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_hasil_akhir&amp;quot;&amp;gt;Hasil akhir&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;$ ./gradlew build --no-daemon

&amp;amp;gt; Task :consoleLauncherTest
[2 containers found]
[2 containers started]
[2 containers successful]

BUILD SUCCESSFUL in 1m 16s&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Testcontainers 2.0.5 + docker-java 3.7.1 akhirnya berhasil membuat containernya PostgreSQL melalui Docker Engine 29.4.1. Kesalahan bisnis akhir (HTTP 500 pada ujian Cucumber) adalah masalah aplikasi yang sepenuhnya independen dari skrip build.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_tabel_ringkasan_koreksi&amp;quot;&amp;gt;Tabel ringkasan koreksi&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;table class=&amp;quot;tableblock frame-all grid-all stretch&amp;quot;&amp;gt;
&amp;lt;colgroup&amp;gt;
&amp;lt;col style=&amp;quot;width: 25%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 25%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 25%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 25%;&amp;quot;&amp;gt;
&amp;lt;/colgroup&amp;gt;
&amp;lt;thead&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;#&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;File&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;Masalah&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;korreksi&amp;lt;/th&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/thead&amp;gt;
&amp;lt;tbody&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;1&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;buildSrc/src/main/groovy/jhipster.cucumber-conventions.gradle&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Variabel`reportsDir`tidak ada&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;outputs.dir(cucumberReportsDir)&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;2&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;buildSrc/src/main/groovy/jhipster.cucumber-conventions.gradle&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;`main`usang Gradle 9&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;mainClass&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;3&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;build.gradle&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;`sourceCompatibility`top-level dilarang Gradle 9&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;blok`java { sourceCompatibility = JavaVersion.VERSION_21 }`&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;4&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;build.gradle&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Pernyataan sintaksis Groovy yang tidak valid&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;assert &amp;amp;#8230;&amp;amp;#8203; in [&amp;quot;17&amp;quot;, &amp;quot;21&amp;quot;, &amp;quot;23&amp;quot;, &amp;quot;24&amp;quot;]&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;5&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;build.gradle&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Ketergantungan implisit`compileKotlin`→&amp;lt;code&amp;gt;openApiGenerate&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;afterEvaluate { tasks.named(&amp;quot;compileKotlin&amp;quot;).dependsOn(&amp;quot;openApiGenerate&amp;quot;) }&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;6&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;gradle.properties&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;`gitPropertiesPluginVersion`tidak kompatibel Java 21&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;2.5.7&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;7&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Cache Gradle&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;docker-java 3.5.1 diuji, diselesaikan, namun tidak cukup&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Pembersihan + perjalanan docker-java 3.7.1&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;8&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;build.gradle&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Testcontainers 1.20.6 tidak kompatibel dengan Docker Engine 29&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Memaksa Testcontainers 2.0.5 + prefix`testcontainers-*`+ strategi resolusi vs Spring Boot BOM&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_kesimpulan&amp;quot;&amp;gt;Kesimpulan&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Jika build JHipster/Gradle Anda gagal setelah upgrade ke Gradle 9 + Docker Engine 29, kesalahannya terbagi menjadi dua kategori :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Kesalahan skrip build&amp;lt;/strong&amp;gt;(6 pertama) : Gradle 9 menghapus properti dan sintaksis yang berfungsi selama bertahun-tahun (&amp;lt;code&amp;gt;sourceCompatibility=&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;main =&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;reportsDir&amp;lt;/code&amp;gt;). Mereka adalah migrasi mekanis setelah Anda mengenal API baru.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Kesalahan runtime Docker&amp;lt;/strong&amp;gt;(kedua terakhir): Docker Engine 29.4.1 mengabaikan klien API &amp;amp;lt; 1.40. Testcontainers 1.20.6 (dipaksa oleh Spring Boot 3.4.5) menggunakan docker-java 3.4.1 yang mengirimkan 1.32. Hanya Testcontainers 2.0.5 + docker-java 3.7.1 yang menyelesaikan masalah, dengan penamaan ulang artefak dan penaklukan versi via`resolutionStrategy.eachDependency`sebagai satu-satunya cara untuk mengatasi BOM implisit Spring Boot.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Skrip build Gradle di akar proyek sekarang bersih.`./gradlew build`melewati atas Java 21, Gradle 9.4.1, Spring Boot 3.4.5 dan Docker Engine 29.4.1&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>Mengelola Agen AI dengan AsciiDoc: Strategi Eager/Lazy saya untuk sesi Opencode tanpa bocor konteks</title>
            <link >https://cheroliv.com/blog/2026/0108_gouvernance_agent_opencode_eager_lazy_post.html</link>
            <pubDate>Fri, 24 Apr 2026 00:00:00 +0000</pubDate>
            <guid isPermaLink="false">blog/2026/0108_gouvernance_agent_opencode_eager_lazy_post.html</guid>
            <description>&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_ringkasan&amp;quot;&amp;gt;ringkasan&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Saat bekerja dengan agen AI seperti Opencode pada proyek-proyek kompleks di beberapa sesi, kita menghadapi masalah fundamental:&amp;lt;strong&amp;gt;pembocoran konteks&amp;lt;/strong&amp;gt;. Agen tidak mengingat sesi sebelumnya. Semua yang dijelaskan kepadanya&amp;amp;#8201;&amp;amp;#8212;&amp;amp;#8201;arsitektur, konvensi, keadaan backlog&amp;amp;#8201;&amp;amp;#8212;&amp;amp;#8201;telah hilang. Membangun kembali konteks ini di setiap sesi membutuhkan biaya, lambat, dan sumber kesalahan.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Artikel ini menjelaskan strategi artesanal yang telah saya bangun untuk menyelesaikan masalah ini: sebuah sistem pemerintahan yang berkelanjutan berbasis file AsciiDoc, dengan sebuah dikotomi&amp;lt;strong&amp;gt;antusias/malas&amp;lt;/strong&amp;gt;untuk mengoptimalkan konsumsi token konteks, dan satu&amp;lt;strong&amp;gt;proses akhir sesi yang harus dilakukan&amp;lt;/strong&amp;gt;untuk menjamin kelangsungan&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_pertunjukan_senin_21_april_09_00&amp;quot;&amp;gt;Pertunjukan : Senin, 21 April, 09.00&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Saya membuka kembali Opencode untuk melanjutkan plugin Gradle saya`plantuml-plugin`. Malam ini, saya menghabiskan tiga jam untuk berdiskusi dengan agen arsitektur dari pool kunci API — rotasi round-robin, pengelolaan kuota, fallback otomatis. Pagi ini, agen itu menatap saya dengan mata ikan mas.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;em&amp;gt;&amp;lt;em&amp;gt;_&amp;amp;#8201;&amp;amp;#8212;&amp;amp;#8201;Halo, saya adalah asisten Opencode Anda. Bagaimana saya bisa membantu Anda hari ini? &amp;lt;/em&amp;gt;_&amp;lt;/em&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Tidak&amp;amp;#8201;&amp;amp;#8212;&amp;amp;#8201;Ah ya, kumpulan kunci API, kita berada di struktur YAML. Tidak&amp;amp;#8201;&amp;amp;#8212;&amp;amp;#8201;Perhatikan,&amp;lt;code&amp;gt;PlantumlManager`adalah objek Kotlin singleton, bukan kelas. Tidak&amp;amp;#8201;&amp;amp;#8212;&amp;amp;#8201;Tidak, kami telah memutuskan kemarin bahwa`SyntaxValidationResult`masih ada sebuah sealed class nested di`PlantumlService&amp;lt;/code&amp;gt;.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Semua harus dibuat ulang. Lebih tepatnya: semua harus dijelaskan ulang. Saya akan menghabiskan dua puluh menit pertama sesi saya untuk merekonstruksi konteks yang sudah dimiliki oleh agen kemarin. Dua puluh menit token terbakar. Dua puluh menit di mana saya bisa melakukan coding, tapi saya justru melakukan pendidikan yang wajib.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Ini bukan bug Opencode. Ini adalah sifat yang mendasar dari LLM berbasis percakapan: antara dua sesi, memori kerja adalah&amp;lt;strong&amp;gt;sepenuhnya terhapus&amp;lt;/strong&amp;gt;. Agen tidak mengingat misi sebelumnya, keputusan yang diambil, jebakan yang teridentifikasi, kode yang telah kita tulis bersama.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Saya telah mengalami itu puluhan kali. Pada empat proyek bersamaan. Dengan sesi yang berlanjut selama minggu-minggu. Saya telah menghitung: rata-rata,&amp;lt;strong&amp;gt;30 hingga 40%&amp;lt;/strong&amp;gt;Ia dipanahkan untuk rekontekstualisasi agen. Pada sesi 87 proyek.&amp;lt;code&amp;gt;plantuml-plugin&amp;lt;/code&amp;gt;, aku sudah tidak tahan lagi. Aku tidak mampu lagi menjelaskan untuk kali kesepuluh bahwa`AttemptEntry`adalah sebuah kelas data level atas dalam`DiagramProcessor.kt`.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Aku butuh sistem. Bukan hack. Tata kelola yang sebenarnya.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_genesis_dari_chaos_ke_metode&amp;quot;&amp;gt;Genesis: Dari Chaos ke Metode&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_sesi_pertama_era_gelap&amp;quot;&amp;gt;Sesi Pertama: Era Gelap&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Proyek pertama saya dengan Opencode,&amp;lt;code&amp;gt;plantuml-plugin&amp;lt;/code&amp;gt;, dimulai tanpa tata kelola. Saya mengajukan pertanyaan, agen menjawab, kita mengulangi, sesi berakhir, dan besok kita memulai dari nol. Itu adalah sesi 1, lalu sesi 2, lalu sesi 3&amp;amp;#8230;&amp;amp;#8203; hingga sesi 62 ketika saya menyadari bahwa saya telah kehilangan jam-jam yang terkumulatif untuk menjelaskan kembali arsitektur yang sama.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Pada sesi 62, angka-angka ada:&amp;lt;strong&amp;gt;198 tes unit lulus&amp;lt;/strong&amp;gt;, &amp;lt;strong&amp;gt;42 tes fungsional yang divalidasi&amp;lt;/strong&amp;gt;, plugin berfungsi. Tetabi biaya kognitif tidak dapat ditoleransi. Setiap sesi baru dimulai dengan sebuah monolog dua puluh menit tentang struktur proyek.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_episode_site_yml_dihancurkan_sesi_2_bakery_plugin&amp;quot;&amp;gt;Episode site.yml Dihancurkan (Sesi 2, bakery-plugin)&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Metode ini juga lahir dari sebuah bencana. Di proyek`bakery-gradle`, pada sesi 2, saya meminta agen untuk mengubah file`site.yml`. Agen, tanpa memverifikasi apakah file tersebut telah diberi versi, membuat`Write`yang lengkap yang menimpa konten. Hasil: token nyata (kunci API Firebase, rahasia penerapan) diganti dengan placeholder palsu. File tidak berada di git&amp;amp;#8201;&amp;amp;#8212;&amp;amp;#8201;ia berada di`.gitignore`untuk melindungi rahasia.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Tanpa cadangan. Tanpa`git restore`mungkin. Saya terjebak. Saya harus membangun ulang file konfigurasi secara manual, menemukan token di manajer kata sandi saya, menempelkan semuanya kembali.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Dari kegelisahan inilah lahir&amp;lt;strong&amp;gt;Aturan Mutlak 1b&amp;lt;/strong&amp;gt;:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;em&amp;gt;&amp;lt;em&amp;gt;_ &amp;lt;strong&amp;gt;Jangan pernah menimpa&amp;lt;/strong&amp;gt;sebuah file config dengan sebuah`Write`lengkap ketika satu`Edit`sebagian cukup.&amp;lt;strong&amp;gt;JANGAN PERNAH mengganti&amp;lt;/strong&amp;gt;nilai sensitif dengan nilai tiruan.&amp;lt;strong&amp;gt;Periksa &amp;lt;code&amp;gt;git check-ignore&amp;lt;/code&amp;gt; dan `git ls-files&amp;lt;/strong&amp;gt;Sebelum modifikasi apa pun. &amp;lt;/em&amp;gt;_&amp;lt;/em&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Aturan ini, hari ini terukir di batu dalam semua file saya`AGENT.adoc` et `INDEX.adoc`pada empat proyek, lahir dari kesalahan nyata yang telah menghabiskan satu jam kerja manual saya&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_migrasi_markdown_asciidoc_sesi_1_cheroliv_com&amp;quot;&amp;gt;Migrasi Markdown → AsciiDoc (Sesi 1, cheroliv.com)&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Pada 25 April 2026, pada`cheroliv.com`, saya mengambil keputusan radikal: mengonversi seluruh tata kelola Markdown ke AsciiDoc. Ini tidak estetis. Ini fungsional. AsciiDoc memberikan struktur semantik yang lebih baik dibaca oleh LLM : bagian hierarkis, tabel bertipe, peringatan (&amp;lt;code&amp;gt;NOTE&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;WARNING&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;CAUTION&amp;lt;/code&amp;gt;), atribut dokumen yang dapat dibaca mesin.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Sesi 1 dari`cheroliv.com`Mengformalkan struktur :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;konversi dari`AGENTS.md` en &amp;lt;code&amp;gt;AGENT.adoc&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Pembuatan agen spesial :`CODER.adoc`, &amp;lt;code&amp;gt;SCRUM_MASTER.adoc&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;PLANTUML_DESIGNER.adoc&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Pembuatan struktur Eager/Lazy:`INDEX.adoc`, &amp;lt;code&amp;gt;SESSIONS_HISTORY.adoc&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;AGENT_SESSION_MANAGER.adoc&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;SESSION_CHECKLIST.adoc&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;PROCEDURES.adoc&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Satu commit :`90975e9 refactor: migrate agent governance from Markdown to AsciiDoc`. Dan situs masih berfungsi.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div id=&amp;quot;diag-timeline&amp;quot; class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE
skinparam handwritten false

title Evolusi Sesi — Dari Sesi 1 hingga 150+
legend top
    |= Couleur |= Projet |
    | &amp;amp;lt;#4CAF50&amp;amp;gt; | cheroliv.com |
    | &amp;amp;lt;#2196F3&amp;amp;gt; | plantuml-plugin |
    | &amp;amp;lt;#FF9800&amp;amp;gt; | bakery-plugin |
    | &amp;amp;lt;#9C27B0&amp;amp;gt; | magic-stick |
endlegend

concise &amp;quot;Sesi aktif&amp;quot; as S

@S
0 is &amp;quot;.md mentah&amp;quot;
1 is &amp;quot;Migrasi\nAsciiDoc&amp;quot;
10 is &amp;quot;Bersemangat/Malas\ndiformalkan&amp;quot;
62 is &amp;quot;Aturan keamanan\n(site.yml)&amp;quot;
87 is &amp;quot;Pecah
konteks&amp;quot;
109 is &amp;quot;Optimasi
-60% token&amp;quot;
133 is &amp;quot;133 sesi
240 tes LULUS&amp;quot;

S@0 -&amp;amp;gt; S@1 : Session 1\n(cheroliv.com)
S@1 -&amp;amp;gt; S@10
S@10 -&amp;amp;gt; S@62 : Session 62\n(plantuml-plugin)
S@62 -&amp;amp;gt; S@87 : Session 87\n(Cry 4 help)
S@87 -&amp;amp;gt; S@109 : Session 109\n(API Key Pool)
S@109 -&amp;amp;gt; S@133 : Session 133\n(Aujourd&amp;#39;hui)

@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Timeline di atas menggambarkan progres aktual. Titik balik adalah sesi 87: itu di mana ketidakpuasan dari rekontekstualisasi berulang melebihi ambang toleransi, dan metode Eager/Lazy tidak lagi hanya sebuah ide, melainkan menjadi kewajiban.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_strategi_eagerlazy_mendalam&amp;quot;&amp;gt;Strategi: Eager/Lazy Mendalam&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_filsafat_cache_informatika_diterapkan_pada_kognisi&amp;quot;&amp;gt;Filsafat : Cache Informatika diterapkan pada Kognisi&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Pendekatan saya terinspirasi langsung dari pengelolaan cache komputer. Semua yang&amp;lt;strong&amp;gt;kritis dan sering digunakan&amp;lt;/strong&amp;gt;harus segera dapat diakses (&amp;lt;strong&amp;gt;bersemangat&amp;lt;/strong&amp;gt;). Semua yang&amp;lt;strong&amp;gt;kontekstual atau berukuran besar&amp;lt;/strong&amp;gt;harus dimuat saat diperlukan (&amp;lt;strong&amp;gt;malas&amp;lt;/strong&amp;gt;)&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;table class=&amp;quot;tableblock frame-all grid-all stretch&amp;quot;&amp;gt;
&amp;lt;colgroup&amp;gt;
&amp;lt;col style=&amp;quot;width: 6.6666%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 6.6666%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 6.6666%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 6.6666%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 6.6666%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 6.6666%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 6.6666%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 6.6666%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 6.6666%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 6.6666%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 6.6666%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 6.6666%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 6.6666%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 6.6666%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 6.6676%;&amp;quot;&amp;gt;
&amp;lt;/colgroup&amp;gt;
&amp;lt;tbody&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Eager (Dashboard)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Malas (Manual Pemilik)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;ukuran&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;amp;lt; 100 baris, &amp;amp;lt; 10k token&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Tidak terbatas, rinci&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;Memuat&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Otomatis, di awal sesi&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Atas permintaan agen&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;Isi&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Aturan mutlak, misi saat ini, kondisi kritis&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Arsip sesi, riwayat lengkap, prosedur terperinci, referensi teknis&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;peran&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Arahkan segera agen&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Menjawab pertanyaan konteks mendalam&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_file_eager_dasbor&amp;quot;&amp;gt;File Eager: Dasbor&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;File-file ini berada di akar setiap proyek dan dimuat secara otomatis oleh agen pada awal setiap sesi. Mereka membentuk le&amp;lt;strong&amp;gt;papan instrumen&amp;lt;/strong&amp;gt;-- informasi kritis, langsung dapat diakses.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div id=&amp;quot;diag-eager-lazy&amp;quot; class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam defaultTextAlignment center
skinparam wrapWidth 200

package &amp;quot;Akar Proyek (Eager - Dimuat otomatis)&amp;quot; {
    component &amp;quot;&amp;amp;lt;b&amp;amp;gt;AGENT.adoc&amp;amp;lt;/b&amp;amp;gt;\nAturan mutlak\nStruktur &amp;amp;amp; Konvensi&amp;quot; as AGENT
    component &amp;quot;&amp;amp;lt;b&amp;amp;gt;PROMPT_REPRISE.adoc&amp;amp;lt;/b&amp;amp;gt;\nMisi sesi N\nRingkasan N-1&amp;quot; as PROMPT
    component &amp;quot;&amp;amp;lt;b&amp;amp;gt;INDEX.adoc&amp;amp;lt;/b&amp;amp;gt;\nTitik masuk\nAturan + Sesi&amp;quot; as INDEX
    component &amp;quot;&amp;amp;lt;b&amp;amp;gt;*_ESSENTIALS.adoc&amp;amp;lt;/b&amp;amp;gt;
Konteks bisnis
kritik&amp;quot; as ESS
}

package &amp;quot;.agents/ (Lazy - Dimuat saat diminta)&amp;quot; {
    component &amp;quot;&amp;amp;lt;b&amp;amp;gt;sessions/N-*.adoc&amp;amp;lt;/b&amp;amp;gt;\nArsip terperinci\nKeputusan &amp;amp;amp; Keluaran&amp;quot; as SESS
    component &amp;quot;&amp;amp;lt;b&amp;amp;gt;SESSIONS_HISTORY.adoc&amp;amp;lt;/b&amp;amp;gt;\nRingkasan tabel\nTanggal/Jenis/Nilai&amp;quot; as HIST
    component &amp;quot;&amp;amp;lt;b&amp;amp;gt;PROCEDURES.adoc&amp;amp;lt;/b&amp;amp;gt;
Templates akhir sesi
6 langkah&amp;quot; as PROC
    component &amp;quot;&amp;amp;lt;b&amp;amp;gt;*_REFERENCE.adoc&amp;amp;lt;/b&amp;amp;gt;\nArsitektur lengkap\nReferensi teknis&amp;quot; as REF
    component &amp;quot;&amp;amp;lt;b&amp;amp;gt;COMPLETED_TASKS_ARCHIVE&amp;amp;lt;/b&amp;amp;gt;\nTugas yang selesai\nPer bulan&amp;quot; as ARCH
    component &amp;quot;&amp;amp;lt;b&amp;amp;gt;AGENT_MODUS_OPERANDI.adoc&amp;amp;lt;/b&amp;amp;gt;\nDokumentasi strategi\nMetodologi&amp;quot; as MOD
    component &amp;quot;&amp;amp;lt;b&amp;amp;gt;*_REFERENCE.adoc&amp;amp;lt;/b&amp;amp;gt;
Pengujian boot, partisi A/B
Konteks spesifik&amp;quot; as SPEC
}

AGENT --&amp;amp;gt; PROMPT : &amp;quot;Referensi&amp;quot;
AGENT --&amp;amp;gt; INDEX : &amp;quot;Referensi&amp;quot;
INDEX --&amp;amp;gt; SESS : &amp;quot;Indeks&amp;quot;
INDEX --&amp;amp;gt; HIST : &amp;quot;Indeks&amp;quot;
INDEX --&amp;amp;gt; PROC : &amp;quot;referensi&amp;quot;
INDEX --&amp;amp;gt; ARCH : &amp;quot;referensi&amp;quot;
INDEX --&amp;amp;gt; REF : &amp;quot;referensi&amp;quot;
PROMPT --&amp;amp;gt; SESS : &amp;quot;Arsip N-1&amp;quot;
PROMPT --&amp;amp;gt; ESS : &amp;quot;Konteks bisnis N&amp;quot;

@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;AGENT.adoc&amp;lt;/strong&amp;gt;-- File utama. Pada`cheroliv.com`, itu memiliki 200 baris dan berisi :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Aturan absolut proyek (tanpa commit tanpa izin, tanpa`rm`tanpa konfirmasi)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Struktur proyek dan konvensi kode&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Perintah penting (&amp;lt;code&amp;gt;./gradlew serve&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;./gradlew test&amp;lt;/code&amp;gt;)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Epik dan backlog produk (user stories yang diprioritaskan)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Kriteria kualitas lintas (aksesibilitas, responsif, kompatibilitas)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;di atas`bakery-plugin`, Aturan 0 berbeda :&amp;lt;strong&amp;gt;./gradlew -q publishToMavenLocal` wajib setelah setiap perubahan kode sumber&amp;lt;/strong&amp;gt;. Karena menguji plugin tanpa menerbitkan ulang JAR lokal membuatku kehilangan satu jam untuk men-debug kode yang belum dipaketkan.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;PROMPT_REPRISE.adoc&amp;lt;/strong&amp;gt;-- Misi sesi yang sedang berlangsung. Diperbarui pada akhir setiap sesi, ia berisi:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Nomor sesi dan misi prioritas&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Ringkasan sesi sebelumnya (apa yang telah dilakukan, apa yang masih perlu dilakukan)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Kriteria penerimaan sesi saat ini&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Pengingat teknis spesifik&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;.agents/INDEX.adoc&amp;lt;/strong&amp;gt;-- Titik masuk. Ia merangkum aturan mutlak, sesi terkini, dan terutama&amp;lt;strong&amp;gt;portofolio proyek&amp;lt;/strong&amp;gt;Dikelola dengan metodologi yang sama. Saat ini, lima proyek tercantum di sana:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-none hljs&amp;quot;&amp;gt;----
----
| magic-stick    | Session 23 | SCRIPT_VERIFICATION.adoc | 2026-04-27 |
| bakery-gradle  | Session 11 | TEST_COVERAGE_ANALYSIS   | 2026-04-27 |
| cheroliv.com   | Session 9  | TEST_COVERAGE_ANALYSIS   | 2026-04-27 |
| plantuml-gradle| Session 133| TEST_COVERAGE_ANALYSIS   | 2026-04-23 |
| jhipster-gradle-plugins | Session 1 | TEST_COVERAGE_ANALYSIS | 2026-04-28 |
----

**`*_ESSENTIALS.adoc`** -- Penambahan baru (Session 109, plantuml-plugin) untuk mengoptimalkan lagi konteks Eager. Alih-alih memuat 200 baris konteks bisnis di pool kunci API, saya memuat 50 baris dari inti, dan 150 baris lainnya tetap dalam keadaan LAZY di`*_REFERENCE.adoc`.

Hasil yang diukur: peralihan**~25k tokens EAGER ke ~10k tokens**(kenaikan 60%). Agen tidak lagi membutuhkan pengingat yang boros energi.

==== Rantai yang Hilang : `opencode.json

Saya harus mengakui suatu hal yang hampir saya lupa untuk didokumentasikan. Di atas semua file .adoc ini, terdapat sebuah file JSON sangat kecil yang tanpa-nya tidak ada yang berfungsi. Namanya`opencode.json`dan dia membuat enam baris. Secara literal enam baris.

[source,json]
----
{
  &amp;quot;$schema&amp;quot;: &amp;quot;https://opencode.ai/config.json&amp;quot;,
  &amp;quot;instructions&amp;quot;: [
    &amp;quot;AGENT.adoc&amp;quot;
  ]
}
----

Ini file yang memberitahu Opencode : « Saat mulai, muat`AGENT.adoc`secara otomatis. » Tanpa dia, agen itu merupakan halaman putih persis seperti yang saya gambarkan di awal artikel. Dengan dia, agen sudah memiliki di tangan rules absolut, arsitektur proyek, dan perintah penting — bahkan sebelum saya katakan halo.

Saya menemukan pentingnya file ini secara kebetulan. Tentang`bakery-plugin`, tidak ada. Saya bertanya-tanya mengapa agen tersebut secara konsisten lebih « bingung » pada proyek ini dibandingkan dengan yang lain. Aturan mutlak berada baik di`AGENT.adoc`— tapi`AGENT.adoc`tidak pernah terisi. Agen hanya membaca apa yang saya katakan kepadanya untuk dibaca, secara manual, di setiap sesi. Ini adalah sesi 11 dari`bakery-plugin`ketika saya menyadari ketidakhadirannya`opencode.json`. Saya telah membuatnya — dan sesi 12 dimulai seperti yang lain.

File ini sangat jelas bagi saya sekarang bahwa saya bahkan tidak lagi memikirkan itu. Ini adalah kesalahan klasik dari seorang pengembang yang terlalu familiar dengan alatnya. Hari ini, saya membuatnya secara sistematis *sebelumnya*.`AGENT.adoc`. Ini adalah batu pertama.

==== Dualitas dari `INDEX.adoc

Satu lagi hal halus yang layak diperjelas:`INDEX.adoc`hidup di`.agents/`— sebuah folder yang saya presentasikan sebagai LAZY. Namun, saya menentukannya sebagai EAGER di semua tabel saya. Ada ketegangan yang jelas di sini.

Realitas lapangan: file-file`.agents/INDEX.adoc`sudah dimuat secara otomatis pada awal sesi, sama seperti`AGENT.adoc` et `PROMPT_REPRISE.adoc`. Mereka berada di`.agents/`karena alasan organisasi — jangan menginvasi akar — tapi perilaku mereka EAGER.

Di`plantuml-plugin`, `INDEX.adoc`Memiliki 200 baris dan memuat aturan mutlak *lengkap* dengan histori mereka (pelajaran dari sesi sebelumnya), EPIC dengan skor, dan portofolio proyek. Ini adalah dokumen yang dikonsultasi oleh agen untuk mengetahui « di mana kita berada ». Pada`bakery-plugin`, dia membuat 150 baris dengan roadmap dan sesi terbaru.

Redundansi sengaja di antara`AGENT.adoc` et `INDEX.adoc`Mungkin mengejutkan. Aturan mutlak ada di keduanya. Mengapa? Karena mereka memenuhi dua peran yang berbeda: dalam`AGENT.adoc`, mereka adalah *explicatives* (storytelling dari aturan, pelajaran yang dipelajari) ; dalam`INDEX.adoc`, mereka adalah *eksekutif* (aturan telanjang, tanpa justifikasi, untuk konsultasi cepat). Agen membaca`AGENT.adoc`sekali untuk *memahami*; ia membaca kembali`INDEX.adoc`pada setiap sesi untuk *menerapkan*. Dua penggunaan, dua format.

[plantuml, format=svg, id=diag-dualite-agent-index, alt=&amp;quot;Comparaison entre AGENT.adoc (narratif) et INDEX.adoc (exécutif)&amp;quot;]
----
@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultTextAlignment center

title Dualitas AGENT.adoc ←→ INDEX.adoc
left to right direction

rectangle &amp;quot;AGENT.adoc\n(Akar — EAGER)&amp;quot; as AGENT #E3F2FD {
  rectangle &amp;quot;📖 **Format Narasi**
Storytelling dari aturan
pelajaran yang dipelajari, konteks&amp;quot; as NARR
  rectangle &amp;quot;🏗️ **Arsitektur Lengkap**
Struktur proyek, komponen
Backlog terinci US&amp;quot; as ARCHI
  rectangle &amp;quot;📋 **Aturan Penjelasan**
Mengapa aturan ada
Riwayat insiden&amp;quot; as EXPL
}

rectangle &amp;quot;INDEX.adoc\n(.agents/ — EAGER)&amp;quot; as INDEX #E8F5E9 {
  rectangle &amp;quot;⚡ **Format Eksekutif**
Aturan telanjang, tanpa justifikasi
Konsultasi cepat&amp;quot; as EXEC
  rectangle &amp;quot;📊 **Roadmap &amp;amp;amp; EPICs**\nTabel ringkasan\nProgres, Skor, Prioritas&amp;quot; as ROAD
  rectangle &amp;quot;🌐 **Portofolio Proyek**
Tampilan melintang
5 proyek disinkronkan&amp;quot; as PORT
}

AGENT --&amp;amp;gt; INDEX : &amp;quot;Agent membaca AGENT.adoc
1 kali untuk **memahami**&amp;quot;
INDEX --&amp;amp;gt; AGENT : &amp;quot;Agent membaca kembali INDEX.adoc
setiap session untuk **menerapkan**&amp;quot;

note bottom of AGENT
  Taille max : 200 lignes
end note

note bottom of INDEX
  Taille max : 200 lignes
  Source de vérité en cas de divergence
end note

@enduml
----

Redundansi yang diasumsikan ini adalah pilihan desain. Dia mengonsumsi sekitar 50 baris tambahan token EAGER — namun ia menjamin bahwa agen selalu memiliki aturan di depan mata, termasuk dalam format singkat yang memfasilitasi kepatuhan segera.

=== File LAZY : Manual Pemilik

File-file ini hidup di`.agents/`dan hanya dibaca ketika agen membutuhkannya. Mereka merepresentasikan kekayaan sejati dari metode tersebut, karena mereka mengakumulasi pengetahuan proyek tanpa mencemari konteks saat ini.

[plantuml, format=svg, id=diag-agents-tree, alt=&amp;quot;Arborescence complète du dossier .agents/&amp;quot;]
----
@startuml
skinparam folderBackgroundColor #E3F2FD
skinparam folderBorderColor #1565C0
skinparam fileBackgroundColor #FFF3E0
skinparam fileBorderColor #EF6C00

folder &amp;quot;.agents/&amp;quot; as ROOT {
  file &amp;quot;INDEX.adoc
(EAGER -- 200 baris)&amp;quot; as IDX #E8F5E9
  file &amp;quot;AGENT_SESSION_MANAGER.adoc\n(Sesi template)&amp;quot; as ASM
  file &amp;quot;SESSION_CHECKLIST.adoc
(Kapan mengubah)&amp;quot; as CHK
  file &amp;quot;PROCEDURES.adoc\n(6 langkah + LAZY/EAGER)&amp;quot; as PRO
  file &amp;quot;SESSIONS_HISTORY.adoc\n(Semua sesi)&amp;quot; as HIS

  folder &amp;quot;sesi/&amp;quot; as SESS {
    file &amp;quot;1-chore-migration.adoc&amp;quot; as S1
    file &amp;quot;109-formalisasi-lazy.adoc&amp;quot; as S109 #FFECB3
    file &amp;quot;133-epic11-article.adoc&amp;quot; as S133
    file &amp;quot;... +130 lainnya&amp;quot; as SMORE
  }

  folder &amp;quot;arsip/&amp;quot; as ARCH {
    file &amp;quot;COMPLETED_TASKS_2026-04.adoc&amp;quot; as CTA
    file &amp;quot;SESSIONS_HISTORY_83-95.adoc&amp;quot; as SHIST
    folder &amp;quot;sessions_summaries/&amp;quot; as SUM {
      file &amp;quot;SESSION_64_SUMMARY.adoc&amp;quot; as SU64
      file &amp;quot;SESSION_73_SUMMARY.adoc&amp;quot; as SU73
      file &amp;quot;...&amp;quot; as SUMORE
    }
    folder &amp;quot;prompts_archive/&amp;quot; as PARCH {
      file &amp;quot;PROMPT_REPRISE_S65.adoc&amp;quot; as PR65
      file &amp;quot;PROMPT_REPRISE_S75.adoc&amp;quot; as PR75
      file &amp;quot;...&amp;quot; as PMORE
    }
  }
}

IDX --&amp;amp;gt; SESS : &amp;quot;Indeks&amp;quot;
IDX --&amp;amp;gt; HIS : &amp;quot;Indeks&amp;quot;
IDX --&amp;amp;gt; ARCH : &amp;quot;Referensi&amp;quot;

note right of S109
  Session 109 =
  Formalisation stratégie
  LAZY/EAGER
  Token : ~25k → ~10k
end note

@enduml
----

Struktur direktori di atas menunjukkan struktur sebenarnya dari folder`.agents/`di atas`plantuml-plugin`, proyek paling matang. Perhatikan kedalaman dalam tiga lapisan: file akar (metadat), folder`sessions/`(arsip kronologis), dan file`archives/`(agregasi dan ringkasan). Ini adalah kedalaman yang mengubah tata kelola dari sebuah file TODO sederhana menjadi**memori organisasional lengkap**.

**.agents/sessions/{N}-{titre}.adoc**-- Arsip terperinci setiap sesi. Saat ini :

* `plantuml-plugin`:**133 sesi terarsip**dari sesi 1 hingga 133
* `bakery-plugin` : **11 sesi**
* `magic-stick`:**23 sesi**
* `cheroliv.com`:**9 sesi formal**+ 7 sesi pra-sistem yang direkonstruksi secara retroaktif

Setiap arsip berisi konteks lengkap sesi, keputusan yang diambil, masalah yang dihadapi dan penyelesaiannya, perintah yang dijalankan dan output.

**.agents/SESSIONS_HISTORY.adoc**-- Sebuah tabel ringkasan dari semua sesi dengan skor. Contoh pada`cheroliv.com` :

----

| -6 | 2025-05 | chore | Initialisation projet Gradle/JBake | 7/10 |  1 | 2026-04-25 | chore | Migration gouvernance agent | 8/10 |  7 | 2026-04-27 | debug/fix | Correction publishSite | 9/10 |  8 | 2026-04-27 | analyse | Analyse article 0108 | 7/10

**.agents/COMPLETED_TASKS_ARCHIVE_{mois}.adoc**Tugas yang selesai diarsipkan per bulan, agar tidak membebani backlog aktif. Saat sebuah user story selesai, ia pindah ke sini. Backlog tetap terbaca: maksimal 10 item aktif.

**.agents/PROCEDURES.adoc**-- Mtemplate terperinci prosedur akhir sesi. Panjang, tetapi hanya dibaca satu kali oleh agen ketika ia belajar metode. Selanjutnya, prosedur menjadi mekanis.

**.agents/AGENT_MODUS_OPERANDI.adoc**-- Dokumentasi strategi lengkap. pada`plantuml-plugin`, file ini melakukan**900+ baris**dan sebenarnya bernama`AGENT_METHODOLOGIES.adoc`— Saya telah mengubah nama antara penulisan artikel ini dan pelaksanaannya yang efektif. Jenis divergensi de naming seperti ini tidak terhindarkan dalam sistem artesan yang berkembang. Yang penting adalah konvensi penamaan: jika file dokumentasi *méthode*, maka dimulai dengan`AGENT_`atau sebuah awalan eksplisit. Dia mendokumentasikan metodologi Eager/Lazy, pola yang harus diikuti, dan anti-pola yang harus dihindari. Dia LAZY karena agen tidak perlu membaca ulang seluruh strategi di setiap sesi, hanya ketika ada ambiguïtas.

&amp;amp;lt;/think&amp;amp;gt; (no output, as there is no French text provided to translate)`*_REFERENCE.adoc`Referensi teknis spesifik untuk proyek. pada`magic-stick`, dua file LAZY padat :

* `AB_PARTITION_REFERENCE.adoc`(147 baris) -- Arsitektur Parti A/B GPT, skrip`update-system.sh`, ukuran yang diperkirakan, mekanisme rollback
* `BOOT_TEST_REFERENCE.adoc`(144 baris) -- Prosedur QEMU + VNC untuk menguji proses boot ISO tanpa perangkat fisik, daftar periksa BIOS/UEFI, keterbatasan CI/CD

Di`plantuml-plugin` :

* `ARCHITECTURE.adoc`(134 baris) -- Struktur dari 11 kelas data, poin perhatian (jebakan yang harus dihindari), perintah pengujian yang dioptimalkan
* `API_KEY_POOL_REFERENCE.adoc`-- detail lengkap pool kunci (LAZY sementara`ESSENTIALS`EAGER)

== Agen Khusus: Tim Virtual

Governance tidak hanya berisi file pasif. Saya telah mengformalkan beberapa**peran agen spesialis**dalam file LAZY khusus, yang menentukan workflow yang diharapkan sesuai jenis tugas.

|===
|agen |File |peran |Proyek |**mengkode** |`CODER.adoc` |Implementasi FTL/CSS/JS, tag semantik, kriteria aksesibilitas |cheroliv.com |**SCRUM Master** |`SCRUM_MASTER.adoc` |Perencanaan US, pembagian menjadi subtugas, deteksi dependensi |cheroliv.com |**PlantUML Designer** |`PLANTUML_DESIGNER.adoc` |Pembuatan diagram, sintaksis PUML, integrasi JBake |cheroliv.com
|===

file`CODER.adoc`di atas`cheroliv.com`mengandung aturan konkret : _Hanya satu`&amp;amp;lt;h1&amp;amp;gt;`per halaman_, _Mengawali jalur dengan`${content.rootpath}`_, _Mendeklarasikan bahasa`&amp;amp;lt;html lang=&amp;quot;${content.lang!&amp;quot;fr&amp;quot;}&amp;quot;&amp;amp;gt;`_. Konvensi ini, yang ditulis sekali, dipatuhi secara otomatis oleh agen sejak sesi 1.

File File`SCRUM_MASTER.adoc`Menetapkan struktur hasil kerja:**Tujuan**, **tugas**(koordinat y dengan alokasi),**Kriteria penerimaan**, **Risiko**. Saat saya meminta rencana tindakan, agen menghasilkan struktur ini tanpa saya memintanya. Tata kelola**program**agen.

==== Ketika Agen Spesialis Menjadi Penting

Pembuatan agen spesialis mengikuti kurva alami. Di awal sebuah proyek, Anda tidak memerlukannya —`AGENT.adoc`Cukup lebih dari cukup. Tetapi ketika proyek bertumbuh (katakanlah, di atas 20 sesi), dua sinyal harus memperingatkanmu :

1. Agen mengcampur konvensi dari dua bidang yang berbeda (misalnya: sintaksis PlantUML dan aturan CSS)
2. Anda menghabiskan lebih banyak waktu untuk memperbaiki agen mengenai konvensi yang sudah Anda jelaskan kepadanya 5 kali

pada`cheroliv.com`, itu terjadi di sesi... 1. Ya, sejak awal. Karena proyek ini adalah situs web dengan tiga bahasa (FTL, CSS, JS), konten AsciiDoc, dan diagram PlantUML — tiga bidang yang tidak ada hubungannya. Agen CODER perlu mengetahui ukuran font dan media query; agen PLANTUML_DESIGNER perlu mengetahui sintaks.`@startuml`. Tanpa pemisahan, agen CODER memberikan saya diagram, dan sebaliknya. Kekacauan.

Di atas`jhipster-gradle-plugins`, saya telah membuat dua agen spesial yang disesuaikan untuk pengembangan plugin Gradle :`PLUGIN_DEVELOPER.adoc` et `BACKLOG_MANAGER.adoc`. Yang pertama mengkodekan semua konvensi Kotlin/Gradle (tidak ada`!!`, kelas data untuk model,`@TaskAction`untuk tugas). Yang kedua tahu bahwa`persistence`harus stabil sebelum`assistant`tidak memulai pengembangannya — sebuah dependensi kritis dalam mono-repo

Kesalahan yang harus dihindari: membuat terlalu banyak agen terlalu cepat.`plantuml-plugin`telah menunggu sesi 108 sebelum mengformalisasi agen khusus untuk kolam kunci API. Sebelum itu, konteks bisnis berada di`AGENT.adoc`. Catatan praktis: agen spesialis dapat dibenarkan ketika bidang bisnisnya melebihi 100 baris dokumentasi.

==== Konvensi Penamaan Sesi

Sepertinya detail yang terlihat sepele tetapi menjadi kritis ketika mencapai 100 sesi. Bagaimana menamai file arsip?

Saya belajar dengan cara keras bahwa sebuah konvensi diperlukan — empat proyek, empat format berbeda di awal, dan saya tidak bisa mengikuti. Hari ini, konvensi yang telah saya stabilkan adalah:&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;{N}-{type}-{sujet-kebab-case}.adoc&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;Contoh konkret :

* `1-chore-migration-gouvernance-agent.adoc`— sesi 1, tipe tugas
* `10-solidification-tests.adoc`— sesi 10, tanpa jenis eksplisit (subjek sudah cukup)
* `036-debug-graphify-symlink-epic9.adoc`— sesi 36 dengan nomor 3 digit untuk pengurutan

Nomor sesi adalah kriteria pengurutan utama. Proyek yang menggunakan nomor dengan 3 digit (001, 036, 133) menghindari masalah pengurutan leksikografis ketika melebihi 99. Itulah yang saya gunakan sekarang pada`magic-stick`:`001-init-projet.adoc`, `036-debug-graphify-symlink-epic9.adoc`.

Tipe bersifat opsional dan diturunkan dari kata kunci sesi (debug, feature, refactor, docs, chore, test). Subjek dalam kebab-case adalah bagian paling penting: ia harus memungkinkan untuk menemukan sesi tanpa membuka file. Jika Anda bertanya « Session mana yang di mana kita memperbaiki timeout tes integrasi? », jawabannya adalah`124-fix-timeout-integration-test.adoc`.

Untuk sesi historis yang direkonstruksi (proyek yang lahir sebelum tata kelola), saya menggunakan angka negatif. pada`cheroliv.com`, sesi -6 hingga 0 mencakup seluruh sejarah pra-pemerintahan proyek. Dan untuk sesi « tidak terdokumentasi » atau yang hilang, saya membuat entri dalam`SESSIONS_HISTORY.adoc`tanpa arsip yang sesuai, dengan skor`?`. Ini lebih jujur daripada berpura-pura.

[plantuml, format=svg, id=diag-naming-convention, alt=&amp;quot;Arbre de décision pour le nommage des fichiers de session&amp;quot;]&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultTextAlignment center
skinparam wrapWidth 200&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;title Konvensi Penamaan Sesi
start&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;:Une session se termine;
note right: Trigger &amp;quot;akhir sesi&amp;quot;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;if (Session antérieure\nà la gouvernance ?) then (oui)
  :Numéro &amp;lt;strong&amp;gt;NÉGATIF&amp;lt;/strong&amp;gt;\n-6, -5 &amp;amp;#8230;&amp;amp;#8203; 0;
  note right: Historique\nreconstitué
  :Suffixe : reconstitution;
else (non)
  :Numéro &amp;lt;strong&amp;gt;POSITIF&amp;lt;/strong&amp;gt;\nsur 3 chiffres si &amp;amp;gt; 99;
  note right: 001, 036, 133\npour le tri lexicographique&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;literalblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;:Détecter le **TYPE**;
if (Mots-clés trouvés ?) then (oui)
  :debug / feature / refactor\ndocs / chore / test;
else (non)
  :Omettre le type\n(le sujet suffit);
endif&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;literalblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;  :Formuler le **SUJET** en kebab-case;
  note right
    Ex: fix-timeout-integration-test
    Doit permettre de retrouver
    sans ouvrir le fichier
  end note
endif&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;note right
  • 1-chore-migration-gouvernance.adoc
  • 036-debug-graphify-symlink.adoc
  • 124-fix-timeout-integration-test.adoc
  • 133-epic11-article-blog-kg.adoc
end note&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;if (Session documentée ?) then (oui)
  :Créer archive dans sessions/;
  :Ajouter ligne SESSIONS_HISTORY\navec score X/10;
else (non)
  :Ajouter ligne SESSIONS_HISTORY\navec score &amp;lt;strong&amp;gt;?&amp;lt;/strong&amp;gt;\n&amp;lt;strong&amp;gt;sans archive&amp;lt;/strong&amp;gt;;
  note right: L&amp;amp;#8217;honnêteté\nplutôt que le vide
endif&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;stop
@enduml&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;==== TEST_COVERAGE_ANALYSIS.adoc` — Langkah 5 Dijelaskan

Langkah 5 dari prosedur penutupan sesi adalah yang paling misterius. Dia berkata “Memperbarui`TEST_COVERAGE_ANALYSIS.adoc`jika tes telah ditambahkan atau diubah. » Tapi bagaimana struktur file ini?

pada`plantuml-plugin`, ia telah berkembang dari beberapa baris menjadi struktur lengkap. Berikut bentuk yang stabilnya:

[source]&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;h1 id=&amp;quot;_analyse_de_couverture_de_tests&amp;quot; class=&amp;quot;sect0&amp;quot;&amp;gt;Analyse de Couverture de Tests&amp;lt;/h1&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_suivi_des_tests&amp;quot;&amp;gt;Suivi des Tests&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;table class=&amp;quot;tableblock frame-all grid-all stretch&amp;quot;&amp;gt;
&amp;lt;colgroup&amp;gt;
&amp;lt;col style=&amp;quot;width: 20%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 20%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 20%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 20%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 20%;&amp;quot;&amp;gt;
&amp;lt;/colgroup&amp;gt;
&amp;lt;tbody&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Classe de test&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Type&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Tests&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Statut&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Dernière MAJ&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;PlantumlServiceTest&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;unit&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;45/45&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;✅ PASS&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;2026-04-23&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;ApiKeyPoolTest&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;integration&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;15/15&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;✅ PASS&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;2026-04-20&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_historique_par_session&amp;quot;&amp;gt;Historique par Session&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;| Session | Tests ajoutés | Tests modifiés | Couverture
| 133     | 0             | 2              | 100%
| 132     | 5             | 0              | 100%&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;table class=&amp;quot;tableblock frame-all grid-all stretch&amp;quot;&amp;gt;
&amp;lt;colgroup&amp;gt;
&amp;lt;col style=&amp;quot;width: 25%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 25%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 25%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 25%;&amp;quot;&amp;gt;
&amp;lt;/colgroup&amp;gt;
&amp;lt;tbody&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;----&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Keuntungan bukan file itu sendiri — ini adalah kewajiban untuk &amp;lt;strong&amp;gt;mencatat&amp;lt;/strong&amp;gt; apa yang berubah. Tanpa langkah ini, setelah 50 sesi, Anda tidak lagi mengetahui tes mana yang mencakup apa. Agen juga tidak. File menjadi sumber satu-satunya kebenaran mengenai cakupan tes proyek.&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Untuk proyek tanpa tes tradisional (seperti`magic-stick`yang menguji skrip bash), langkah 5 diganti dengan`SCRIPT_VERIFICATION.adoc`. Mekanisme sama: sebuah file yang melacak status validasi skrip. Sesuaikan langkah 5 dengan proyek Anda, tapi jangan pernah melewatkanlah. Ini adalah jaring keamanan yang mencegah regresi diam-diam.&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Jika proyek Anda tidak memiliki &amp;lt;strong&amp;gt;aucun&amp;lt;/strong&amp;gt; test — tidak unitaire, tidak fungsional, tidak skrip — buatlah tetap file kosong dengan bagian « Kerja yang harus dilakukan: Menetapkan strategi pengujian. » Ini adalah penanda yang akan mengingatkan diri Anda di masa depan bahwa topik ini belum ditangani.&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;[plantuml, format=svg, id=diag-session-flow, alt=&amp;quot;Flux d&amp;amp;#8217;une session type avec Eager/Lazy et agents&amp;quot;]
----
@startuml
skinparam backgroundColor #FEFEFE&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;start&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;:Début session;
note right: L&amp;amp;#8217;agent est une page blanche&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;:Chargement EAGER auto;
note right
  * AGENT.adoc (règles absolues)
  * PROMPT_REPRISE.adoc (mission N)
  * INDEX.adoc (état projet)
end note&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;if (Mission claire ?) then (oui)
  :Exécution directe;
else (non)
  :Charge LAZY sur demande;
  note right
    * SESSIONS_HISTORY.adoc (contexte passé)
    * sessions/{N-1}-&amp;lt;strong&amp;gt;.adoc (décisions)
    * *&amp;lt;em&amp;gt;REFERENCE.adoc (architechture)
  end note
endif&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;:Délégation agent spécialisé ?;&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;if (CODER ?) then (oui)
  :Lit CODER.adoc;
  :Suit conventions FTL/CSS;
elseif (SCRUM Master ?) then (oui)
  :Lit SCRUM_MASTER.adoc;
  :Structure livrable imposée;
elseif (PlantUML ?) then (oui)
  :Lit PLANTUML_DESIGNER.adoc;
  :Syntaxe PUML + intégration;
else (non)
endif&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;:Travail de la session;&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;:Fin de session (trigger utilisateur);&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;:Procédure 6 étapes;
note right
  1. Archive sessions/N-&amp;lt;/strong&amp;gt;.adoc
  2. Maj PROMPT_REPRISE.adoc (N+1)
  3. Maj SESSIONS_HISTORY.adoc
  4. Maj INDEX.adoc
  5. Maj TEST_COVERAGE (si applicable)
  6. Maj COMPLETED_TASKS_ARCHIVE.adoc
end note&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;:Checklist [✅] x 6;&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;stop
@enduml
----&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Diagram di atas menunjukkan siklus hidup penuh sebuah sesi. Poin kunci adalah percabangan setelah pemuatan EAGER: baik misi cukup jelas untuk dieksekusi langsung (80% kasus), baik agen memuat LAZY untuk menyelesaikan ambigu (20% kasus). Diskriminasi ini menghemat token.&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;== Prosedur Akhir Sesi : Aturan Emas&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;=== Mengapa ia sangat penting?&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Tanpa prosedur ini, strategi Eager/Lazy tidak berguna. Dia yang mengubah pekerjaan sesi menjadi informasi yang persisten. Dieksekusi&amp;lt;strong&amp;gt;atas permintaan eksplisit pengguna&amp;lt;/strong&amp;gt;(kata kunci : &amp;quot;akhir sesi&amp;quot;, &amp;quot;saya pergi&amp;quot;, dsb.), dan ia adalah&amp;lt;strong&amp;gt;wajib&amp;lt;/strong&amp;gt;-- tidak ada pengecualian, tidak ada pengabaian.&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;berkas`SESSION_CHECKLIST.adoc`Menentukan metrik sesi ideal :&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;* Durasi : 15-30 menit
* File yang dimodifikasi : 1-3 maksimal
* Pertukaran LLM : 5-10 pesan
* konteks tokens: &amp;amp;lt; 50k&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Dan tanda-tanda bahwa harus mengganti sesi: _LLM mengulangi kesalahan yang sudah diperbaiki&amp;lt;/em&amp;gt;, &amp;lt;em&amp;gt;Lebih dari 3 file yang dimodifikasi secara paralel&amp;lt;/em&amp;gt;, &amp;lt;em&amp;gt;Percakapan &amp;amp;gt; 50 pesan&amp;lt;/em&amp;gt;. Aturan emas:&amp;lt;strong&amp;gt;Lebih baik 5 sesi 20 menit daripada satu sesi 2 jam dengan debugging yang kacau.&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;=== Aliran 6 Langkah (diam-diam)&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;[plantuml, format=svg, id=diag-end-session-flow, alt=&amp;quot;Flux de la procédure de fin de session&amp;quot;]
----
@startuml
skinparam defaultTextAlignment center
skinparam wrapWidth 200
skinparam activityBackgroundColor #E3F2FD&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;start
:L&amp;amp;#8217;utilisateur dit &amp;quot;akhir sesi&amp;quot;;
note right: Mots-clés déclencheurs&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;:Agent détecte le trigger;&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;:Étape 1\nCréer archive\n`.agents/sessions/N-&amp;lt;strong&amp;gt;.adoc`;
note right: Tout le contexte de la session&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;:Étape 2\nMettre à jour\n`PROMPT_REPRISE.adoc`;
note right: Mission N + critères d&amp;amp;#8217;acceptation N+1&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;:Étape 3\nMettre à jour\n`SESSIONS_HISTORY.adoc`;
note right: Ligne récap : # / Date / Type / Sujet / Score&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;:Étape 4\nMettre à jour\n`INDEX.adoc`;
note right: État courant, roadmap, fichiers modifiés&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;:Étape 5\nMettre à jour\n`TEST_COVERAGE_ANALYSIS.adoc`;
note right: Si tests ajoutés ou modifiés&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;:Étape 6\nMettre à jour\n`COMPLETED_TASKS_ARCHIVE.adoc`;
note right: Archiver tâches terminées&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;:Afficher la checklist de confirmation;
note right: Vérifier que chaque [✅] est mérité&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;stop
@enduml
----&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;=== Hasil dari 150+ Sesi&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Berikut hasil dari prosedur ini yang diterapkan secara sistematis pada keempat proyek saya :&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;plantuml-plugin&amp;lt;/strong&amp;gt; :&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;* &amp;lt;strong&amp;gt;133 sesi&amp;lt;/strong&amp;gt;sejak awal proyek
* &amp;lt;strong&amp;gt;240/240 tes LULUS&amp;lt;/strong&amp;gt;(100% cakupan)&amp;amp;#8201;&amp;amp;#8212;&amp;amp;#8201;EPICs 1-7 selesai
* &amp;lt;strong&amp;gt;57 skenario Cucumber BDD&amp;lt;/strong&amp;gt;telah divalidasi
* Aturan keamanan pada file konfigurasi yang berasal dari kesalahan nyata (Session 2 bakery-plugin)&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;plugin bakery&amp;lt;/strong&amp;gt;:&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;* &amp;lt;strong&amp;gt;11 sesi&amp;lt;/strong&amp;gt;dalam dua minggu
* Migrasi Supabase → Firebase selesai (9 tes diperbaiki)
* EPIC 6 (publishProfile) fungsional di produksi
* Aturan 0 dibuat :`publishToMavenLocal`wajib setelah setiap modifikasi&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;tongkat-ajaib&amp;lt;/strong&amp;gt;:&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;* &amp;lt;strong&amp;gt;23 sesi&amp;lt;/strong&amp;gt;untuk membangun sistem live Xubuntu dengan partisi A/B
* ISO pertama yang dihasilkan pada sesi 10
* Tes boot QEMU + VNC yang formal (dokumentasi LAZY 144 baris)
* CI/CD SourceForge fungsional&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;cheroliv.com&amp;lt;/strong&amp;gt; :&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;* &amp;lt;strong&amp;gt;9 sesi formal&amp;lt;/strong&amp;gt;+ rekonstruksi 7 sesi pra-sistem
* Pasal 0101 (OpenCode PATH) diterbitkan
* Article 0108 (ini) ditulis ulang setelah analisis kekurangannya
* Governance lengkap migrasi dari Markdown ke AsciiDoc&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;=== Checklist Akhir&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Setelah eksekusi diam dari 6 langkah, agen harus menampilkan checklist konfirmasi :&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;----&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;✅ Procédure de fin de session exécutée 📋 Checklist :&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;Aturan mutlak&amp;lt;/strong&amp;gt;: tidak ada tahap yang dapat ditandai`[✅]&amp;lt;code&amp;gt;jika file tidak telah benar-benar dimodifikasi dan diverifikasi.&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;== Panduan Bootstrap: Hari 1, Sesi 0&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Anda yakin dengan metode tersebut. Anda ingin menerapkannya pada proyek baru. Mulai dari mana?&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Aku mengalami momen ini pada 28 April 2026. Saya membuka Opencode di`jhipster-gradle-plugins&amp;lt;/code&amp;gt;, monorepo saya dari dua plugin Gradle JHipster. Ini adalah proyek yang sudah ada — kode sudah ada, tugas Gradle berfungsi. Tapi pengelolaan agen? Nol. Halaman kosong. Seperti`plantuml-plugin`pada sesi 1-nya, beberapa bulan lalu.&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Berikut prosedur tepat yang saya ikuti, dan saya akan mengikuti untuk setiap proyek baru. Perhatikan urutan — itu penting.&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;[plantuml, format=svg, id=diag-bootstrap, alt=&amp;quot;Flux de bootstrap en 6 étapes pour initialiser la gouvernance agent sur un nouveau projet&amp;quot;]
----
@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultTextAlignment center
skinparam wrapWidth 250
skinparam activityBackgroundColor #E8F5E9&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;title Bootstrap Tata Kelola — Hari 1, Sesi 0
start
:Étape 0\nCréer &amp;lt;strong&amp;gt;opencode.json&amp;lt;/strong&amp;gt;\n(6 lignes, instructions: AGENT.adoc);
note right: Le pont qui charge\nAGENT.adoc automatiquement&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;:Étape 1\nCréer les dossiers\n&amp;lt;strong&amp;gt;mkdir -p .agents/sessions/ .agents/archives/&amp;lt;/strong&amp;gt;;
note right: Les conteneurs vides\navant que l&amp;amp;#8217;agent écrive dedans&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;:Étape 2\nCréer &amp;lt;strong&amp;gt;AGENT.adoc&amp;lt;/strong&amp;gt;\n(200 lignes, règles absolues);
note right: Le fichier maître\nStructure minimale v1&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;:Étape 3\nCréer &amp;lt;strong&amp;gt;PROMPT_REPRISE.adoc&amp;lt;/strong&amp;gt;\nMission session 1 (max 70 lignes);
note right: Ce que l&amp;amp;#8217;agent doit\nfaire à la prochaine session&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;:Étape 4\nCréer &amp;lt;strong&amp;gt;.agents/INDEX.adoc&amp;lt;/strong&amp;gt;\nRègles exécutives + roadmap;
note right: Point d&amp;amp;#8217;entrée EAGER\ndans le dossier LAZY&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;:Étape 5\nCréer les fichiers LAZY structurants\n6 fichiers : SESSIONS_HISTORY, CHECKLIST, etc.;
note right
  • SESSIONS_HISTORY.adoc
  • SESSION_CHECKLIST.adoc
  • PROCEDURES.adoc
  • AGENT_SESSION_MANAGER.adoc
  • TEST_COVERAGE_ANALYSIS.adoc
  • Agents spécialisés (si besoin)
end note&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;:Étape 6\nAjouter le projet au Portefeuille\nMettre à jour TOUS les INDEX.adoc existants;
note right: Maintenance transverse\nObligatoire mais fastidieuse&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;:✅ Bootstrap terminé\nSession 1 prête;
note right: 20 minutes investies\nDes centaines économisées&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;stop
@enduml
----&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;=== Langkah 0: Buat &amp;lt;code&amp;gt;opencode.json&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Ini adalah file pertama. Tidak`AGENT.adoc&amp;lt;/code&amp;gt;, tidak`INDEX.adoc`.&amp;lt;code&amp;gt;opencode.json`Pertama, untuk alasan sederhana: jika Anda membuat`AGENT.adoc`Pertama, Anda akan lupa membuat jembatan yang memuatnya secara otomatis. Saya telah melakukannya di`bakery-plugin&amp;lt;/code&amp;gt;, saya tahu apa yang saya bicarakan.&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;=== Langkah 1 : Membuat Folder&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;[source,bash]
----
mkdir -p .agents/sessions .agents/archives
----&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Dua folder kosong.&amp;lt;code&amp;gt;sessions/`akan menerima arsip setiap sesi.`archives/`akan menerima `COMPLETED_TASKS_ARCHIVE&amp;lt;/code&amp;gt; bulanan. Folder ini harus ada *sebelum&amp;lt;/strong&amp;gt; agen tersebut perlu menulis ke dalamnya. Agen yang harus membuat folder DAN file sekaligus, merupakan agen yang dapat gagal secara diam.&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;=== Langkah 2: Membuat &amp;lt;code&amp;gt;AGENT.adoc&amp;lt;/code&amp;gt; — File Utama&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Struktur minimal untuk versi pertama (ia akan tumbuh):&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;[source]
----
= {NOM_PROJET} — Directives Agent&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;[CAUTION]
----&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;BERHENTI WAJIB&amp;lt;/strong&amp;gt;sebelum rm, Write, penghapusan :&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;1. BACA file secara penuh
2. Periksa git ls-files
3. Meminta konfirmasi
4. MENUNGGU &amp;quot;ya&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;== proyek&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;nama&amp;lt;/strong&amp;gt;: &amp;amp;#8230;&amp;amp;#8203; &amp;lt;strong&amp;gt;tumpukan&amp;lt;/strong&amp;gt;: &amp;amp;#8230;&amp;amp;#8203; &amp;lt;strong&amp;gt;Dokumentasi&amp;lt;/strong&amp;gt;: AsciiDoc&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;== Aturan Mutlak&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;=== 0. LINGKUNGAN PENGEMBANGAN&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Perintah penting&amp;amp;#8230;&amp;amp;#8203;&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;=== 1. KOMIT/GIT&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Larangan resmi&amp;amp;#8230;&amp;amp;#8203;&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;=== 1b. FILE KONFIGURASI — ATURAN MUTLAK KESELAMATAN&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Jangan pernah menghancurkan&amp;amp;#8230;&amp;amp;#8203;&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;=== 2. UJIAN DI AKHIR SESI&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;larangan resmi&amp;amp;#8230;&amp;amp;#8203;&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;=== 3. PROSEDUR AKHIR SESI&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;6 langkah wajib&amp;amp;#8230;&amp;amp;#8203;&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;== Pengelolaan Konteks — LAZY/EAGER&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;File EAGER / File LAZY&amp;amp;#8230;&amp;amp;#8203;&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Template minimal ini memungkinkan agen untuk memulai. Versi kaya — dengan struktur proyek, komponen kunci, EPIC, backlog — akan datang pada sesi 1, ketika agen sudah memiliki aturan dasar di tangan dan dapat membantu mengkaya dokumen.&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;=== Langkah 3 : Membuat &amp;lt;code&amp;gt;PROMPT_REPRISE.adoc&amp;lt;/code&amp;gt; — Sesi Misi 1&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Sebuah file yang secara eksplisit berkata: « Ini adalah sesi 1, misi yang harus ditentukan. » Maksimal 70 baris, dengan sebuah bagian Sesi 0 (ringkasan bootstrap) dan sebuah bagian Sesi 1 (prioritas yang harus ditentukan bersama pengguna).&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;=== Langkah 4: Buat &amp;lt;code&amp;gt;.agents/INDEX.adoc&amp;lt;/code&amp;gt; — Titik Masuk&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;File yang akan berisi aturan absolut (versi eksekutif) dan tabel sesi. Untuk bootstrap, ia mencantumkan aturan 0 hingga 3 dalam format singkatnya, portofolio proyek (termasuk proyek baru dengan emoji 🆕), dan roadmap kosong yang siap diisi.&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;=== Langkah 5 : Membuat File LAZY yang Struktural&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Secara berurutan:&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;1. &amp;lt;code&amp;gt;.agents/SESSIONS_HISTORY.adoc&amp;lt;/code&amp;gt;— sebuah tabel dengan hanya sesi 0
2. &amp;lt;code&amp;gt;.agents/SESSION_CHECKLIST.adoc&amp;lt;/code&amp;gt;— template « Kapan mengganti sesi
3. &amp;lt;code&amp;gt;.agents/PROCEDURES.adoc&amp;lt;/code&amp;gt;— prosedur 6 tahap lengkap + EAGER/LAZY
4. &amp;lt;code&amp;gt;.agents/AGENT_SESSION_MANAGER.adoc&amp;lt;/code&amp;gt;— template arsip
5. &amp;lt;code&amp;gt;.agents/TEST_COVERAGE_ANALYSIS.adoc&amp;lt;/code&amp;gt;— tabel pelacakan tes, kosong pada awalnya&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Jika proyek Anda memiliki domain bisnis kompleks (seperti`jhipster-gradle-plugins`dengan mono-repo persistence/assistant-nya,) buatlah agen spesialisasi sekarang :&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;1. &amp;lt;code&amp;gt;.agents/PLUGIN_DEVELOPER.adoc&amp;lt;/code&amp;gt;— konvensi kode dan batas dependensi (jika plugin)
2. &amp;lt;code&amp;gt;.agents/BACKLOG_MANAGER.adoc&amp;lt;/code&amp;gt;— struktur perencanaan dan risiko yang teridentifikasi&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Jangan buat agen yang tidak akan Anda gunakan. Agen tanpa konvensi konkret untuk dienkode adalah file mati yang mencemari`.agents/&amp;lt;code&amp;gt;.&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;=== Langkah 6: Menambahkan Proyek ke Portofolio SEMUA Proyek&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Ini adalah langkah yang selalu dilupakan. Setiap`INDEX.adoc`de setiap proyek berisi tabel « Portefeuille de Projets » yang mencantumkan SEMUA proyek dengan metodologi yang sama. Saat Anda membuat proyek baru, Anda harus :&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;1. Menambahkan satu baris ke portofolio proyek baru (logika)
2. Menambahkan satu baris dalam portofolio SEMUA proyek yang ada — ya, semua&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Dari empat (sekarang lima) proyek saya, ini berarti membuka`INDEX.adoc&amp;lt;/code&amp;gt; de &amp;lt;code&amp;gt;magic-stick&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;bakery-gradle&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;cheroliv.com&amp;lt;/code&amp;gt;, `plantuml-gradle`dan menambahkan baris di sana`jhipster-gradle-plugins&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Session 1&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;amp;#8230;&amp;amp;#8203;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;2026-04-28 🆕&amp;lt;code&amp;gt;.&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Ini merepotkan. Ini manual. Ini juga satu-satunya cara untuk memastikan bahwa, terlepas dari proyek yang Anda kerjakan, agen tahu proyek-proyek lain yang ada dan kondisinya. Pada sesi 012 de`magic-stick&amp;lt;/code&amp;gt;, agen menemukan dua ketidaksesuaian dalam portofolio —`bakery-gradle`mempunyai COMPLETED_TASKS_ARCHIVE-nya terlambat, dan`plantuml-gradle`Terdapat selisih antara dokumentasi prosedurnya (5 langkah) dan INDEKSnya (6 langkah). Tanpa tabel lintas ini, ketidaksesuaian ini akan tetap tersembunyi.&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;[plantuml, format=svg, id=diag-portfolio-graph, alt=&amp;quot;Graphe du portefeuille de projets — références croisées entre INDEX.adoc&amp;quot;]
----
@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultTextAlignment center
skinparam nodeBackgroundColor #E3F2FD&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;title Portofolio Proyek — Referensi Silang INDEX.adoc
node &amp;quot;&amp;lt;strong&amp;gt;magic-stick&amp;lt;/strong&amp;gt;\nSession 037\nSCRIPT_VERIFICATION&amp;quot; as MS #E1BEE7
node &amp;quot;&amp;lt;strong&amp;gt;bakery-gradle&amp;lt;/strong&amp;gt;
Sesi 11
TEST_COVERAGE&amp;quot; as BG #FFE0B2
node &amp;quot;&amp;lt;strong&amp;gt;cheroliv.com&amp;lt;/strong&amp;gt;
Sesi 10
TEST_COVERAGE&amp;quot; as CH #C8E6C9
node &amp;quot;plantuml-gradle
Sesi 133
TEST_COVERAGE&amp;quot; as PG #BBDEFB
node &amp;quot;&amp;lt;strong&amp;gt;jhipster-gradle&amp;lt;/strong&amp;gt;
Sesi 1 🆕
UJI_COBERTURAN&amp;quot; as JG #FFCDD2&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;MS -&amp;amp;#8594; BG : INDEX.adoc référence
MS -&amp;amp;#8594; CH : INDEX.adoc référence
MS -&amp;amp;#8594; PG : INDEX.adoc référence
MS -&amp;amp;#8594; JG : INDEX.adoc référence 🆕&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;BG -&amp;amp;#8594; MS : INDEX.adoc référence
BG -&amp;amp;#8594; CH : INDEX.adoc référence
BG -&amp;amp;#8594; PG : INDEX.adoc référence
BG -&amp;amp;#8594; JG : INDEX.adoc référence 🆕&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;CH -&amp;amp;#8594; MS : INDEX.adoc référence
CH -&amp;amp;#8594; BG : INDEX.adoc référence
CH -&amp;amp;#8594; PG : INDEX.adoc référence
CH -&amp;amp;#8594; JG : INDEX.adoc référence 🆕&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;PG -&amp;amp;#8594; MS : INDEX.adoc référence
PG -&amp;amp;#8594; BG : INDEX.adoc référence
PG -&amp;amp;#8594; CH : INDEX.adoc référence
PG -&amp;amp;#8594; JG : INDEX.adoc référence 🆕&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;JG -&amp;amp;#8594; MS : INDEX.adoc référence
JG -&amp;amp;#8594; BG : INDEX.adoc référence
JG -&amp;amp;#8594; CH : INDEX.adoc référence
JG -&amp;amp;#8594; PG : INDEX.adoc référence&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;note bottom of JG
  Quand on ajoute un projet :
  • 4 INDEX.adoc à mettre à jour
  • 1 ligne par portefeuille
  • Coût : 5 minutes
end note&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;legend bottom&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;= Couleur&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;= Projet&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;amp;lt;#E1BEE7&amp;amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;magic-stick — ISO Linux live&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;amp;lt;#FFE0B2&amp;amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;bakery-gradle — Plugin JBake&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;amp;lt;#C8E6C9&amp;amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;cheroliv.com — Site personnel&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;amp;lt;#BBDEFB&amp;amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;plantuml-gradle — Plugin IA&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;amp;lt;#FFCDD2&amp;amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>Mengapa `serve` menampilkan situs jelek sementara `publishSite` sempurna</title>
            <link >https://cheroliv.com/blog/2026/0107_jbake_serve_rendering_bug_fix_post.html</link>
            <pubDate>Wed, 22 Apr 2026 00:00:00 +0000</pubDate>
            <guid isPermaLink="false">blog/2026/0107_jbake_serve_rendering_bug_fix_post.html</guid>
            <description>&amp;lt;div id=&amp;quot;toc&amp;quot; class=&amp;quot;toc&amp;quot;&amp;gt;
&amp;lt;div id=&amp;quot;toctitle&amp;quot;&amp;gt;Table des matières&amp;lt;/div&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel1&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_adegan_empat_screenshot_dua_rendering&amp;quot;&amp;gt;Adegan: empat screenshot, dua rendering&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_render_lokal&amp;quot;&amp;gt;Render lokal&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_dirender_secara_online&amp;quot;&amp;gt;Dirender secara online&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_diagnosa_serve_tidak_menunjuk_ke_direktori_yang_benar&amp;quot;&amp;gt;Diagnosa : &amp;lt;code&amp;gt;serve&amp;lt;/code&amp;gt; tidak menunjuk ke direktori yang benar&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_penyebab_utama_jbake_cli_tidak_berfungsi_seperti_itu&amp;quot;&amp;gt;Penyebab utama: JBake CLI tidak berfungsi seperti itu&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_solusi_argumen_positional_bukan_flag&amp;quot;&amp;gt;Solusi: argumen positional, bukan flag&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_pemeriksaan_cepat_dengan_curl&amp;quot;&amp;gt;Pemeriksaan cepat dengan curl&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_mengapa_bug_ini_jahat&amp;quot;&amp;gt;Mengapa bug ini jahat?&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_kesimpulan&amp;quot;&amp;gt;Kesimpulan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div id=&amp;quot;preamble&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;waktu membaca : 10 minutes&amp;lt;/div&amp;gt;
&amp;lt;p&amp;gt;Apakah Anda pernah mengalami saat ketika situs lokal Anda terlihat tidak ada, tapi setelah dideploy online semuanya menjadi sempurna? Itu yang terjadi dengan plugin Gradle JBake saya. Tugas`serve`Menampilkan tombol berbentuk kotak, teks gelap di latar belakang hitam, dan CSS yang aneh tidak ada. Namun`publishSite`Menghasilkan situs yang sama, itu, sempurna. Penyebabnya bukan CSS, bukan peramban, bukan cache. Ini adalah satu baris kode &amp;lt;code&amp;gt;Kotlin&amp;lt;/code&amp;gt; — dan kebiasaan buruk dengan argumen baris perintah.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;dlist&amp;quot;&amp;gt;
&amp;lt;dl&amp;gt;
&amp;lt;dt class=&amp;quot;hdlist1&amp;quot;&amp;gt;ketuk&amp;lt;/dt&amp;gt;
&amp;lt;dd&amp;gt;
&amp;lt;p&amp;gt;[]&amp;lt;/p&amp;gt;
&amp;lt;/dd&amp;gt;
&amp;lt;/dl&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_adegan_empat_screenshot_dua_rendering&amp;quot;&amp;gt;Adegan: empat screenshot, dua rendering&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Itu terjadi setelah sistem crash. Saya telah membuat empat tangkapan layar untuk membandingkan rendering lokal (&amp;lt;code&amp;gt;./gradlew serve`pada`localhost:8820&amp;lt;/code&amp;gt;) dengan rendering inline (`publishSite`pada GitHub Pages). Dua screenshot per halaman : bagian atas dan bagian bawah.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_render_lokal&amp;quot;&amp;gt;Render lokal&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Atas halaman&amp;lt;/strong&amp;gt;— Tombol &amp;lt;strong&amp;gt;Project&amp;lt;/strong&amp;gt; dan &amp;lt;strong&amp;gt;Template&amp;lt;/strong&amp;gt; adalah&amp;lt;strong&amp;gt;kecil, persegi panjang&amp;lt;/strong&amp;gt;, ke warna standar (biru dan hijau). Kartu &amp;lt;strong&amp;gt;Start Writing&amp;lt;/strong&amp;gt; memiliki sebuah&amp;lt;strong&amp;gt;latar belakang hitam polos&amp;lt;/strong&amp;gt;, tanpa kerangka.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;imageblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;img src=&amp;quot;./images/screen_local1.png&amp;quot; alt=&amp;quot;Rendu local - haut de page&amp;quot;&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Bawah halaman&amp;lt;/strong&amp;gt;— Subjudul &amp;lt;strong&amp;gt;Artikel dan Sumber Daya Terbaru&amp;lt;/strong&amp;gt; adalah&amp;lt;strong&amp;gt;hampir tidak dapat dibaca&amp;lt;/strong&amp;gt;, terlalu gelap di latar hitam. Tanggal di bawah gambar blog (&amp;quot;17 October 2013&amp;quot;, dst.) adalah&amp;lt;strong&amp;gt;tidak hadir&amp;lt;/strong&amp;gt;.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;imageblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;img src=&amp;quot;./images/screen_local2.png&amp;quot; alt=&amp;quot;Rendu local - bas de page&amp;quot;&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_dirender_secara_online&amp;quot;&amp;gt;Dirender secara online&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Bagian atas halaman&amp;lt;/strong&amp;gt;— Tombol-tombol yang sama adalah&amp;lt;strong&amp;gt;besar, domba, hijau neon mencolok&amp;lt;/strong&amp;gt;, teks dalam huruf besar. Kartu memiliki satu&amp;lt;strong&amp;gt;bingkai putih melengkung dengan bayangan jatuh&amp;lt;/strong&amp;gt;.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;imageblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;img src=&amp;quot;./images/screen_online1.png&amp;quot; alt=&amp;quot;Rendu en ligne - haut de page&amp;quot;&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;footer&amp;lt;/strong&amp;gt;— Subjudul adalah&amp;lt;strong&amp;gt;mudah dibaca&amp;lt;/strong&amp;gt;. Tanggal adalah&amp;lt;strong&amp;gt;terlihat&amp;lt;/strong&amp;gt;. Kartu artikel memiliki&amp;lt;strong&amp;gt;sudut melengkung&amp;lt;/strong&amp;gt;dan judul-judul adalah&amp;lt;strong&amp;gt;tebal&amp;lt;/strong&amp;gt;.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;imageblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;img src=&amp;quot;./images/screen_online2.png&amp;quot; alt=&amp;quot;Rendu en ligne - bas de page&amp;quot;&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Refleks pertama: tema. Saya menggunakan sistem tema dinamis (terang, gelap, kontras tinggi) berdasarkan`localStorage`. Mungkin bahwa toko lokal memicu tema kontras tinggi? Tidak. Saya sudah menulis sebelumnya.https://cheroliv.com/blog/2025/0098_preloading_css_variable_post.html[sebuah artikel lengkap tentang pra-pemuatan tema], saya tahu cara kerjanya. Dan kemudian, bahkan dalam mode high-contrast, struktur CSS (tombol oval, bayangan) seharusnya ada di sana. Dia tidak ada di sana.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Refleks kedua: cache browser. Tidak, saya sudah menguji dengan profil bersih. Perbedaannya tetap ada.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Refleks ketiga :`serve`tidak menyajikan file yang sama seperti`publishSite`.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_diagnosa_serve_tidak_menunjuk_ke_direktori_yang_benar&amp;quot;&amp;gt;Diagnosa : &amp;lt;code&amp;gt;serve&amp;lt;/code&amp;gt; tidak menunjuk ke direktori yang benar&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Plugin Gradle saya`bakery`memiliki dua tugas utama:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;code&amp;gt;bake&amp;lt;/code&amp;gt;: menghasilkan situs statis di`build/bake/`&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;code&amp;gt;serve&amp;lt;/code&amp;gt;: menjalankan server web lokal&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;code&amp;gt;publishSite&amp;lt;/code&amp;gt;: tunas`build/bake/`ke GitHub Pages&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Divergensi hanya bisa datang dari`serve`. Si &amp;lt;code&amp;gt;publishSite`mempublikasikan konten yang benar, itu adalah`build/bake/`adalah baik. Jika`serve`menampilkan hal lain, berarti ia tidak berfungsi`build/bake/&amp;lt;/code&amp;gt;.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Mari kita lihat kode. Di`SiteManager.kt`, tugas`serve`adalah didefinisikan seperti ini :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;SiteManager.kt — La tâche serve (version buggy)&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;tasks.register(&amp;quot;serve&amp;quot;, JavaExec::class.java) { task -&amp;amp;gt;
    task.apply {
        mainClass.set(&amp;quot;org.jbake.launcher.Main&amp;quot;)
        classpath = jbakeRuntime
        environment(&amp;quot;GEM_PATH&amp;quot;, jbakeRuntime.asPath)
        jvmArgs(/* ... */)
        args = listOf(
            &amp;quot;-b&amp;quot;, file(site.bake.srcPath).absolutePath,
            &amp;quot;-s&amp;quot;, layout.buildDirectory.get()
                .asFile.resolve(site.bake.destDirPath)
                .absolutePath
        )
    }
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;code&amp;gt;org.jbake.launcher.Main`adalah titik masuk CLI JBake 2.7.0. Ideanya: melewati&amp;lt;/code&amp;gt;-b`untuk folder sumber dan`-s`untuk folder tujuan, lalu jalankan mode server. Kecuali&amp;amp;#8230;&amp;amp;#8203;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_penyebab_utama_jbake_cli_tidak_berfungsi_seperti_itu&amp;quot;&amp;gt;Penyebab utama: JBake CLI tidak berfungsi seperti itu&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;JBake 2.7.0 menunggu beberapa&amp;lt;strong&amp;gt;argumen posisi&amp;lt;/strong&amp;gt;untuk sumber dan tujuan, lalu flags opsional. Tanda tandanya adalah :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-text hljs&amp;quot; data-lang=&amp;quot;text&amp;quot;&amp;gt;jbake &amp;amp;lt;source&amp;amp;gt; &amp;amp;lt;destination&amp;amp;gt; [options]&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Namun di kode saya, saya menulis:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-text hljs&amp;quot; data-lang=&amp;quot;text&amp;quot;&amp;gt;args = listOf(&amp;quot;-b&amp;quot;, &amp;quot;/path/to/site&amp;quot;, &amp;quot;-s&amp;quot;, &amp;quot;/path/to/build/bake&amp;quot;)&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;JBake mengartikannya sebagai :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;olist arabic&amp;quot;&amp;gt;
&amp;lt;ol class=&amp;quot;arabic&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;code&amp;gt;-b&amp;lt;/code&amp;gt;→ parse flag &amp;#39;bake&amp;#39; (boolean), telah dikonsumsi&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;code&amp;gt;/path/to/site&amp;lt;/code&amp;gt;→ menjadi argumen posisi 1 (sumber)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;code&amp;gt;-s&amp;lt;/code&amp;gt;→ mengurai flag &amp;quot;serve&amp;quot; (boolean), server dimulai&amp;lt;strong&amp;gt;segera&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;`/path/to/build/bake`menjadi sebuah argumen yang terasing, diabaikan atau salah ditafsirkan&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Hasil : JBake sedang mengambil`/path/to/site`sebagai sumber,&amp;lt;strong&amp;gt;tidak mempertimbangkan destinasi&amp;lt;/strong&amp;gt;yang disediakan (&amp;lt;code&amp;gt;build/bake&amp;lt;/code&amp;gt;), dan menggunakan repertoar standar-nya (sering`./output`atau salinan sementara). File`css/styles.css`Build tersebut sehingga di-overwrite atau diabaikan, dan CSS default lama (tanpa variabel kustom, tanpa radius, tanpa bayangan) yang disajikan.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Dalam perbandingan,&amp;lt;code&amp;gt;bake&amp;lt;/code&amp;gt; et &amp;lt;code&amp;gt;publishSite`menggunakan&amp;lt;strong&amp;gt;plugin Gradle JBake resmi&amp;lt;/strong&amp;gt;( )`jbake-gradle-plugin&amp;lt;/code&amp;gt;) yang menulis dengan rapi di`build/bake/`via Gradle API. Mereka tidak melewati CLI.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_solusi_argumen_positional_bukan_flag&amp;quot;&amp;gt;Solusi: argumen positional, bukan flag&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Koreksi ini trivial — setelah tahu. Cukup meneruskan sumber dan destinasi sebagai argumen posisional, lalu`-s`akhirnya :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;SiteManager.kt — La tâche serve (version corrigée)&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;args = listOf(
    file(site.bake.srcPath).absolutePath,
    layout.buildDirectory.get()
        .asFile.resolve(site.bake.destDirPath)
        .absolutePath,
    &amp;quot;-s&amp;quot;
)&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Itu saja. Tiga baris diubah, bug telah diperbaiki.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Setelah publikasi lokal plugin ini ()&amp;lt;code&amp;gt;publishToMavenLocal&amp;lt;/code&amp;gt;), un `./gradlew serve`Menjalankan JBake dengan sintaksis yang benar :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-text hljs&amp;quot; data-lang=&amp;quot;text&amp;quot;&amp;gt;jbake /home/user/project/site /home/user/project/build/bake -s&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Dan kali ini, server Jetty yang terintegrasi dengan JBake melayani konten dari`build/bake/`— sama dengan yang dipublikasikan secara online.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
!define RECTANGLE class

package &amp;quot;SEBELUM (bug)&amp;quot; {
  RECTANGLE JBakeCLI1 {
    + args = [&amp;quot;-b&amp;quot;, &amp;quot;situs&amp;quot;, &amp;quot;-s&amp;quot;, &amp;quot;membangun/memanggang&amp;quot;]
  }

  RECTANGLE Source1 {
    + site/
  }

  RECTANGLE Dest1 {
    + ??? (dossier temporaire)
  }

  RECTANGLE Jetty1 {
    + sert mauvais fichiers
  }

  JBakeCLI1 --&amp;amp;gt; Source1 : lit
  JBakeCLI1 -[#red]x Dest1 : destination ignorée
  Jetty1 --&amp;amp;gt; Dest1 : sert
}

package &amp;quot;SETELAH (perbaikan)&amp;quot; {
  RECTANGLE JBakeCLI2 {
    + args = [&amp;quot;situs&amp;quot;, &amp;quot;membangun/memanggang&amp;quot;, &amp;quot;-s&amp;quot;]
  }

  RECTANGLE Source2 {
    + site/
  }

  RECTANGLE Dest2 {
    + build/bake/
  }

  RECTANGLE Jetty2 {
    + sert les bons fichiers
  }

  JBakeCLI2 --&amp;amp;gt; Source2 : lit
  JBakeCLI2 --&amp;amp;gt; Dest2 : écrit dans
  Jetty2 --&amp;amp;gt; Dest2 : sert OK
}
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_pemeriksaan_cepat_dengan_curl&amp;quot;&amp;gt;Pemeriksaan cepat dengan curl&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Agar memastikan bahwa server menyajikan dengan baik konten yang benar, sebuah sederhana`curl`mengonfirmasi bahwa`index.html`mengandung`data-bs-theme=&amp;quot;light&amp;quot;`dan bahwa`build/bake/css/styles.css`beratnya tepat 31 402 byte — identik dengan file yang dihasilkan oleh`bake`.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;$ ./gradlew serve
# Dans un autre terminal :
$ curl -s http://localhost:8820/ | grep data-bs-theme
&amp;amp;lt;html ... data-bs-theme=&amp;quot;light&amp;quot; ...&amp;amp;gt;

$ curl -I http://localhost:8820/css/styles.css
HTTP/1.1 200 OK
Content-Length: 31402
Content-Type: text/css&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Render lokal sekarang&amp;lt;strong&amp;gt;pixel-identik&amp;lt;/strong&amp;gt;ke render dideploy.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_mengapa_bug_ini_jahat&amp;quot;&amp;gt;Mengapa bug ini jahat?&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Kamu tahu mengapa ini sulit ditangkap?&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;olist arabic&amp;quot;&amp;gt;
&amp;lt;ol class=&amp;quot;arabic&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Tidak ada kesalahan eksplisit&amp;lt;/strong&amp;gt;JBake tidak crash. Dia &amp;quot;berfungsi&amp;quot;, hanya dengan sebuah direktori yang salah.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Build berfungsi&amp;lt;/strong&amp;gt;:`./gradlew bake`membuat dengan benar file-file di`build/bake/`.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Penerapan berfungsi&amp;lt;/strong&amp;gt;:`publishSite`mengunggah konten yang baik secara online&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Hanya &amp;lt;code&amp;gt;serve&amp;lt;/code&amp;gt; yang rusak&amp;lt;/strong&amp;gt;: tugas pengembangan, yang selalu digunakan untuk iterasi.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Kebiasaan &amp;lt;code&amp;gt;-key value&amp;lt;/strong&amp;gt;: sebagai seorang developer, kita dikondisikan oleh beberapa tahun CLI Unix (&amp;lt;/code&amp;gt;-o output`, &amp;lt;code&amp;gt;-i input&amp;lt;/code&amp;gt;). JBake CLI adalah sebuah pengecualian — sumber dan destinasi bersifat posisi.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_kesimpulan&amp;quot;&amp;gt;Kesimpulan&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Jika Anda memelihara plugin Gradle yang membungkus JBake (atau alat CLI apa pun),&amp;lt;strong&amp;gt;baca dokumentasi argumen&amp;lt;/strong&amp;gt;meskipun Anda kira Anda mengenal mereka. Sebuah hipotesis implisit (&amp;lt;code&amp;gt;-s&amp;lt;/code&amp;gt;= &amp;quot;set destination&amp;quot;) dapat menguras waktu berjam-jam untuk debug visual.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Di sini, perbaikan itu secara harfiah adalah mengubah:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-text hljs&amp;quot; data-lang=&amp;quot;text&amp;quot;&amp;gt;args = listOf(&amp;quot;-b&amp;quot;, src, &amp;quot;-s&amp;quot;, dest)   // ❌ BUG&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;en :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-text hljs&amp;quot; data-lang=&amp;quot;text&amp;quot;&amp;gt;args = listOf(src, dest, &amp;quot;-s&amp;quot;)         // ✅ FIX&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Tiga token telah dipindahkan, dan situs lokal saya kembali sama indah dengan situs produksi.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Pelajaran&amp;lt;/strong&amp;gt;: ketika rendering berbeda antara lokal dan produksi tanpa alasan jelas, pertimbangkan dulu &amp;lt;strong&amp;gt;pipeline&amp;lt;/strong&amp;gt; — bukan CSS, bukan browser, dan jauh lebih bukan framework. Seringnya langkah sebelum rendering yang menipu.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>OpenCode dan PATH yang tidak lengkap: mengapa alat-alat Anda tidak ada dalam shell agen</title>
            <link >https://cheroliv.com/blog/2026/0106_opencode_path_sdkman_nvm_post.html</link>
            <pubDate>Tue, 21 Apr 2026 00:00:00 +0000</pubDate>
            <guid isPermaLink="false">blog/2026/0106_opencode_path_sdkman_nvm_post.html</guid>
            <description>&amp;lt;div id=&amp;quot;toc&amp;quot; class=&amp;quot;toc&amp;quot;&amp;gt;
&amp;lt;div id=&amp;quot;toctitle&amp;quot;&amp;gt;Table des matières&amp;lt;/div&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel1&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_pemandangan_seorang_agen_buta_di_dunia_alat&amp;quot;&amp;gt;Pemandangan: seorang agen buta di dunia alat&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_anatomi_bug_dua_shell_dua_dunia&amp;quot;&amp;gt;Anatomi bug : dua shell, dua dunia&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_pelaku_zshrc_vs_zshenv&amp;quot;&amp;gt;Pelaku : .zshrc vs .zshenv&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_diagnostik_langkah_demi_langkah&amp;quot;&amp;gt;Diagnostik langkah demi langkah&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_memeriksa_path_agen&amp;quot;&amp;gt;Memeriksa PATH agen&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_2_identifikasi_apa_yang_ada_di_zshrc_tetapi_tidak_ada_di_zshenv&amp;quot;&amp;gt;2. Identifikasi apa yang ada di .zshrc tetapi tidak ada di .zshenv&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_3_memeriksa_jika_zshenv_ada&amp;quot;&amp;gt;3. Memeriksa jika .zshenv ada&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_solusi_zshenv_jalur_yang_tepat&amp;quot;&amp;gt;Solusi: .zshenv + jalur yang tepat&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_membuat_zshenv_dengan_semua_jalur_esensial&amp;quot;&amp;gt;Membuat .zshenv dengan semua jalur esensial&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_mengapa_tidak_source_sdkman_init_sh_di_zshenv&amp;quot;&amp;gt;Mengapa tidak &amp;lt;code&amp;gt;source sdkman-init.sh&amp;lt;/code&amp;gt; di .zshenv?&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_kasus_nvm_ketika_manajer_versi_melupakan_untuk_meninggalkan_jejak&amp;quot;&amp;gt;Kasus NVM: ketika manajer versi melupakan untuk meninggalkan jejak&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_masalah_nvm&amp;quot;&amp;gt;Masalah NVM&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_solusi_membuat_symlink_current_untuk_nvm&amp;quot;&amp;gt;Solusi : membuat symlink &amp;lt;code&amp;gt;current&amp;lt;/code&amp;gt; untuk NVM&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_mengotomatiskan_pembaruan_symlink&amp;quot;&amp;gt;Mengotomatiskan pembaruan symlink&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_tabel_ringkasan_jalur&amp;quot;&amp;gt;Tabel ringkasan jalur&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_jebakan_dan_mitigasi&amp;quot;&amp;gt;Jebakan dan mitigasi&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_pelajaran_yang_dipelajari&amp;quot;&amp;gt;Pelajaran yang dipelajari&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_verifikasi_akhir&amp;quot;&amp;gt;Verifikasi akhir&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_tautan&amp;quot;&amp;gt;Tautan&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_dokumentasi_resmi&amp;quot;&amp;gt;Dokumentasi resmi&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_alat_yang_disebutkan&amp;quot;&amp;gt;Alat yang disebutkan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_untuk_pergi_lebih_jauh&amp;quot;&amp;gt;Untuk pergi lebih jauh&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div id=&amp;quot;preamble&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;waktu membaca : 13 minutes&amp;lt;/div&amp;gt;
&amp;lt;p&amp;gt;Anda menjalankan OpenCode di terminal Anda, semuanya berfungsi. Agen mencoba sebuah`gh`— tidak ditemukan. Satu`java -version`— tidak hadir. Satu`node`— tidak ada di mana-mana. Namun, alat-alat ini memang ada di &amp;lt;em&amp;gt;votre&amp;lt;/em&amp;gt; shell. Masalah? OpenCode menjalankan shell.&amp;lt;strong&amp;gt;non-interaktif&amp;lt;/strong&amp;gt;yang tidak pernah membaca Anda`.zshrc`. Berikut cara mendiagnosis dan memperbaikinya dengan rapi.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;dlist&amp;quot;&amp;gt;
&amp;lt;dl&amp;gt;
&amp;lt;dt class=&amp;quot;hdlist1&amp;quot;&amp;gt;tok&amp;lt;/dt&amp;gt;
&amp;lt;dd&amp;gt;
&amp;lt;p&amp;gt;[]&amp;lt;/p&amp;gt;
&amp;lt;/dd&amp;gt;
&amp;lt;/dl&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_pemandangan_seorang_agen_buta_di_dunia_alat&amp;quot;&amp;gt;Pemandangan: seorang agen buta di dunia alat&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Itu adalah malam Selasa. Saya baru saja menginstal.https://opencode.ai[OpenCode], agen AI yang menjanjikan untuk mengubah cara saya coding. Pertama tes : meminta agar menampilkan repositori GitHub saya.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;$ opencode
&amp;amp;gt; Utilise gh pour lister mes repos
❌ bash: gh: command not found&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Aneh`gh`Berfungsi dengan sempurna di terminal saya. Saya mencoba hal lain :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;&amp;amp;gt; Vérifie la version de Java
❌ bash: java: command not found&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Kemudian :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;&amp;amp;gt; Lance le build Gradle
❌ bash: gradle: command not found&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Java, Gradle, Node,&amp;lt;code&amp;gt;gh&amp;lt;/code&amp;gt;— semuanya tak terlihat bagi agen. Terminalku, justru, melihat semuanya. Seolah-olah agen dan aku hidup di dua dunia yang paralel.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Saya menghabiskan berjam-jam untuk memahaminya. Berjam-jam dari`echo $PATH`, de &amp;lt;code&amp;gt;which java&amp;lt;/code&amp;gt;, dari frustrasi yang semakin meningkat. Saya akhirnya menyadari bahwa masalah bukan alat-alat — itu adalah&amp;lt;strong&amp;gt;kerang&amp;lt;/strong&amp;gt;. OpenCode, seperti agen apa pun yang meluncurkan subprocess, bekerja dalam shell&amp;lt;strong&amp;gt;tidak interaktif&amp;lt;/strong&amp;gt;. Dan Zsh, di shell-shell tersebut, sekadar mengabaikan Anda`.zshrc`.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Yang berikut adalah cerita lengkap dari diagnosis tersebut, penyelesaiannya, dan pelajaran yang saya pelajari. Jika Anda menggunakan agen AI — OpenCode, Aider, Cursor, atau bahkan skrip cron — masalah ini akan memengaruhi Anda suatu hari.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_anatomi_bug_dua_shell_dua_dunia&amp;quot;&amp;gt;Anatomi bug : dua shell, dua dunia&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Ketika Anda membuka sebuah terminal, Zsh memperlakukannya seperti sebuah shell.&amp;lt;strong&amp;gt;interaktif&amp;lt;/strong&amp;gt;. Dia memuat`.zshrc`, yang menginisialisasi semuanya : SDKMAN, NVM, pnpm, alias, prompt yang cantik Anda. Lingkungan Anda lengkap.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Tetapi ketika OpenCode mengeksekusi perintah, ia tidak meluncurkan terminal interaktif. Ia meluncurkan sebuah shell&amp;lt;strong&amp;gt;tidak interaktif&amp;lt;/strong&amp;gt;— sebuah shell tugas, tanpa manusia di balik layar. Dan Zsh, dalam konteks ini, melompat.&amp;lt;code&amp;gt;.zshrc&amp;lt;/code&amp;gt;. Dia hanya membaca`.zshenv`.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Mengapa perbedaan ini ada? Karena shell non-interaktif dirancang untuk menjalankan skrip, bukan untuk melayani pengguna manusia. Memuat alias, prompt, dan pengisian dalam skrip yang berjalan di latar belakang akan sia-sia. Masalahnya adalah PATH Anda — informasi paling kritis untuk menemukan executable — sering diinisialisasi di`.zshrc`, tidak dalam`.zshenv`.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE

actor &amp;quot;Anda&amp;quot; as user
actor &amp;quot;OpenCode
(agen)&amp;quot; as agent
participant &amp;quot;Shell\ninteraktif\n(.zshrc di-source)&amp;quot; as ishell
participant &amp;quot;Shell\nnon-interaktif\n(.zshrc diabaikan)&amp;quot; as nshell

user -&amp;amp;gt; ishell : gh, java, node ✅
agent -&amp;amp;gt; nshell : gh, java, node ❌

note right of ishell
  Source .zshrc :
  - SDKMAN init
  - NVM init
  - ~/apps dans PATH
  - pnpm dans PATH
end note

note right of nshell
  .zshrc ignoré !
  PATH = /usr/bin:/bin
  + quelques répertoires défaut
end note

@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Akar masalah :&amp;lt;strong&amp;gt;Zsh hanya men-source &amp;lt;code&amp;gt;.zshrc&amp;lt;/code&amp;gt; untuk shell interaktif&amp;lt;/strong&amp;gt;. OpenCode, seperti agen AI lain yang meluncurkan subproses, menggunakan shell non-interaktif. Shell-shell ini hanya membaca`.zshenv`.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_pelaku_zshrc_vs_zshenv&amp;quot;&amp;gt;Pelaku : .zshrc vs .zshenv&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Zsh memiliki&amp;lt;strong&amp;gt;empat file inisialisasi&amp;lt;/strong&amp;gt;, masing-masing dengan peran yang spesifik. Ini sebuah desain yang elegan—tapi juga inti masalah:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;table class=&amp;quot;tableblock frame-all grid-all stretch&amp;quot;&amp;gt;
&amp;lt;colgroup&amp;gt;
&amp;lt;col style=&amp;quot;width: 5%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 5%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 5%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 5%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 5%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 5%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 5%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 5%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 5%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 5%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 5%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 5%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 5%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 5%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 5%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 5%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 5%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 5%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 5%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 5%;&amp;quot;&amp;gt;
&amp;lt;/colgroup&amp;gt;
&amp;lt;tbody&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;berkas&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Diperoleh ketika&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Peran&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Ubah PATH ?&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;.zshenv&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;Selalu&amp;lt;/strong&amp;gt;(interaktif + non-interaktif + login)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Variabel lingkungan penting, PATH&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;✅ Ya — itu adalah tempat SA&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;.zprofile&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Shell login saja&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Perintah lambat (sekali per sesi)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Mungkin&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;.zshrc&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;Hanya shell interaktif&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Alias, prompt, penyelesaian, alat interaktif&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;❌ Bukan untuk variabel kritis&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;.zlogin&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Shell login (setelah .zshrc)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Pesan sambutan, penyelesaian&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;jarang&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Tabel memberi sebuah petunjuk, tapi harus dipahami secara mendalam. Pikirkan itu seperti bagian-bagian rumah:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;`.zshenv`adalah&amp;lt;strong&amp;gt;aula masuk&amp;lt;/strong&amp;gt;— semua orang lewat di sini, pengunjung atau warga. Jika Anda menaruh sesuatu di sini, semua shell akan dapat melihatnya.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;`.zshrc`adalah&amp;lt;strong&amp;gt;ruang tamu&amp;lt;/strong&amp;gt;— hanya penghuni (shell interaktif) yang masuk. Pengunjung (shell non-interaktif) tetap berada di aula.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;code&amp;gt;.zprofile&amp;lt;/code&amp;gt; et `.zlogin`adalah bagian khusus untuk login shell (seperti saat Anda login melalui SSH)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Tragisnya, kebanyakan dari kita menaruh PATH di ruang tamu. Dan agen AI, ia, tidak pernah berhak masuk ke sana.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Masalah dalam satu diagram :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE

start

if (Shell interactif ?) then (Oui)
  :Source .zshenv;
  :Source .zprofile;
  :Source .zshrc;
  :Source .zlogin;
  note right
    SDKMAN ✅
    NVM ✅
    ~/apps ✅
    pnpm ✅
  end note
else (Non — shell non-interactif)
  :Source .zshenv uniquement;
  note right
    SDKMAN ❌
    NVM ❌
    ~/apps ❌
    pnpm ❌
  end note
endif

stop

@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Ketika Anda membuka terminal, Zsh bersifat interaktif: ia membaca`.zshrc`, semuanya berfungsi. Ketika OpenCode meluncurkan shell untuk menjalankan perintah, Zsh adalah non-interaktif: ia melompat`.zshrc`, hanya membaca`.zshenv`. Et si `.zshenv`tidak ada atau tidak mengandung PATH — ini adalah gurun.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;admonitionblock note&amp;quot;&amp;gt;
&amp;lt;table&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;icon&amp;quot;&amp;gt;
&amp;lt;i class=&amp;quot;fa icon-note&amp;quot; title=&amp;quot;Note&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Mengapa Zsh melakukan itu ?&amp;lt;/strong&amp;gt;Ini adalah pilihan desain yang diwarisi dari Unix. Shell non-interaktif harus&amp;lt;strong&amp;gt;cepat&amp;lt;/strong&amp;gt; et &amp;lt;strong&amp;gt;dapat direproduksi&amp;lt;/strong&amp;gt;. Memuat alias, prompt berwarna, dan inisialisasi berat dari SDKMAN dalam skrip cron atau agen AI akan lambat dan rapuh. Oleh karena itu, pemisahan itu logis :`.zshenv`utamanya (variabel, PATH)`.zshrc`untuk kenyamanan (aliases, prompt, complétons). Masalah terjadi ketika kita menempatkan yang penting dalam kenyamanan.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_diagnostik_langkah_demi_langkah&amp;quot;&amp;gt;Diagnostik langkah demi langkah&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Sebelum memperbaiki, kamu harus memahami persis apa yang hilang. Berikut adalah metode diagnostik yang dapat direproduksi — simpan sebagai favorit jika Anda bekerja dengan agen AI.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_memeriksa_path_agen&amp;quot;&amp;gt;Memeriksa PATH agen&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Kita membandingkan apa yang dilihat oleh terminal interaktif Anda dengan apa yang dilihat oleh shell non-interaktif — yaitu apa yang dilihat oleh OpenCode.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;# Dans votre terminal interactif (tout fonctionne)
echo $PATH | tr &amp;#39;:&amp;#39; &amp;#39;\n&amp;#39; | grep -v &amp;#39;^/usr&amp;#39; | sort&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Hasil tipikal :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;/home/cheroliv/apps
/home/cheroliv/.nvm/versions/node/v22.19.0/bin
/home/cheroliv/.sdkman/candidates/java/current/bin
/home/cheroliv/.sdkman/candidates/gradle/current/bin
/home/cheroliv/.local/share/pnpm
/home/cheroliv/.local/bin&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Sekarang, mari kita mensimulasikan apa yang dilihat agen—sebuah shell non-interaktif:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;# Shell non-interactif : pas de .zshrc
zsh -c &amp;#39;echo $PATH&amp;#39; | tr &amp;#39;:&amp;#39; &amp;#39;\n&amp;#39; | grep -v &amp;#39;^/usr&amp;#39; | sort&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Hasil :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;/home/cheroliv/.local/bin&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Lima dari enam jalur telah menghilang.&amp;lt;/strong&amp;gt;Agen dipotong 83% dari lingkungannya. Seolah-olah diminta untuk memasak tanpa setengah dari peralatan Anda — Anda bisa merebus air, tetapi tidak banyak lagi.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;admonitionblock warning&amp;quot;&amp;gt;
&amp;lt;table&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;icon&amp;quot;&amp;gt;
&amp;lt;i class=&amp;quot;fa icon-warning&amp;quot; title=&amp;quot;Warning&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Perintah`zsh -c &amp;#39;echo $PATH&amp;#39;`adalah Anda&amp;lt;strong&amp;gt;alat diagnostik nomor 1&amp;lt;/strong&amp;gt;. Jika alat yang Anda gunakan sehari-hari tidak ada dalam hasil, agen AI Anda juga tidak akan dapat melihatnya. Uji sebelum dan setelah setiap perubahan`.zshenv`.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_2_identifikasi_apa_yang_ada_di_zshrc_tetapi_tidak_ada_di_zshenv&amp;quot;&amp;gt;2. Identifikasi apa yang ada di .zshrc tetapi tidak ada di .zshenv&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Sekarang, kami mencari pelaku di`.zshrc`. Kita memfilter baris yang menyebutkan alat kita :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;grep -n &amp;#39;apps\|SDKMAN\|NVM\|PNPM\|PATH&amp;#39; ~/.zshrc&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Seringkali ditemukan :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;119: PATH=&amp;quot;/usr/bin/python3:$HOME/apps:$PATH&amp;quot;           # ← pas exporté !
171: export NVM_DIR=&amp;quot;$HOME/.nvm&amp;quot;
172: [ -s &amp;quot;$NVM_DIR/nvm.sh&amp;quot; ] &amp;amp;amp;&amp;amp;amp; \. &amp;quot;$NVM_DIR/nvm.sh&amp;quot;   # ← NVM init
176: nvm use --lts --silent                              # ← active une version
179: export PNPM_HOME=&amp;quot;/home/cheroliv/.local/share/pnpm&amp;quot;
187: export SDKMAN_DIR=&amp;quot;$HOME/.sdkman&amp;quot;
188: [[ -s &amp;quot;$HOME/.sdkman/bin/sdkman-init.sh&amp;quot; ]] &amp;amp;amp;&amp;amp;amp; source &amp;quot;$HOME/.sdkman/bin/sdkman-init.sh&amp;quot;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Semua ini adalah&amp;lt;strong&amp;gt;tidak terlihat&amp;lt;/strong&amp;gt;untuk OpenCode. Dan le`PATH`tidak bahkan dari baris 119`export`é — dia tidak pernah keluar dari shell saat ini. Ini adalah sebuah rincian teknis krusial : sebuah variabel tanpa`export`Tetap lokal untuk shell yang mendefinisikannya. Sub-shell — seperti yang diluncurkan oleh OpenCode — tidak pernah mewarisinya.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_3_memeriksa_jika_zshenv_ada&amp;quot;&amp;gt;3. Memeriksa jika .zshenv ada&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;cat ~/.zshenv 2&amp;amp;gt;/dev/null || echo &amp;quot;FICHIER ABSENT&amp;quot;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Jika jawabannya adalah « FICHIER ABSENT », itulah tempatnya semuanya terjadi.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_solusi_zshenv_jalur_yang_tepat&amp;quot;&amp;gt;Solusi: .zshenv + jalur yang tepat&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Strategi ini sederhana, tapi membutuhkan ketelitian: menaruh di dalam`.zshenv` &amp;lt;strong&amp;gt;hanya&amp;lt;/strong&amp;gt;jalan penting, tanpa melakukan source pada skrip inisialisasi yang berat. Kita tidak memindahkan`.zshrc`dalam`.zshenv`— inti diambil.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_membuat_zshenv_dengan_semua_jalur_esensial&amp;quot;&amp;gt;Membuat .zshenv dengan semua jalur esensial&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;`.zshenv`adalah&amp;lt;strong&amp;gt;file tunggal&amp;lt;/strong&amp;gt;bahwa Zsh menjamin untuk menyource di&amp;lt;strong&amp;gt;semua&amp;lt;/strong&amp;gt;les konteks. Itu lah tempat PATH harus pergi.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;export SDKMAN_DIR=&amp;quot;$HOME/.sdkman&amp;quot;
export NVM_DIR=&amp;quot;$HOME/.nvm&amp;quot;
export PNPM_HOME=&amp;quot;$HOME/.local/share/pnpm&amp;quot;
export PATH=&amp;quot;$HOME/apps:$HOME/.sdkman/candidates/java/current/bin:$HOME/.sdkman/candidates/gradle/current/bin:$HOME/.nvm/current/bin:$PNPM_HOME:$HOME/.local/bin:$HOME/.local/share/JetBrains/Toolbox/scripts:/usr/bin/python3:$PATH&amp;quot;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;admonitionblock note&amp;quot;&amp;gt;
&amp;lt;table&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;icon&amp;quot;&amp;gt;
&amp;lt;i class=&amp;quot;fa icon-note&amp;quot; title=&amp;quot;Note&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Kita menggunakan`$HOME/.nvm/current/bin`dan tidak`$HOME/.nvm/versions/node/v22.19.0/bin`. Alasan: versi Node berubah. Jalur hard-coded menjadi salah pada versi berikutnya.&amp;lt;code&amp;gt;nvm install&amp;lt;/code&amp;gt;. Lebih banyak detail di bagian berikutnya.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_mengapa_tidak_source_sdkman_init_sh_di_zshenv&amp;quot;&amp;gt;Mengapa tidak &amp;lt;code&amp;gt;source sdkman-init.sh&amp;lt;/code&amp;gt; di .zshenv?&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Pendekatan yang paling menggoda akan hanya mereproduksi di`.zshenv`apa yang kita lakukan di`.zshrc`— meng-source skrip inisialisasi. On&amp;lt;strong&amp;gt;mungkin&amp;lt;/strong&amp;gt;tergoda untuk melakukan :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;# ❌ MAUVAISE IDÉE
[[ -s &amp;quot;$HOME/.sdkman/bin/sdkman-init.sh&amp;quot; ]] &amp;amp;amp;&amp;amp;amp; source &amp;quot;$HOME/.sdkman/bin/sdkman-init.sh&amp;quot;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Masalah :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;lambat&amp;lt;/strong&amp;gt;:`sdkman-init.sh`Membuat resolusi jaringan dan verifikasi di setiap shell. Dalam shell non-interaktif, ini adalah biaya yang tidak perlu.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;rapuh&amp;lt;/strong&amp;gt;: SDKMAN mengharapkan konteks interaktif. Inisialisasinya dapat gagal secara diam-diam dalam pipa atau subshell.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;tidak berguna&amp;lt;/strong&amp;gt;: SDKMAN menempatkan calon-calonya di`~/.sdkman/candidates/&amp;amp;lt;tool&amp;amp;gt;/current/bin`— symlink stabil yang menunjuk ke versi aktif. Mereka dapat digunakan.&amp;lt;strong&amp;gt;langsung&amp;lt;/strong&amp;gt;.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Pendekatan yang baik: menghindari inisialisasi SDKMAN dan menunjuk langsung ke symlink`current`. Ini adalah kunci dari seluruh solusi :&amp;lt;strong&amp;gt;menggunakan struktur file sebagai kontrak, bukan kode inisialisasi&amp;lt;/strong&amp;gt;.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE

rectangle &amp;quot;Pendekatan LAMBAT\n(source sdkman-init.sh)&amp;quot; as slow {
  card &amp;quot;.zshenv source sdkman-init.sh
2-3 detik per shell
Pemeriksaan jaringan
Risiko kegagalan&amp;quot; as s1 #FDEDEC
}

rectangle &amp;quot;Pendekatan cepat
(symlinks langsung)&amp;quot; as fast {
  card &amp;quot;.zshenv export PATH=...current/bin\n0 ms\nTidak ada jaringan\nTidak ada inisialisasi&amp;quot; as s2 #E8F8E8
}

slow --&amp;amp;gt; fast : Même résultat final\nLe PATH pointe sur current/bin\ndans les deux cas

@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_kasus_nvm_ketika_manajer_versi_melupakan_untuk_meninggalkan_jejak&amp;quot;&amp;gt;Kasus NVM: ketika manajer versi melupakan untuk meninggalkan jejak&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_masalah_nvm&amp;quot;&amp;gt;Masalah NVM&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Ini adalah tempat di mana penelitian telah membawa saya sejauh ini. SDKMAN memiliki desain yang elegan: ketika seseorang melakukan`sdk install java 25.0.2-tem`, ia membuat sebuah symlink:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;~/.sdkman/candidates/java/current -&amp;amp;gt; ~/.sdkman/candidates/java/25.0.2-tem&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Ini symlink adalah&amp;lt;strong&amp;gt;selalu terbaru&amp;lt;/strong&amp;gt;. Arahkan ke dalam`.zshenv`Anda terlindungi, apa pun shell-nya. Indah.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;NVM, dia, tidak membuat&amp;lt;strong&amp;gt;tidak ada&amp;lt;/strong&amp;gt;seperti itu. Tidak ada symlink`current`. Tidak ada titik ancrage yang stabil. Kita harus menunjuk ke jalur versi yang ditetapkan secara keras, yang terlihat seperti ini:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;~/.nvm/versions/node/v22.19.0/bin/node&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Berikutnya`nvm install 24`, jalan ini mati. Anda`.zshenv`Akan menunjuk ke versi yang tidak lagi versi aktif. Ini adalah bom waktu.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;admonitionblock note&amp;quot;&amp;gt;
&amp;lt;table&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;icon&amp;quot;&amp;gt;
&amp;lt;i class=&amp;quot;fa icon-note&amp;quot; title=&amp;quot;Note&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Mengapa NVM melakukan itu?&amp;lt;/strong&amp;gt;NVM berfungsi dengan mengubah secara dinamis variabel PATH setiap`nvm use`. Ini dirancang untuk pengembang yang sering berganti versi, dalam shell interaktif. Symlink`current`tidak ada dalam spesifikasi awal — ini adalah kesalahan desain yang akan kita perbaiki sendiri.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE

package &amp;quot;SDKMAN ✅&amp;quot; {
  [~/.sdkman/candidates/java/current] as sdk_current
  [~/.sdkman/candidates/java/25.0.2-tem/] as java_25
  [~/.sdkman/candidates/java/21.0.7-tem/] as java_21

  sdk_current --&amp;amp;gt; java_25 : symlink
}

package &amp;quot;NVM ❌ (sebelum perbaikan)&amp;quot; as nvm_before {
  [~/.nvm/versions/node/v22.19.0/] as node_22
  [~/.nvm/versions/node/v20.16.0/] as node_20
  note right of node_22
    Pas de symlink current !
    .zshenv doit pointer en dur
    Cassé au prochain nvm install
  end note
}

package &amp;quot;NVM ✅ (setelah koreksi)&amp;quot; as nvm_after {
  [~/.nvm/current] as nvm_current
  [~/.nvm/versions/node/v22.19.0/] as node_22b
  [~/.nvm/versions/node/v20.16.0/] as node_20b

  nvm_current --&amp;amp;gt; node_22b : symlink\n(mis à jour auto)
}

@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_solusi_membuat_symlink_current_untuk_nvm&amp;quot;&amp;gt;Solusi : membuat symlink &amp;lt;code&amp;gt;current&amp;lt;/code&amp;gt; untuk NVM&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Karena NVM tidak melakukannya, kami melakukannya sendiri. Prinsipnya sama dengan SDKMAN — sebuah symlink.`current`yang selalu menunjuk ke versi aktif. Kita membuatnya sekali, lalu mengotomatiskannya agar ia dapat memperbarui dirinya sendiri.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;# Créer le symlink initial
ln -sfn &amp;quot;$HOME/.nvm/versions/node/v22.19.0&amp;quot; &amp;quot;$HOME/.nvm/current&amp;quot;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Sekarang, dalam`.zshenv`, kita menggunakan :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;$HOME/.nvm/current/bin&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Daripada:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;# ❌ Chemin en dur — cassé au prochain changement de version
$HOME/.nvm/versions/node/v22.19.0/bin&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_mengotomatiskan_pembaruan_symlink&amp;quot;&amp;gt;Mengotomatiskan pembaruan symlink&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Symlink tidak berguna jika tidak diperbarui. Symlink`current`harus diperbarui ketika kita melakukan`nvm use` ou &amp;lt;code&amp;gt;nvm install&amp;lt;/code&amp;gt;. Solusi: sebuah&amp;lt;strong&amp;gt;bungkus&amp;lt;/strong&amp;gt;— sebuah fungsi yang meliputi perintah yang sebenarnya`nvm`dan memperbarui symlink setelah setiap panggilan.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;# À la fin de .zshrc, APRÈS le chargement de NVM
nvm use --lts --silent
ln -sfn &amp;quot;$(nvm_version_path &amp;quot;$(nvm current)&amp;quot;)&amp;quot; &amp;quot;$NVM_DIR/current&amp;quot;

_nvm() {
  command nvm &amp;quot;$@&amp;quot;
  local rc=$?
  ln -sfn &amp;quot;$(nvm_version_path &amp;quot;$(nvm current)&amp;quot;)&amp;quot; &amp;quot;$NVM_DIR/current&amp;quot;
  return $rc
}
alias nvm=&amp;#39;_nvm&amp;#39;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Bagaimana cara kerjanya, secara detail :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE

actor Développeur
participant &amp;quot;nvm wrapper
(_nvm)&amp;quot; as wrapper
participant &amp;quot;nvm nyata\n(perintah nvm)&amp;quot; as realnvm
participant &amp;quot;~/.nvm/current\n(symlink)&amp;quot; as symlink

Développeur -&amp;amp;gt; wrapper : nvm use 20
wrapper -&amp;amp;gt; realnvm : command nvm use 20
realnvm --&amp;amp;gt; wrapper : version activée
wrapper -&amp;amp;gt; symlink : ln -sfn .../v20.16.0 ~/.nvm/current
wrapper --&amp;amp;gt; Développeur : retour

note right of symlink
  Toujours à jour !
  Utilisé par .zshenv
  → accessible par OpenCode
end note

@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Pembungkus`_nvm`panggil perintah yang sebenarnya`nvm`, lalu memperbarui symlink. Alias`nvm=&amp;#39;_nvm&amp;#39;`memastikan bahwa saat mengetik`nvm`, lewat wrapper. Dan saat inisialisasi shell, kita melakukan hal yang sama setelahnya`nvm use --lts`.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;admonitionblock tip&amp;quot;&amp;gt;
&amp;lt;table&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;icon&amp;quot;&amp;gt;
&amp;lt;i class=&amp;quot;fa icon-tip&amp;quot; title=&amp;quot;Tip&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;`nvm_version_path`adalah fungsi internal NVM yang menyelesaikan jalur lengkap sebuah versi. Ini menghindari rekonstruksi jalur secara manual.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_tabel_ringkasan_jalur&amp;quot;&amp;gt;Tabel ringkasan jalur&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Sebelum beralih ke jebakan, sebuah ringkasan visual dari transformasi. Di sebelah kiri, apa yang Anda miliki (semua di`.zshrc`, tidak terlihat bagi agen). Di sebelah kanan, apa yang Anda miliki sekarang (jalan-jalan penting di`.zshenv`, terlihat di mana-mana).&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;table class=&amp;quot;tableblock frame-all grid-all stretch&amp;quot;&amp;gt;
&amp;lt;colgroup&amp;gt;
&amp;lt;col style=&amp;quot;width: 25%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 25%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 25%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 25%;&amp;quot;&amp;gt;
&amp;lt;/colgroup&amp;gt;
&amp;lt;thead&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;alat&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;Sebelum (.zshrc hanya)&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;Sesudah (.zshenv + symlink)&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;Terlihat oleh OpenCode?&amp;lt;/th&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/thead&amp;gt;
&amp;lt;tbody&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;~/apps&amp;lt;/code&amp;gt;(gh, vscode)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;PATH tidak diekspor di .zshrc&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;`$HOME/apps`dalam .zshenv&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;✅&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;SDKMAN Java&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;`sdkman-init.sh`diambil dalam .zshrc&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;`$HOME/.sdkman/candidates/java/current/bin`di .zshenv&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;(Empty)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;SDKMAN Gradle&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;`sdkman-init.sh`disumberkan dalam .zshrc&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;`$HOME/.sdkman/candidates/gradle/current/bin`dalam .zshenv&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;✅&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;NVM Node&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;`nvm use --lts`di .zshrc&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;`$HOME/.nvm/current/bin`dalam .zshenv (symlink)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;✅&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;pnpm&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;`$PNPM_HOME`dalam .zshrc&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;`$PNPM_HOME`dalam .zshenv&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;✅&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;JetBrains Toolbox&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Ditambahkan secara otomatis oleh Toolbox di .zshrc&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;`$HOME/.local/share/JetBrains/Toolbox/scripts`dalam .zshenv&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;✅&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Python 3&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;`/usr/bin/python3`dalam PATH .zshrc&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Secara eksplisit dalam .zshenv&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;✅&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_jebakan_dan_mitigasi&amp;quot;&amp;gt;Jebakan dan mitigasi&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Tidak semua sempurna dengan pendekatan ini. Berikut masalah yang saya temui, dan cara mengatasinya.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;table class=&amp;quot;tableblock frame-all grid-all stretch&amp;quot;&amp;gt;
&amp;lt;colgroup&amp;gt;
&amp;lt;col style=&amp;quot;width: 33.3333%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 33.3333%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 33.3334%;&amp;quot;&amp;gt;
&amp;lt;/colgroup&amp;gt;
&amp;lt;thead&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;jebakan&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;Deskripsi&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;Mitigasi&amp;lt;/th&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/thead&amp;gt;
&amp;lt;tbody&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;PATH duplikat&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Jika .zshenv dan .zshrc menambahkan jalur yang sama, jalur tersebut muncul dua kali&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;`.zshenv`adalah bersumber&amp;lt;strong&amp;gt;sebelum&amp;lt;/strong&amp;gt;.zshrc. Kedua dibaca dalam shell interaktif. PATH dapat duplikat. Ini hanya kosmetik, tidak fungsional. Untuk menghindarinya: jangan menaruh di .zshenv kecuali jalur &amp;lt;em&amp;gt;absents&amp;lt;/em&amp;gt; dari PATH default.&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;NVM : versi tetap dalam .zshenv&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Menaruh`~/.nvm/versions/node/v22.19.0/bin`beton menjadi salah pada berikutnya`nvm install`&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Menggunakan symlink`$HOME/.nvm/current/bin`+ pembungkus`_nvm`dalam .zshrc&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;SDKMAN : source sdkman-init.sh di .zshenv&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Lambat, rapuh, tidak berguna dalam shell non-interaktif&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Menggunakan symlink`candidates/&amp;amp;lt;tool&amp;amp;gt;/current/bin`secara langsung&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Alias hilang setelah modifikasi&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Bungkus`nvm=&amp;#39;_nvm&amp;#39;`.zshrc hanya berlaku setelah dimuat ulang&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Mulai ulang shell atau`source ~/.zshrc`&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;OpenCode tidak melihat perubahan&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Agent telah meluncurkan sub-shell-nya dengan .zshenv lama&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Mulai ulang OpenCode setelah modifikasi .zshenv&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;.zshenv terlalu banyak&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Letakkan fungsi berat atau inisialisasi interaktif di .zshenv&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;.zshenv = variabel lingkungan + PATH hanya. Tidak ada`source`, tidak ada fungsi berat, tidak ada prompt.&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_pelajaran_yang_dipelajari&amp;quot;&amp;gt;Pelajaran yang dipelajari&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;olist arabic&amp;quot;&amp;gt;
&amp;lt;ol class=&amp;quot;arabic&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;zshrc` adalah interaktif, &amp;lt;code&amp;gt;.zshenv&amp;lt;/code&amp;gt; adalah universal&amp;lt;/strong&amp;gt;— Jika variabel harus ada di &amp;lt;em&amp;gt;semua&amp;lt;/em&amp;gt; shell (agen AI, cron, skrip, IDE), ia pergi ke`.zshenv`. Ruang tamu nyaman, tetapi aula masuk adalah satu-satunya tempat di mana semua orang berlalu.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Manajer versi tidak sama&amp;lt;/strong&amp;gt;— SDKMAN membuat symlink`current`secara sengaja. NVM tidak. Kita harus mengisi kekurangan ini secara manual. Ini adalah pelajaran penting: sebelum mengatur PATH Anda, periksa apakah manajer Anda menawarkan titik ancuran yang stabil.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Jangan menyource skrip inisialisasi di .zshenv&amp;lt;/strong&amp;gt;—&amp;lt;code&amp;gt;sdkman-init.sh&amp;lt;/code&amp;gt; et `nvm.sh`dirancang untuk shell interaktif. Mereka lambat dan rapuh dalam konteks non-interaktif. Symlink`current`cukup dan terjadi secara instan.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Selalu menguji dalam shell non-interaktif&amp;lt;/strong&amp;gt;—`zsh -c &amp;#39;echo $PATH&amp;#39;`Simulasi tepat apa yang dilihat oleh agen. Ini adalah tes validasi. Tanpa tes ini, Anda tidak tahu apakah konfigurasi Anda berfungsi untuk agen.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Polah wrapper dapat dipakai ulang.&amp;lt;/strong&amp;gt;— Pola yang sama`_nvm`+`alias nvm=&amp;#39;_nvm&amp;#39;`berlaku untuk setiap alat yang mengubah PATH secara dinamis tanpa meninggalkan jejak yang stabil. Ini adalah alat tambahan di kotak ide Anda.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE

rectangle &amp;quot;sebelum&amp;quot; as avant {
  card &amp;quot;Shell interaktif : ✅
Shell non-interaktif : ❌
OpenCode : ❌
Cron : ❌
Scripts : ❌&amp;quot; as av1 #FDEDEC
}

rectangle &amp;quot;setelah&amp;quot; as apres {
  card &amp;quot;Shell interaktif : ✅
Shell non-interaktif : ✅
OpenCode : ✅
Cron : ✅
Skrip : ✅&amp;quot; as ap1 #E8F8E8
}

avant --&amp;amp;gt; apres : .zshenv +\nsymlink ~/.nvm/current

@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_verifikasi_akhir&amp;quot;&amp;gt;Verifikasi akhir&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Setelah membuat`.zshenv`dan symlink NVM, pastikan bahwa semuanya berfungsi — dalam kedua konteks:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;# Shell interactif (votre terminal)
gh --version &amp;amp;amp;&amp;amp;amp; java -version &amp;amp;amp;&amp;amp;amp; node --version&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;# Shell non-interactif (simulation OpenCode)
zsh -c &amp;#39;gh --version &amp;amp;amp;&amp;amp;amp; java -version &amp;amp;amp;&amp;amp;amp; node --version&amp;#39;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Kedua harus berhasil. Jika ya, agen AI Anda akan melihat alat yang sama dengan Anda. Jika tidak, kembali ke diagnosa langkah demi langkah — Anda mungkin telah melupakan jalur atau symlink NVM tidak diperbarui.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;admonitionblock tip&amp;quot;&amp;gt;
&amp;lt;table&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;icon&amp;quot;&amp;gt;
&amp;lt;i class=&amp;quot;fa icon-tip&amp;quot; title=&amp;quot;Tip&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Otomatisasi tes ini.&amp;lt;/strong&amp;gt;Tambahkan pemeriksaan ini dalam skrip healthcheck yang Anda jalankan setelah setiap pembaruan alat Anda. Sebuah`zsh -c &amp;#39;which java &amp;amp;amp;&amp;amp;amp; which node &amp;amp;amp;&amp;amp;amp; which gh&amp;#39;`di CI, ini adalah jaminan terhadap kejutan.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;em&amp;gt;&amp;lt;em&amp;gt;_ Shell non-interaktif seperti tamu yang diam: ia hanya membaca apa yang ditampilkan di pintu masuk. Jika PATH ada di ruang tamu, ia tidak akan pernah melihatnya. &amp;lt;/em&amp;gt;_&amp;lt;/em&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_tautan&amp;quot;&amp;gt;Tautan&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_dokumentasi_resmi&amp;quot;&amp;gt;Dokumentasi resmi&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://zsh.sourceforge.io/Doc/Release/Files.html&amp;quot;&amp;gt;Zsh dokumentasi : File Startup&amp;lt;/a&amp;gt;— Referensi resmi tentang file inisialisasi Zsh. Itu di mana semuanya dijelaskan, meskipun sering dilupakan.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://opencode.ai/docs/config/&amp;quot;&amp;gt;OpenCode : konfigurasi&amp;lt;/a&amp;gt;— Cara mengkonfigurasi OpenCode dan lingkungan eksekusi.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_alat_yang_disebutkan&amp;quot;&amp;gt;Alat yang disebutkan&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://github.com/nvm-sh/nvm&amp;quot;&amp;gt;NVM di GitHub&amp;lt;/a&amp;gt;— Node Version Manager. Manajer Versi Node.js.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://sdkman.io&amp;quot;&amp;gt;SDKMAN — situs resmi&amp;lt;/a&amp;gt;— SDKMAN! Pengelola versi untuk JVM (Java, Kotlin, Gradle&amp;amp;#8230;&amp;amp;#8203;)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://sdkman.io/install&amp;quot;&amp;gt;SDKMAN : instalasi&amp;lt;/a&amp;gt;— Panduan instalasi SDKMAN.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://cli.github.com&amp;quot;&amp;gt;gh` — CLI GitHub&amp;lt;/a&amp;gt;— Alat baris perintah GitHub, penting bagi setiap pengembang.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://gradle.org&amp;quot;&amp;gt;Gradle — situs resmi&amp;lt;/a&amp;gt;— Sistem build yang kami instal melalui SDKMAN.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://pnpm.io&amp;quot;&amp;gt;pnpm — situs resmi&amp;lt;/a&amp;gt;— Pengelola paket Node.js yang cepat dan hemat ruang.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://www.jetbrains.com/toolbox-app/&amp;quot;&amp;gt;JetBrains Toolbox&amp;lt;/a&amp;gt;— Pengelola IDE JetBrains, yang menambahkan skripnya ke PATH.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_untuk_pergi_lebih_jauh&amp;quot;&amp;gt;Untuk pergi lebih jauh&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://blog.andy2003.org/posts/2022/zsh-dotfiles/&amp;quot;&amp;gt;Zsh dotfiles : panduan lengkap&amp;lt;/a&amp;gt;— Artikel mendalam mengenai pengelolaan dotfiles Zsh.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://wiki.archlinux.org/title/Zsh&amp;quot;&amp;gt;Zsh di ArchWiki&amp;lt;/a&amp;gt;— Salah satu dokumentasi komunitas terbaik tentang Zsh.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://github.com/nvm-sh/nvm/issues&amp;quot;&amp;gt;Masalah NVM GitHub&amp;lt;/a&amp;gt;— Untuk melihat diskusi di sekitar symlink`current`dan masalah dengan PATH.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>Mengintegrasikan Graphify ke dalam alur kerja Gradle: dari Knowledge Graph hingga diagram PlantUML dalam satu perintah.</title>
            <link >https://cheroliv.com/blog/pilot-batch/0105_integrer_graphify_workflow_gradle_post.html</link>
            <pubDate>Sun, 19 Apr 2026 00:00:00 +0000</pubDate>
            <guid isPermaLink="false">blog/pilot-batch/0105_integrer_graphify_workflow_gradle_post.html</guid>
            <description>&amp;lt;div id=&amp;quot;toc&amp;quot; class=&amp;quot;toc&amp;quot;&amp;gt;
&amp;lt;div id=&amp;quot;toctitle&amp;quot;&amp;gt;Table des matières&amp;lt;/div&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel1&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_masalah_diagram_selalu_tertinggal_dari_kode&amp;quot;&amp;gt;Masalah: diagram selalu tertinggal dari kode&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_solusi_alur_deterministik_knowledge_graph_plantuml&amp;quot;&amp;gt;Solusi: alur deterministik Knowledge Graph → PlantUML&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_langkah_1_menginstal_graphify_dan_mengekstrak_knowledge_graph&amp;quot;&amp;gt;Langkah 1: Menginstal Graphify dan mengekstrak Knowledge Graph&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_instalasi&amp;quot;&amp;gt;Instalasi&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_mengonfigurasi_pengecualian&amp;quot;&amp;gt;Mengonfigurasi pengecualian&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_ekstrak_knowledge_graph&amp;quot;&amp;gt;Ekstrak Knowledge Graph&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_langkah_2_plugin_gradle_mengubah_knowledge_graph_menjadi_plantuml&amp;quot;&amp;gt;Langkah 2: Plugin Gradle mengubah Knowledge Graph menjadi PlantUML&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_arsitektur_pipeline&amp;quot;&amp;gt;Arsitektur pipeline&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_komponen_internal&amp;quot;&amp;gt;Komponen internal&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_aturan_rendering&amp;quot;&amp;gt;Aturan rendering&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_langkah_3_penggunaan_sehari_hari&amp;quot;&amp;gt;Langkah 3: Penggunaan sehari-hari&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_diagram_lengkap&amp;quot;&amp;gt;Diagram lengkap&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_filter_berdasarkan_komunitas&amp;quot;&amp;gt;Filter berdasarkan komunitas&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_menyaring_berdasarkan_jenis_sisi&amp;quot;&amp;gt;Menyaring berdasarkan jenis sisi&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_filter_berdasarkan_tipe_node_dan_kepercayaan&amp;quot;&amp;gt;Filter berdasarkan tipe node dan kepercayaan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_direktori_output_khusus&amp;quot;&amp;gt;Direktori output khusus&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_referensi_lengkap_properti&amp;quot;&amp;gt;Referensi lengkap properti&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_pipeline_lengkap_dalam_workflow_gradle&amp;quot;&amp;gt;Pipeline lengkap dalam workflow Gradle&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_tipe_workflow&amp;quot;&amp;gt;Tipe workflow&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_integrasi_ke_dalam_siklus_pengembangan&amp;quot;&amp;gt;Integrasi ke dalam siklus pengembangan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_pembaruan_inkremental&amp;quot;&amp;gt;Pembaruan inkremental&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_dogfooding_plugin_mendokumentasikan_dirinya_sendiri&amp;quot;&amp;gt;Dogfooding: plugin mendokumentasikan dirinya sendiri&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_mengapa_itu_deterministik_dan_mengapa_itu_penting&amp;quot;&amp;gt;Mengapa itu deterministik (dan mengapa itu penting)&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_diagram_pipeline_itu_sendiri&amp;quot;&amp;gt;Diagram pipeline itu sendiri&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_integrasi_dalam_tata_kelola_proyek&amp;quot;&amp;gt;Integrasi dalam tata kelola proyek&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_penyiapan_daftar_periksa_dalam_5_menit&amp;quot;&amp;gt;Penyiapan : daftar periksa dalam 5 menit&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_jebakan_dan_mitigasi&amp;quot;&amp;gt;Jebakan dan mitigasi&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_yang_didapatkan_di_akhir&amp;quot;&amp;gt;Yang didapatkan di akhir&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_tautan&amp;quot;&amp;gt;Tautan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div id=&amp;quot;preamble&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;waktu membaca : 12 minutes&amp;lt;/div&amp;gt;
&amp;lt;p&amp;gt;Kode basis Anda tumbuh. Dependensi antar module bertambah banyak. Dokumentasi arsitektur menjadi ketinggalan zaman bahkan sebelum ditulis. Dan jika build Gradle Anda bisa &amp;lt;em&amp;gt;otomatis&amp;lt;/em&amp;gt; menghasilkan diagram terkini dari struktur kode sebenarnya? Itu persis yang dilakukan oleh pipeline Graphify + PlantUML Gradle Plugin :`graphify . --no-viz`mengekstrak Knowledge Graph,`./gradlew generateKnowledgeGraphDiagram`mengubahnya menjadi diagram PlantUML. Tanpa LLM, tanpa manual, 100% deterministik.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;dlist&amp;quot;&amp;gt;
&amp;lt;dl&amp;gt;
&amp;lt;dt class=&amp;quot;hdlist1&amp;quot;&amp;gt;tik&amp;lt;/dt&amp;gt;
&amp;lt;dd&amp;gt;
&amp;lt;p&amp;gt;[]&amp;lt;/p&amp;gt;
&amp;lt;/dd&amp;gt;
&amp;lt;/dl&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_masalah_diagram_selalu_tertinggal_dari_kode&amp;quot;&amp;gt;Masalah: diagram selalu tertinggal dari kode&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Setiap proyek yang melebihi beberapa ribu baris mengalami sindrom ini:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;olist arabic&amp;quot;&amp;gt;
&amp;lt;ol class=&amp;quot;arabic&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Kita membuat diagram arsitektur di awal proyek&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Kode berkembang, dependensi berubah&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Diagram menjadi kebohongan dekoratif&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Tidak ada yang memperbaruinya karena itu merepotkan&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Pendatang baru mengandalkan hal itu dan membuat kesalahan&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE
skinparam ActivityBackgroundColor #FDEDEC
skinparam ActivityDiamondBackgroundColor #FFF3CD

start

:Diagramme d&amp;#39;architecture créé\n(manuellement, à la main);

:Le code évolue\n(nouvelles classes, nouvelles dépendances);

:Diagramme obsolète\n(ne reflète plus le code);

if (Qui met à jour ?) then (Personne)
    :Documentation mensongère;
    note right: Les nouveaux développeurs\nse basent dessus... et font des erreurs
else (Quelqu&amp;#39;un)
    :Mise à jour manuelle\n(2-3 heures de travail);
    note right: Déjà obsolète\nà la prochaine PR
endif

stop

@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Pertanyaan itu bukan &amp;lt;em&amp;gt;apakah diperlukan diagram?&amp;lt;/em&amp;gt; — semua orang tahu bahwa ya. Pertanyaan adalah :&amp;lt;strong&amp;gt;Siapa yang menjaga mereka tetap terkini?&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Jawaban: tidak ada. Kecuali jika itu otomatis.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_solusi_alur_deterministik_knowledge_graph_plantuml&amp;quot;&amp;gt;Solusi: alur deterministik Knowledge Graph → PlantUML&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Prinsipnya sederhana: alih-alih menggambar diagram dengan tangan, kita&amp;lt;strong&amp;gt;mengasilkan dari struktur kode sebenarnya&amp;lt;/strong&amp;gt;.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle

actor Développeur
component &amp;quot;Graphify\n(pasang graphifyy)&amp;quot; as Graphify
collections &amp;quot;graphify-out/graph.json
(Graf Pengetahuan)&amp;quot; as KGJSON
component &amp;quot;Plugin Gradle PlantUML
(generateKnowledgeGraphDiagram)&amp;quot; as Plugin
component &amp;quot;ParserGrafPengetahuan&amp;quot; as Parser
component &amp;quot;KnowledgeGraphRenderer&amp;quot; as Renderer
component &amp;quot;PlantumlService
(validasi + rendering PNG)&amp;quot; as PS
collections &amp;quot;diagrams/knowledge-graph/
(.puml + .png)&amp;quot; as Output

Développeur --&amp;amp;gt; Graphify : graphify . --no-viz
Graphify --&amp;amp;gt; KGJSON : extrait la structure\ndu code source
Développeur --&amp;amp;gt; Plugin : ./gradlew generateKnowledgeGraphDiagram
Plugin --&amp;amp;gt; Parser : parse(graph.json)
Parser --&amp;amp;gt; Plugin : KnowledgeGraph\n(noeuds + arêtes + communautés)
Plugin --&amp;amp;gt; Renderer : render(graph, filters)
Renderer --&amp;amp;gt; Plugin : code PlantUML\ndéterministe
Plugin --&amp;amp;gt; PS : validateSyntax + generateImage
PS --&amp;amp;gt; Output : .puml + .png

note bottom of Plugin
  Pipeline DÉTERMINISTE
  Aucun appel LLM
  Résultat reproductible
end note

@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Dua perintah. Itu saja.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;# Étape 1 : extraire le Knowledge Graph
graphify . --no-viz

# Étape 2 : générer les diagrammes PlantUML
./gradlew generateKnowledgeGraphDiagram&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Hasilnya ? Berkas`.puml` et &amp;lt;code&amp;gt;.png`dalam`diagrams/knowledge-graph/&amp;lt;/code&amp;gt;, yang tercatat versinya di Git, selalu terkini dengan kode.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_langkah_1_menginstal_graphify_dan_mengekstrak_knowledge_graph&amp;quot;&amp;gt;Langkah 1: Menginstal Graphify dan mengekstrak Knowledge Graph&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_instalasi&amp;quot;&amp;gt;Instalasi&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Graphify adalah alat Python yang menganalisis codebase Anda dan membangun grafik pengetahuan terstruktur :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;# Méthode recommandée
uv tool install graphifyy &amp;amp;amp;&amp;amp;amp; graphify install --platform opencode

# Alternative avec pip
pip install graphifyy &amp;amp;amp;&amp;amp;amp; graphify install --platform opencode&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_mengonfigurasi_pengecualian&amp;quot;&amp;gt;Mengonfigurasi pengecualian&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Membuat file`.graphifyignore`di akar proyek untuk mengecualikan file yang bukan bagian dari logika bisnis :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-gitignore hljs&amp;quot; data-lang=&amp;quot;gitignore&amp;quot;&amp;gt;# Secrets — JAMAIS dans le graphe
*-context.yml
*.env

# Fichiers générés
build/
.gradle/

# Tests fonctionnels
src/functionalTest/&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_ekstrak_knowledge_graph&amp;quot;&amp;gt;Ekstrak Knowledge Graph&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;graphify . --no-viz&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Bendera`--no-viz`melewati pembuatan HTML (tidak diperlukan dalam pipeline Gradle). Hasilnya adalah sebuah file`graphify-out/graph.json`mengandung :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Simpul&amp;lt;/strong&amp;gt;: kelas, fungsi, berkas — dengan jenis dan komunitas mereka&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;tulang ikan&amp;lt;/strong&amp;gt;relasi antar node (EXTRACTED dari kode, INFERRED oleh LLM)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Komunitas&amp;lt;/strong&amp;gt;: kelompokan otomatis dari node yang terhubung&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;Contoh struktur`graph.json`&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-json hljs&amp;quot; data-lang=&amp;quot;json&amp;quot;&amp;gt;{
  &amp;quot;nodes&amp;quot;: [
    {&amp;quot;id&amp;quot;: &amp;quot;0&amp;quot;, &amp;quot;label&amp;quot;: &amp;quot;LlmService&amp;quot;, &amp;quot;file_type&amp;quot;: &amp;quot;code&amp;quot;, &amp;quot;community&amp;quot;: 0},
    {&amp;quot;id&amp;quot;: &amp;quot;1&amp;quot;, &amp;quot;label&amp;quot;: &amp;quot;ApiKeyPool&amp;quot;, &amp;quot;file_type&amp;quot;: &amp;quot;code&amp;quot;, &amp;quot;community&amp;quot;: 0},
    {&amp;quot;id&amp;quot;: &amp;quot;2&amp;quot;, &amp;quot;label&amp;quot;: &amp;quot;PlantumlService&amp;quot;, &amp;quot;file_type&amp;quot;: &amp;quot;code&amp;quot;, &amp;quot;community&amp;quot;: 1}
  ],
  &amp;quot;links&amp;quot;: [
    {&amp;quot;source&amp;quot;: &amp;quot;1&amp;quot;, &amp;quot;target&amp;quot;: &amp;quot;0&amp;quot;, &amp;quot;relation&amp;quot;: &amp;quot;uses&amp;quot;, &amp;quot;confidence&amp;quot;: &amp;quot;EXTRACTED&amp;quot;, &amp;quot;weight&amp;quot;: 0.9},
    {&amp;quot;source&amp;quot;: &amp;quot;0&amp;quot;, &amp;quot;target&amp;quot;: &amp;quot;2&amp;quot;, &amp;quot;relation&amp;quot;: &amp;quot;calls&amp;quot;, &amp;quot;confidence&amp;quot;: &amp;quot;INFERRED&amp;quot;, &amp;quot;weight&amp;quot;: 0.7}
  ]
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;admonitionblock note&amp;quot;&amp;gt;
&amp;lt;table&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;icon&amp;quot;&amp;gt;
&amp;lt;i class=&amp;quot;fa icon-note&amp;quot; title=&amp;quot;Note&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Kode sumber (&amp;lt;code&amp;gt;.kt&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;.java&amp;lt;/code&amp;gt;) dianalisis secara lokal oleh tree-sitter,&amp;lt;strong&amp;gt;tidak dapat dirayakan LLM&amp;lt;/strong&amp;gt;. Hanya file dokumentasi (&amp;lt;code&amp;gt;.adoc&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;.md&amp;lt;/code&amp;gt;) membutuhkan panggilan LLM untuk ekstraksi semantik. Jadi`--update`Saat bekerja dengan kode Kotlin hampir seketika.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_langkah_2_plugin_gradle_mengubah_knowledge_graph_menjadi_plantuml&amp;quot;&amp;gt;Langkah 2: Plugin Gradle mengubah Knowledge Graph menjadi PlantUML&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_arsitektur_pipeline&amp;quot;&amp;gt;Arsitektur pipeline&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;plugin`com.cheroliv.plantuml`mengintegrasikan sebuah tugas`generateKnowledgeGraphDiagram`yang mengubah`graph.json`dalam diagram PlantUML dengan cara&amp;lt;strong&amp;gt;sepenuhnya deterministik&amp;lt;/strong&amp;gt;:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE

participant &amp;quot;Gradle&amp;quot; as G
participant &amp;quot;GenerateKnowledgeGraphDiagramTask&amp;quot; as Task
participant &amp;quot;KnowledgeGraphParser&amp;quot; as Parser
participant &amp;quot;KnowledgeGraphRenderer&amp;quot; as Renderer
participant &amp;quot;PlantumlLayanan&amp;quot; as PS

collections &amp;quot;graphify-out/graph.json&amp;quot; as JSON

G -&amp;amp;gt; Task : execute
Task -&amp;amp;gt; JSON : read
Task -&amp;amp;gt; Parser : parse(graph.json)
Parser --&amp;amp;gt; Task : KnowledgeGraph\n(noeuds + arêtes + communautés)
Task -&amp;amp;gt; Renderer : render(graph, filters)
Renderer --&amp;amp;gt; Task : code PlantUML\n(déterministe)
Task -&amp;amp;gt; PS : validateSyntax(plantumlCode)
alt syntaxe valide
    Task -&amp;amp;gt; PS : generateImage → .png
else syntaxe invalide
    Task -&amp;amp;gt; Task : save .puml avec warning
end

@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_komponen_internal&amp;quot;&amp;gt;Komponen internal&amp;lt;/h3&amp;gt;
&amp;lt;table class=&amp;quot;tableblock frame-all grid-all stretch&amp;quot;&amp;gt;
&amp;lt;colgroup&amp;gt;
&amp;lt;col style=&amp;quot;width: 50%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 50%;&amp;quot;&amp;gt;
&amp;lt;/colgroup&amp;gt;
&amp;lt;thead&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;Komponen&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;Peran&amp;lt;/th&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/thead&amp;gt;
&amp;lt;tbody&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;KnowledgeGraphParser&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Mengurai`graph.json`— mendukung tiga format : graphify asli (&amp;lt;code&amp;gt;nodes&amp;lt;/code&amp;gt;+&amp;lt;code&amp;gt;links&amp;lt;/code&amp;gt;), warisan (&amp;lt;code&amp;gt;communities&amp;lt;/code&amp;gt;), flat. Menyelesaikan IDs numerik menjadi label.&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;KnowledgeGraphRenderer&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Mentransformasi secara deterministik satu`KnowledgeGraph`dalam kode PlantUML. kelompok berdasarkan tipe, komunitas dalam paket, legenda otomatis.&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;GenerateKnowledgeGraphDiagramTask&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Tugas Gradle yang mengorestrasi: parse → render → validate → PNG. Dapat dikonfigurasi melalui properti Gradle.&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;kgmodels.kt&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Model data :`KnowledgeGraph`, &amp;lt;code&amp;gt;KnowledgeGraphNode&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;KnowledgeGraphEdge&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;KnowledgeGraphCommunity&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;EdgeType&amp;lt;/code&amp;gt;.&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;PlantumlService&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Validasi sintaks + rendering PNG (dipakai ulang oleh semua tugas plugin).&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_aturan_rendering&amp;quot;&amp;gt;Aturan rendering&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Renderer menerapkan konvensi visual yang deterministik :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;table class=&amp;quot;tableblock frame-all grid-all stretch&amp;quot;&amp;gt;
&amp;lt;colgroup&amp;gt;
&amp;lt;col style=&amp;quot;width: 33.3333%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 33.3333%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 33.3334%;&amp;quot;&amp;gt;
&amp;lt;/colgroup&amp;gt;
&amp;lt;thead&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;Jenis tepi&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;Notasi PlantUML&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;Arti&amp;lt;/th&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/thead&amp;gt;
&amp;lt;tbody&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Diekstrak&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;-&amp;amp;#8594;&amp;lt;/code&amp;gt;(garis lurus, hitam)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Relasi yang diekstrak dari kode sumber (keyakinan)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;disimpulkan&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;..&amp;amp;gt;&amp;lt;/code&amp;gt;(garis bertitik)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Relation yang disimpulkan oleh LLM (skor kepercayaan)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;abur&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;--x&amp;lt;/code&amp;gt;(garis titik merah)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Hubungan ambigu (perlu dicek)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Komunitasdirender sebagai paket PlantUML dengan palet warna otomatis.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_langkah_3_penggunaan_sehari_hari&amp;quot;&amp;gt;Langkah 3: Penggunaan sehari-hari&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_diagram_lengkap&amp;quot;&amp;gt;Diagram lengkap&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;# Générer le diagramme du Knowledge Graph complet
./gradlew generateKnowledgeGraphDiagram&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;keluaran`diagrams/knowledge-graph/knowledge-graph-full.puml` + &amp;lt;code&amp;gt;.png&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_filter_berdasarkan_komunitas&amp;quot;&amp;gt;Filter berdasarkan komunitas&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;# Une seule communauté
./gradlew generateKnowledgeGraphDiagram \
  -Pplantuml.kg.community=community_0

# Limiter le nombre de noeuds (lisibilité)
./gradlew generateKnowledgeGraphDiagram \
  -Pplantuml.kg.community=community_0 \
  -Pplantuml.kg.maxNodes=15&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_menyaring_berdasarkan_jenis_sisi&amp;quot;&amp;gt;Menyaring berdasarkan jenis sisi&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;# Uniquement les relations certaines (EXTRACTED)
./gradlew generateKnowledgeGraphDiagram \
  -Pplantuml.kg.edgeTypes=EXTRACTED

# Relations certaines + inférées
./gradlew generateKnowledgeGraphDiagram \
  -Pplantuml.kg.edgeTypes=EXTRACTED,INFERRED&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_filter_berdasarkan_tipe_node_dan_kepercayaan&amp;quot;&amp;gt;Filter berdasarkan tipe node dan kepercayaan&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;# Uniquement les classes de code
./gradlew generateKnowledgeGraphDiagram \
  -Pplantuml.kg.nodeTypes=code

# Seuil de confiance minimum (pour les INFERRED)
./gradlew generateKnowledgeGraphDiagram \
  -Pplantuml.kg.minConfidence=0.7&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_direktori_output_khusus&amp;quot;&amp;gt;Direktori output khusus&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;./gradlew generateKnowledgeGraphDiagram \
  -Pplantuml.kg.outputDir=docs/architecture&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_referensi_lengkap_properti&amp;quot;&amp;gt;Referensi lengkap properti&amp;lt;/h3&amp;gt;
&amp;lt;table class=&amp;quot;tableblock frame-all grid-all stretch&amp;quot;&amp;gt;
&amp;lt;colgroup&amp;gt;
&amp;lt;col style=&amp;quot;width: 33.3333%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 33.3333%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 33.3334%;&amp;quot;&amp;gt;
&amp;lt;/colgroup&amp;gt;
&amp;lt;thead&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;sifat&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;default&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;Deskripsi&amp;lt;/th&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/thead&amp;gt;
&amp;lt;tbody&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;plantuml.kg.community&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;(semua)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Filter komunitas berdasarkan nama (cocokan substring)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;plantuml.kg.edgeTypes&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;em&amp;gt;(semua)&amp;lt;/em&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Jenis sisi yang dipisahkan dengan koma :`EXTRACTED`, &amp;lt;code&amp;gt;INFERRED&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;AMBIGUOUS&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;plantuml.kg.minConfidence&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;0.0&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Ambang kepercayaan minimum untuk tepi&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;plantuml.kg.maxNodes&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;em&amp;gt;(tidak terbatas)&amp;lt;/em&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Jumlah maksimum node yang akan ditampilkan&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;plantuml.kg.nodeTypes&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;(semua)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Jenis simpul yang dipisahkan dengan koma (contoh`class`, &amp;lt;code&amp;gt;code&amp;lt;/code&amp;gt;)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;plantuml.kg.outputDir&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;diagrams/knowledge-graph&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Direktori output untuk file`.puml` et &amp;lt;code&amp;gt;.png&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_pipeline_lengkap_dalam_workflow_gradle&amp;quot;&amp;gt;Pipeline lengkap dalam workflow Gradle&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_tipe_workflow&amp;quot;&amp;gt;Tipe workflow&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE
skinparam ActivityBackgroundColor #E8F4FD
skinparam ActivityDiamondBackgroundColor #FFF3CD
skinparam ActivityBorderColor #2C3E50

start

partition &amp;quot;extraksi&amp;quot; {
    :graphify . --no-viz;
    note right: Analyse le code source\ntree-sitter (local, pas de LLM)

    :graphify-out/graph.json;
    note right: Noeuds + arêtes\n+ communautés
}

partition &amp;quot;generasi&amp;quot; {
    :./gradlew generateKnowledgeGraphDiagram;
    note right: Pipeline déterministe\nparse → render → validate → PNG

    if (Filtres actifs ?) then (Oui)
        :Appliquer filtres\n(community, edgeTypes,\nmaxNodes, nodeTypes);
    else (Non)
        :Diagramme complet;
    endif

    :diagrams/knowledge-graph/\nknowledge-graph-full.puml\nknowledge-graph-full.png;
}

partition &amp;quot;INTEGRASI&amp;quot; {
    :git add diagrams/knowledge-graph/;
    :git commit;
    note right: Diagrammes versionnés\ntoujours à jour avec le code
}

stop

@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_integrasi_ke_dalam_siklus_pengembangan&amp;quot;&amp;gt;Integrasi ke dalam siklus pengembangan&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Pipeline ini secara alami terintegrasi ke dalam tahap kunci pengembangan :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE

state &amp;quot;pengembangan&amp;quot; as dev
state &amp;quot;Ekstraksi
graphify . --no-viz&amp;quot; as extract
state &amp;quot;Generasi
./gradlew generateKnowledgeGraphDiagram&amp;quot; as generate
state &amp;quot;Commit
diagram ber-versi&amp;quot; as commit

[*] --&amp;amp;gt; dev
dev --&amp;amp;gt; extract : Code modifié
extract --&amp;amp;gt; generate : graph.json à jour
generate --&amp;amp;gt; commit : .puml + .png générés
commit --&amp;amp;gt; dev : Diagrammes dans le repo

note right of extract
  Quasi instantané sur du code Kotlin
  (tree-sitter, pas de LLM)
end note

note right of generate
  Déterministe
  Pas de LLM
  Résultat reproductible
end note

@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_pembaruan_inkremental&amp;quot;&amp;gt;Pembaruan inkremental&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Ketika kode berubah, tidak membuat kembali seluruh grafik dari nol:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;# Mise à jour incrémentale (fichiers modifiés uniquement)
graphify . --update

# Puis régénérer les diagrammes
./gradlew generateKnowledgeGraphDiagram&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;admonitionblock note&amp;quot;&amp;gt;
&amp;lt;table&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;icon&amp;quot;&amp;gt;
&amp;lt;i class=&amp;quot;fa icon-note&amp;quot; title=&amp;quot;Note&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;code&amp;gt;--update`mengekstrak ulang hanya file yang dimodifikasi (terdeteksi oleh SHA256 dalam`graphify-out/cache/&amp;lt;/code&amp;gt;). Pada kode Kotlin, ini hampir seketika karena tree-sitter bekerja secara lokal tanpa memanggil LLM.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_dogfooding_plugin_mendokumentasikan_dirinya_sendiri&amp;quot;&amp;gt;Dogfooding: plugin mendokumentasikan dirinya sendiri&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Plugin PlantUML ada untuk mengubah prompt menjadi diagram. Dia juga dapat mentransformasi Knowledge Graph dari codebase &amp;lt;em&amp;gt;propre&amp;lt;/em&amp;gt; nya menjadi diagram dokumentasi. Ini adalah &amp;lt;em&amp;gt;dogfooding&amp;lt;/em&amp;gt; : plugin mengonsumsi layanan sendiri.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle

package &amp;quot;Alur normal
(pengguna → diagram)&amp;quot; as normal {
    [Fichier .prompt] as prompt
    [LlmService\n+ ApiKeyPool] as llm1
    [ProcessPlantumlPromptsTask] as task1
    [Diagramme PNG] as out1

    prompt --&amp;amp;gt; task1
    task1 --&amp;amp;gt; llm1
    llm1 --&amp;amp;gt; out1
}

package &amp;quot;Pipeline Knowledge Graph
(deterministik, tanpa LLM)&amp;quot; as kg {
    [graphify-out/graph.json] as kgjson
    [KnowledgeGraphParser] as parser
    [KnowledgeGraphRenderer] as renderer
    [PlantumlService] as ps
    [Diagramme PNG\n(documentation du plugin)] as out2

    kgjson --&amp;amp;gt; parser
    parser --&amp;amp;gt; renderer
    renderer --&amp;amp;gt; ps
    ps --&amp;amp;gt; out2
}

package &amp;quot;Pipeline Dogfooding
(LLM → dokumentasi plugin)&amp;quot; as dogfood {
    [GraphifyPromptAdapter] as gpa
    [Fichiers .prompt\nauto-générés] as auto_prompt
    [LlmService\n+ ApiKeyPool] as llm2
    [ProcessPlantumlPromptsTask] as task2
    [Diagramme PNG\n(documentation LLM)] as out3

    kgjson --&amp;amp;gt; gpa
    gpa --&amp;amp;gt; auto_prompt
    auto_prompt --&amp;amp;gt; task2
    task2 --&amp;amp;gt; llm2
    llm2 --&amp;amp;gt; out3
}

note &amp;quot;Sama LlmService, sama ApiKeyPool,
sama PlantumlService
— duplikasi nol&amp;quot; as N

@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Tugas Gradle terkait :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;table class=&amp;quot;tableblock frame-all grid-all stretch&amp;quot;&amp;gt;
&amp;lt;colgroup&amp;gt;
&amp;lt;col style=&amp;quot;width: 33.3333%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 33.3333%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 33.3334%;&amp;quot;&amp;gt;
&amp;lt;/colgroup&amp;gt;
&amp;lt;thead&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;tugas&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;LLM ?&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;Deskripsi&amp;lt;/th&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/thead&amp;gt;
&amp;lt;tbody&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;generateKnowledgeGraphDiagram&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Tidak&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Transformasikan`graph.json`dalam PlantUML (deterministik)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;generateDiagramDocs&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Ya&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Menghasilkan beberapa`.prompt`dari grafik, mereka memproses melalui LLM (dogfooding)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;# Documentation déterministe (rapide, pas de LLM)
./gradlew generateKnowledgeGraphDiagram

# Documentation LLM (plus riche, consomme des tokens)
./gradlew generateDiagramDocs&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_mengapa_itu_deterministik_dan_mengapa_itu_penting&amp;quot;&amp;gt;Mengapa itu deterministik (dan mengapa itu penting)&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Titik kunci dari pipeline`generateKnowledgeGraphDiagram`&amp;amp;lt;/think&amp;amp;gt;&amp;lt;strong&amp;gt;Dia tidak memanggil LLM apa pun.&amp;lt;/strong&amp;gt;. transformasi`graph.json`→ PlantUML adalah fungsi murni.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle

rectangle &amp;quot;Pipeline deterministik
(generateKnowledgeGraphDiagram)&amp;quot; as det {
    [graph.json] as json
    [KnowledgeGraphParser] as parser
    [KnowledgeGraphRenderer] as renderer
    [PlantUML code] as puml

    json --&amp;amp;gt; parser : parse
    parser --&amp;amp;gt; renderer : KnowledgeGraph
    renderer --&amp;amp;gt; puml : render (fonction pure)
}

rectangle &amp;quot;Pipeline LLM
(processPlantumlPrompts)&amp;quot; as llm {
    [.prompt] as prompt
    [LlmService] as llmSvc
    [ChatModel] as model
    [PlantUML code] as puml2

    prompt --&amp;amp;gt; llmSvc
    llmSvc --&amp;amp;gt; model : API call
    model --&amp;amp;gt; puml2 : réponse non-déterministe
}

note bottom of det
  Même entrée → même sortie
  Pas de latence réseau
  Pas de coût en tokens
  Reproductible en CI
end note

note bottom of llm
  Même entrée → sortie variable
  Latence réseau (1-5s)
  Coût en tokens
  Nécessite une clave API
end note

@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Keuntungan konkret :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;table class=&amp;quot;tableblock frame-all grid-all stretch&amp;quot;&amp;gt;
&amp;lt;colgroup&amp;gt;
&amp;lt;col style=&amp;quot;width: 50%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 50%;&amp;quot;&amp;gt;
&amp;lt;/colgroup&amp;gt;
&amp;lt;thead&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;Keuntungan&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;dampak&amp;lt;/th&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/thead&amp;gt;
&amp;lt;tbody&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Reproduktivitas&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;bahkan`graph.json`→ diagram yang sama. Tepat. Setiap kali.&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;biaya nol&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Tanpa panggilan LLM = tanpa token = tanpa tagihan API.&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Nol latensi&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Parse + render membutuhkan ~100ms, bukan 1-5 detik.&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Kompatibel CI&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Tidak perlu kunci API. Tidak ada ujian flaky karena respons LLM yang variabel.&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;dapat di-versikan&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Le &amp;lt;code&amp;gt;.puml`adalah teks yang dihasilkan. Kita bisa`diff&amp;lt;/code&amp;gt;, reviewer untuk PR, me-versionnya di Git.&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_diagram_pipeline_itu_sendiri&amp;quot;&amp;gt;Diagram pipeline itu sendiri&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Untuk menutup lingkaran, ini adalah diagram pipeline seperti yang akan dibuat oleh plugin :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE

participant &amp;quot;Pengembang&amp;quot; as Dev
participant &amp;quot;Gradle&amp;quot; as G
participant &amp;quot;GenerateKnowledgeGraphDiagramTask&amp;quot; as Task
participant &amp;quot;KnowledgeGraphParser&amp;quot; as Parser
participant &amp;quot;PerenderGrafPengetahuan&amp;quot; as Renderer
participant &amp;quot;PlantumlService&amp;quot; as PS
collections &amp;quot;graphify-out/graph.json&amp;quot; as JSON
collections &amp;quot;diagrams/knowledge-graph/&amp;quot; as Out

Dev -&amp;amp;gt; G : ./gradlew generateKnowledgeGraphDiagram
G -&amp;amp;gt; Task : execute
Task -&amp;amp;gt; JSON : read
Task -&amp;amp;gt; Parser : parse(graph.json)
Parser --&amp;amp;gt; Task : KnowledgeGraph

alt filtres actifs
    Task -&amp;amp;gt; Task : appliquer filtres\n(community, edgeTypes, maxNodes…)
end

Task -&amp;amp;gt; Renderer : render(graph, filters)
Renderer --&amp;amp;gt; Task : code PlantUML

Task -&amp;amp;gt; PS : validateSyntax()
alt valide
    Task -&amp;amp;gt; PS : generateImage()
    PS --&amp;amp;gt; Out : .puml + .png
else invalide
    Task -&amp;amp;gt; Out : .puml (avec warning)
end

@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_integrasi_dalam_tata_kelola_proyek&amp;quot;&amp;gt;Integrasi dalam tata kelola proyek&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Dalam proyek kami, pipeline ini terintegrasi dalam strategi manajemen konteks EAGER/LAZY untuk agen IA. Knowledge Graph menggantikan dokumentasi arsitektur manual dengan sebuah graphe terstruktur, yang dapat di-query, dan diperbarui secara otomatis.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle

package &amp;quot;EAGER\n(selalu terisi)&amp;quot; as eager {
    [PROMPT_REPRISE.adoc\n(contexte session)] as pr
    [INDEX.adoc\n(vue d&amp;#39;ensemble)] as idx
    [GRAPH_REPORT.adoc\n(~50 lignes)] as gr
}

package &amp;quot;LAZY — Strategi
(atas permintaan)&amp;quot; as lazy_strat {
    [Méthodologies] as meth
    [Archives sessions] as sessions
}

package &amp;quot;LAZY — Graphify
(kueri yang ditargetkan)&amp;quot; as lazy_graph {
    [graph.json] as gj
    [Queries\n(query/path/explain)] as queries
}

package &amp;quot;Pipeline PlantUML\n(oto-generate)&amp;quot; as puml {
    [generateKnowledgeGraphDiagram] as kgTask
    [generateDiagramDocs] as ddTask
    [Diagrammes PNG] as diagrams
}

Agent --&amp;amp;gt; eager : Lit en début de session
Agent ..&amp;amp;gt; lazy_strat : Charge si type détecté
Agent ..&amp;amp;gt; lazy_graph : Query si besoin structurel
kgTask --&amp;amp;gt; diagrams : Déterministe
ddTask --&amp;amp;gt; diagrams : Via LLM

note right of gr
  Condensé du Knowledge Graph
  God nodes + communautés
  Remplace ECOSYSTEM_OVERVIEW
  (225 lignes → 50 lignes)
end note

@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Perkawinan kedua sistem tersebut komplementer :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Strategi mengelola kapan dan bagaimana&amp;lt;/strong&amp;gt;— tata kelola, alur kerja, pengarsipan&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Graphify mengelola APA dan DIMANA&amp;lt;/strong&amp;gt;— struktur kode, hubungan, kueri target&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;em&amp;gt;&amp;lt;em&amp;gt;_ Strategi sesi mengelola&amp;lt;strong&amp;gt;KAPAN&amp;lt;/strong&amp;gt; et le &amp;lt;strong&amp;gt;bagaimana&amp;lt;/strong&amp;gt;(pemerintahan, alur kerja, batas), Graphify mengelola&amp;lt;strong&amp;gt;apa&amp;lt;/strong&amp;gt; et le &amp;lt;strong&amp;gt;di mana&amp;lt;/strong&amp;gt;(struktur kode, hubungan, kueri yang ditargetkan). Pipeline PlantUML mengelola&amp;lt;strong&amp;gt;DENGAN APA&amp;lt;/strong&amp;gt;(diagram deterministik, terversion, selalu terkini). &amp;lt;/em&amp;gt;_&amp;lt;/em&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_penyiapan_daftar_periksa_dalam_5_menit&amp;quot;&amp;gt;Penyiapan : daftar periksa dalam 5 menit&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;table class=&amp;quot;tableblock frame-all grid-all stretch&amp;quot;&amp;gt;
&amp;lt;colgroup&amp;gt;
&amp;lt;col style=&amp;quot;width: 33.3333%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 33.3333%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 33.3334%;&amp;quot;&amp;gt;
&amp;lt;/colgroup&amp;gt;
&amp;lt;thead&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;#&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;Tahap&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;pesanan&amp;lt;/th&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/thead&amp;gt;
&amp;lt;tbody&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;1&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Instal Graphify&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;uv tool install graphifyy&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;2&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Mengkonfigurasi pengecualian&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;membuat`.graphifyignore`&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;3&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Ekstrak Knowledge Graph&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;graphify . --no-viz&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;4&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Menghasilkan diagram&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;./gradlew generateKnowledgeGraphDiagram&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;5&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Mengkomit hasil&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;git add diagrams/knowledge-graph/&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;# Script complet en 5 commandes
uv tool install graphifyy
cat &amp;amp;gt; .graphifyignore &amp;amp;lt;&amp;amp;lt; &amp;#39;EOF&amp;#39;
*-context.yml
*.env
build/
.gradle/
EOF
graphify . --no-viz
./gradlew generateKnowledgeGraphDiagram
git add graphify-out/GRAPH_REPORT.adoc graphify-out/graph.json diagrams/knowledge-graph/&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_jebakan_dan_mitigasi&amp;quot;&amp;gt;Jebakan dan mitigasi&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;table class=&amp;quot;tableblock frame-all grid-all stretch&amp;quot;&amp;gt;
&amp;lt;colgroup&amp;gt;
&amp;lt;col style=&amp;quot;width: 33.3333%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 33.3333%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 33.3334%;&amp;quot;&amp;gt;
&amp;lt;/colgroup&amp;gt;
&amp;lt;thead&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;jerat&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;deskripsi&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;mitigasi&amp;lt;/th&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/thead&amp;gt;
&amp;lt;tbody&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Grafik terlalu padat&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Sebuah proyek besar menghasilkan ratusan simpul yang tidak terbaca&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;menggunakan`-Pplantuml.kg.maxNodes=30`dan filter berdasarkan komunitas&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Pengecualian terlalu luas&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Terlalu banyak file di`.graphifyignore`mengurangi nilai grafik&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Memulai dengan hanya mengecualikan credentials dan build&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Arah panah&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Sisi-sisi INFERRED dapat memiliki arah ambigu&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Filter berdasarkan`-Pplantuml.kg.edgeTypes=EXTRACTED`untuk hubungan tertentu saja&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Graf usang&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Kode berubah tetapi grafik tidak dibangun ulang&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;menggunakan`graphify . --update`sekara teratur atau hook git`graphify hook install`&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Biaya yang diperbarui&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Pembaruan inkremental hampir gratis (tree-sitter lokal)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Hanya dokumen (&amp;lt;code&amp;gt;&amp;amp;#8230;&amp;amp;#8203;&amp;lt;/code&amp;gt;)&amp;lt;code&amp;gt;.adoc&amp;lt;/code&amp;gt;) mengkonsumsi token LLM&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_yang_didapatkan_di_akhir&amp;quot;&amp;gt;Yang didapatkan di akhir&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE

rectangle &amp;quot;sebelum&amp;quot; as avant {
    card &amp;quot;Diagram yang digambar tangan
Selalu usang
Tidak ada yang memperbarui mereka&amp;quot; as av1 #FDEDEC
}

rectangle &amp;quot;setelah&amp;quot; as apres {
    card &amp;quot;Diagram yang dihasilkan secara otomatis
Selalu diperbarui dengan kode
Disimpan dalam Git dengan sistem versi
Determinis dan dapat direproduksi&amp;quot; as ap1 #E8F8E8
}

avant --&amp;amp;gt; apres : graphify . --no-viz\n+ ./gradlew generateKnowledgeGraphDiagram

@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Manfaat konkret :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;table class=&amp;quot;tableblock frame-all grid-all stretch&amp;quot;&amp;gt;
&amp;lt;colgroup&amp;gt;
&amp;lt;col style=&amp;quot;width: 50%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 50%;&amp;quot;&amp;gt;
&amp;lt;/colgroup&amp;gt;
&amp;lt;thead&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;Keuntungan&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;Detail&amp;lt;/th&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/thead&amp;gt;
&amp;lt;tbody&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Dokumentasi selalu terkini&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Diagram tersebut mencerminkan kode saat ini, bukan snapshot manual&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Tidak ada upaya pemeliharaan&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Diagram diregenerasi setiap build&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Pengurangan utang teknis&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Tidak perlu lagi memelihara diagram secara manual&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Konteks visual untuk yang baru&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Seorang pengembang baru memahami arsitektur dengan melihat diagram&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Potensi penyesuaian halus&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Pasangan (subgraf → diagram) adalah contoh pelatihan AI&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Validasi otomatis&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;`PlantumlService.validateSyntax()`memeriksa setiap diagram yang dihasilkan&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Onboarding cepat&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;5 diagram = tampilan lengkap dari arsitektur&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;em&amp;gt;&amp;lt;em&amp;gt;_ Siapa yang memiliki _mengapa&amp;lt;/em&amp;gt; dapat menahan semua &amp;lt;em&amp;gt;bagaimana&amp;lt;/em&amp;gt;. &amp;lt;/em&amp;gt;__&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Alasannya: diagram selalu terkini. Cara: dua perintah dalam pipeline Gradle.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_tautan&amp;quot;&amp;gt;Tautan&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://github.com/nicholasgasior/graphify&amp;quot;&amp;gt;Graphify di GitHub&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://github.com/cheroliv/plantuml-gradle&amp;quot;&amp;gt;Plugin Gradle PlantUML&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;/blog/2026/0104_fg_command_super_tips_post.html&amp;quot;&amp;gt;Tips super dari perintah fg&amp;lt;/a&amp;gt;— artikel sebelumnya tentang workflow terminal&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>Mengintegrasikan Graphify ke dalam alur kerja Gradle : dari Knowledge Graph ke diagram PlantUML dalam satu perintah</title>
            <link >https://cheroliv.com/blog/2026/0105_integrer_graphify_workflow_gradle_post.html</link>
            <pubDate>Sun, 19 Apr 2026 00:00:00 +0000</pubDate>
            <guid isPermaLink="false">blog/2026/0105_integrer_graphify_workflow_gradle_post.html</guid>
            <description>&amp;lt;div id=&amp;quot;toc&amp;quot; class=&amp;quot;toc&amp;quot;&amp;gt;
&amp;lt;div id=&amp;quot;toctitle&amp;quot;&amp;gt;Table des matières&amp;lt;/div&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel1&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_masalah_diagram_selalu_tertinggal_dibanding_kode&amp;quot;&amp;gt;Masalah: diagram selalu tertinggal dibanding kode&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_solusi_pipeline_knowledge_graph_plantuml_yang_deterministik&amp;quot;&amp;gt;Solusi: pipeline Knowledge Graph → PlantUML yang deterministik&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_langkah_1_menginstal_graphify_dan_mengekstrak_knowledge_graph&amp;quot;&amp;gt;Langkah 1: Menginstal Graphify dan mengekstrak Knowledge Graph&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_instalasi&amp;quot;&amp;gt;Instalasi&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_mengatur_pengecualian&amp;quot;&amp;gt;Mengatur pengecualian&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_ekstrak_knowledge_graph&amp;quot;&amp;gt;Ekstrak Knowledge Graph&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_langkah_2_plugin_gradle_mengubah_knowledge_graph_menjadi_plantuml&amp;quot;&amp;gt;Langkah 2 : plugin Gradle mengubah Knowledge Graph menjadi PlantUML&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_arsitektur_pipa&amp;quot;&amp;gt;Arsitektur pipa&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_komponen_internal&amp;quot;&amp;gt;Komponen internal&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_aturan_rendering&amp;quot;&amp;gt;Aturan rendering&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_langkah_3_penggunaan_sehari_hari&amp;quot;&amp;gt;Langkah 3: Penggunaan sehari-hari&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_diagram_lengkap&amp;quot;&amp;gt;Diagram lengkap&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_filter_berdasarkan_komunitas&amp;quot;&amp;gt;Filter berdasarkan komunitas&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_filter_berdasarkan_jenis_tepi&amp;quot;&amp;gt;Filter berdasarkan jenis tepi&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_filter_menurut_jenis_node_dan_kepercayaan&amp;quot;&amp;gt;Filter menurut jenis node dan kepercayaan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_direktori_output_khusus&amp;quot;&amp;gt;Direktori output khusus&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_referensi_lengkap_properti&amp;quot;&amp;gt;Referensi lengkap properti&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_seluruh_pipeline_dalam_sebuah_workflow_gradle&amp;quot;&amp;gt;Seluruh pipeline dalam sebuah workflow Gradle&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_jenis_workflow&amp;quot;&amp;gt;jenis workflow&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_integrasi_dalam_siklus_pengembangan&amp;quot;&amp;gt;Integrasi dalam siklus pengembangan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_pembaruan_inkremental&amp;quot;&amp;gt;Pembaruan inkremental&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_dogfooding_plugin_mendokumentasikan_dirinya_sendiri&amp;quot;&amp;gt;Dogfooding : plugin mendokumentasikan dirinya sendiri&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_mengapa_itu_deterministik_dan_mengapa_itu_penting&amp;quot;&amp;gt;Mengapa itu deterministik (dan mengapa itu penting)&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_diagram_pipelinenya_sendiri&amp;quot;&amp;gt;Diagram pipelinenya sendiri&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_integrasi_dalam_tata_kelola_proyek&amp;quot;&amp;gt;Integrasi dalam tata kelola proyek&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_penyiapan_checklist_dalam_5_menit&amp;quot;&amp;gt;Penyiapan: checklist dalam 5 menit&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_penjebakan_dan_mitigasi&amp;quot;&amp;gt;Penjebakan dan mitigasi&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_apa_yang_kita_dapatkan_pada_akhirnya&amp;quot;&amp;gt;Apa yang kita dapatkan pada akhirnya&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_tautan&amp;quot;&amp;gt;tautan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div id=&amp;quot;preamble&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;waktu membaca : 12 minutes&amp;lt;/div&amp;gt;
&amp;lt;p&amp;gt;Codebase Anda tumbuh. Ketergantungan antar-modul bertambah banyak. Dokumentasi arsitektur menjadi usang bahkan sebelum ditulis. Dan jika build Gradle Anda bisa &amp;lt;em&amp;gt;otomatis&amp;lt;/em&amp;gt; membuat diagram terkini dari struktur kode nyata? Ini persis yang dilakukan oleh pipeline Graphify + PlantUML Gradle Plugin :`graphify . --no-viz`mengambil Knowledge Graph,`./gradlew generateKnowledgeGraphDiagram`dia mengubahnya menjadi diagram PlantUML. Nol LLM, nol manual, 100% deterministik.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;dlist&amp;quot;&amp;gt;
&amp;lt;dl&amp;gt;
&amp;lt;dt class=&amp;quot;hdlist1&amp;quot;&amp;gt;tok&amp;lt;/dt&amp;gt;
&amp;lt;dd&amp;gt;
&amp;lt;p&amp;gt;[]&amp;lt;/p&amp;gt;
&amp;lt;/dd&amp;gt;
&amp;lt;/dl&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_masalah_diagram_selalu_tertinggal_dibanding_kode&amp;quot;&amp;gt;Masalah: diagram selalu tertinggal dibanding kode&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Setiap proyek yang melebihi beberapa ribu baris mengalami sindrom ini:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;olist arabic&amp;quot;&amp;gt;
&amp;lt;ol class=&amp;quot;arabic&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Kami menggambar diagram arsitektur pada awal proyek&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Kode berkembang, dependensi berubah&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Diagram menjadi kebohongan dekoratif&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Tidak ada yang memperbaruinya karena ini merepotkan&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Pendatang baru mengandalkan hal itu dan membuat kesalahan&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE
skinparam ActivityBackgroundColor #FDEDEC
skinparam ActivityDiamondBackgroundColor #FFF3CD

start

:Diagramme d&amp;#39;architecture créé\n(manuellement, à la main);

:Le code évolue\n(nouvelles classes, nouvelles dépendances);

:Diagramme obsolète\n(ne reflète plus le code);

if (Qui met à jour ?) then (Personne)
    :Documentation mensongère;
    note right: Les nouveaux développeurs\nse basent dessus... et font des erreurs
else (Quelqu&amp;#39;un)
    :Mise à jour manuelle\n(2-3 heures de travail);
    note right: Déjà obsolète\nà la prochaine PR
endif

stop

@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Pertanyaannya bukan &amp;lt;em&amp;gt;apakah harus ada diagram?&amp;lt;/em&amp;gt; — semua orang tahu bahwa ya. Pertanyaannya adalah :&amp;lt;strong&amp;gt;Siapa yang menjaga agar tetap terkini?&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Jawabannya: tidak ada siapa-siapa. Kecuali jika otomatis.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_solusi_pipeline_knowledge_graph_plantuml_yang_deterministik&amp;quot;&amp;gt;Solusi: pipeline Knowledge Graph → PlantUML yang deterministik&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Prinsipnya sederhana: ali-alih menggambar diagram secara tangan, kita mereka&amp;lt;strong&amp;gt;dihasilkan dari struktur kode sebenarnya&amp;lt;/strong&amp;gt;.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle

actor Développeur
component &amp;quot;Graphify\n(pip install graphifyy)&amp;quot; as Graphify
collections &amp;quot;graphify-out/graph.json
(Graf Pengetahuan)&amp;quot; as KGJSON
component &amp;quot;PlantUML Plugin Gradle
(generateKnowledgeGraphDiagram)&amp;quot; as Plugin
component &amp;quot;KnowledgeGraphParser&amp;quot; as Parser
component &amp;quot;KnowledgeGraphRenderer&amp;quot; as Renderer
component &amp;quot;PlantumlService
(validasi + rendering PNG)&amp;quot; as PS
collections &amp;quot;diagram/graf-pengetahuan
(.puml + .png)&amp;quot; as Output

Développeur --&amp;amp;gt; Graphify : graphify . --no-viz
Graphify --&amp;amp;gt; KGJSON : extrait la structure\ndu code source
Développeur --&amp;amp;gt; Plugin : ./gradlew generateKnowledgeGraphDiagram
Plugin --&amp;amp;gt; Parser : parse(graph.json)
Parser --&amp;amp;gt; Plugin : KnowledgeGraph\n(noeuds + arêtes + communautés)
Plugin --&amp;amp;gt; Renderer : render(graph, filters)
Renderer --&amp;amp;gt; Plugin : code PlantUML\ndéterministe
Plugin --&amp;amp;gt; PS : validateSyntax + generateImage
PS --&amp;amp;gt; Output : .puml + .png

note bottom of Plugin
  Pipeline DÉTERMINISTE
  Aucun appel LLM
  Résultat reproductible
end note

@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Dua perintah. Itu saja.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;# Étape 1 : extraire le Knowledge Graph
graphify . --no-viz

# Étape 2 : générer les diagrammes PlantUML
./gradlew generateKnowledgeGraphDiagram&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Hasil? Berkas`.puml` et &amp;lt;code&amp;gt;.png`di dalam`diagrams/knowledge-graph/&amp;lt;/code&amp;gt;, yang tercatat dalam Git, selalu terkini dengan kode.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_langkah_1_menginstal_graphify_dan_mengekstrak_knowledge_graph&amp;quot;&amp;gt;Langkah 1: Menginstal Graphify dan mengekstrak Knowledge Graph&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_instalasi&amp;quot;&amp;gt;Instalasi&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Graphify adalah alat Python yang menganalisis codebase Anda dan membangun grafik pengetahuan terstruktur :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;# Méthode recommandée
uv tool install graphifyy &amp;amp;amp;&amp;amp;amp; graphify install --platform opencode

# Alternative avec pip
pip install graphifyy &amp;amp;amp;&amp;amp;amp; graphify install --platform opencode&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_mengatur_pengecualian&amp;quot;&amp;gt;Mengatur pengecualian&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Buat sebuah file`.graphifyignore`di root proyek untuk mengecualikan file-file yang bukan bagian dari logika bisnis:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-gitignore hljs&amp;quot; data-lang=&amp;quot;gitignore&amp;quot;&amp;gt;# Secrets — JAMAIS dans le graphe
*-context.yml
*.env

# Fichiers générés
build/
.gradle/

# Tests fonctionnels
src/functionalTest/&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_ekstrak_knowledge_graph&amp;quot;&amp;gt;Ekstrak Knowledge Graph&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;graphify . --no-viz&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Bendera`--no-viz`melewati pembuatan HTML (tidak berguna dalam pipeline Gradle). Hasilnya adalah sebuah file.`graphify-out/graph.json`berisi :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;simpul&amp;lt;/strong&amp;gt;: kelas, fungsi, file — dengan tipe dan komunitas mereka&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;rusuk&amp;lt;/strong&amp;gt;: relasi antara node (EXTRACTED dari kode, INFERRED oleh LLM)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Komunitas&amp;lt;/strong&amp;gt;: kelompokan otomatis dari node-node yang terhubung&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;Contoh struktur`graph.json`&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-json hljs&amp;quot; data-lang=&amp;quot;json&amp;quot;&amp;gt;{
  &amp;quot;nodes&amp;quot;: [
    {&amp;quot;id&amp;quot;: &amp;quot;0&amp;quot;, &amp;quot;label&amp;quot;: &amp;quot;LlmService&amp;quot;, &amp;quot;file_type&amp;quot;: &amp;quot;code&amp;quot;, &amp;quot;community&amp;quot;: 0},
    {&amp;quot;id&amp;quot;: &amp;quot;1&amp;quot;, &amp;quot;label&amp;quot;: &amp;quot;ApiKeyPool&amp;quot;, &amp;quot;file_type&amp;quot;: &amp;quot;code&amp;quot;, &amp;quot;community&amp;quot;: 0},
    {&amp;quot;id&amp;quot;: &amp;quot;2&amp;quot;, &amp;quot;label&amp;quot;: &amp;quot;PlantumlService&amp;quot;, &amp;quot;file_type&amp;quot;: &amp;quot;code&amp;quot;, &amp;quot;community&amp;quot;: 1}
  ],
  &amp;quot;links&amp;quot;: [
    {&amp;quot;source&amp;quot;: &amp;quot;1&amp;quot;, &amp;quot;target&amp;quot;: &amp;quot;0&amp;quot;, &amp;quot;relation&amp;quot;: &amp;quot;uses&amp;quot;, &amp;quot;confidence&amp;quot;: &amp;quot;EXTRACTED&amp;quot;, &amp;quot;weight&amp;quot;: 0.9},
    {&amp;quot;source&amp;quot;: &amp;quot;0&amp;quot;, &amp;quot;target&amp;quot;: &amp;quot;2&amp;quot;, &amp;quot;relation&amp;quot;: &amp;quot;calls&amp;quot;, &amp;quot;confidence&amp;quot;: &amp;quot;INFERRED&amp;quot;, &amp;quot;weight&amp;quot;: 0.7}
  ]
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;admonitionblock note&amp;quot;&amp;gt;
&amp;lt;table&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;icon&amp;quot;&amp;gt;
&amp;lt;i class=&amp;quot;fa icon-note&amp;quot; title=&amp;quot;Note&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Kode sumber (&amp;lt;code&amp;gt;.kt&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;.java&amp;lt;/code&amp;gt;) dianalisis secara lokal oleh tree-sitter,&amp;lt;strong&amp;gt;tanpa banding LLM&amp;lt;/strong&amp;gt;. Hanya file dokumentasi (&amp;lt;code&amp;gt;.adoc&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;.md&amp;lt;/code&amp;gt;) memerlukan panggilan LLM untuk ekstraksi semantik. Jadi`--update`pada kode Kotlin hampir seketika.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_langkah_2_plugin_gradle_mengubah_knowledge_graph_menjadi_plantuml&amp;quot;&amp;gt;Langkah 2 : plugin Gradle mengubah Knowledge Graph menjadi PlantUML&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_arsitektur_pipa&amp;quot;&amp;gt;Arsitektur pipa&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Plugin`com.cheroliv.plantuml`mengintegrasikan sebuah tugas`generateKnowledgeGraphDiagram`python print(&amp;quot; yang mentransformasi&amp;quot;, end=&amp;#39;&amp;#39;)`graph.json`dalam diagram PlantUML dengan cara&amp;lt;strong&amp;gt;sepenuhnya deterministik&amp;lt;/strong&amp;gt;:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE

participant &amp;quot;Gradle&amp;quot; as G
participant &amp;quot;GenerateKnowledgeGraphDiagramTask&amp;quot; as Task
participant &amp;quot;KnowledgeGraphParser&amp;quot; as Parser
participant &amp;quot;KnowledgeGraphRenderer&amp;quot; as Renderer
participant &amp;quot;PlantumlService&amp;quot; as PS

collections &amp;quot;graphify-out/graph.json&amp;quot; as JSON

G -&amp;amp;gt; Task : execute
Task -&amp;amp;gt; JSON : read
Task -&amp;amp;gt; Parser : parse(graph.json)
Parser --&amp;amp;gt; Task : KnowledgeGraph\n(noeuds + arêtes + communautés)
Task -&amp;amp;gt; Renderer : render(graph, filters)
Renderer --&amp;amp;gt; Task : code PlantUML\n(déterministe)
Task -&amp;amp;gt; PS : validateSyntax(plantumlCode)
alt syntaxe valide
    Task -&amp;amp;gt; PS : generateImage → .png
else syntaxe invalide
    Task -&amp;amp;gt; Task : save .puml avec warning
end

@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_komponen_internal&amp;quot;&amp;gt;Komponen internal&amp;lt;/h3&amp;gt;
&amp;lt;table class=&amp;quot;tableblock frame-all grid-all stretch&amp;quot;&amp;gt;
&amp;lt;colgroup&amp;gt;
&amp;lt;col style=&amp;quot;width: 50%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 50%;&amp;quot;&amp;gt;
&amp;lt;/colgroup&amp;gt;
&amp;lt;thead&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;Komponen&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;Peran&amp;lt;/th&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/thead&amp;gt;
&amp;lt;tbody&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;KnowledgeGraphParser&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;mengurai`graph.json`— mendukung 3 format : graphify asli (&amp;lt;code&amp;gt;nodes&amp;lt;/code&amp;gt;+&amp;lt;code&amp;gt;links&amp;lt;/code&amp;gt;), legacy (&amp;lt;code&amp;gt;communities&amp;lt;/code&amp;gt;), flat. Menyelesaikan IDs numerik menjadi label.&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;KnowledgeGraphRenderer&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Transformaskan secara deterministik sebuah`KnowledgeGraph`dalam kode PlantUML. Kelompok berdasarkan tipe, komunitas di paket, legenda otomatis.&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;GenerateKnowledgeGraphDiagramTask&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Tugas Gradle yang mengorakstratkan: parse → render → validate → PNG. Dapat dikonfigurasi melalui properti Gradle.&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;kgmodels.kt&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Model data:`KnowledgeGraph`, &amp;lt;code&amp;gt;KnowledgeGraphNode&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;KnowledgeGraphEdge&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;KnowledgeGraphCommunity&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;EdgeType&amp;lt;/code&amp;gt;.&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;PlantumlService&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Validasi sintaksis + rendering PNG (digunakan kembali oleh semua tugas plugin).&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_aturan_rendering&amp;quot;&amp;gt;Aturan rendering&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Perender menerapkan konvensi visual deterministik:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;table class=&amp;quot;tableblock frame-all grid-all stretch&amp;quot;&amp;gt;
&amp;lt;colgroup&amp;gt;
&amp;lt;col style=&amp;quot;width: 33.3333%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 33.3333%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 33.3334%;&amp;quot;&amp;gt;
&amp;lt;/colgroup&amp;gt;
&amp;lt;thead&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;Jenis tepi&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;Notasi PlantUML&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;Arti&amp;lt;/th&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/thead&amp;gt;
&amp;lt;tbody&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;diekstrak&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;-&amp;amp;#8594;&amp;lt;/code&amp;gt;(garis penuh, hitam)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Relasi diekstrak dari kode sumber (kepastian)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;disimpulkan&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;..&amp;amp;gt;&amp;lt;/code&amp;gt;(garis titik-titik)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Relasi yang disimpulkan oleh LLM (skor kepercayaan)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;AMBIGUOUS&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;--x&amp;lt;/code&amp;gt;(garis titik merah)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Hubungan ambigu (perlu diperiksa)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Komunitas dirender seperti paket PlantUML dengan palet warna otomatis.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_langkah_3_penggunaan_sehari_hari&amp;quot;&amp;gt;Langkah 3: Penggunaan sehari-hari&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_diagram_lengkap&amp;quot;&amp;gt;Diagram lengkap&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;# Générer le diagramme du Knowledge Graph complet
./gradlew generateKnowledgeGraphDiagram&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Keluaran :`diagrams/knowledge-graph/knowledge-graph-full.puml`+&amp;lt;code&amp;gt;.png&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_filter_berdasarkan_komunitas&amp;quot;&amp;gt;Filter berdasarkan komunitas&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;# Une seule communauté
./gradlew generateKnowledgeGraphDiagram \
  -Pplantuml.kg.community=community_0

# Limiter le nombre de noeuds (lisibilité)
./gradlew generateKnowledgeGraphDiagram \
  -Pplantuml.kg.community=community_0 \
  -Pplantuml.kg.maxNodes=15&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_filter_berdasarkan_jenis_tepi&amp;quot;&amp;gt;Filter berdasarkan jenis tepi&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;# Uniquement les relations certaines (EXTRACTED)
./gradlew generateKnowledgeGraphDiagram \
  -Pplantuml.kg.edgeTypes=EXTRACTED

# Relations certaines + inférées
./gradlew generateKnowledgeGraphDiagram \
  -Pplantuml.kg.edgeTypes=EXTRACTED,INFERRED&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_filter_menurut_jenis_node_dan_kepercayaan&amp;quot;&amp;gt;Filter menurut jenis node dan kepercayaan&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;# Uniquement les classes de code
./gradlew generateKnowledgeGraphDiagram \
  -Pplantuml.kg.nodeTypes=code

# Seuil de confiance minimum (pour les INFERRED)
./gradlew generateKnowledgeGraphDiagram \
  -Pplantuml.kg.minConfidence=0.7&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_direktori_output_khusus&amp;quot;&amp;gt;Direktori output khusus&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;./gradlew generateKnowledgeGraphDiagram \
  -Pplantuml.kg.outputDir=docs/architecture&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_referensi_lengkap_properti&amp;quot;&amp;gt;Referensi lengkap properti&amp;lt;/h3&amp;gt;
&amp;lt;table class=&amp;quot;tableblock frame-all grid-all stretch&amp;quot;&amp;gt;
&amp;lt;colgroup&amp;gt;
&amp;lt;col style=&amp;quot;width: 33.3333%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 33.3333%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 33.3334%;&amp;quot;&amp;gt;
&amp;lt;/colgroup&amp;gt;
&amp;lt;thead&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;properti&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;default&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;Deskripsi&amp;lt;/th&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/thead&amp;gt;
&amp;lt;tbody&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;plantuml.kg.community&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;(semua)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Filter komunitas berdasarkan nama (cocokan substring)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;plantuml.kg.edgeTypes&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;(semua)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Tipe sisi dipisahkan dengan koma:`EXTRACTED`, &amp;lt;code&amp;gt;INFERRED&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;AMBIGUOUS&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;plantuml.kg.minConfidence&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;0.0&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Ambang kepercayaan minimum untuk tepi&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;plantuml.kg.maxNodes&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;em&amp;gt;(tak terbatas)&amp;lt;/em&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Jumlah maksimum node yang akan ditampilkan&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;plantuml.kg.nodeTypes&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;em&amp;gt;(semua)&amp;lt;/em&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Jenis simpul dipisahkan dengan koma (contoh`class`, &amp;lt;code&amp;gt;code&amp;lt;/code&amp;gt;)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;plantuml.kg.outputDir&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;diagrams/knowledge-graph&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Direktori output untuk file`.puml` et &amp;lt;code&amp;gt;.png&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_seluruh_pipeline_dalam_sebuah_workflow_gradle&amp;quot;&amp;gt;Seluruh pipeline dalam sebuah workflow Gradle&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_jenis_workflow&amp;quot;&amp;gt;jenis workflow&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE
skinparam ActivityBackgroundColor #E8F4FD
skinparam ActivityDiamondBackgroundColor #FFF3CD
skinparam ActivityBorderColor #2C3E50

start

partition &amp;quot;EKSTRAKSI&amp;quot; {
    :graphify . --no-viz;
    note right: Analyse le code source\ntree-sitter (local, pas de LLM)

    :graphify-out/graph.json;
    note right: Noeuds + arêtes\n+ communautés
}

partition &amp;quot;GENERASI&amp;quot; {
    :./gradlew generateKnowledgeGraphDiagram;
    note right: Pipeline déterministe\nparse → render → validate → PNG

    if (Filtres actifs ?) then (Oui)
        :Appliquer filtres\n(community, edgeTypes,\nmaxNodes, nodeTypes);
    else (Non)
        :Diagramme complet;
    endif

    :diagrams/knowledge-graph/\nknowledge-graph-full.puml\nknowledge-graph-full.png;
}

partition &amp;quot;INTEGRASI&amp;quot; {
    :git add diagrams/knowledge-graph/;
    :git commit;
    note right: Diagrammes versionnés\ntoujours à jour avec le code
}

stop

@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_integrasi_dalam_siklus_pengembangan&amp;quot;&amp;gt;Integrasi dalam siklus pengembangan&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Pipeline ini terintegrasi secara alami ke dalam tahap-tahap kunci dari pengembangan :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE

state &amp;quot;Pengembangan&amp;quot; as dev
state &amp;quot;Ekstraksi
graphify . --no-viz&amp;quot; as extract
state &amp;quot;Generasi
./gradlew generateKnowledgeGraphDiagram&amp;quot; as generate
state &amp;quot;Commit
diagram versi&amp;quot; as commit

[*] --&amp;amp;gt; dev
dev --&amp;amp;gt; extract : Code modifié
extract --&amp;amp;gt; generate : graph.json à jour
generate --&amp;amp;gt; commit : .puml + .png générés
commit --&amp;amp;gt; dev : Diagrammes dans le repo

note right of extract
  Quasi instantané sur du code Kotlin
  (tree-sitter, pas de LLM)
end note

note right of generate
  Déterministe
  Pas de LLM
  Résultat reproductible
end note

@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_pembaruan_inkremental&amp;quot;&amp;gt;Pembaruan inkremental&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Ketika kode berubah, kita tidak membangun kembali seluruh grafik dari nol:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;# Mise à jour incrémentale (fichiers modifiés uniquement)
graphify . --update

# Puis régénérer les diagrammes
./gradlew generateKnowledgeGraphDiagram&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;admonitionblock note&amp;quot;&amp;gt;
&amp;lt;table&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;icon&amp;quot;&amp;gt;
&amp;lt;i class=&amp;quot;fa icon-note&amp;quot; title=&amp;quot;Note&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;code&amp;gt;--update`hanya mengekstrak kembali file yang telah dimodifikasi (terdeteksi oleh SHA256 di`graphify-out/cache/&amp;lt;/code&amp;gt;). Pada kode Kotlin, ini hampir instan karena tree-sitter bekerja secara lokal tanpa panggilan LLM.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_dogfooding_plugin_mendokumentasikan_dirinya_sendiri&amp;quot;&amp;gt;Dogfooding : plugin mendokumentasikan dirinya sendiri&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Plugin PlantUML ada untuk mengubah prompt menjadi diagram. Dia juga dapat mengubah Knowledge Graph dari codebase &amp;lt;em&amp;gt;propre&amp;lt;/em&amp;gt;-nya menjadi diagram dokumentasi. Ini adalah &amp;lt;em&amp;gt;dogfooding&amp;lt;/em&amp;gt; : plugin mengonsumsi layanan sendiri.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle

package &amp;quot;pipeline normal
(pengguna → diagram)&amp;quot; as normal {
    [Fichier .prompt] as prompt
    [LlmService\n+ ApiKeyPool] as llm1
    [ProcessPlantumlPromptsTask] as task1
    [Diagramme PNG] as out1

    prompt --&amp;amp;gt; task1
    task1 --&amp;amp;gt; llm1
    llm1 --&amp;amp;gt; out1
}

package &amp;quot;Pipeline Knowledge Graph
(deterministis, tanpa LLM)&amp;quot; as kg {
    [graphify-out/graph.json] as kgjson
    [KnowledgeGraphParser] as parser
    [KnowledgeGraphRenderer] as renderer
    [PlantumlService] as ps
    [Diagramme PNG\n(documentation du plugin)] as out2

    kgjson --&amp;amp;gt; parser
    parser --&amp;amp;gt; renderer
    renderer --&amp;amp;gt; ps
    ps --&amp;amp;gt; out2
}

package &amp;quot;Pipeline Dogfooding
(LLM → dokumentasi plugin)&amp;quot; as dogfood {
    [GraphifyPromptAdapter] as gpa
    [Fichiers .prompt\nauto-générés] as auto_prompt
    [LlmService\n+ ApiKeyPool] as llm2
    [ProcessPlantumlPromptsTask] as task2
    [Diagramme PNG\n(documentation LLM)] as out3

    kgjson --&amp;amp;gt; gpa
    gpa --&amp;amp;gt; auto_prompt
    auto_prompt --&amp;amp;gt; task2
    task2 --&amp;amp;gt; llm2
    llm2 --&amp;amp;gt; out3
}

note &amp;quot;Sama LlmService, sama ApiKeyPool,
sama PlantumlService
— TIDAK ADA duplikasi&amp;quot; as N

@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Tugas Gradle terkait :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;table class=&amp;quot;tableblock frame-all grid-all stretch&amp;quot;&amp;gt;
&amp;lt;colgroup&amp;gt;
&amp;lt;col style=&amp;quot;width: 33.3333%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 33.3333%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 33.3334%;&amp;quot;&amp;gt;
&amp;lt;/colgroup&amp;gt;
&amp;lt;thead&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;Tugas&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;LLM ?&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;Keterangan&amp;lt;/th&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/thead&amp;gt;
&amp;lt;tbody&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;generateKnowledgeGraphDiagram&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Tidak&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Mentransformasi`graph.json`en PlantUML (determinis)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;generateDiagramDocs&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Ya&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Menghasilkan beberapa`.prompt`dari grafik, mereka memprosesnya melalui LLM (dogfooding)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;# Documentation déterministe (rapide, pas de LLM)
./gradlew generateKnowledgeGraphDiagram

# Documentation LLM (plus riche, consomme des tokens)
./gradlew generateDiagramDocs&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_mengapa_itu_deterministik_dan_mengapa_itu_penting&amp;quot;&amp;gt;Mengapa itu deterministik (dan mengapa itu penting)&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Titik kunci pipeline`generateKnowledgeGraphDiagram` : &amp;lt;strong&amp;gt;Dia tidak memanggil LLM apa pun&amp;lt;/strong&amp;gt;. Transformasi`graph.json`PlantUML adalah sebuah fungsi murni.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle

rectangle &amp;quot;Pipeline Determinis
(generateKnowledgeGraphDiagram)&amp;quot; as det {
    [graph.json] as json
    [KnowledgeGraphParser] as parser
    [KnowledgeGraphRenderer] as renderer
    [PlantUML code] as puml

    json --&amp;amp;gt; parser : parse
    parser --&amp;amp;gt; renderer : KnowledgeGraph
    renderer --&amp;amp;gt; puml : render (fonction pure)
}

rectangle &amp;quot;Pipeline LLM
(processPlantumlPrompts)&amp;quot; as llm {
    [.prompt] as prompt
    [LlmService] as llmSvc
    [ChatModel] as model
    [PlantUML code] as puml2

    prompt --&amp;amp;gt; llmSvc
    llmSvc --&amp;amp;gt; model : API call
    model --&amp;amp;gt; puml2 : réponse non-déterministe
}

note bottom of det
  Même entrée → même sortie
  Pas de latence réseau
  Pas de coût en tokens
  Reproductible en CI
end note

note bottom of llm
  Même entrée → sortie variable
  Latence réseau (1-5s)
  Coût en tokens
  Nécessite une clave API
end note

@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Manfaat konkret:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;table class=&amp;quot;tableblock frame-all grid-all stretch&amp;quot;&amp;gt;
&amp;lt;colgroup&amp;gt;
&amp;lt;col style=&amp;quot;width: 50%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 50%;&amp;quot;&amp;gt;
&amp;lt;/colgroup&amp;gt;
&amp;lt;thead&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;Keuntungan&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;Dampak&amp;lt;/th&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/thead&amp;gt;
&amp;lt;tbody&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Reproduksi&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;bahkan`graph.json`→ diagram yang sama. Tepat. Setiap kali.&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Biaya nol&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Tanpa panggilan LLM = tanpa token = tanpa tagihan API&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Latensi nol&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Parse + render membutuhkan ~100ms, bukan 1-5 detik.&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Kompatibel CI&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Tidak perlu kunci API. Tidak ada tes flaky karena respons LLM yang bervariasi.&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;dapat diberi versi&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Le &amp;lt;code&amp;gt;.puml`yang dihasilkan adalah teks. Kita bisa`diff&amp;lt;/code&amp;gt;, reviewer dalam PR, versikan di Git.&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_diagram_pipelinenya_sendiri&amp;quot;&amp;gt;Diagram pipelinenya sendiri&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Untuk menutup lingkaran, berikut adalah diagram pipelining seperti yang akan dihasilkan oleh plugin :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE

participant &amp;quot;Pengembang&amp;quot; as Dev
participant &amp;quot;Gradle&amp;quot; as G
participant &amp;quot;GenerateKnowledgeGraphDiagramTask&amp;quot; as Task
participant &amp;quot;KnowledgeGraphParser&amp;quot; as Parser
participant &amp;quot;KnowledgeGraphRenderer&amp;quot; as Renderer
participant &amp;quot;PlantumlService&amp;quot; as PS
collections &amp;quot;graphify-out/graph.json&amp;quot; as JSON
collections &amp;quot;diagrams/knowledge-graph/&amp;quot; as Out

Dev -&amp;amp;gt; G : ./gradlew generateKnowledgeGraphDiagram
G -&amp;amp;gt; Task : execute
Task -&amp;amp;gt; JSON : read
Task -&amp;amp;gt; Parser : parse(graph.json)
Parser --&amp;amp;gt; Task : KnowledgeGraph

alt filtres actifs
    Task -&amp;amp;gt; Task : appliquer filtres\n(community, edgeTypes, maxNodes…)
end

Task -&amp;amp;gt; Renderer : render(graph, filters)
Renderer --&amp;amp;gt; Task : code PlantUML

Task -&amp;amp;gt; PS : validateSyntax()
alt valide
    Task -&amp;amp;gt; PS : generateImage()
    PS --&amp;amp;gt; Out : .puml + .png
else invalide
    Task -&amp;amp;gt; Out : .puml (avec warning)
end

@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_integrasi_dalam_tata_kelola_proyek&amp;quot;&amp;gt;Integrasi dalam tata kelola proyek&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Dalam proyek kami, pipeline ini terintegrasi dalam strategi pengelolaan konteks EAGER/LAZY untuk agen IA. Knowledge Graph menggantikan dokumentasi arsitektur manual dengan sebuah grafi terstruktur yang dapat di-query dan diperbarui otomatis.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle

package &amp;quot;EAGER\n(selalu terisi)&amp;quot; as eager {
    [PROMPT_REPRISE.adoc\n(contexte session)] as pr
    [INDEX.adoc\n(vue d&amp;#39;ensemble)] as idx
    [GRAPH_REPORT.adoc\n(~50 lignes)] as gr
}

package &amp;quot;LAZY — Strategi\n(atas permintaan)&amp;quot; as lazy_strat {
    [Méthodologies] as meth
    [Archives sessions] as sessions
}

package &amp;quot;LAZY — Graphify\n(permintaan terarah)&amp;quot; as lazy_graph {
    [graph.json] as gj
    [Queries\n(query/path/explain)] as queries
}

package &amp;quot;Pipeline PlantUML
(oto-generate)&amp;quot; as puml {
    [generateKnowledgeGraphDiagram] as kgTask
    [generateDiagramDocs] as ddTask
    [Diagrammes PNG] as diagrams
}

Agent --&amp;amp;gt; eager : Lit en début de session
Agent ..&amp;amp;gt; lazy_strat : Charge si type détecté
Agent ..&amp;amp;gt; lazy_graph : Query si besoin structurel
kgTask --&amp;amp;gt; diagrams : Déterministe
ddTask --&amp;amp;gt; diagrams : Via LLM

note right of gr
  Condensé du Knowledge Graph
  God nodes + communautés
  Remplace ECOSYSTEM_OVERVIEW
  (225 lignes → 50 lignes)
end note

@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Perkawinan dua sistem adalah komplementer:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Strategi mengelola QUAND dan COMMENT&amp;lt;/strong&amp;gt;— tata kelola, alur kerja, pengarsipan&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Graphify mengelola APA dan DI MANA&amp;lt;/strong&amp;gt;— struktur kode, relasi, kueri yang ditargetkan&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;(Empty) Strategi sesi mengelola&amp;lt;strong&amp;gt;kapan&amp;lt;/strong&amp;gt; et le &amp;lt;strong&amp;gt;bagaimana&amp;lt;/strong&amp;gt;(tata kelola, workflow, batas), Graphify mengelola&amp;lt;strong&amp;gt;APA&amp;lt;/strong&amp;gt; et le &amp;lt;strong&amp;gt;DI MANA&amp;lt;/strong&amp;gt;(struktur kode, hubungan, kueri target). Pipeline PlantUML mengelola&amp;lt;strong&amp;gt;DENGAN APA&amp;lt;/strong&amp;gt;(diagram deterministik, berisi versi, selalu terkini). &amp;lt;em&amp;gt;__&amp;lt;/em&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_penyiapan_checklist_dalam_5_menit&amp;quot;&amp;gt;Penyiapan: checklist dalam 5 menit&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;table class=&amp;quot;tableblock frame-all grid-all stretch&amp;quot;&amp;gt;
&amp;lt;colgroup&amp;gt;
&amp;lt;col style=&amp;quot;width: 33.3333%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 33.3333%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 33.3334%;&amp;quot;&amp;gt;
&amp;lt;/colgroup&amp;gt;
&amp;lt;thead&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;#&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;Tahap&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;pesanan&amp;lt;/th&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/thead&amp;gt;
&amp;lt;tbody&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;1&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Instal Graphify&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;uv tool install graphifyy&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;2&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Mengkonfigurasi pengecualian&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Membuat`.graphifyignore`&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;3&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Mengekstrak Knowledge Graph&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;graphify . --no-viz&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;4&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Menghasilkan diagram&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;./gradlew generateKnowledgeGraphDiagram&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;5&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Menyimpan hasil&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;git add diagrams/knowledge-graph/&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;# Script complet en 5 commandes
uv tool install graphifyy
cat &amp;amp;gt; .graphifyignore &amp;amp;lt;&amp;amp;lt; &amp;#39;EOF&amp;#39;
*-context.yml
*.env
build/
.gradle/
EOF
graphify . --no-viz
./gradlew generateKnowledgeGraphDiagram
git add graphify-out/GRAPH_REPORT.adoc graphify-out/graph.json diagrams/knowledge-graph/&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_penjebakan_dan_mitigasi&amp;quot;&amp;gt;Penjebakan dan mitigasi&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;table class=&amp;quot;tableblock frame-all grid-all stretch&amp;quot;&amp;gt;
&amp;lt;colgroup&amp;gt;
&amp;lt;col style=&amp;quot;width: 33.3333%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 33.3333%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 33.3334%;&amp;quot;&amp;gt;
&amp;lt;/colgroup&amp;gt;
&amp;lt;thead&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;jebakan&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;Deskripsi&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;mitigasi&amp;lt;/th&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/thead&amp;gt;
&amp;lt;tbody&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Grafik terlalu padat&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Proyek besar menghasilkan ratusan node yang tidak dapat dibaca&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Menggunakan`-Pplantuml.kg.maxNodes=30`dan memfilter berdasarkan komunitas&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Pengecualian terlalu luas&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Terlalu banyak file di`.graphifyignore`mengurangi nilai grafik&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Mulai dengan mengecualikan hanya credentials dan build&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Arah panah&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Sisi-sisi INFERRED dapat memiliki arah yang ambigu&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Filter berdasarkan`-Pplantuml.kg.edgeTypes=EXTRACTED`hanya untuk hubungan tertentu&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Grafik usang&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Kode berubah tetapi grafik tidak dibangun ulang&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Menggunakan`graphify . --update`secara teratur atau hook git`graphify hook install`&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Biaya diperbarui&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Pembaruan inkremental hampir gratis (tree-sitter local)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Hanya dokumen (&amp;lt;code&amp;gt;.adoc&amp;lt;/code&amp;gt;) mengonsumsi token LLM&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_apa_yang_kita_dapatkan_pada_akhirnya&amp;quot;&amp;gt;Apa yang kita dapatkan pada akhirnya&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE

rectangle &amp;quot;SEBELUM&amp;quot; as avant {
    card &amp;quot;Diagram yang digambar secara tangan
Selalu usang
Tidak ada yang memperbarui mereka&amp;quot; as av1 #FDEDEC
}

rectangle &amp;quot;setelah&amp;quot; as apres {
    card &amp;quot;Diagram yang dihasilkan secara otomatis
Selalu terkini dengan kode
Di-versionkan di Git
Deterministik dan dapat direproduksi&amp;quot; as ap1 #E8F8E8
}

avant --&amp;amp;gt; apres : graphify . --no-viz\n+ ./gradlew generateKnowledgeGraphDiagram

@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Manfaat konkret :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;table class=&amp;quot;tableblock frame-all grid-all stretch&amp;quot;&amp;gt;
&amp;lt;colgroup&amp;gt;
&amp;lt;col style=&amp;quot;width: 50%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 50%;&amp;quot;&amp;gt;
&amp;lt;/colgroup&amp;gt;
&amp;lt;thead&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;Keuntungan&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;Detail&amp;lt;/th&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/thead&amp;gt;
&amp;lt;tbody&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Dokumentasi selalu terkini&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Diagram mencerminkan kode saat ini, bukan snapshot manual&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Upaya pemeliharaan nol&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Diagram diagram diregenerasikan setiap build&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Pengurangan utang teknis&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Tidak perlu lagi memelihara diagram secara manual&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Konteks visual untuk yang baru&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Seorang pengembang baru memahami arsitektur dengan melihat diagram&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Potensi penyesuaian halus&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Pasangan (subgraf → diagram) adalah contoh pelatihan AI&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Validasi otomatis&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;`PlantumlService.validateSyntax()`Periksa setiap diagram yang dihasilkan&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Onboarding cepat&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;5 diagram = tampilan lengkap dari arsitektur&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;em&amp;gt;mengapa&amp;lt;/em&amp;gt; : diagram selalu terkini. &amp;lt;em&amp;gt;bagaimana&amp;lt;/em&amp;gt; : dua perintah dalam pipeline Gradle.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_tautan&amp;quot;&amp;gt;tautan&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://github.com/nicholasgasior/graphify&amp;quot;&amp;gt;Graphify di GitHub&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://github.com/cheroliv/plantuml-gradle&amp;quot;&amp;gt;PlantUML Gradle Plugin&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;/blog/2026/0104_fg_command_super_tips_post.html&amp;quot;&amp;gt;Tips super dari perintah fg&amp;lt;/a&amp;gt;— artikel sebelumnya tentang workflow terminal&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>Tips super dari perintah fg: ketika Ctrl+Z menyelamatkan sesi terminal Anda</title>
            <link >https://cheroliv.com/blog/2026/0104_fg_command_super_tips_post.html</link>
            <pubDate>Sat, 18 Apr 2026 00:00:00 +0000</pubDate>
            <guid isPermaLink="false">blog/2026/0104_fg_command_super_tips_post.html</guid>
            <description>&amp;lt;div id=&amp;quot;toc&amp;quot; class=&amp;quot;toc&amp;quot;&amp;gt;
&amp;lt;div id=&amp;quot;toctitle&amp;quot;&amp;gt;Table des matières&amp;lt;/div&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel1&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_masalah_terminal_yang_sibuk&amp;quot;&amp;gt;Masalah: terminal yang sibuk&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_tiga_perintah_esensial&amp;quot;&amp;gt;tiga perintah esensial&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_ctrlz_menangguhkan_sigtstp&amp;quot;&amp;gt;Ctrl+Z — Menangguhkan (SIGTSTP)&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_fg_membawa_ke_depan&amp;quot;&amp;gt;fg — Membawa ke depan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_bg_lanjutkan_di_latar_belakang&amp;quot;&amp;gt;bg — Lanjutkan di latar belakang&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_jobs_daftar_pekerjaan_shell&amp;quot;&amp;gt;jobs — Daftar pekerjaan shell&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_skenario_1_build_yang_panjang&amp;quot;&amp;gt;Skenario 1: Build yang panjang&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_langkah_demi_langkah&amp;quot;&amp;gt;langkah demi langkah&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_skenario_2_editor_yang_dilapkan&amp;quot;&amp;gt;Skenario 2: editor yang dilapkan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_scenario_3_proses_opencode_ditangguhkan&amp;quot;&amp;gt;Scenario 3 : Proses opencode ditangguhkan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_skenario_4_mengelola_beberapa_pekerjaan_secara_bersamaan&amp;quot;&amp;gt;Skenario 4: Mengelola beberapa pekerjaan secara bersamaan&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_ringkasan_perintah_pekerjaan&amp;quot;&amp;gt;Ringkasan perintah pekerjaan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_perbedaan_antara_fg_bg_ctrlz_ctrlc_dan&amp;quot;&amp;gt;Perbedaan antara fg, bg, Ctrl+Z, Ctrl+C dan &amp;amp;amp;&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_siklus_hidup_lengkap_dari_sebuah_pekerjaan&amp;quot;&amp;gt;Siklus hidup lengkap dari sebuah pekerjaan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_tips_lanjut&amp;quot;&amp;gt;Tips lanjut&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_fg_dengan_pekerjaan_paling_baru_dan_pertukaran&amp;quot;&amp;gt;fg dengan pekerjaan paling baru dan pertukaran&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_mengerjakan_pekerjaan_dengan_baik&amp;quot;&amp;gt;Mengerjakan pekerjaan dengan baik&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_disown_melepaskan_dari_shell&amp;quot;&amp;gt;disown : melepaskan dari shell&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_gabungkan_dengan_nohup_untuk_proses_proses_yang_lama&amp;quot;&amp;gt;Gabungkan dengan nohup untuk proses-proses yang lama&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_jebakan_dan_kesalahan_umum&amp;quot;&amp;gt;Jebakan dan kesalahan umum&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_mengalirkan_ctrlz_dan_ctrlc&amp;quot;&amp;gt;Mengalirkan Ctrl+Z dan Ctrl+C&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_pekerjaan_pekerjaan_adalah_lokal_untuk_shell&amp;quot;&amp;gt;Pekerjaan-pekerjaan adalah lokal untuk shell&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_pekerjaan_tidak_bertahan_saat_shell_ditutup&amp;quot;&amp;gt;Pekerjaan tidak bertahan saat shell ditutup&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_proses_interaktif_dan_fg&amp;quot;&amp;gt;Proses interaktif dan fg&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_diagram_ringkasan&amp;quot;&amp;gt;Diagram Ringkasan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_mengapa_fg_dianggap_kurang&amp;quot;&amp;gt;Mengapa fg dianggap kurang?&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_catatan_teknis_sinyal&amp;quot;&amp;gt;Catatan teknis : sinyal&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_kesimpulan&amp;quot;&amp;gt;Kesimpulan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_referensi&amp;quot;&amp;gt;Referensi&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div id=&amp;quot;preamble&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;waktu membaca : 10 minutes&amp;lt;/div&amp;gt;
&amp;lt;p&amp;gt;Anda melempar`./gradlew build`, terminal terblokir selama 3 menit, dan Anda perlu melakukan sesuatu yang lain &amp;lt;em&amp;gt;menunggu&amp;lt;/em&amp;gt; ? Tidak perlu membuka tab baru.`Ctrl+Z`menangguhkan proses,`fg`Dia mengembalikannya tepat di tempat Anda meninggalkan. Ini adalah tips Unix yang paling kurang dihargai dalam kehidupan sehari-hari seorang pengembang.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;dlist&amp;quot;&amp;gt;
&amp;lt;dl&amp;gt;
&amp;lt;dt class=&amp;quot;hdlist1&amp;quot;&amp;gt;toc&amp;lt;/dt&amp;gt;
&amp;lt;dd&amp;gt;
&amp;lt;p&amp;gt;[]&amp;lt;/p&amp;gt;
&amp;lt;/dd&amp;gt;
&amp;lt;/dl&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_masalah_terminal_yang_sibuk&amp;quot;&amp;gt;Masalah: terminal yang sibuk&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Setiap developer telah menjalani situasi ini:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;$ ./gradlew build
&amp;amp;gt; Building...&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Terminal terblokir. Anda tidak dapat lagi mengetik perintah apa pun. Dan Anda perlu melihat sebuah file, menjalankan sebuah`git status`, atau memeriksa port yang sedang digunakan.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE

actor Développeur as dev
participant &amp;quot;Terminal&amp;quot; as term
participant &amp;quot;Gradle\n( proses )&amp;quot; as gradle

dev -&amp;amp;gt; term : ./gradlew build
term -&amp;amp;gt; gradle : Lance le build
gradle -&amp;amp;gt; term : Occupe le terminal\n(pas de prompt)
dev -&amp;amp;gt; term : veut taper une commande
term --&amp;amp;gt; dev : ❌ Terminal bloqué

note right of dev
  Le développeur est
  paralysé pendant
  toute la durée du build
end note
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Kebanyakan pengembang membuka terminal baru. Namun ada solusi yang lebih elegan, lebih cepat, dan bawaan: manajemen pekerjaan.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_tiga_perintah_esensial&amp;quot;&amp;gt;tiga perintah esensial&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_ctrlz_menangguhkan_sigtstp&amp;quot;&amp;gt;Ctrl+Z — Menangguhkan (SIGTSTP)&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;`Ctrl+Z`mengirim sinyal`SIGTSTP`ke proses latar depan. Prosesnya adalah&amp;lt;strong&amp;gt;ditangguhkan&amp;lt;/strong&amp;gt;— tidak terbunuh — dan terminal kembali mendapatkan prompt-nya.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;$ ./gradlew build
&amp;amp;gt; Building...
^Z
[1]+  Stoppé    ./gradlew build
$&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Angka di antara tanda kurung`[1]`adalah&amp;lt;strong&amp;gt;nomor pekerjaan&amp;lt;/strong&amp;gt;. Le `+`menunjukkan pekerjaan default&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_fg_membawa_ke_depan&amp;quot;&amp;gt;fg — Membawa ke depan&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;`fg`Memulihkan job yang dijeda ke latar depan. Proses melanjutkan tepat dari tempat ia berhenti.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;$ fg
./gradlew build
&amp;amp;gt; Building... (reprend où il en était)&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Dengan nomor pekerjaan spesifik:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;$ fg %2&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_bg_lanjutkan_di_latar_belakang&amp;quot;&amp;gt;bg — Lanjutkan di latar belakang&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;`bg`lanjutkan proses tanpa memblokir terminal. Ideal untuk build yang panjang.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;$ bg
[1]+ ./gradlew build &amp;amp;amp;
$&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Le `&amp;amp;amp;`pada akhirnya berarti bahwa proses berjalan di latar belakang.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_jobs_daftar_pekerjaan_shell&amp;quot;&amp;gt;jobs — Daftar pekerjaan shell&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;$ jobs
[1]-  Stoppé    vim README.md
[2]+  En cours   ./gradlew build&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultFontName sans-serif

state &amp;quot;Depan
(terminal diblokir)&amp;quot; as fg #LightCoral
state &amp;quot;Ditangguhkan\n(Ctrl+Z)&amp;quot; as stopped #LightYellow
state &amp;quot;Latar belakang\n(bg)&amp;quot; as bg_ #LightGreen

[*] --&amp;amp;gt; fg : Lance une commande
fg --&amp;amp;gt; stopped : Ctrl+Z\n(SIGTSTP)
stopped --&amp;amp;gt; fg : fg\nreprend en avant-plan
stopped --&amp;amp;gt; bg_ : bg\nreprend en arrière-plan
bg_ --&amp;amp;gt; fg : fg\nramène en avant-plan
fg --&amp;amp;gt; [*] : Commande terminée

note right of stopped
  Le processus est figé
  mais _pas_ tué
end note

note left of bg_
  Le terminal est libre
  le build continue
end note
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_skenario_1_build_yang_panjang&amp;quot;&amp;gt;Skenario 1: Build yang panjang&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Ini adalah kasus penggunaan yang paling umum. Anda menjalankan build Gradle, terminal terblokir, dan Anda perlu melakukan hal lain.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE
autonumber

actor Développeur as dev
participant &amp;quot;Terminal&amp;quot; as term
participant &amp;quot;Gradle&amp;quot; as gradle
database &amp;quot;kondisi
proses&amp;quot; as state

dev -&amp;amp;gt; term : ./gradlew build
term -&amp;amp;gt; gradle : Lance le build
gradle -&amp;amp;gt; state : En cours (FG)
dev -&amp;amp;gt; term : Ctrl+Z
term -&amp;amp;gt; state : Suspendu (job [1])
term --&amp;amp;gt; dev : Prompt libre $

dev -&amp;amp;gt; term : git status
term --&amp;amp;gt; dev : working tree clean

dev -&amp;amp;gt; term : fg
term -&amp;amp;gt; state : Reprend (FG)
gradle --&amp;amp;gt; dev : Build réussi ✓

note right of state
  Le processus Gradle
  n&amp;#39;a jamais été tué
  Il reprend exactement
  où il s&amp;#39;était arrêté
end note
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_langkah_demi_langkah&amp;quot;&amp;gt;langkah demi langkah&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;$ ./gradlew build
&amp;amp;gt; Building...
^Z
[1]+  Stoppé    ./gradlew build

$ git status
On branch main
nothing to commit, working tree clean

$ fg
./gradlew build
&amp;amp;gt; Building...
BUILD SUCCESSFUL in 2m 34s&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_skenario_2_editor_yang_dilapkan&amp;quot;&amp;gt;Skenario 2: editor yang dilapkan&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Anda berada di`vim`, Anda perlu membuka file lain &amp;lt;em&amp;gt;tanpa&amp;lt;/em&amp;gt; meninggalkan`vim`.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;# Dans vim, en mode commande :
:shell
$ cat /path/to/other/file.txt
$ exit
# Retour dans vim&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Tetapi ini berat. Dengan`Ctrl+Z`, ini langsung :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;# Dans vim, n&amp;#39;importe quel moment :
Ctrl+Z
[1]+  Stoppé    vim README.md

$ cat /path/to/other/file.txt
# ... lire le fichier ...

$ fg
vim README.md
# Retour exact là où on en était, curseur en place&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE
autonumber

actor Développeur as dev
participant &amp;quot;semangat&amp;quot; as vim
participant &amp;quot;Kerang&amp;quot; as shell

dev -&amp;amp;gt; vim : édition en cours
dev -&amp;amp;gt; vim : Ctrl+Z
vim -&amp;amp;gt; shell : Suspendu (job [1])
dev -&amp;amp;gt; shell : cat autre_fichier.txt
shell --&amp;amp;gt; dev : contenu du fichier
dev -&amp;amp;gt; shell : fg
shell -&amp;amp;gt; vim : Reprend (job [1])
vim --&amp;amp;gt; dev : Curseur exactement\nà la même position

note right of vim
  vim restaure
  intégralement l&amp;#39;état :
  position du curseur,
  buffer, undo stack
end note
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;amp;lt;/think&amp;amp;gt; &amp;amp;lt;/think&amp;amp;gt; (No content to translate)&amp;lt;code&amp;gt;vim`mengembalikan dengan sempurna keadaan setelah sebuah`fg&amp;lt;/code&amp;gt;: posisi kursor, isi buffer, riwayat pembatalan — semua dipertahankan.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_scenario_3_proses_opencode_ditangguhkan&amp;quot;&amp;gt;Scenario 3 : Proses opencode ditangguhkan&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Ini adalah skenario yang memotivasi artikel ini. Anda menggunakan`opencode`(alat CLI untuk mengendalikan LLM pada kode Anda), dan prosesnya ditangguhkan.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultFontName sans-serif
skinparam componentStyle rectangle

actor Développeur as dev
participant &amp;quot;opcode\n(agen LLM)&amp;quot; as agent
participant &amp;quot;Shell&amp;quot; as shell
participant &amp;quot;git&amp;quot; as git

dev -&amp;amp;gt; agent : Session active
agent -&amp;amp;gt; agent : Tour de parole\ndu LLM en cours

note over agent
  Ctrl+Z arrive
  en plein milieu
  d&amp;#39;une analyse de code
end note

dev -&amp;amp;gt; agent : Ctrl+Z
agent -&amp;amp;gt; shell : Suspendu (job [1])
dev -&amp;amp;gt; git : git diff
git --&amp;amp;gt; dev : changements visibles
dev -&amp;amp;gt; shell : fg
shell -&amp;amp;gt; agent : Reprend (job [1])
agent --&amp;amp;gt; dev : Reprend exactement\nl&amp;#39;analyse en cours

note right of agent
  Aucune perte de contexte
  Le LLM reprend sa réponse
  exactement où il en était
end note
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;$ opencode
# ... session en cours, LLM analyse le code ...
^Z
[1]+  Stoppé    opencode

$ git diff HEAD~1
# vérifier les changements récents

$ fg
opencode
# Le LLM reprend là où il s&amp;#39;était arrêté&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Keuntungannya adalah ganda:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;olist arabic&amp;quot;&amp;gt;
&amp;lt;ol class=&amp;quot;arabic&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Tidak ada kehilangan konteks&amp;lt;/strong&amp;gt;: sesi LLM, riwayat percakapan, file yang terbuka — semuanya dipertahankan&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Tidak ada restart&amp;lt;/strong&amp;gt;: tidak perlu menjalankan ulang opencode, tidak perlu memuat ulang konteks, tidak perlu menjelaskan ulang masalah kepada LLM&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_skenario_4_mengelola_beberapa_pekerjaan_secara_bersamaan&amp;quot;&amp;gt;Skenario 4: Mengelola beberapa pekerjaan secara bersamaan&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;code&amp;gt;fg&amp;lt;/code&amp;gt; et `bg`Menerima nomor pekerjaan untuk menargetkan proses tertentu ketika beberapa proses ditangguhkan.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;$ vim config.yml
^Z
[1]+  Stoppé    vim config.yml

$ ./gradlew test
^Z
[2]+  Stoppé    ./gradlew test

$ htop
^Z
[3]+  Stoppé    htop

$ jobs
[1]   Stoppé    vim config.yml
[2]-  Stoppé    ./gradlew test
[3]+  Stoppé    htop

$ fg %2
./gradlew test
# Le build reprend en avant-plan

$ bg %3
[3] htop &amp;amp;amp;
# htop reprend en arrière-plan&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE

rectangle &amp;quot;Pekerjaan ditangguhkan&amp;quot; as jobs #LightYellow {
  card &amp;quot;[1] vim config.yml&amp;quot; as j1
  card &amp;quot;[2] ./gradlew test&amp;quot; as j2
  card &amp;quot;[3] htop&amp;quot; as j3
}

rectangle &amp;quot;Tindakan&amp;quot; as actions #LightGreen {
  card &amp;quot;fg %2\n→ latar depan&amp;quot; as a1
  card &amp;quot;bg %3\n→ arrière-plan&amp;quot; as a2
  card &amp;quot;fg %1\n→ latar depan&amp;quot; as a3
}

jobs --&amp;amp;gt; actions : fg/bg + numéro de job
actions --&amp;amp;gt; j2 : Reprend
actions --&amp;amp;gt; j3 : En arrière-plan

note bottom of jobs
  Chaque job conserve
  son état exact
  entre les suspend/resume
end note
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_ringkasan_perintah_pekerjaan&amp;quot;&amp;gt;Ringkasan perintah pekerjaan&amp;lt;/h3&amp;gt;
&amp;lt;table class=&amp;quot;tableblock frame-all grid-all stretch&amp;quot;&amp;gt;
&amp;lt;colgroup&amp;gt;
&amp;lt;col style=&amp;quot;width: 20%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 40%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 40%;&amp;quot;&amp;gt;
&amp;lt;/colgroup&amp;gt;
&amp;lt;thead&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;Pesanana&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;Efek&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;Contoh&amp;lt;/th&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/thead&amp;gt;
&amp;lt;tbody&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;Ctrl+Z&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Jeda proses latar depan&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Pengiriman`SIGTSTP`&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;fg&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Mengambil kembali pekerjaan default ke latar depan&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;fg&amp;lt;/code&amp;gt; ou &amp;lt;code&amp;gt;fg %1&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;fg %n&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Lanjutkan pekerjaan n di latar depan&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;fg %2&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;bg&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Melanjutkan job default di latar belakang&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;bg&amp;lt;/code&amp;gt; ou &amp;lt;code&amp;gt;bg %1&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;bg %n&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Meneruskan pekerjaan n di latar belakang&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;bg %2&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;jobs&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Daftar pekerjaan shell saat ini&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;jobs -l&amp;lt;/code&amp;gt;(dengan PID)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;kill %n&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Selesaikan pekerjaan n&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;kill %2&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_perbedaan_antara_fg_bg_ctrlz_ctrlc_dan&amp;quot;&amp;gt;Perbedaan antara fg, bg, Ctrl+Z, Ctrl+C dan &amp;amp;amp;&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Banyak pengembang bingung dengan mekanisme ini. Ini adalah penjelasan yang penting.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultFontName sans-serif

rectangle &amp;quot;**Ctrl+Z**\nSIGTSTP&amp;quot; as ctrlz #LightYellow {
  card &amp;quot;Menjeda proses
(masih berada di memori)
Dapat digunakan dengan fg/bg&amp;quot; as c1
}

rectangle &amp;quot;**Ctrl+C**\nSIGINT&amp;quot; as ctrlc #LightCoral {
  card &amp;quot;Bunuh proses
(Tidak ada kemungkinan kembali)
Kehilangan seluruh status&amp;quot; as c2
}

rectangle &amp;quot;**&amp;amp;amp; **
(latar belakang)&amp;quot; as ampersand #LightGreen {
  card &amp;quot;Mulai langsung
di latar belakang
Terminal gratis segera&amp;quot; as c3
}

rectangle &amp;quot;**fg**
(lat depan)&amp;quot; as fg_ #SkyBlue {
  card &amp;quot;Bawa pekerjaan
ditangguhkan di latar depan
Terminal sibuk&amp;quot; as c4
}

rectangle &amp;quot;**bg**
(lat belakang)&amp;quot; as bg_ #Lavender {
  card &amp;quot;Mengambil kembali pekerjaan
ditangguhkan di latar belakang
Terminal bebas&amp;quot; as c5
}

ctrlz -right-&amp;amp;gt; fg_ : fg pour reprendre
ctrlz -right-&amp;amp;gt; bg_ : bg pour continuer\nen arrière-plan
ctrlc -right-&amp;amp;gt; c2 : Pas de retour\nprocessus tué

note bottom of ctrlz
  Le processus est **figé**
  mais **pas tué**
end note

note bottom of ampersand
  Alternative : lancer
  ./gradlew build &amp;amp;amp;
  directement en arrière-plan
end note
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;table class=&amp;quot;tableblock frame-all grid-all stretch&amp;quot;&amp;gt;
&amp;lt;colgroup&amp;gt;
&amp;lt;col style=&amp;quot;width: 16.6666%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 33.3333%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 16.6666%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 33.3335%;&amp;quot;&amp;gt;
&amp;lt;/colgroup&amp;gt;
&amp;lt;thead&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;Pintasan/Perintah&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;sinyal&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;Efek&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;Kembali?&amp;lt;/th&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/thead&amp;gt;
&amp;lt;tbody&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;Ctrl+Z&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;SIGTSTP&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Menjeda proses (beku, di memori)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Ya :`fg` ou &amp;lt;code&amp;gt;bg&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;Ctrl+C&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;SIGINT&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Matikan proses (selesai)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Tidak : proses dihancurkan&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;Ctrl+\&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;SIGQUIT&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Membunuh proses + core dump&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Tidak: proses dihancurkan&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;commande &amp;amp;amp;&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;—&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Jalankan langsung di latar belakang&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;`fg`untuk membawakannya kembali ke lat depan&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;div class=&amp;quot;admonitionblock tip&amp;quot;&amp;gt;
&amp;lt;table&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;icon&amp;quot;&amp;gt;
&amp;lt;i class=&amp;quot;fa icon-tip&amp;quot; title=&amp;quot;Tip&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;code&amp;gt;Ctrl+Z`adalah&amp;lt;strong&amp;gt;non destruktif&amp;lt;/strong&amp;gt;. Prosesnya beku seperti yang ada, dalam memori. Anda dapat melanjutkan segera dengan`fg&amp;lt;/code&amp;gt;. Ini adalah &amp;lt;em&amp;gt;jeda&amp;lt;/em&amp;gt;, bukan &amp;lt;em&amp;gt;berhenti&amp;lt;/em&amp;gt;.
&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_siklus_hidup_lengkap_dari_sebuah_pekerjaan&amp;quot;&amp;gt;Siklus hidup lengkap dari sebuah pekerjaan&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultFontName sans-serif

title Siklus hidup lengkap dari pekerja shell
state &amp;quot;tidak ada&amp;quot; as none
state &amp;quot;Foreground\n(menempati terminal)&amp;quot; as foreground #LightCoral
state &amp;quot;Dijeda\n(Ctrl+Z / SIGTSTP)&amp;quot; as suspended #LightYellow
state &amp;quot;Latar belakang\n(bg atau &amp;amp;amp;)&amp;quot; as background #LightGreen
state &amp;quot;Selesai\n(exit code 0)&amp;quot; as done #LightGray

[*] --&amp;amp;gt; none
none --&amp;amp;gt; foreground : tape une commande
foreground --&amp;amp;gt; suspended : Ctrl+Z
foreground --&amp;amp;gt; done : fin normale

suspended --&amp;amp;gt; foreground : fg
suspended --&amp;amp;gt; background : bg
suspended --&amp;amp;gt; done : kill %n

background --&amp;amp;gt; foreground : fg
background --&amp;amp;gt; done : fin normale\n(en arrière-plan)
background --&amp;amp;gt; suspended : Ctrl+Z\n(si fg puis Ctrl+Z)

done --&amp;amp;gt; [*]

note right of suspended
  L&amp;#39;état suspendu est
  le pivot de tout le
  système de jobs
end note
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_tips_lanjut&amp;quot;&amp;gt;Tips lanjut&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_fg_dengan_pekerjaan_paling_baru_dan_pertukaran&amp;quot;&amp;gt;fg dengan pekerjaan paling baru dan pertukaran&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;code&amp;gt;fg`tanpa argumen mengembalikan pekerjaan yang ditandai&amp;lt;/code&amp;gt;+&amp;lt;code&amp;gt;(terbaru). Anda juga dapat menggunakan&amp;lt;/code&amp;gt;%-`untuk menargetkan pekerjaan sebelumnya :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;$ vim file1.txt
^Z
[1]+  Stoppé    vim file1.txt

$ vim file2.txt
^Z
[2]+  Stoppé    vim file2.txt

$ fg      # ramène job [2] (le plus récent)
$ fg %-   # ramène job [1] (le précédent)&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_mengerjakan_pekerjaan_dengan_baik&amp;quot;&amp;gt;Mengerjakan pekerjaan dengan baik&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;$ jobs
[1]-  Stoppé    vim config.yml
[2]+  Stoppé    ./gradlew test

$ kill %2       # envoie SIGTERM au job 2
$ kill -9 %1    # envoie SIGKILL au job 1 (force)&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_disown_melepaskan_dari_shell&amp;quot;&amp;gt;disown : melepaskan dari shell&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;code&amp;gt;disown`Menghapus satu pekerjaan dari tabel pekerjaan shell. Proses terus berjalan tetapi tidak dapat lagi dikembalikan dengan`fg&amp;lt;/code&amp;gt;&amp;amp;lt;/think&amp;amp;gt; (empty output)&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;$ ./gradlew build &amp;amp;amp;
[1] 12345

$ disown %1
# Le processus continue mais n&amp;#39;est plus lié au shell
# Ça survives à la fermeture du terminal&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_gabungkan_dengan_nohup_untuk_proses_proses_yang_lama&amp;quot;&amp;gt;Gabungkan dengan nohup untuk proses-proses yang lama&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Agar proses dapat bertahan saat terminal ditutup:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;$ nohup ./gradlew build &amp;amp;amp;
[1] 12345
$ disown %1
# Le build continue même si vous fermez le terminal&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultFontName sans-serif

rectangle &amp;quot;fg&amp;quot; as fg_ #SkyBlue {
  card &amp;quot;Bawa pekerjaan
terbaru (+)&amp;quot; as f1
  card &amp;quot;Sasarkan pekerjaan\nspesifik: fg %n&amp;quot; as f2
  card &amp;quot;Beralih : fg %-&amp;quot; as f3
}

rectangle &amp;quot;membunuh %n&amp;quot; as kill_ #LightCoral {
  card &amp;quot;SIGTERM (bersih)&amp;quot; as k1
  card &amp;quot;SIGKILL -9 (paksa)&amp;quot; as k2
}

rectangle &amp;quot;mengalakkan&amp;quot; as disown #Lavender {
  card &amp;quot;Lepaskan dari shell
(Bertahan saat putus koneksi)&amp;quot; as d1
}

rectangle &amp;quot;nohup + &amp;amp;amp;&amp;quot; as nohup_ #LightGreen {
  card &amp;quot;Proses yang diimmunisasi
terhadap SIGHUP
(masih berjalan saat penutupan
terminal)&amp;quot; as n1
}

fg_ -right-&amp;amp;gt; kill_ : si faut\nterminer
kill_ -right-&amp;amp;gt; disown_ : ou détacher
nohup_ -left-&amp;amp;gt; fg_ : fg ramène en\navant-plan

note bottom of disown_
  Après disown, fg ne peut
  plus ramener le job
end note
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_jebakan_dan_kesalahan_umum&amp;quot;&amp;gt;Jebakan dan kesalahan umum&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_mengalirkan_ctrlz_dan_ctrlc&amp;quot;&amp;gt;Mengalirkan Ctrl+Z dan Ctrl+C&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Ini adalah kesalahan yang paling sering.`Ctrl+C`menghentikan proses`Ctrl+Z`Dia menangguhkannya. Jika Anda menekan`Ctrl+C`Secara refleks, seluruh keadaan hilang — file terbuka, sesi LLM, build sedang berlangsung.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE

rectangle &amp;quot;Ctrl+Z (SIGTSTP)&amp;quot; as z #LightGreen {
  card &amp;quot;Proses yang membeku di memori&amp;quot; as z1
  card &amp;quot;fg membawanya kembali utuh&amp;quot; as z2
  card &amp;quot;Tidak ada kerugian&amp;quot; as z3
}

rectangle &amp;quot;Ctrl+C (SIGINT)&amp;quot; as c #LightCoral {
  card &amp;quot;Proses selesai&amp;quot; as c1
  card &amp;quot;Kondisi hilang secara permanen&amp;quot; as c2
  card &amp;quot;Tidak dapat dikembalikan&amp;quot; as c3
}

note bottom of z
  Pause → Reprendre
end note

note bottom of c
  Kill → Perdu
end note
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_pekerjaan_pekerjaan_adalah_lokal_untuk_shell&amp;quot;&amp;gt;Pekerjaan-pekerjaan adalah lokal untuk shell&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;code&amp;gt;jobs&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fg&amp;lt;/code&amp;gt; et `bg`Berfungsi hanya di shell yang meluncurkan proses-proses tersebut. Jika Anda membuka terminal baru, Anda tidak akan melihat pekerjaan dari yang lain.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;# Terminal 1
$ vim file.txt
^Z
[1]+  Stoppé    vim file.txt

# Terminal 2 (nouveau)
$ jobs
# (rien — les jobs sont locaux au shell)&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Untuk melihat proses system-wide, gunakan`ps` ou &amp;lt;code&amp;gt;htop&amp;lt;/code&amp;gt;(empty)&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;ps aux | grep vim&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_pekerjaan_tidak_bertahan_saat_shell_ditutup&amp;quot;&amp;gt;Pekerjaan tidak bertahan saat shell ditutup&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Jika Anda menutup terminal, semua pekerja yang ditangguhkan atau yang berjalan di latar belakang akan dihancurkan. Agar sebuah proses tetap hidup, gunakan`nohup`+&amp;lt;code&amp;gt;disown&amp;lt;/code&amp;gt; :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;$ nohup ./gradlew build &amp;amp;amp;
[1] 12345
$ disown %1
# Fermer le terminal → le build continue&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_proses_interaktif_dan_fg&amp;quot;&amp;gt;Proses interaktif dan fg&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Beberapa program interaktif tidak berjalan baik setelah satu`Ctrl+Z`+&amp;lt;code&amp;gt;fg&amp;lt;/code&amp;gt;. Ini jarang tetapi terjadi :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Beberapa program yang menggunakan sinyal (vim, htop, less menangani dengan baik`Ctrl+Z`)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Program jaringan yang berjalan lama (ssh, opencode) biasanya pulih dengan benar&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Program yang memodifikasi terminal (ncurses) terkadang dapat meninggalkan terminal dalam keadaan yang tidak terduga&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Jika terminal rusak setelah satu`fg`, ketik :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;reset&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_diagram_ringkasan&amp;quot;&amp;gt;Diagram Ringkasan&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultFontName sans-serif
skinparam ArrowColor #444444

title &amp;quot;fg — Tips yang mengubah semuanya&amp;quot;

rectangle &amp;quot;Masalah&amp;quot; as problem #LightCoral {
  card &amp;quot;Terminal diblokir\noleh proses&amp;quot; as p1
  card &amp;quot;Buka sebuah baru
terminal = solusi berat&amp;quot; as p2
}

rectangle &amp;quot;**Solusi**&amp;quot; as solution #LightGreen {
  card &amp;quot;Ctrl+Z → menangguhkan&amp;quot; as s1
  card &amp;quot;fg → melanjutkan&amp;quot; as s2
  card &amp;quot;bg → di latar belakang&amp;quot; as s3
  card &amp;quot;jobs → lihat yang ditangguhkan&amp;quot; as s4
}

rectangle &amp;quot;**Kelebihan**&amp;quot; as advantages #SkyBlue {
  card &amp;quot;Tidak ada kehilangan konteks&amp;quot; as a1
  card &amp;quot;Tidak ada restart&amp;quot; as a2
  card &amp;quot;Satu terminal cukup&amp;quot; as a3
}

problem -right-&amp;amp;gt; solution : &amp;quot;Solusi asli\nUnix selama 40 tahun&amp;quot;
solution -right-&amp;amp;gt; advantages : &amp;quot;Hasil&amp;quot;

note bottom of advantages
  fg n&amp;#39;est pas un tips obscur
  C&amp;#39;est un mécanisme fondamental
  du shell Unix
end note
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_mengapa_fg_dianggap_kurang&amp;quot;&amp;gt;Mengapa fg dianggap kurang?&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Kebanyakan pengembang modern menemukan`fg`lambat, bahkan tidak pernah. Beberapa alasan :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;olist arabic&amp;quot;&amp;gt;
&amp;lt;ol class=&amp;quot;arabic&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;IDE menyembunyikan terminal&amp;lt;/strong&amp;gt;: VS Code menyediakan terminal terintegrasi, tetapi tab-tab banyak membuat`fg`Terlihat kurang diperlukan&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Alat grafis mengganti CLI&amp;lt;/strong&amp;gt;: manajer file, pemantau sistem, klien Git grafis&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;tmux dan screen&amp;lt;/strong&amp;gt;: multiplexer ini menyelesaikan masalah yang sama dengan cara yang berbeda, tapi`fg`lebih sederhana dan selalu tersedia&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Namun,`fg`adalah :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;olist arabic&amp;quot;&amp;gt;
&amp;lt;ol class=&amp;quot;arabic&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Universal&amp;lt;/strong&amp;gt;: ada di semua shell POSIX (bash, zsh, fish&amp;amp;#8230;&amp;amp;#8203;)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Tanpa ketergantungan&amp;lt;/strong&amp;gt;: tidak perlu menginstal tmux atau screen&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;seketika&amp;lt;/strong&amp;gt;: dua pukulan (&amp;lt;code&amp;gt;Ctrl+Z`lalu`fg&amp;lt;/code&amp;gt;) untuk menangguhkan dan melanjutkan&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Penyimpan konteks&amp;lt;/strong&amp;gt;: keadaan proses masih utuh&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultFontName sans-serif

rectangle &amp;quot;Mengapa fg adalah
diperkirakan terlalu rendah ?&amp;quot; as why #LightCoral {
  card &amp;quot;IDE dengan terminal\nmulti-tab&amp;quot; as w1
  card &amp;quot;Outils graphiques\nqui remplacent le CLI&amp;quot; as w2
  card &amp;quot;tmux/screen\nrésolvent le problème\nautrement&amp;quot; as w3
}

rectangle &amp;quot;Mengapa fg adalah
lebih baik ?&amp;quot; as superior #LightGreen {
  card &amp;quot;Universal : bash,\nzsh, fish, sh...&amp;quot; as s1
  card &amp;quot;Tanpa ketergantungan:
tidak ada instalasi&amp;quot; as s2
  card &amp;quot;Dua pukulan:
Ctrl+Z lalu fg&amp;quot; as s3
  card &amp;quot;Konteks yang terpreservasi :\nnol kehilangan status&amp;quot; as s4
}

why -right-&amp;amp;gt; superior : &amp;quot;Kesederhanaan
selalu menang&amp;quot;
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_catatan_teknis_sinyal&amp;quot;&amp;gt;Catatan teknis : sinyal&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Untuk yang penasaran, berikut sinyal yang terlibat:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;table class=&amp;quot;tableblock frame-all grid-all stretch&amp;quot;&amp;gt;
&amp;lt;colgroup&amp;gt;
&amp;lt;col style=&amp;quot;width: 14.2857%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 28.5714%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 28.5714%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 28.5715%;&amp;quot;&amp;gt;
&amp;lt;/colgroup&amp;gt;
&amp;lt;thead&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;sinyal&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;nama&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;Dikirim oleh&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;Efek&amp;lt;/th&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/thead&amp;gt;
&amp;lt;tbody&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;2&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;SIGINT&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;Ctrl+C&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Interupsi — menghentikan proses&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;3&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;SIGQUIT&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;Ctrl+\&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Keluar dengan core dump&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;18&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;SIGTSTP&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;Ctrl+Z&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Berhenti interaktif — menghentikan proses&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;19&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;SIGCONT&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;fg&amp;lt;/code&amp;gt;/&amp;lt;code&amp;gt;bg&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Continue — melanjutkan proses yang ditangguhkan&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;15&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;SIGTERM&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;kill %n&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Penghentian bersih&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;9&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;SIGKILL&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;kill -9 %n&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Pengakhiran paksa (tidak dapat diintersep)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Alur lengkap :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultFontName sans-serif

participant &amp;quot;pengguna&amp;quot; as user
participant &amp;quot;cangkang&amp;quot; as shell
participant &amp;quot;Proses&amp;quot; as proc

user -&amp;amp;gt; shell : Ctrl+Z
shell -&amp;amp;gt; proc : SIGTSTP (signal 18)
note right of proc : Processus figé\nen mémoire

user -&amp;amp;gt; shell : fg
shell -&amp;amp;gt; proc : SIGCONT (signal 19)
note right of proc : Processus reprend\nen avant-plan

user -&amp;amp;gt; shell : bg
shell -&amp;amp;gt; proc : SIGCONT (signal 19)
note right of proc : Processus reprend\nen arrière-plan

user -&amp;amp;gt; shell : Ctrl+C
shell -&amp;amp;gt; proc : SIGINT (signal 2)
note right of proc : Processus terminé\nétat perdu
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_kesimpulan&amp;quot;&amp;gt;Kesimpulan&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;`fg`Bukanlah sebuah tips yang tidak jelas — ini adalah mekanisme dasar dari Unix shell, yang tersedia sejak tahun 1970-an. Fakta bahwa kebanyakan pengembang mengabaikannya adalah gejala dari jaman kita : IDE dan multiplexer telah menjauhkan kita dari dasar.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Tetapi ketika Anda berada pada server SSH, di terminal minimal, atau sekadar sedang menggunakan`opencode`dan LLM sedang bekerja penuh —&amp;lt;code&amp;gt;Ctrl+Z&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fg&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;bg&amp;lt;/code&amp;gt;, `jobs`adalah teman terbaik Anda.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;em&amp;gt;&amp;lt;em&amp;gt;_ Ctrl+Z menghentikan, fg melanjutkan. Ini adalah pause-play terminal. &amp;lt;/em&amp;gt;_&amp;lt;/em&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_referensi&amp;quot;&amp;gt;Referensi&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;code&amp;gt;man bash&amp;lt;/code&amp;gt;— bagian KONTROL PEKERJAAN&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;code&amp;gt;man signal&amp;lt;/code&amp;gt;— daftar sinyal POSIX&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://www.gnu.org/software/bash/manual/bash.html#Job-Control&amp;quot;&amp;gt;Manual Referensi Bash — Kontrol Pekerjaan&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://man7.org/linux/man-pages/man7/signal.7.html&amp;quot;&amp;gt;man 7 signal — sinyal Linux&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://tldp.org/LDP/abs/html/x312743.htm&amp;quot;&amp;gt;Panduan Penyandihan Bash Lanjutan — Pengendalian Pekerja&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>VS Code di Home : instalasi lokal dan penyesuaian font tanpa sudo</title>
            <link >https://cheroliv.com/blog/2026/0103_vscode_local_install_custom_fonts_post.html</link>
            <pubDate>Fri, 17 Apr 2026 00:00:00 +0000</pubDate>
            <guid isPermaLink="false">blog/2026/0103_vscode_local_install_custom_fonts_post.html</guid>
            <description>&amp;lt;div id=&amp;quot;toc&amp;quot; class=&amp;quot;toc&amp;quot;&amp;gt;
&amp;lt;div id=&amp;quot;toctitle&amp;quot;&amp;gt;Table des matières&amp;lt;/div&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel1&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_gambaran_umum_jalur&amp;quot;&amp;gt;Gambaran Umum Jalur&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_mengapa_vs_code_di_home&amp;quot;&amp;gt;Mengapa : VS Code di Home&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_masalah_dengan_instalasi_sistem&amp;quot;&amp;gt;Masalah dengan instalasi sistem&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_solusi_instalasi_lokal&amp;quot;&amp;gt;Solusi: instalasi lokal&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_keuntungan_konkret&amp;quot;&amp;gt;keuntungan konkret&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_sesuaikan_font_menu_utama&amp;quot;&amp;gt;Sesuaikan font menu utama&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_temukan&amp;quot;&amp;gt;Temukan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_tekniknya_patch_css_langsung&amp;quot;&amp;gt;Tekniknya: patch CSS langsung&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_hapus_peringatan_integritas&amp;quot;&amp;gt;Hapus peringatan integritas&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_menyesuaikan_font_panel_explorer_pertarungan&amp;quot;&amp;gt;Menyesuaikan font panel Explorer — pertarungan&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_percobaan_1_sebuah_parameter_yang_tidak_ada&amp;quot;&amp;gt;Percobaan 1 : sebuah parameter yang tidak ada&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_percobaan_2_ekstensi_custom_css_and_js_loader&amp;quot;&amp;gt;Percobaan 2 : ekstensi Custom CSS and JS Loader&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_jebakan_anda_harus_mengaktifkan_ekstensi_setiap_kali&amp;quot;&amp;gt;Jebakan: Anda harus mengaktifkan ekstensi setiap kali&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_percobaan_3_garis_garis_saling_tumpang_tindih&amp;quot;&amp;gt;Percobaan 3 : garis-garis saling tumpang tindih&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_solusi_zoom_pada_kontainer_induk&amp;quot;&amp;gt;Solusi: &amp;lt;code&amp;gt;zoom&amp;lt;/code&amp;gt; pada kontainer induk&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_ringkasan_selector_dan_apa_yang_berfungsi&amp;quot;&amp;gt;Ringkasan selector dan apa yang berfungsi&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_skrip_pembaruan_otomatis&amp;quot;&amp;gt;Skrip pembaruan otomatis&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_masalah&amp;quot;&amp;gt;Masalah&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_skrip_vscode_update_sh&amp;quot;&amp;gt;Skrip `vscode-update.sh&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_berkas_yang_terlibat_gambaran_umum&amp;quot;&amp;gt;Berkas yang terlibat — gambaran umum&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_pelajaran_yang_dipelajari&amp;quot;&amp;gt;Pelajaran yang dipelajari&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_penginstalan_di_home_membebaskan_semua&amp;quot;&amp;gt;Penginstalan di home membebaskan semua&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_zoom_font_size_untuk_komponen_daftar&amp;quot;&amp;gt;zoom` &amp;amp;gt; &amp;lt;code&amp;gt;font-size&amp;lt;/code&amp;gt; untuk komponen daftar&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_ekstensi_custom_css_memerlukan_aktivasi_eksplisit&amp;quot;&amp;gt;Ekstensi Custom CSS memerlukan aktivasi eksplisit&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_skrip_pembaruan_diperlukan&amp;quot;&amp;gt;Skrip pembaruan diperlukan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_referensi_cepat&amp;quot;&amp;gt;Referensi cepat&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div id=&amp;quot;preamble&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;waktu membaca : 8 minutes&amp;lt;/div&amp;gt;
&amp;lt;p&amp;gt;Mengapa menginstal VS Code di`/opt`atau via`apt`ketika kita dapat meletakkannya di rumah dan mengubah IDE kita menjadi taman bermain tanpa pernah mengetik`sudo`?Berikut adalah cerita tentang pemasangan lokal diikuti dengan pertempuran sengit untuk memperbesar font menu dan penjelajah file.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;dlist&amp;quot;&amp;gt;
&amp;lt;dl&amp;gt;
&amp;lt;dt class=&amp;quot;hdlist1&amp;quot;&amp;gt;tok&amp;lt;/dt&amp;gt;
&amp;lt;dd&amp;gt;
&amp;lt;p&amp;gt;[]&amp;lt;/p&amp;gt;
&amp;lt;/dd&amp;gt;
&amp;lt;/dl&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_gambaran_umum_jalur&amp;quot;&amp;gt;Gambaran Umum Jalur&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle

actor Développeur as dev
actor &amp;quot;opencode
+ LLM&amp;quot; as agent

package &amp;quot;Beranda Pengguna (~)&amp;quot; #LightGreen {
    package &amp;quot;~/apps/&amp;quot; {
        component &amp;quot;VS Code
(tarball)&amp;quot; as vscode #SkyBlue
        component &amp;quot;vscode-update.sh&amp;quot; as script #LightYellow
    }
    package &amp;quot;~/.config/Code/&amp;quot; {
        component &amp;quot;settings.json&amp;quot; as settings #Lavender
    }
    package &amp;quot;~/.vscode-custom-css/&amp;quot; {
        component &amp;quot;custom.css
(zoom: 1.25)&amp;quot; as customcss #Lavender
    }
}

cloud &amp;quot;code.visualstudio.com&amp;quot; as download

dev --&amp;amp;gt; vscode : Télécharge &amp;amp;amp; installe
dev --&amp;amp;gt; script : Lance mise à jour
script --&amp;amp;gt; download : wget dernière version
script --&amp;amp;gt; vscode : Remplace + re-patch CSS
agent --&amp;amp;gt; settings : Écrit config
agent --&amp;amp;gt; customcss : Écrit zoom CSS
agent --&amp;amp;gt; vscode : Patch workbench CSS

note right of vscode
  Installation dans le home
  = contrôle total sans sudo
end note
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_mengapa_vs_code_di_home&amp;quot;&amp;gt;Mengapa : VS Code di Home&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_masalah_dengan_instalasi_sistem&amp;quot;&amp;gt;Masalah dengan instalasi sistem&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;VS Code terpasang melalui`apt`, &amp;lt;code&amp;gt;dnf`atau di&amp;lt;/code&amp;gt;/opt`Mengunci file aplikasi di balik hak root. Setiap modifikasi pada CSS asli, dari`product.json`atau folder instalasi membutuhkan`sudo`.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Namun, ketika menggunakan&amp;lt;strong&amp;gt;opencode&amp;lt;/strong&amp;gt;(tool CLI untuk mengendalikan sebuah LLM pada kode-nya), agen tersebut perlu bisa :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Menulis dalam file konfigurasi (&amp;lt;code&amp;gt;~/.config/Code/User/settings.json&amp;lt;/code&amp;gt;) — ini, itu sudah berada di rumah&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Ubah aset IDE (CSS workbench,&amp;lt;code&amp;gt;product.json&amp;lt;/code&amp;gt;) untuk kustomisasi tingkat lanjut — ini diblokir tanpa sudo&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_solusi_instalasi_lokal&amp;quot;&amp;gt;Solusi: instalasi lokal&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Kami mengunduh arsip tarball resmi dan mengekstraknya langsung ke`~/apps/` :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;mkdir -p ~/apps
cd ~/apps
wget &amp;quot;https://code.visualstudio.com/sha/download?build=stable&amp;amp;amp;os=linux-x64&amp;quot; -O vscode.tar.gz
tar -xzf vscode.tar.gz
rm vscode.tar.gz&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Biner kemudian berada di`~/apps/VSCode-linux-x64/bin/code`. Tidak ada`sudo` tidak diperlukan untuk membaca, mengedit, atau mengganti apa pun file di bawah`~/apps/VSCode-linux-x64/`.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_keuntungan_konkret&amp;quot;&amp;gt;keuntungan konkret&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE

rectangle &amp;quot;Instalasi Sistem
(/opt, apt, dnf)&amp;quot; #LightCoral {
    card &amp;quot;VS Code kepemilikan root&amp;quot; {
        [CSS du workbench : sudo requis]
        [product.json : sudo requis]
        [Dossier d&amp;#39;install : non modifiable]
    }
}

rectangle &amp;quot;Instalasi Rumah\n(~/apps)&amp;quot; #LightGreen {
    card &amp;quot;VS Code properti pengguna&amp;quot; {
        [CSS du workbench : libre]
        [product.json : libre]
        [Dossier d&amp;#39;install : libre]
        [Script update maison]
    }
}

&amp;quot;Instalasi Sistem
(/opt, apt, dnf)&amp;quot; -right-&amp;amp;gt; &amp;quot;Instalasi Rumah\n(~/apps)&amp;quot; : **Non, merci**\nJe prends le home

note bottom of &amp;quot;Instalasi Rumah\n(~/apps)&amp;quot;
  L&amp;#39;agent opencode+LLM peut
  modifier les fichiers de l&amp;#39;IDE
  sans aucune élévation de privilèges
end note
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;table class=&amp;quot;tableblock frame-all grid-all stretch&amp;quot;&amp;gt;
&amp;lt;colgroup&amp;gt;
&amp;lt;col style=&amp;quot;width: 33.3333%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 66.6667%;&amp;quot;&amp;gt;
&amp;lt;/colgroup&amp;gt;
&amp;lt;thead&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;Kelebihan&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;detail&amp;lt;/th&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/thead&amp;gt;
&amp;lt;tbody&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Tidak ada sudo&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Semua file IDE milik pengguna&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Modifikasi langsung CSS&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Kita bisa patch`workbench.desktop.main.css`tanpa peningkatan hak istimewa&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Skrip pembaruan rumah&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Skrip shell sederhana mengunduh, mengganti, dan melakukan patch ulang secara otomatis&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Isolasi&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Installasi sistem tidak tercemar ; rollback = hapus folder&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_sesuaikan_font_menu_utama&amp;quot;&amp;gt;Sesuaikan font menu utama&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_temukan&amp;quot;&amp;gt;Temukan&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Font default menu utama (File, Edit, View&amp;amp;#8230;&amp;amp;#8203;) dan dropdown terlalu kecil. VS Code tidak menawarkan pengaturan apa pun untuk mengubahnya.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_tekniknya_patch_css_langsung&amp;quot;&amp;gt;Tekniknya: patch CSS langsung&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Menambahkan CSS ke akhir file`workbench.desktop.main.css`:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE
autonumber

actor Développeur as dev
participant &amp;quot;workbench.desktop
.main.css&amp;quot; as css
participant &amp;quot;product.json&amp;quot; as product
participant &amp;quot;VS Code&amp;quot; as vscode

== Patch CSS ==
dev -&amp;amp;gt; css : Ajouter règles font-size\n(.menubar-menu-title, etc.)
dev -&amp;amp;gt; vscode : Redémarrer VS Code
vscode -&amp;amp;gt; vscode : Charge le CSS patché
vscode --&amp;amp;gt; dev : Menus et dropdowns en 23px/22px ✓

== Suppression warning ==
vscode --&amp;amp;gt; dev : ⚠️ &amp;quot;Instalasi Anda rusak&amp;quot;
dev -&amp;amp;gt; product : Supprimer clé &amp;quot;checksums&amp;quot;\n(python3 -c ...)
dev -&amp;amp;gt; vscode : Redémarrer VS Code
vscode --&amp;amp;gt; dev : Plus de warning ✓

note over css, product
  Deux fichiers dans ~/apps/
  = modifiables sans sudo
end note
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;CSS_FILE=&amp;quot;$HOME/apps/VSCode-linux-x64/resources/app/out/vs/workbench/workbench.desktop.main.css&amp;quot;

cat &amp;amp;gt;&amp;amp;gt; &amp;quot;$CSS_FILE&amp;quot; &amp;amp;lt;&amp;amp;lt; &amp;#39;EOF&amp;#39;
.menubar-menu-title{font-size:23px!important}
.menubar&amp;amp;gt;.menubar-menu-button{font-size:23px!important}
.monaco-menu-option{font-size:22px!important;line-height:34px!important}
.monaco-menu .action-label:not(.codicon){font-size:22px!important}
.menubar-menu-items-holder .monaco-menu .action-item .action-label{font-size:22px!important}
EOF&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_hapus_peringatan_integritas&amp;quot;&amp;gt;Hapus peringatan integritas&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;VS Code mendeteksi perubahan pada file-nya dan menampilkan banner &amp;quot;Your installation is corrupt&amp;quot;. Untuk menghindarinya, kita menghapus checksum dari`product.json`&amp;amp;lt;/think&amp;amp;gt; &amp;amp;lt;/think&amp;amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-python hljs&amp;quot; data-lang=&amp;quot;python&amp;quot;&amp;gt;import json
product_json = &amp;quot;$HOME/apps/VSCode-linux-x64/resources/app/product.json&amp;quot;
with open(product_json, &amp;#39;r&amp;#39;) as f:
    data = json.load(f)
data.pop(&amp;#39;checksums&amp;#39;, None)
with open(product_json, &amp;#39;w&amp;#39;) as f:
    json.dump(data, f, indent=2)&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Setelah reboot, tidak ada lagi peringatan dan menu akhirnya dapat dibaca.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_menyesuaikan_font_panel_explorer_pertarungan&amp;quot;&amp;gt;Menyesuaikan font panel Explorer — pertarungan&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_percobaan_1_sebuah_parameter_yang_tidak_ada&amp;quot;&amp;gt;Percobaan 1 : sebuah parameter yang tidak ada&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Mencoba terlebih dahulu parameter bawaan`workbench.tree.fontSize`dalam`settings.json` :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-json hljs&amp;quot; data-lang=&amp;quot;json&amp;quot;&amp;gt;{
    &amp;quot;workbench.tree.fontSize&amp;quot;: 20
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Hasil :&amp;lt;strong&amp;gt;tidak ada efek&amp;lt;/strong&amp;gt;. Parameter ini tidak ada di VS Code. Bukti bahwa parameter yang tidak dikenali diabaikan diam-diam.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_percobaan_2_ekstensi_custom_css_and_js_loader&amp;quot;&amp;gt;Percobaan 2 : ekstensi Custom CSS and JS Loader&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Menginstal ekstensi`be5invis.vscode-custom-css`:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;~/apps/VSCode-linux-x64/bin/code --install-extension be5invis.vscode-custom-css&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Kami membuat file CSS di`~/.vscode-custom-css/custom.css`avec des sélecteurs ciblant les éléments de l&amp;amp;#8217;explorateur :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-css hljs&amp;quot; data-lang=&amp;quot;css&amp;quot;&amp;gt;.monaco-tree-rows .monaco-tree-row,
.sidebar .monaco-list-row,
.explorer-viewlet .monaco-list-row,
.monaco-list-row .monaco-icon-label {
    font-size: 22px !important;
    line-height: 36px !important;
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Dan kami mereferensikan file ini di pengaturan :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-json hljs&amp;quot; data-lang=&amp;quot;json&amp;quot;&amp;gt;{
    &amp;quot;vscode_custom_css.imports&amp;quot;: [
        &amp;quot;file:///home/user/.vscode-custom-css/custom.css&amp;quot;
    ]
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Hasil :&amp;lt;strong&amp;gt;perubahan tidak berlaku&amp;lt;/strong&amp;gt;.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_jebakan_anda_harus_mengaktifkan_ekstensi_setiap_kali&amp;quot;&amp;gt;Jebakan: Anda harus mengaktifkan ekstensi setiap kali&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Dokumentasi ekstensi tidak banyak memberikan informasi tentang poin penting ini : setelah setiap modifikasi CSS custom, harus&amp;lt;strong&amp;gt;perlu&amp;lt;/strong&amp;gt;menjalankan perintah dari palet :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;olist arabic&amp;quot;&amp;gt;
&amp;lt;ol class=&amp;quot;arabic&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;code&amp;gt;Ctrl+Shift+P&amp;lt;/code&amp;gt;→ mengetik&amp;lt;strong&amp;gt;Aktifkan CSS dan JS kustom&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;VS Code meminta restart → terima&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Tanpa langkah ini, CSS tidak pernah di‑injeksi ke IDE.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_percobaan_3_garis_garis_saling_tumpang_tindih&amp;quot;&amp;gt;Percobaan 3 : garis-garis saling tumpang tindih&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Meski dengan`font-size: 22px` et &amp;lt;code&amp;gt;line-height: 36px&amp;lt;/code&amp;gt;, huruf dengan jambangan (g, p, f, b) meluap ke baris sebelah. Kita mencoba menambahkan`height` et &amp;lt;code&amp;gt;min-height&amp;lt;/code&amp;gt;:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-css hljs&amp;quot; data-lang=&amp;quot;css&amp;quot;&amp;gt;.monaco-list-row {
    height: 36px !important;
    min-height: 36px !important;
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Hasil :&amp;lt;strong&amp;gt;tidak ada perubahan&amp;lt;/strong&amp;gt;. VS Code menghitung tinggi baris dalam JavaScript dan meng-override nilai CSS.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_solusi_zoom_pada_kontainer_induk&amp;quot;&amp;gt;Solusi: &amp;lt;code&amp;gt;zoom&amp;lt;/code&amp;gt; pada kontainer induk&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE

rectangle &amp;quot;Percobaan 1\nParameter tidak ada&amp;quot; #LightCoral {
    card &amp;quot;workbench.tree.fontSize&amp;quot; as t1 {
        [VS Code ignore le paramètre]
    }
}

rectangle &amp;quot;Percobaan 2
CSS Kustom + font-size&amp;quot; #LightYellow {
    card &amp;quot;.monaco-list-row { font-size: 22px }&amp;quot; as t2 {
        [Texte plus grand\nmais lignes superposées]
    }
}

rectangle &amp;quot;Percobaan 3\nfont-size + line-height + height&amp;quot; #LightYellow {
    card &amp;quot;height: 36px !important&amp;quot; as t3 {
        [VS Code override\npar JavaScript]
        [Aucun effet]
    }
}

rectangle &amp;quot;Solusi
zoom pada kontainer induk&amp;quot; #LightGreen {
    card &amp;quot;.monaco-list-rows { zoom: 1.25 }&amp;quot; as t4 {
        [Texte + espacement\nproportionnels]
        [Layout JS respecté]
        [✓ Résultat conforme]
    }
}

t1 -down-&amp;amp;gt; t2 : Pas de paramètre ?\nOn essaie le CSS
t2 -down-&amp;amp;gt; t3 : Lignes superposées ?\nOn force la hauteur
t3 -down-&amp;amp;gt; t4 : Override JS ?\nOn utilise zoom

@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Alih-alih melawan layout JavaScript, kita menggunakan properti`zoom`pada kontainer baris :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-css hljs&amp;quot; data-lang=&amp;quot;css&amp;quot;&amp;gt;.explorer-viewlet .monaco-list-rows,
.sidebar .monaco-list-rows {
    zoom: 1.25;
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;`zoom`memperbesar secara seragam rendering kontainer — teks dan spasi — tanpa merusak layout yang dihitung oleh mesin JavaScript VS Code. Faktor 1,25 sesuai dengan perkiraan transisi dari 13px menjadi 16px.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Hasil :&amp;lt;strong&amp;gt;nama file akhirnya dapat dibaca, baris dengan spasi yang tepat&amp;lt;/strong&amp;gt;.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_ringkasan_selector_dan_apa_yang_berfungsi&amp;quot;&amp;gt;Ringkasan selector dan apa yang berfungsi&amp;lt;/h3&amp;gt;
&amp;lt;table class=&amp;quot;tableblock frame-all grid-all stretch&amp;quot;&amp;gt;
&amp;lt;colgroup&amp;gt;
&amp;lt;col style=&amp;quot;width: 25%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 50%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 25%;&amp;quot;&amp;gt;
&amp;lt;/colgroup&amp;gt;
&amp;lt;thead&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;pendekatan&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;CSS&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;Hasil&amp;lt;/th&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/thead&amp;gt;
&amp;lt;tbody&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;font-size`pada&amp;lt;/code&amp;gt;.monaco-list-row`&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;font-size: 22px !important&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Teks lebih besar tetapi baris yang tumpang tindih&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;line-height&amp;lt;/code&amp;gt;+&amp;lt;code&amp;gt;height`pada&amp;lt;/code&amp;gt;.monaco-list-row`&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;line-height: 36px; height: 36px&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Tidak ada efek (mengganti JS)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;zoom`pada&amp;lt;/code&amp;gt;.monaco-list-rows`&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;zoom: 1.25&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Berhasil — teks + spasi proporsional&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_skrip_pembaruan_otomatis&amp;quot;&amp;gt;Skrip pembaruan otomatis&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE
autonumber

actor &amp;quot;Pengembang
atau cron&amp;quot; as dev
participant &amp;quot;vscode-update.sh&amp;quot; as script
participant &amp;quot;vscode tarball\n(code.visualstudio.com)&amp;quot; as remote
participant &amp;quot;~/apps/
VSCode-linux-x64&amp;quot; as local
participant &amp;quot;~/apps/\nvscode-backup&amp;quot; as backup
participant &amp;quot;workbench\n.desktop.main.css&amp;quot; as css
participant &amp;quot;product.json&amp;quot; as product

dev -&amp;amp;gt; script : ./vscode-update.sh
script -&amp;amp;gt; local : Lire version courante
script -&amp;amp;gt; remote : wget dernière version
script -&amp;amp;gt; script : Comparer versions

alt Version identique
    script --&amp;amp;gt; dev : &amp;quot;Sudah terkini!&amp;quot;
else Nouvelle version
    script -&amp;amp;gt; backup : Sauvegarder installation courante\n(horodatée)
    script -&amp;amp;gt; local : rm -rf ancienne installation
    script -&amp;amp;gt; local : mv nouvelle installation
    script -&amp;amp;gt; css : Ajouter patch CSS\n(menubar 23px, dropdown 22px)
    script -&amp;amp;gt; product : Supprimer checksums\n(python3)
    script --&amp;amp;gt; dev : &amp;quot;Pembaruan selesai\n1.xx.yy → 1.zz.ww&amp;quot;
end note
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_masalah&amp;quot;&amp;gt;Masalah&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Ketika VS Code memperbarui, ia mengganti folder instalasi dan&amp;lt;strong&amp;gt;Hapus semua patch CSS&amp;lt;/strong&amp;gt;. Harus mengaplikasikan ulang perubahan secara manual setiap kali.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_skrip_vscode_update_sh&amp;quot;&amp;gt;Skrip `vscode-update.sh&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Kita menciptakan`~/apps/vscode-update.sh`:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;#!/bin/bash
set -e

VSCODE_DIR=&amp;quot;$HOME/apps/VSCode-linux-x64&amp;quot;
BACKUP_DIR=&amp;quot;$HOME/apps/vscode-backup&amp;quot;
TEMP_DIR=&amp;quot;/tmp/vscode-update&amp;quot;

MENUBAR_FONT_SIZE=&amp;quot;23px&amp;quot;
DROPDOWN_FONT_SIZE=&amp;quot;22px&amp;quot;

echo &amp;quot;=== VS Code Local Updater ===&amp;quot;

CURRENT_VERSION=$(&amp;quot;$VSCODE_DIR/bin/code&amp;quot; --version 2&amp;amp;gt;/dev/null | head -1)
echo &amp;quot;Current version: $CURRENT_VERSION&amp;quot;

echo &amp;quot;Downloading latest VS Code...&amp;quot;
mkdir -p &amp;quot;$TEMP_DIR&amp;quot;
wget -q &amp;quot;https://code.visualstudio.com/sha/download?build=stable&amp;amp;amp;os=linux-x64&amp;quot; \
    -O &amp;quot;$TEMP_DIR/vscode.tar.gz&amp;quot;

echo &amp;quot;Extracting...&amp;quot;
mkdir -p &amp;quot;$TEMP_DIR/extracted&amp;quot;
tar -xzf &amp;quot;$TEMP_DIR/vscode.tar.gz&amp;quot; -C &amp;quot;$TEMP_DIR/extracted&amp;quot;

NEW_VERSION=$(&amp;quot;$TEMP_DIR/extracted/VSCode-linux-x64/bin/code&amp;quot; \
    --version 2&amp;amp;gt;/dev/null | head -1)
echo &amp;quot;New version: $NEW_VERSION&amp;quot;

if [ &amp;quot;$CURRENT_VERSION&amp;quot; = &amp;quot;$NEW_VERSION&amp;quot; ]; then
    echo &amp;quot;Already up to date!&amp;quot;
    rm -rf &amp;quot;$TEMP_DIR&amp;quot;
    exit 0
fi

echo &amp;quot;Backing up current installation...&amp;quot;
mkdir -p &amp;quot;$BACKUP_DIR&amp;quot;
cp -r &amp;quot;$VSCODE_DIR&amp;quot; &amp;quot;$BACKUP_DIR/VSCode-linux-x64-$(date +%Y%m%d-%H%M%S)&amp;quot;

echo &amp;quot;Updating...&amp;quot;
rm -rf &amp;quot;$VSCODE_DIR&amp;quot;
mv &amp;quot;$TEMP_DIR/extracted/VSCode-linux-x64&amp;quot; &amp;quot;$VSCODE_DIR&amp;quot;
rm -rf &amp;quot;$TEMP_DIR&amp;quot;

CSS_FILE=&amp;quot;$VSCODE_DIR/resources/app/out/vs/workbench/workbench.desktop.main.css&amp;quot;
echo &amp;quot;Re-applying custom CSS patch \
    (menubar=${MENUBAR_FONT_SIZE}, dropdown=${DROPDOWN_FONT_SIZE})...&amp;quot;
cat &amp;amp;gt;&amp;amp;gt; &amp;quot;$CSS_FILE&amp;quot; &amp;amp;lt;&amp;amp;lt; CSSEOF
.menubar-menu-title{font-size:${MENUBAR_FONT_SIZE}!important}\
.menubar&amp;amp;gt;.menubar-menu-button{font-size:${MENUBAR_FONT_SIZE}!important}\
.monaco-menu-option{font-size:${DROPDOWN_FONT_SIZE}!important;\
line-height:34px!important}\
.monaco-menu .action-label:not(.codicon)\
{font-size:${DROPDOWN_FONT_SIZE}!important}\
.menubar-menu-items-holder .monaco-menu .action-item .action-label\
{font-size:${DROPDOWN_FONT_SIZE}!important}
CSSEOF

PRODUCT_JSON=&amp;quot;$VSCODE_DIR/resources/app/product.json&amp;quot;
if [ -f &amp;quot;$PRODUCT_JSON&amp;quot; ]; then
    echo &amp;quot;Removing checksums to prevent integrity warning...&amp;quot;
    python3 -c &amp;quot;
import json
with open(&amp;#39;$PRODUCT_JSON&amp;#39;,&amp;#39;r&amp;#39;) as f: data=json.load(f)
data.pop(&amp;#39;checksums&amp;#39;, None)
with open(&amp;#39;$PRODUCT_JSON&amp;#39;,&amp;#39;w&amp;#39;) as f: json.dump(data,f,indent=2)
print(&amp;#39;Done.&amp;#39;)
&amp;quot;
fi

echo &amp;quot;=== Update complete: $CURRENT_VERSION -&amp;amp;gt; $NEW_VERSION ===&amp;quot;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Skrip :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;olist arabic&amp;quot;&amp;gt;
&amp;lt;ol class=&amp;quot;arabic&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Unduh versi terbaru yang stabil&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Periksa apakah versi berbeda dari versi saat ini&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Simpan instalasi saat ini (berlabel waktu) di`~/apps/vscode-backup/`&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Ganti folder instalasi&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Terapkan kembali&amp;lt;/strong&amp;gt;patch CSS menu utama&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Hapus checksums&amp;lt;/strong&amp;gt; de `product.json`untuk menghindari peringatan integritas&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Catatan: Kustomisasi panel Explorer melalui ekstensi Custom CSS dan file`~/.vscode-custom-css/custom.css`mereka bertahan terhadap pembaruan karena mereka berada di luar folder instalasi&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_berkas_yang_terlibat_gambaran_umum&amp;quot;&amp;gt;Berkas yang terlibat — gambaran umum&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle

rectangle &amp;quot;Instalasi VS Code\n(~/apps/VSCode-linux-x64/)&amp;quot; #LightCoral {
    component &amp;quot;workbench.desktop
.main.css&amp;quot; as css #LightCoral
    component &amp;quot;product.json&amp;quot; as product #LightCoral
    component &amp;quot;biner
bin/code&amp;quot; as binary #LightCoral
}

rectangle &amp;quot;Konfigurasi pengguna\n(~/.config/Code/)&amp;quot; #LightGreen {
    component &amp;quot;settings.json&amp;quot; as settings #LightGreen
    component &amp;quot;ekstensi/&amp;quot; as extensions #LightGreen
}

rectangle &amp;quot;Kustomisasi Persisten
(~/.vscode-custom-css/)&amp;quot; #SkyBlue {
    component &amp;quot;custom.css
(zoom: 1.25)&amp;quot; as customcss #SkyBlue
}

rectangle &amp;quot;Otomatisasi
(~/apps/)&amp;quot; #LightYellow {
    component &amp;quot;vscode-update.sh&amp;quot; as script #LightYellow
    component &amp;quot;vscode-backup/&amp;quot; as backup #LightYellow
}

script ..&amp;amp;gt; css : Re-patch après MAJ
script ..&amp;amp;gt; product : Supprime checksums\naprès MAJ
settings --&amp;amp;gt; customcss : Référence via\nvscode_custom_css.imports
extensions ..&amp;amp;gt; css : Custom CSS Loader\ninjecte custom.css

note bottom of css
  **Ne survit pas** aux mises à jour
  Re-patché par le script
end note

note bottom of customcss
  **Survit** aux mises à jour
  Vit hors du dossier d&amp;#39;installation
end note

note bottom of settings
  **Survit** aux mises à jour
  Vit dans ~/.config/
end note
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;table class=&amp;quot;tableblock frame-all grid-all stretch&amp;quot;&amp;gt;
&amp;lt;colgroup&amp;gt;
&amp;lt;col style=&amp;quot;width: 28.5714%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 42.8571%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 28.5715%;&amp;quot;&amp;gt;
&amp;lt;/colgroup&amp;gt;
&amp;lt;thead&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;Jalan&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;peran&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;Tetap bertahan setelah update?&amp;lt;/th&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/thead&amp;gt;
&amp;lt;tbody&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;~/apps/VSCode-linux-x64/&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Instalasi IDE&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Tidak (diganti)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;~/apps/VSCode-linux-x64/resources/app/out/vs/workbench/workbench.desktop.main.css&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;CSS dari workbench (menu, dropdown)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Tidak (dipatch ulang oleh skrip)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;~/apps/VSCode-linux-x64/resources/app/product.json&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Checksums integritas&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Tidak (dibersihkan kembali oleh skrip)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;~/apps/vscode-update.sh&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Skrip pembaruan otomatis&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Ya&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;~/.config/Code/User/settings.json&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Pengaturan pengguna (fontSize, custom CSS)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Ya&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;~/.vscode-custom-css/custom.css&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;CSS khusus untuk Explorer (zoom)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Ya&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_pelajaran_yang_dipelajari&amp;quot;&amp;gt;Pelajaran yang dipelajari&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultFontName sans-serif
skinparam ArrowColor #444444

rectangle &amp;quot;**Instalasi lokal**\n di rumah&amp;quot; as install #LightGreen {
    card &amp;quot;Tidak ada sudo\n untuk mengubah CSS/config&amp;quot; as i1
    card &amp;quot;opencode+LLM dapat
 mengubah semuanya&amp;quot; as i2
    card &amp;quot;Rollback = memulihkan
 cadangan berlabel waktu&amp;quot; as i3
}

rectangle &amp;quot;**Kustomisasi CSS**
zoom vs ukuran font&amp;quot; as css #LightYellow {
    card &amp;quot;zoom &amp;amp;gt; font-size\nfor component list&amp;quot; as c1
    card &amp;quot;Override tata letak JS
tinggi/line-height&amp;quot; as c2
    card &amp;quot;Extension Custom CSS
memerlukan aktivasi
manual (Ctrl+Shift+P)&amp;quot; as c3
}

rectangle &amp;quot;**Otomatisasi**
skrip pembaruan&amp;quot; as auto #LightCoral {
    card &amp;quot;Re-patch CSS setelah\n setiap pembaruan&amp;quot; as a1
    card &amp;quot;Menghapus checksums\n product.json&amp;quot; as a2
    card &amp;quot;Backup berstempel waktu
 sebelum penggantian&amp;quot; as a3
}

install -right-&amp;amp;gt; css : ensuite
css -right-&amp;amp;gt; auto : enfin

note top of install
  Contrôle total sans élévation
  de privilèges
end note

note top of auto
  Sans script, chaque MAJ
  efface les patches CSS
end note
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_penginstalan_di_home_membebaskan_semua&amp;quot;&amp;gt;Penginstalan di home membebaskan semua&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Sebuah instalasi lokal di`~/apps`memberi kontrol penuh atas VS Code :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Tidak`sudo`untuk mengubah CSS atau konfigurasi&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Agent opencode+LLM dapat menulis ke file konfigurasi dan aset IDE&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Rollback sederhana: memulihkan cadangan berstempel waktu&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_zoom_font_size_untuk_komponen_daftar&amp;quot;&amp;gt;zoom` &amp;amp;gt; &amp;lt;code&amp;gt;font-size&amp;lt;/code&amp;gt; untuk komponen daftar&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;VS Code mengontrol tinggi baris melalui JavaScript. Ubah`font-size`Tidak dapat menyentuh tinggi yang dialokasikan oleh mesin layout menghasilkan teks yang meluap. Properti`zoom`pada kontainer induk memperbesar semuanya secara proporsional dan mengatasi masalah ini&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_ekstensi_custom_css_memerlukan_aktivasi_eksplisit&amp;quot;&amp;gt;Ekstensi Custom CSS memerlukan aktivasi eksplisit&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Ini bukan parameter &amp;quot;set and forget&amp;quot;. Setiap modifikasi pada file CSS custom memerlukan menjalankan ulang &amp;quot;Enable Custom CSS and JS&amp;quot; dari palet perintah (&amp;lt;code&amp;gt;Ctrl+Shift+P&amp;lt;/code&amp;gt;).&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_skrip_pembaruan_diperlukan&amp;quot;&amp;gt;Skrip pembaruan diperlukan&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Tanpa skrip, setiap pembaruan VS Code menghapus patch CSS dari workbench. Skrip`vscode-update.sh`mengotomatisasi pengunduhan, penggantian, re-patch, dan pembersihan checksum.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_referensi_cepat&amp;quot;&amp;gt;Referensi cepat&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Pengunduhan VS Code : &amp;lt;a href=&amp;quot;https://code.visualstudio.com/Download&amp;quot; class=&amp;quot;bare&amp;quot;&amp;gt;https://code.visualstudio.com/Download&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Pemuatan CSS dan JS Kustom Ekstensi :`be5invis.vscode-custom-css`&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Masalah VS Code yang meminta pengaturan asli untuk font Explorer :https://github.com/microsoft/vscode/issues/149[github.com/microsoft/vscode#149]&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>Migrasi plugin Asciidoctor RevealJS ke buildSrc Kotlin: reverse engineering dari API Groovy</title>
            <link >https://cheroliv.com/blog/2026/0102_migration_gradle_script_to_plugin_post.html</link>
            <pubDate>Thu, 16 Apr 2026 00:00:00 +0000</pubDate>
            <guid isPermaLink="false">blog/2026/0102_migration_gradle_script_to_plugin_post.html</guid>
            <description>&amp;lt;div id=&amp;quot;toc&amp;quot; class=&amp;quot;toc&amp;quot;&amp;gt;
&amp;lt;div id=&amp;quot;toctitle&amp;quot;&amp;gt;Table des matières&amp;lt;/div&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel1&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_konteks_dan_tujuan&amp;quot;&amp;gt;Konteks dan tujuan&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_titik_awal&amp;quot;&amp;gt;titik awal&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_tujuan&amp;quot;&amp;gt;Tujuan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_halangan_pertama_ruby_gems&amp;quot;&amp;gt;Halangan pertama : `ruby { gems() }&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_apa_yang_disembunyikan_oleh_gula_sintaksis_groovy&amp;quot;&amp;gt;Apa yang disembunyikan oleh gula sintaksis Groovy&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_dekomposisi_mekanisme&amp;quot;&amp;gt;Dekomposisi mekanisme&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_solusi_dalam_tiga_bagian&amp;quot;&amp;gt;Solusi dalam tiga bagian&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_bagian_1_repositori_ivy_untuk_rubygems&amp;quot;&amp;gt;Bagian 1: repositori Ivy untuk rubygems&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_bagian_2_ketergantungan_asciidoctorgems&amp;quot;&amp;gt;Bagian 2: ketergantungan asciidoctorGems&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_bagian_3_settings_gradle_kts&amp;quot;&amp;gt;Bagian 3 : settings.gradle.kts&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_introspeksi_api_dengan_bytecode&amp;quot;&amp;gt;Introspeksi API dengan bytecode&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_mengapa_javap&amp;quot;&amp;gt;Mengapa &amp;lt;code&amp;gt;javap&amp;lt;/code&amp;gt; ?&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_hierarki_tugas&amp;quot;&amp;gt;Hierarki tugas&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_penemuan_forkoptions&amp;quot;&amp;gt;Penemuan `forkOptions&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_api_javaforkoptions_grolifant&amp;quot;&amp;gt;API JavaForkOptions (grolifant)&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_revealjsextension&amp;quot;&amp;gt;RevealJSExtension&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_konfigurasi_toolchain_java&amp;quot;&amp;gt;Konfigurasi toolchain Java&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_masalah_dari_javatoolchainservice&amp;quot;&amp;gt;Masalah dari JavaToolchainService&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_deteksi_otomatis_docker&amp;quot;&amp;gt;Deteksi otomatis Docker&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_konteks&amp;quot;&amp;gt;konteks&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_strategi&amp;quot;&amp;gt;Strategi&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_hasil_akhir&amp;quot;&amp;gt;Hasil akhir&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_buildscript_konsumen&amp;quot;&amp;gt;buildscript konsumen&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_settings_gradle_kts&amp;quot;&amp;gt;settings.gradle.kts&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_ringkasan_jebakan_dan_solusi&amp;quot;&amp;gt;Ringkasan jebakan dan solusi&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_metode_penyelidikan_membaca_api_yang_tidak_diketahui_dengan_javap&amp;quot;&amp;gt;Metode penyelidikan : membaca API yang tidak diketahui dengan javap&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_prinsip&amp;quot;&amp;gt;prinsip&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_langkah_1_mencari_jar_di_cache_gradle&amp;quot;&amp;gt;Langkah 1: mencari jar di cache Gradle&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_langkah_2_mencantumkan_kelas_dari_jar&amp;quot;&amp;gt;Langkah 2: Mencantumkan kelas dari jar&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_langkah_3_memeriksa_sebuah_kelas&amp;quot;&amp;gt;Langkah 3: memeriksa sebuah kelas&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_langkah_4_naik_ke_atas_dalam_hierarki&amp;quot;&amp;gt;Langkah 4 : naik ke atas dalam hierarki&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_langkah_5_mengikuti_tipe_tidak_dikenal&amp;quot;&amp;gt;Langkah 5 : mengikuti tipe tidak dikenal&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_langkah_6_memeriksa_ekstensi_proyek&amp;quot;&amp;gt;Langkah 6: memeriksa ekstensi proyek&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_ringkasan_metode&amp;quot;&amp;gt;Ringkasan metode&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_langkah_selanjutnya&amp;quot;&amp;gt;Langkah selanjutnya&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_konteks_dan_tujuan&amp;quot;&amp;gt;Konteks dan tujuan&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_titik_awal&amp;quot;&amp;gt;titik awal&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Proyek`slider-gradle`Menghasilkan presentasi Reveal.js dari file AsciiDoc menggunakan plugin Gradle`org.asciidoctor.jvm.revealjs`. Konfigurasi tugas utama`asciidoctorRevealJs`berada langsung dalam buildscript root`build.gradle.kts`(blank)&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;plugins { id(&amp;quot;org.asciidoctor.jvm.revealjs&amp;quot;) }

apply&amp;amp;lt;slides.SlidesPlugin&amp;amp;gt;()

project.tasks.getByName&amp;amp;lt;AsciidoctorJRevealJSTask&amp;amp;gt;(TASK_ASCIIDOCTOR_REVEALJS) {
    repositories { ruby { gems() } }
    revealjs {
        version = &amp;quot;3.1.0&amp;quot;
        templateGitHub {
            setOrganisation(&amp;quot;hakimel&amp;quot;)
            setRepository(&amp;quot;reveal.js&amp;quot;)
            setTag(&amp;quot;3.9.1&amp;quot;)
        }
    }
    revealjsOptions {
        // ... configuration complète
    }
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_tujuan&amp;quot;&amp;gt;Tujuan&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Memindahkan seluruh konfigurasi ini ke`buildSrc/src/main/kotlin/slides/SlidesPlugin.kt`supaya buildscript konsumen berkurang menjadi :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;apply&amp;amp;lt;slides.SlidesPlugin&amp;amp;gt;()&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Plugin harus sepenuhnya mandiri: ia mengaplikasikan sendiri dependensi plugin-nya, mengonfigurasi repositori-nya, dan mengelola gem Ruby-nya.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_halangan_pertama_ruby_gems&amp;quot;&amp;gt;Halangan pertama : `ruby { gems() }&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_apa_yang_disembunyikan_oleh_gula_sintaksis_groovy&amp;quot;&amp;gt;Apa yang disembunyikan oleh gula sintaksis Groovy&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Baris`repositories { ruby { gems() } }`adalah ekstensi DSL Groovy yang hanya tersedia dalam konteks eksekusi buildscript. Ia tidak ada sebagai API Kotlin statis yang dapat diakses dari buildSrc.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Saat mencoba memanggilnya dari`SlidesPlugin.kt`, kompilasi gagal segera.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_dekomposisi_mekanisme&amp;quot;&amp;gt;Dekomposisi mekanisme&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Setelah menganalisis cache Gradle (&amp;lt;code&amp;gt;~/.gradle/caches/modules-2/files-2.1/rubygems/&amp;lt;/code&amp;gt;), kami menemukan di file`ivy-3.1.0.xml`:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-xml hljs&amp;quot; data-lang=&amp;quot;xml&amp;quot;&amp;gt;&amp;amp;lt;artifact type=&amp;#39;gem&amp;#39; url=&amp;#39;https://rubygems.org/gems/asciidoctor-revealjs-3.1.0.gem&amp;#39; /&amp;amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;`ruby { gems() }`sebenarnya melakukan&amp;lt;strong&amp;gt;tiga operasi berbeda&amp;lt;/strong&amp;gt;:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;olist arabic&amp;quot;&amp;gt;
&amp;lt;ol class=&amp;quot;arabic&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Menyimpan repo Ivy yang menunjuk ke`https://rubygems.org/gems/`&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Mengecualikan kelompok`rubygems`des repositori Maven untuk menghindari konflik&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Simpan gem dalam konfigurasi`asciidoctorGems`agar JRuby memuatnya saat runtime&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Ketiga tanggung jawab ini harus direproduksi secara terpisah dalam Kotlin.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_solusi_dalam_tiga_bagian&amp;quot;&amp;gt;Solusi dalam tiga bagian&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_bagian_1_repositori_ivy_untuk_rubygems&amp;quot;&amp;gt;Bagian 1: repositori Ivy untuk rubygems&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;project.repositories.mavenCentral() {
    content { excludeGroup(&amp;quot;rubygems&amp;quot;) }
}
project.repositories.ivy {
    url = project.uri(&amp;quot;https://rubygems.org/gems/&amp;quot;)
    patternLayout { artifact(&amp;quot;[module]-[revision].gem&amp;quot;) } &amp;lt;i class=&amp;quot;conum&amp;quot; data-value=&amp;quot;1&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&amp;lt;b&amp;gt;(1)&amp;lt;/b&amp;gt;
    metadataSources { artifact() }
    content { includeGroup(&amp;quot;rubygems&amp;quot;) }
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Ekstensi`.gem`harus dihardcode.&amp;lt;code&amp;gt;[ext]`menyelesaikan secara default dalam&amp;lt;/code&amp;gt;.jar`, yang menyebabkan kesalahan`Resource missing`.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;admonitionblock note&amp;quot;&amp;gt;
&amp;lt;table&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;icon&amp;quot;&amp;gt;
&amp;lt;i class=&amp;quot;fa icon-note&amp;quot; title=&amp;quot;Note&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;content&amp;quot;&amp;gt;
Repo Ivy dideklarasikan langsung di`project.repositories`dan tidak dalam sebuah blok`repositories { }`karena receiver dari blok ini bukan`RepositoryHandler`standar Gradle namun sebuah API grolifant tidak kompatibel dengan ekstensi`ivy`Kotlin DSL.
&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_bagian_2_ketergantungan_asciidoctorgems&amp;quot;&amp;gt;Bagian 2: ketergantungan asciidoctorGems&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Plugin`org.asciidoctor.jvm.gems`harus diterapkan terlebih dahulu — ia membuat konfigurasi`asciidoctorGems`dan tugas`asciidoctorGemsPrepare`:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;project.plugins.apply(&amp;quot;org.asciidoctor.jvm.gems&amp;quot;)
project.plugins.apply(&amp;quot;org.asciidoctor.jvm.revealjs&amp;quot;) &amp;lt;i class=&amp;quot;conum&amp;quot; data-value=&amp;quot;1&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&amp;lt;b&amp;gt;(1)&amp;lt;/b&amp;gt;

project.dependencies {
    add(&amp;quot;asciidoctorGems&amp;quot;, &amp;quot;rubygems:asciidoctor-revealjs:3.1.0@gem&amp;quot;) &amp;lt;i class=&amp;quot;conum&amp;quot; data-value=&amp;quot;2&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&amp;lt;b&amp;gt;(2)&amp;lt;/b&amp;gt;
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;colist arabic&amp;quot;&amp;gt;
&amp;lt;table&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td&amp;gt;&amp;lt;i class=&amp;quot;conum&amp;quot; data-value=&amp;quot;1&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&amp;lt;b&amp;gt;1&amp;lt;/b&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td&amp;gt;Urutan penerapan penting:`gems`sebelum`revealjs`. &amp;amp;lt;2&amp;amp;gt; Pemenuh syarat`@gem`Paksa ekstensi yang benar pada dependensi.&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_bagian_3_settings_gradle_kts&amp;quot;&amp;gt;Bagian 3 : settings.gradle.kts&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;`dependencyResolutionManagement`dalam`settings.gradle.kts`harus mengizinkan proyek-proyek untuk mendeklarasikan repositori mereka sendiri :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;@Suppress(&amp;quot;UnstableApiUsage&amp;quot;)
dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.PREFER_PROJECT)
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Tanpa baris ini, Gradle mengabaikan repos yang dideklarasikan dalam plugin dan resolusi gems gagal.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_introspeksi_api_dengan_bytecode&amp;quot;&amp;gt;Introspeksi API dengan bytecode&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_mengapa_javap&amp;quot;&amp;gt;Mengapa &amp;lt;code&amp;gt;javap&amp;lt;/code&amp;gt; ?&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Plugin`asciidoctor-gradle-jvm-slides`adalah dalam versi`4.0.0-alpha.1`. Dokumentasinya tidak ada atau tidak lengkap. Sumber terpercaya yang satu-satunya adalah inspeksi langsung dari kelas yang dikompilasi.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_hierarki_tugas&amp;quot;&amp;gt;Hierarki tugas&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;javap -p -classpath asciidoctor-gradle-jvm-slides-4.0.0-alpha.1.jar \
  org.asciidoctor.gradle.jvm.slides.AsciidoctorJRevealJSTask&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Hasil :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-none hljs&amp;quot;&amp;gt;public class AsciidoctorJRevealJSTask
  extends org.asciidoctor.gradle.jvm.AbstractAsciidoctorTask
  implements org.asciidoctor.gradle.base.slides.SlidesToExportAware&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_penemuan_forkoptions&amp;quot;&amp;gt;Penemuan `forkOptions&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Saat memeriksa`AbstractAsciidoctorTask`:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;javap -p -classpath asciidoctor-gradle-jvm-4.0.0-alpha.1.jar \
  org.asciidoctor.gradle.jvm.AbstractAsciidoctorTask | grep -i &amp;quot;fork\|exec\|jvm&amp;quot;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Ada :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-none hljs&amp;quot;&amp;gt;final org.ysb33r.grolifant.api.v4.JavaForkOptions javaForkOptions;
public void forkOptions(org.gradle.api.Action&amp;amp;lt;org.ysb33r.grolifant.api.v4.JavaForkOptions&amp;amp;gt;);
public static final org.asciidoctor.gradle.base.process.ProcessMode JAVA_EXEC;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_api_javaforkoptions_grolifant&amp;quot;&amp;gt;API JavaForkOptions (grolifant)&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;`javaLauncher`tidak ada pada tugas ini. API nyata dari`org.ysb33r.grolifant.api.v4.JavaForkOptions`penjelasan :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-none hljs&amp;quot;&amp;gt;public void executable(java.lang.Object);  &amp;lt;i class=&amp;quot;conum&amp;quot; data-value=&amp;quot;1&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&amp;lt;b&amp;gt;(1)&amp;lt;/b&amp;gt;
public void setExecutable(java.lang.Object);&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;colist arabic&amp;quot;&amp;gt;
&amp;lt;table&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td&amp;gt;&amp;lt;i class=&amp;quot;conum&amp;quot; data-value=&amp;quot;1&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&amp;lt;b&amp;gt;1&amp;lt;/b&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td&amp;gt;Metode`executable(Object)`mengganti penugasan`executable = &amp;amp;#8230;&amp;amp;#8203;`yang tidak dapat dikompilasi (`val`tidak dapat ditetapkan ulang).&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_revealjsextension&amp;quot;&amp;gt;RevealJSExtension&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;`revealjs { }`bukan metode dari tugas ini tetapi sebuah&amp;lt;strong&amp;gt;perpanjangan proyek&amp;lt;/strong&amp;gt;:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;javap -p -classpath asciidoctor-gradle-jvm-slides-4.0.0-alpha.1.jar \
  org.asciidoctor.gradle.jvm.slides.RevealJSExtension&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Diakses melalui:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;project.extensions.getByType&amp;amp;lt;RevealJSExtension&amp;amp;gt;().apply {
    version = &amp;quot;3.1.0&amp;quot;
    templateGitHub {
        setOrganisation(&amp;quot;hakimel&amp;quot;)
        setRepository(&amp;quot;reveal.js&amp;quot;)
        setTag(&amp;quot;3.9.1&amp;quot;)
    }
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_konfigurasi_toolchain_java&amp;quot;&amp;gt;Konfigurasi toolchain Java&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_masalah_dari_javatoolchainservice&amp;quot;&amp;gt;Masalah dari JavaToolchainService&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;`JavaToolchainService`bukan merupakan ekstensi proyek. Pemanggilan berikutnya gagal :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;// ERREUR : Extension of type &amp;#39;JavaToolchainService&amp;#39; does not exist
project.extensions.getByType&amp;amp;lt;JavaToolchainService&amp;amp;gt;()&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;API yang baik adalah`serviceOf` :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;import org.gradle.kotlin.dsl.support.serviceOf

project.tasks.getByName&amp;amp;lt;AsciidoctorJRevealJSTask&amp;amp;gt;(TASK_ASCIIDOCTOR_REVEALJS) {
    setInProcess(&amp;quot;JAVA_EXEC&amp;quot;)
    forkOptions {
        executable(
            project.serviceOf&amp;amp;lt;JavaToolchainService&amp;amp;gt;()
                .launcherFor {
                    languageVersion.set(JavaLanguageVersion.of(17))
                    vendor.set(JvmVendorSpec.ADOPTIUM)
                }
                .get()
                .executablePath
                .asFile
                .absolutePath
        )
    }
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_deteksi_otomatis_docker&amp;quot;&amp;gt;Deteksi otomatis Docker&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_konteks&amp;quot;&amp;gt;konteks&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Plugin Asciidoctor/JRuby membutuhkan Java 17. Kotlin 2.0.x di buildSrc tidak mendukung Java 25 (parser versi crash pada`&amp;quot;25.0.2&amp;quot;`). Daemon Gradle harus berjalan di Java 17 atau Docker harus digunakan.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_strategi&amp;quot;&amp;gt;Strategi&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;olist arabic&amp;quot;&amp;gt;
&amp;lt;ol class=&amp;quot;arabic&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Docker tersedia → eksekusi melalui kontainer`eclipse-temurin:17`(perilaku default)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Docker tidak ada + Java 17 → eksekusi lokal&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Docker tidak ada + Java &amp;amp;gt; 17 → error eksplisit&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;val isDockerAvailable = try {
    Runtime.getRuntime().exec(arrayOf(&amp;quot;docker&amp;quot;, &amp;quot;info&amp;quot;)).waitFor() == 0
} catch (e: Exception) {
    false
}

val javaVersion = JavaVersion.current().majorVersion.toInt()

when {
    isDockerAvailable -&amp;amp;gt; project.tasks.register&amp;amp;lt;Exec&amp;amp;gt;(TASK_ASCIIDOCTOR_REVEALJS) {
        group = GROUP_TASK_SLIDER
        description = &amp;quot;Slider settings and generation (via Docker)&amp;quot;
        dependsOn(TASK_CLEAN_SLIDES_BUILD)
        finalizedBy(TASK_DASHBOARD_SLIDES_BUILD)
        commandLine(
            &amp;quot;docker&amp;quot;, &amp;quot;run&amp;quot;, &amp;quot;--rm&amp;quot;,
            &amp;quot;-v&amp;quot;, &amp;quot;${project.rootDir.absolutePath}:/workspace&amp;quot;,
            &amp;quot;-v&amp;quot;, &amp;quot;${System.getProperty(&amp;quot;user.home&amp;quot;)}/.gradle:/root/.gradle&amp;quot;,
            &amp;quot;-w&amp;quot;, &amp;quot;/workspace&amp;quot;,
            &amp;quot;eclipse-temurin:17&amp;quot;,
            &amp;quot;./gradlew&amp;quot;, TASK_ASCIIDOCTOR_REVEALJS
        )
        workingDir = project.rootDir
    }
    javaVersion == 17 -&amp;amp;gt; {
        project.repositories.mavenCentral() {
            content { excludeGroup(&amp;quot;rubygems&amp;quot;) }
        }
        project.repositories.ivy {
            url = project.uri(&amp;quot;https://rubygems.org/gems/&amp;quot;)
            patternLayout { artifact(&amp;quot;[module]-[revision].gem&amp;quot;) }
            metadataSources { artifact() }
            content { includeGroup(&amp;quot;rubygems&amp;quot;) }
        }
        project.extensions.getByType&amp;amp;lt;RevealJSExtension&amp;amp;gt;().apply {
            version = &amp;quot;3.1.0&amp;quot;
            templateGitHub {
                setOrganisation(&amp;quot;hakimel&amp;quot;)
                setRepository(&amp;quot;reveal.js&amp;quot;)
                setTag(&amp;quot;3.9.1&amp;quot;)
            }
        }
        project.tasks.getByName&amp;amp;lt;AsciidoctorJRevealJSTask&amp;amp;gt;(TASK_ASCIIDOCTOR_REVEALJS) {
            setInProcess(&amp;quot;JAVA_EXEC&amp;quot;)
            forkOptions {
                executable(
                    project.serviceOf&amp;amp;lt;JavaToolchainService&amp;amp;gt;()
                        .launcherFor {
                            languageVersion.set(JavaLanguageVersion.of(17))
                            vendor.set(JvmVendorSpec.ADOPTIUM)
                        }
                        .get()
                        .executablePath
                        .asFile
                        .absolutePath
                )
            }
            // ... reste de la configuration
        }
    }
    else -&amp;amp;gt; error(
        &amp;quot;Docker est requis pour exécuter $TASK_ASCIIDOCTOR_REVEALJS &amp;quot; +
        &amp;quot;avec Java $javaVersion. Installez Docker ou utilisez Java 17.&amp;quot;
    )
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_hasil_akhir&amp;quot;&amp;gt;Hasil akhir&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_buildscript_konsumen&amp;quot;&amp;gt;buildscript konsumen&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;apply&amp;amp;lt;slides.SlidesPlugin&amp;amp;gt;()&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Itu saja. Plugin menanggung seluruh tanggung jawab.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_settings_gradle_kts&amp;quot;&amp;gt;settings.gradle.kts&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;pluginManagement {
    repositories {
        mavenLocal()
        gradlePluginPortal()
    }
}

plugins {
    id(&amp;quot;org.gradle.toolchains.foojay-resolver-convention&amp;quot;) version &amp;quot;0.8.0&amp;quot;
}

@Suppress(&amp;quot;UnstableApiUsage&amp;quot;)
dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.PREFER_PROJECT)
}

rootProject.name = &amp;quot;slider-gradle&amp;quot;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_ringkasan_jebakan_dan_solusi&amp;quot;&amp;gt;Ringkasan jebakan dan solusi&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;table class=&amp;quot;tableblock frame-all grid-all stretch&amp;quot;&amp;gt;
&amp;lt;colgroup&amp;gt;
&amp;lt;col style=&amp;quot;width: 25%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 37.5%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 37.5%;&amp;quot;&amp;gt;
&amp;lt;/colgroup&amp;gt;
&amp;lt;thead&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;Masalah&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;Penyebab&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;Solusi&amp;lt;/th&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/thead&amp;gt;
&amp;lt;tbody&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;`ruby { gems() }`tidak tersedia di Kotlin&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Ekstensi DSL Groovy hanya&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Tiga mekanisme terpisah : repo Ivy + pengecualian Maven +&amp;lt;code&amp;gt;asciidoctorGems&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Gradle sedang mencari satu`.jar`alih-alih sebuah`.gem`&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;`[ext]`menyelesaikan dalam`jar`secara default&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Hardcoder`.gem`dalam pola Ivy + kualifikasi`@gem`&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;ivy { }`tidak kompiliasi di`repositories { }&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Receiver grolifant tidak kompatibel dengan DSL Kotlin&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Panggilan langsung`project.repositories.ivy { }`&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;`javaLauncher`belum diselesaikan&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Properti tidak ada pada`AsciidoctorJRevealJSTask`&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;setInProcess(&amp;quot;JAVA_EXEC&amp;quot;)&amp;lt;/code&amp;gt;+&amp;lt;code&amp;gt;forkOptions { executable(&amp;amp;#8230;&amp;amp;#8203;) }&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;`executable = &amp;amp;#8230;&amp;amp;#8203;`tidak bisa dikompilasi&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;sifat`val`dalam`JavaForkOptions`grolifant&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Metode`executable(Object)`alih-alih&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;JavaToolchainService`tidak dapat ditemukan via`extensions&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Ini adalah layanan Gradle, bukan ekstensi&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;project.serviceOf&amp;amp;lt;JavaToolchainService&amp;amp;gt;()&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;`revealjs { }`tidak terselesaikan dalam tugas&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Ekstensi proyek, bukan metode tugas&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;project.extensions.getByType&amp;amp;lt;RevealJSExtension&amp;amp;gt;()&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Build crash dengan Java 25&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Kotlin 2.0.x tidak memparse versi Java dua digit&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Deteksi Docker otomatis + fallback Java 17&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_metode_penyelidikan_membaca_api_yang_tidak_diketahui_dengan_javap&amp;quot;&amp;gt;Metode penyelidikan : membaca API yang tidak diketahui dengan javap&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_prinsip&amp;quot;&amp;gt;prinsip&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Ketika dokumentasi tidak ada atau tidak lengkap, bytecode adalah sumber kebenaran. &amp;lt;code&amp;gt;javap`adalah alat standar JDK yang mendekompilasi file&amp;lt;/code&amp;gt;.class`dalam tanda tangan Java yang dapat dibaca, tanpa memerlukan kode sumber.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_langkah_1_mencari_jar_di_cache_gradle&amp;quot;&amp;gt;Langkah 1: mencari jar di cache Gradle&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Gradle mengunduh semua dependensinya di`~/.gradle/caches/modules-2/files-2.1/`. Langkah pertama adalah menemukan jar yang berisi kelas yang akan diperiksa :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;find ~/.gradle/caches -name &amp;quot;asciidoctor-gradle-jvm-slides*.jar&amp;quot; 2&amp;amp;gt;/dev/null&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;Hasil :&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-none hljs&amp;quot;&amp;gt;/home/user/.gradle/caches/modules-2/files-2.1/org.asciidoctor/
asciidoctor-gradle-jvm-slides/4.0.0-alpha.1/.../
asciidoctor-gradle-jvm-slides-4.0.0-alpha.1.jar&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_langkah_2_mencantumkan_kelas_dari_jar&amp;quot;&amp;gt;Langkah 2: Mencantumkan kelas dari jar&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Sebelum memeriksa sebuah kelas, kita memastikan bahwa kelas tersebut memang ada di dalam jar :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;jar tf asciidoctor-gradle-jvm-slides-4.0.0-alpha.1.jar | grep -i &amp;quot;RevealJS\|revealjs&amp;quot;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_langkah_3_memeriksa_sebuah_kelas&amp;quot;&amp;gt;Langkah 3: memeriksa sebuah kelas&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;Ini menampilkan semua kelas yang tersedia : &amp;lt;code&amp;gt;AsciidoctorJRevealJSTask&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;RevealJSExtension&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;RevealJSOptions&amp;lt;/code&amp;gt;, dst.&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;javap -p -classpath asciidoctor-gradle-jvm-slides-4.0.0-alpha.1.jar \
org.asciidoctor.gradle.jvm.slides.AsciidoctorJRevealJSTask&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;opsi`-p`Menampilkan semua anggota termasuk yang pribadi Hasilnya langsung menunjukkan baris kunci:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-none hljs&amp;quot;&amp;gt;public class AsciidoctorJRevealJSTask
extends org.asciidoctor.gradle.jvm.AbstractAsciidoctorTask&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_langkah_4_naik_ke_atas_dalam_hierarki&amp;quot;&amp;gt;Langkah 4 : naik ke atas dalam hierarki&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Tugas meluas`AbstractAsciidoctorTask`. Kita memeriksanya bergantian dengan mencari jar-nya terlebih dahulu :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;find ~/.gradle/caches -name &amp;quot;asciidoctor-gradle-jvm-[0-9]*.jar&amp;quot; 2&amp;amp;gt;/dev/null

javap -p -classpath asciidoctor-gradle-jvm-4.0.0-alpha.1.jar \
org.asciidoctor.gradle.jvm.AbstractAsciidoctorTask | grep -i &amp;quot;fork\|exec\|jvm\|java&amp;quot;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Di sinilah kita menemukan`forkOptions`, &amp;lt;code&amp;gt;JAVA_EXEC&amp;lt;/code&amp;gt;, et &amp;lt;code&amp;gt;javaForkOptions&amp;lt;/code&amp;gt; jenis`org.ysb33r.grolifant.api.v4.JavaForkOptions`.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_langkah_5_mengikuti_tipe_tidak_dikenal&amp;quot;&amp;gt;Langkah 5 : mengikuti tipe tidak dikenal&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;`JavaForkOptions`adalah kelas grolifant yang tidak diketahui. Kami menemukan jar-nya :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;find ~/.gradle/caches -name &amp;quot;grolifant*.jar&amp;quot; 2&amp;amp;gt;/dev/null&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Kemudian kita memeriksanya :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;javap -p -classpath grolifant40-legacy-api-2.0.0-alpha.6.jar \
org.ysb33r.grolifant.api.v4.JavaForkOptions&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Di sini ada`executable(java.lang.Object)&amp;lt;code&amp;gt;— metode yang benar untuk dipanggil, berlawanan dengan`executable = &amp;amp;#8230;&amp;amp;#8203;`yang tidak dapat dikompilasi karena itu adalah sebuah properti`val&amp;lt;/code&amp;gt;.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_langkah_6_memeriksa_ekstensi_proyek&amp;quot;&amp;gt;Langkah 6: memeriksa ekstensi proyek&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;untuk`revealjs { }`, pertanyaannya adalah: apakah ini adalah metode dari tugas atau ekstensi proyek ? Pemeriksaan`AsciidoctorJRevealJSTask` tidak menunjukkan metode`revealjs`. Kemudian diperiksa`RevealJSExtension`:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;javap -p -classpath asciidoctor-gradle-jvm-slides-4.0.0-alpha.1.jar \
org.asciidoctor.gradle.jvm.slides.RevealJSExtension | head -5&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-none hljs&amp;quot;&amp;gt;public class RevealJSExtension implements groovy.lang.GroovyObject {
public static final java.lang.String NAME;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Keberadaan`NAME`mengonfirmasi bahwa ini adalah ekstensi yang terdaftar di proyek, dapat diakses melalui`project.extensions.getByType&amp;amp;lt;RevealJSExtension&amp;amp;gt;()`.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_ringkasan_metode&amp;quot;&amp;gt;Ringkasan metode&amp;lt;/h3&amp;gt;
&amp;lt;table class=&amp;quot;tableblock frame-all grid-all stretch&amp;quot;&amp;gt;
&amp;lt;colgroup&amp;gt;
&amp;lt;col style=&amp;quot;width: 25%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 75%;&amp;quot;&amp;gt;
&amp;lt;/colgroup&amp;gt;
&amp;lt;thead&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;Langkah&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;Aksi&amp;lt;/th&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/thead&amp;gt;
&amp;lt;tbody&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;1&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;find ~/.gradle/caches -name &amp;quot;*.jar&amp;quot;&amp;lt;/code&amp;gt;— menemukan jar&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;2&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;jar tf jar.jar | grep NomClasse&amp;lt;/code&amp;gt;— memastikan bahwa kelas ada&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;3&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;javap -p -classpath jar.jar NomCompletClasse&amp;lt;/code&amp;gt;— memeriksa kelas&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;4&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;pengenal`extends`dan naik dalam hierarki&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;5&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Melacak jenis yang tidak dikenal dalam jars mereka sendiri&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;6&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Mencari`NAME`untuk mengidentifikasi ekstensi proyek&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Metode ini berlaku untuk setiap plugin Gradle yang APInya tidak didokumentasikan atau yang versi alpha-nya tidak lagi sesuai dengan dokumentasi yang ada.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_langkah_selanjutnya&amp;quot;&amp;gt;Langkah selanjutnya&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Plugin buildSrc ini akan diekstrak ke dalam proyek mandiri yang dipublikasikan di Gradle Plugin Portal atau Maven Local. Buildscript yang dikonsumsi kemudian akan menjadi :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;plugins { id(&amp;quot;slides&amp;quot;) version &amp;quot;1.0.0&amp;quot; }&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Et &amp;lt;code&amp;gt;settings.gradle.kts`akan diperkecil menjadi minimum ketat tanpa referensi pada`foojay-resolver-convention&amp;lt;/code&amp;gt;, provisioning JDK dikelola oleh plugin itu sendiri atau didokumentasikan sebagai prasyarat.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>Arsitektur Portofolio JBake - Vision dan Kasus Penggunaan</title>
            <link >https://cheroliv.com/blog/2026/0101_jbake_portfolio_post.html</link>
            <pubDate>Thu, 15 Jan 2026 00:00:00 +0000</pubDate>
            <guid isPermaLink="false">blog/2026/0101_jbake_portfolio_post.html</guid>
            <description>&amp;lt;div id=&amp;quot;toc&amp;quot; class=&amp;quot;toc&amp;quot;&amp;gt;
&amp;lt;div id=&amp;quot;toctitle&amp;quot;&amp;gt;Table des matières&amp;lt;/div&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel1&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_visi_arkitektonik&amp;quot;&amp;gt;Visi Arkitektonik&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_prinsip_pedoman&amp;quot;&amp;gt;Prinsip pedoman&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_pilihan_strategis_asciidoc_untuk_portofolio&amp;quot;&amp;gt;Pilihan strategis : AsciiDoc untuk portofolio&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_model_data&amp;quot;&amp;gt;Model data&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_use_cases_rinci&amp;quot;&amp;gt;Use Cases Rinci&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_uc1_menambahkan_sebuah_elemen_ke_dalam_portofolio&amp;quot;&amp;gt;UC1 : Menambahkan sebuah elemen ke dalam portofolio&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel3&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_aliran_nominal&amp;quot;&amp;gt;Aliran nominal&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_template_file_yang_akan_dibuat&amp;quot;&amp;gt;Template file yang akan dibuat&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_poin_validasi&amp;quot;&amp;gt;Poin validasi&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_uc2_menghapus_sebuah_elemen_dari_portofolio&amp;quot;&amp;gt;UC2 : Menghapus sebuah elemen dari portofolio&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel3&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_fluks_nominal&amp;quot;&amp;gt;fluks nominal&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_strategi_alternatif_pengarsipan&amp;quot;&amp;gt;Strategi alternatif: pengarsipan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_pembersihan_sumber_daya&amp;quot;&amp;gt;Pembersihan sumber daya&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_uc3_menetapkan_sebagai_tidak_dipublikasikan_draft&amp;quot;&amp;gt;UC3 : Menetapkan sebagai tidak dipublikasikan (draft)&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel3&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_fluks_nominal_2&amp;quot;&amp;gt;Fluks nominal&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_status_yang_mungkin&amp;quot;&amp;gt;status yang mungkin&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_kasus_penggunaan_tipikal&amp;quot;&amp;gt;Kasus penggunaan tipikal&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_arsitektur_templating&amp;quot;&amp;gt;Arsitektur Templating&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_strategi_template_yang_dapat_digunakan_kembali&amp;quot;&amp;gt;Strategi template yang dapat digunakan kembali&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_pola_ekstraksi_data&amp;quot;&amp;gt;Pola ekstraksi data&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_konvensi_penamaan&amp;quot;&amp;gt;Konvensi penamaan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_alur_kerja_publikasi&amp;quot;&amp;gt;Alur Kerja Publikasi&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_alur_pengembangan&amp;quot;&amp;gt;Alur pengembangan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_lingkungan&amp;quot;&amp;gt;lingkungan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_ekstensibilitas&amp;quot;&amp;gt;ekstensibilitas&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_penambahan_atribut_baru&amp;quot;&amp;gt;Penambahan atribut baru&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_kategorisasi_lanjutan&amp;quot;&amp;gt;Kategorisasi lanjutan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_praktik_baik&amp;quot;&amp;gt;Praktik Baik&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_organisasi_file&amp;quot;&amp;gt;Organisasi file&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_manajemen_konten&amp;quot;&amp;gt;Manajemen konten&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_kinerja&amp;quot;&amp;gt;kinerja&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_kesimpulan&amp;quot;&amp;gt;Kesimpulan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_visi_arkitektonik&amp;quot;&amp;gt;Visi Arkitektonik&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_prinsip_pedoman&amp;quot;&amp;gt;Prinsip pedoman&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Arsitektur didasarkan pada prinsip &amp;lt;strong&amp;gt;pemisahan perhatian&amp;lt;/strong&amp;gt; :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Konten&amp;lt;/strong&amp;gt; : file AsciiDoc terstruktur dengan metadata kaya&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Presentasi&amp;lt;/strong&amp;gt; : templates Thymeleaf yang dapat digunakan kembali&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Data&amp;lt;/strong&amp;gt; : model diekstrak otomatis oleh JBake dari atribut AsciiDoc&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Gaya&amp;lt;/strong&amp;gt; : Bootstrap 5 untuk konsistensi visual&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_pilihan_strategis_asciidoc_untuk_portofolio&amp;quot;&amp;gt;Pilihan strategis : AsciiDoc untuk portofolio&amp;lt;/h3&amp;gt;
&amp;lt;table class=&amp;quot;tableblock frame-all grid-all stretch&amp;quot;&amp;gt;
&amp;lt;caption class=&amp;quot;title&amp;quot;&amp;gt;Table 1. Mengapa AsciiDoc ?&amp;lt;/caption&amp;gt;
&amp;lt;colgroup&amp;gt;
&amp;lt;col style=&amp;quot;width: 33.3333%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 66.6667%;&amp;quot;&amp;gt;
&amp;lt;/colgroup&amp;gt;
&amp;lt;thead&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;kriteria&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;keuntungan&amp;lt;/th&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/thead&amp;gt;
&amp;lt;tbody&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Konsistensi&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Format yang sama dengan artikel blog&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;metadata&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Atribut terstruktur dan dapat diperluas`project-*`)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Kemudahan pemeliharaan&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Pengeditan teks sederhana, dapat di-versi dengan Git&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;fleksibilitas&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Mungkin mengandung konten kaya (tabel, kode, gambar)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;pembuatan template&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;JBake mengekstrak secara otomatis atribut untuk Thymeleaf&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_model_data&amp;quot;&amp;gt;Model data&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Setiap proyek portofolio adalah dokumen AsciiDoc dengan :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Metadata header&amp;lt;/strong&amp;gt; : informasi terstruktur (klien, durasi, teknologi, dll)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Badan dokumen&amp;lt;/strong&amp;gt; : deskripsi naratif, tantangan, solusi, hasil&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Atribut kustom&amp;lt;/strong&amp;gt; : berawalan`project-*`untuk ekstraksi otomatis&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE
skinparam handwritten false

class ProjetAsciiDoc {
  +titre: String
  +jbake-type: &amp;quot;proyek&amp;quot;
  +jbake-status: published|draft
  +jbake-date: Date
  +jbake-tags: List&amp;amp;lt;String&amp;amp;gt;
  --
  +project-category: String
  +project-client: String
  +project-duration: String
  +project-role: String
  +project-thumbnail: String
  +project-gallery: List&amp;amp;lt;String&amp;amp;gt;
  +project-tech-stack: String
  +project-highlights: List&amp;amp;lt;String&amp;amp;gt;
  +project-demo-url: String
  +project-github-url: String
  --
  +body: HTML (converti)
}

class JBakeEngine {
  +parse(asciidoc)
  +extractMetadata()
  +convertToHTML()
}

class ThymeleafTemplate {
  +portfolio.html
  +project.html
  +partials/project-card.html
}

class PortfolioPage {
  +published_projects: List
  +filtres: categories
}

ProjetAsciiDoc --&amp;amp;gt; JBakeEngine : parse
JBakeEngine --&amp;amp;gt; ThymeleafTemplate : fournit données
ThymeleafTemplate --&amp;amp;gt; PortfolioPage : génère
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_use_cases_rinci&amp;quot;&amp;gt;Use Cases Rinci&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_uc1_menambahkan_sebuah_elemen_ke_dalam_portofolio&amp;quot;&amp;gt;UC1 : Menambahkan sebuah elemen ke dalam portofolio&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;sect3&amp;quot;&amp;gt;
&amp;lt;h4 id=&amp;quot;_aliran_nominal&amp;quot;&amp;gt;Aliran nominal&amp;lt;/h4&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE
actor Développeur as dev
participant &amp;quot;Editor
Teks&amp;quot; as editor
participant &amp;quot;File
AsciiDoc&amp;quot; as file
participant &amp;quot;JBake\nmesin&amp;quot; as jbake
participant &amp;quot;Situs\nStatis&amp;quot; as site

dev -&amp;amp;gt; editor : Créer nouveau fichier
activate editor
editor -&amp;amp;gt; file : portfolio/nouveau-projet.adoc
activate file

dev -&amp;amp;gt; editor : Rédiger en-tête avec métadonnées\n(titre, type=project, status=published,\nattributs project-*)
dev -&amp;amp;gt; editor : Rédiger contenu narratif\n(contexte, défis, solutions)
dev -&amp;amp;gt; editor : Ajouter images dans assets/img/portfolio/

editor -&amp;amp;gt; file : Sauvegarder
deactivate editor

dev -&amp;amp;gt; jbake : Lancer build (jbake -b)
activate jbake
jbake -&amp;amp;gt; file : Lire et parser
jbake -&amp;amp;gt; jbake : Extraire métadonnées
jbake -&amp;amp;gt; jbake : Convertir AsciiDoc → HTML
jbake -&amp;amp;gt; jbake : Appliquer template project.html
jbake -&amp;amp;gt; jbake : Ajouter à la liste portfolio.html
jbake -&amp;amp;gt; site : Générer pages statiques
deactivate jbake

dev -&amp;amp;gt; site : Vérifier résultat
activate site
site --&amp;amp;gt; dev : Afficher projet
deactivate site
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect3&amp;quot;&amp;gt;
&amp;lt;h4 id=&amp;quot;_template_file_yang_akan_dibuat&amp;quot;&amp;gt;Template file yang akan dibuat&amp;lt;/h4&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Pengembang membuat`content/portfolio/nom-projet.adoc`dengan struktur yang terstandar :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Header dengan semua atribut yang diperlukan&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Sections terstandar (Konteks, Tantangan, Solusi, Hasil)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Penamaan gambar yang konsisten&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect3&amp;quot;&amp;gt;
&amp;lt;h4 id=&amp;quot;_poin_validasi&amp;quot;&amp;gt;Poin validasi&amp;lt;/h4&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Atribut yang diperlukan ada (&amp;lt;code&amp;gt;jbake-type&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;jbake-status&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;project-thumbnail&amp;lt;/code&amp;gt;)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Gambar yang dirujuk ada di`assets/img/portfolio/`&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Build JBake berhasil tanpa kesalahan&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Proyek muncul di halaman portofolio&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Halaman individual proyek ditampilkan dengan benar&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_uc2_menghapus_sebuah_elemen_dari_portofolio&amp;quot;&amp;gt;UC2 : Menghapus sebuah elemen dari portofolio&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;sect3&amp;quot;&amp;gt;
&amp;lt;h4 id=&amp;quot;_fluks_nominal&amp;quot;&amp;gt;fluks nominal&amp;lt;/h4&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE
actor Développeur as dev
participant &amp;quot;Sistem
File&amp;quot; as fs
participant &amp;quot;JBake
Engine&amp;quot; as jbake
participant &amp;quot;Situs\nStatis&amp;quot; as site
database &amp;quot;Cache\nJBake&amp;quot; as cache

dev -&amp;amp;gt; fs : Supprimer portfolio/projet-ancien.adoc
activate fs
fs --&amp;amp;gt; dev : Fichier supprimé
deactivate fs

dev -&amp;amp;gt; fs : (Optionnel) Supprimer images associées\nassets/img/portfolio/projet-ancien-*
activate fs
fs --&amp;amp;gt; dev : Images supprimées
deactivate fs

dev -&amp;amp;gt; cache : Nettoyer cache JBake
activate cache
cache --&amp;amp;gt; dev : Cache vidé
deactivate cache

dev -&amp;amp;gt; jbake : Rebuild complet (jbake -b)
activate jbake
jbake -&amp;amp;gt; jbake : Scanner content/portfolio/
jbake -&amp;amp;gt; jbake : Projet absent → non généré
jbake -&amp;amp;gt; jbake : Régénérer portfolio.html\n(sans le projet supprimé)
jbake -&amp;amp;gt; site : Déployer nouveau build
deactivate jbake

dev -&amp;amp;gt; site : Vérifier
activate site
site --&amp;amp;gt; dev : Projet absent de la liste
deactivate site
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect3&amp;quot;&amp;gt;
&amp;lt;h4 id=&amp;quot;_strategi_alternatif_pengarsipan&amp;quot;&amp;gt;Strategi alternatif: pengarsipan&amp;lt;/h4&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Alih-alih menghapus secara permanen, ada kemungkinan untuk membuat folder`content/portfolio/archive/`(Empty output)&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Pindahkan file alih-alih menghapusnya&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Memudahkan pemulihan&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Jaga riwayat Git lebih jelas&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect3&amp;quot;&amp;gt;
&amp;lt;h4 id=&amp;quot;_pembersihan_sumber_daya&amp;quot;&amp;gt;Pembersihan sumber daya&amp;lt;/h4&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Memeriksa gambar yatim di`assets/img/portfolio/`&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Hapus gambar yang tidak dirujuk oleh proyek lain&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Membersihkan cache JBake untuk menghindari referensi hantu&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_uc3_menetapkan_sebagai_tidak_dipublikasikan_draft&amp;quot;&amp;gt;UC3 : Menetapkan sebagai tidak dipublikasikan (draft)&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;sect3&amp;quot;&amp;gt;
&amp;lt;h4 id=&amp;quot;_fluks_nominal_2&amp;quot;&amp;gt;Fluks nominal&amp;lt;/h4&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE
actor Développeur as dev
participant &amp;quot;File
AsciiDoc&amp;quot; as file
participant &amp;quot;JBake\nMesin&amp;quot; as jbake
participant &amp;quot;Site
Statis&amp;quot; as site

dev -&amp;amp;gt; file : Ouvrir portfolio/projet.adoc
activate file

dev -&amp;amp;gt; file : Modifier attribut\n:jbake-status: published\n↓\n:jbake-status: draft

file --&amp;amp;gt; dev : Sauvegardé
deactivate file

dev -&amp;amp;gt; jbake : Rebuild (jbake -b)
activate jbake
jbake -&amp;amp;gt; file : Parser le fichier
jbake -&amp;amp;gt; jbake : Détecter status=draft
jbake -&amp;amp;gt; jbake : Exclure de published_projects
jbake -&amp;amp;gt; jbake : Ne pas créer page publique
note right
  Le projet existe toujours
  mais n&amp;#39;est pas publié
end note
jbake -&amp;amp;gt; site : Générer site (sans ce projet)
deactivate jbake

dev -&amp;amp;gt; site : Vérifier portfolio
activate site
site --&amp;amp;gt; dev : Projet absent de la liste publique
deactivate site
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect3&amp;quot;&amp;gt;
&amp;lt;h4 id=&amp;quot;_status_yang_mungkin&amp;quot;&amp;gt;status yang mungkin&amp;lt;/h4&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE

[*] --&amp;amp;gt; Draft : Création initiale
Draft --&amp;amp;gt; Published : Validation et publication
Published --&amp;amp;gt; Draft : Retrait temporaire
Draft --&amp;amp;gt; Archived : Projet abandonné
Published --&amp;amp;gt; Archived : Projet obsolète
Archived --&amp;amp;gt; [*] : Suppression définitive
Published --&amp;amp;gt; Published : Mises à jour

note right of Draft
  :jbake-status: draft
  Non visible publiquement
  Utile pour projets en cours
end note

note right of Published
  :jbake-status: published
  Visible sur le portfolio
  Indexé par JBake
end note

note right of Archived
  Déplacé dans archive/
  ou jbake-status: archived
  Non publié mais conservé
end note
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect3&amp;quot;&amp;gt;
&amp;lt;h4 id=&amp;quot;_kasus_penggunaan_tipikal&amp;quot;&amp;gt;Kasus penggunaan tipikal&amp;lt;/h4&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Proyek yang sedang dalam proses penulisan&amp;lt;/strong&amp;gt; : membuat sebagai draft, publikasikan ketika sudah selesai&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Proyek rahasia sementara&amp;lt;/strong&amp;gt; : beralih ke draft sampai persetujuan klien&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Pembaruan besar&amp;lt;/strong&amp;gt; : beralih ke draft, mengedit, menerbitkan kembali&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;A/B testing&amp;lt;/strong&amp;gt; : duplikasi dalam draft, uji, terbitkan versi terbaik&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_arsitektur_templating&amp;quot;&amp;gt;Arsitektur Templating&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_strategi_template_yang_dapat_digunakan_kembali&amp;quot;&amp;gt;Strategi template yang dapat digunakan kembali&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle

package &amp;quot;Template Thymeleaf&amp;quot; {
  component [index.html] as index
  component [portfolio.html] as portfolio
  component [project.html] as project

  package &amp;quot;parsial&amp;quot; {
    component [header.html] as header
    component [footer.html] as footer
    component [project-card.html] as card
    component [tech-badge.html] as badge
    component [gallery.html] as gallery
  }
}

package &amp;quot;Data JBake&amp;quot; {
  database &amp;quot;proyek_dipublikasikan&amp;quot; as data
  database &amp;quot;(proyek)&amp;quot; as content
}

index --&amp;amp;gt; header
index --&amp;amp;gt; footer

portfolio --&amp;amp;gt; header
portfolio --&amp;amp;gt; footer
portfolio --&amp;amp;gt; card
data --&amp;amp;gt; portfolio : itération

project --&amp;amp;gt; header
project --&amp;amp;gt; footer
project --&amp;amp;gt; badge
project --&amp;amp;gt; gallery
content --&amp;amp;gt; project : projet individuel

note right of card
  Fragment réutilisable
  pour afficher une carte
  de projet avec :
  - thumbnail
  - titre
  - tags
  - highlights
end note
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_pola_ekstraksi_data&amp;quot;&amp;gt;Pola ekstraksi data&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;JBake secara otomatis mengubah atribut AsciiDoc menjadi properti yang dapat diakses di Thymeleaf :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE

rectangle &amp;quot;Dokumen AsciiDoc&amp;quot; {
  (&amp;quot;:project-client: Acme Corp&amp;quot;) as attr1
  (&amp;quot;:project-duration: 6 bulan&amp;quot;) as attr2
  (&amp;quot;:project-tags: java, react&amp;quot;) as attr3
}

rectangle &amp;quot;Model JBake&amp;quot; {
  (content[&amp;#39;project-client&amp;#39;]) as prop1
  (content[&amp;#39;project-duration&amp;#39;]) as prop2
  (content.tags) as prop3
}

rectangle &amp;quot;Template Thymeleaf&amp;quot; {
  (th:text=&amp;quot;${project[&amp;#39;project-client&amp;#39;]}&amp;quot;) as tmpl1
  (th:text=&amp;quot;${project[&amp;#39;project-duration&amp;#39;]}&amp;quot;) as tmpl2
  (th:each=&amp;quot;tag : ${project.tags}&amp;quot;) as tmpl3
}

attr1 --&amp;amp;gt; prop1 : parsing
attr2 --&amp;amp;gt; prop2 : parsing
attr3 --&amp;amp;gt; prop3 : parsing

prop1 --&amp;amp;gt; tmpl1 : binding
prop2 --&amp;amp;gt; tmpl2 : binding
prop3 --&amp;amp;gt; tmpl3 : binding
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_konvensi_penamaan&amp;quot;&amp;gt;Konvensi penamaan&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Atribut proyek&amp;lt;/strong&amp;gt; : awalan`project-*` (ex: &amp;lt;code&amp;gt;project-client&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;project-tech-stack&amp;lt;/code&amp;gt;)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Berkas&amp;lt;/strong&amp;gt; : kebab-case (ex:`ecommerce-platform.adoc`)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Images&amp;lt;/strong&amp;gt; : awalan nama-proyek (contoh:`ecommerce-platform-thumb.jpg`)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Templates&amp;lt;/strong&amp;gt; : nama fungsional (contoh:`project-card.html`, &amp;lt;code&amp;gt;tech-badge.html&amp;lt;/code&amp;gt;)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_alur_kerja_publikasi&amp;quot;&amp;gt;Alur Kerja Publikasi&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_alur_pengembangan&amp;quot;&amp;gt;Alur pengembangan&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE
skinparam handwritten false

class ProjetAsciiDoc {
  +titre: String
  +jbake-type: &amp;quot;proyek&amp;quot;
  +jbake-status: published|draft
  +jbake-date: Date
  +jbake-tags: List&amp;amp;lt;String&amp;amp;gt;
  __
  +project-category: String
  +project-client: String
  +project-duration: String
  +project-role: String
  +project-thumbnail: String
  +project-gallery: List&amp;amp;lt;String&amp;amp;gt;
  +project-tech-stack: String
  +project-highlights: List&amp;amp;lt;String&amp;amp;gt;
  +project-demo-url: String
  +project-github-url: String
  __
  +body: HTML (converti)
}

class JBakeEngine {
  +parse(asciidoc)
  +extractMetadata()
  +convertToHTML()
}

class ThymeleafTemplate {
  +portfolio.html
  +project.html
  +partials/project-card.html
}

class PortfolioPage {
  +published_projects: List
  +filtres: categories
}

ProjetAsciiDoc --&amp;amp;gt; JBakeEngine : parse
JBakeEngine --&amp;amp;gt; ThymeleafTemplate : fournit données
ThymeleafTemplate --&amp;amp;gt; PortfolioPage : génère
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_lingkungan&amp;quot;&amp;gt;lingkungan&amp;lt;/h3&amp;gt;
&amp;lt;table class=&amp;quot;tableblock frame-all grid-all stretch&amp;quot;&amp;gt;
&amp;lt;colgroup&amp;gt;
&amp;lt;col style=&amp;quot;width: 20%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 40%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 40%;&amp;quot;&amp;gt;
&amp;lt;/colgroup&amp;gt;
&amp;lt;thead&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;Lingkungan&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;Penggunaan&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;Status diterima&amp;lt;/th&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/thead&amp;gt;
&amp;lt;tbody&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;lokal&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Pengembangan dan pratinjau&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;draft, diterbitkan&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;penataan&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Validasi pra-produksi&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;diterbitkan hanya&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Produksi&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Situs publik&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;diterbitkan hanya&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_ekstensibilitas&amp;quot;&amp;gt;ekstensibilitas&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_penambahan_atribut_baru&amp;quot;&amp;gt;Penambahan atribut baru&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Untuk memperkaya model data, cukup menambahkan atribut baru yang diawali`project-*`:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;`project-awards`Harga dan Penghargaan&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;code&amp;gt;project-testimonial&amp;lt;/code&amp;gt;: Kutipan klien&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;code&amp;gt;project-team-size&amp;lt;/code&amp;gt;: Ukuran tim&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;code&amp;gt;project-budget-range&amp;lt;/code&amp;gt;: jangka anggaran&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Atribut-atribut ini secara otomatis tersedia di dalam template tanpa modifikasi mesin JBake.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_kategorisasi_lanjutan&amp;quot;&amp;gt;Kategorisasi lanjutan&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE

object Projet {
  project-category = &amp;quot;web&amp;quot;
  project-subcategory = &amp;quot;perdagangan elektronik&amp;quot;
  project-industry = &amp;quot;ritel&amp;quot;
  project-complexity = &amp;quot;tinggi&amp;quot;
}

object Taxonomie {
  categories : [web, mobile, data, devops]
  subcategories : Map&amp;amp;lt;category, List&amp;amp;gt;
  industries : [retail, finance, healthcare, ...]
  complexities : [low, medium, high]
}

Projet --&amp;amp;gt; Taxonomie : classifié selon

note right of Taxonomie
  Permet filtrage multi-critères
  dans le template portfolio.html
  via JavaScript ou côté serveur
end note
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_praktik_baik&amp;quot;&amp;gt;Praktik Baik&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_organisasi_file&amp;quot;&amp;gt;Organisasi file&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Satu file = satu proyek&amp;lt;/strong&amp;gt; : hindari mencampurkan beberapa proyek&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Gambar di folder khusus&amp;lt;/strong&amp;gt; :`assets/img/portfolio/nom-projet/`&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Nomenklatur konsisten&amp;lt;/strong&amp;gt; : memudahkan pencarian dan pemeliharaan&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Versioning Git&amp;lt;/strong&amp;gt; : pelacakan lengkap perubahan&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_manajemen_konten&amp;quot;&amp;gt;Manajemen konten&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Status draft secara default&amp;lt;/strong&amp;gt; : hanya menerbitkan ketika siap&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Tinjauan sebelum publikasi&amp;lt;/strong&amp;gt; : validasi kualitas dan kerahasiaan&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Metadata lengkap&amp;lt;/strong&amp;gt; : mengisi semua kolom yang relevan&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Konten naratif kaya&amp;lt;/strong&amp;gt;: tidak membatasi diri pada metadata&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_kinerja&amp;quot;&amp;gt;kinerja&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Mengoptimalkan gambar&amp;lt;/strong&amp;gt; : kompresi sebelum commit&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Pagination yang diperlukan&amp;lt;/strong&amp;gt; : jika &amp;amp;gt;20 proyek&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Lazy loading&amp;lt;/strong&amp;gt; : gambar galeri yang dimuat saat diperlukan&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Cache peramban&amp;lt;/strong&amp;gt; : header yang sesuai untuk aset&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_kesimpulan&amp;quot;&amp;gt;Kesimpulan&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Arsitektur ini memungkinkan:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Kemudahan penggunaan&amp;lt;/strong&amp;gt; : menambahkan proyek = membuat file teks&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Kefleksibilitas&amp;lt;/strong&amp;gt; : dapat diperluas melalui atribut baru&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Kemudahan pemeliharaan&amp;lt;/strong&amp;gt; : pemisahan konten/presentasi&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;pelacakan&amp;lt;/strong&amp;gt; : versioning Git lengkap&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Otomatisasi&amp;lt;/strong&amp;gt; : pembangunan dan penyebaran kontinu mungkin&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Pemilihan AsciiDoc menjamin konsistensi dengan sisanya situs sambil memberikan kekayaan metadata yang diperlukan untuk portofolio profesional.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>Arsitektur Hibrid : Yang Terbaik dari Kedua Dunia dengan Supabase dan Spring Boot</title>
            <link >https://cheroliv.com/blog/pilot-batch/0100_jbake_supabase_springboot_hybride_post.html</link>
            <pubDate>Wed, 14 Jan 2026 00:00:00 +0000</pubDate>
            <guid isPermaLink="false">blog/pilot-batch/0100_jbake_supabase_springboot_hybride_post.html</guid>
            <description>&amp;lt;div id=&amp;quot;preamble&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;waktu membaca : 12 minutes&amp;lt;/div&amp;gt;
&amp;lt;p&amp;gt;Alih-alih memilih antara kesederhanaan Supabase dan kekuatan Spring Boot, mengapa tidak menggabungkan keduanya? Temukan arsitektur hibrid yang memanfaatkan yang terbaik dari setiap teknologi.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;dlist&amp;quot;&amp;gt;
&amp;lt;dl&amp;gt;
&amp;lt;dt class=&amp;quot;hdlist1&amp;quot;&amp;gt;gangguan obsesif-kompulsi&amp;lt;/dt&amp;gt;
&amp;lt;dd&amp;gt;
&amp;lt;p&amp;gt;[]&amp;lt;/p&amp;gt;
&amp;lt;/dd&amp;gt;
&amp;lt;/dl&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_dilema_palsu_arsitektur_modern&amp;quot;&amp;gt;Dilema Palsu Arsitektur Modern&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Saat membangun aplikasi modern dengan frontend statis, kita sering menemukan diri kita dihadapkan pada pilihan biner :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Menaruh seluruh harapan pada solusi Backend-as-a-Service&amp;lt;/strong&amp;gt;(Supabase, Firebase) - sederhana namun terbatas&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Membangun backend lengkap&amp;lt;/strong&amp;gt;(Spring Boot, Node.js) - kuat namun kompleks&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Pilihan ini adalah dilemma palsu. Arsitektur hibrid menawarkan jalan ketiga: &amp;lt;strong&amp;gt;menggunakan setiap teknologi di mana iaExcels&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_visi_arsitektur&amp;quot;&amp;gt;Visi Arsitektur&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_arsitektur_tradisional_monolitik&amp;quot;&amp;gt;Arsitektur Tradisional: Monolitik&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle

package &amp;quot;Arsitektur Tradisional&amp;quot; {
  actor Utilisateur as user
  component &amp;quot;frontend&amp;quot; as frontend #LightBlue
  component &amp;quot;Backend monolitik&amp;quot; as backend #LightCoral {
    [Authentification]
    [CRUD]
    [Logique Métier]
    [Jobs]
    [APIs Tierces]
  }
  database &amp;quot;Basis Data&amp;quot; as db #LightGray

  user --&amp;amp;gt; frontend
  frontend --&amp;amp;gt; backend : Toutes les requêtes
  backend --&amp;amp;gt; db
}

note right of backend
  Tout passe par le backend
  même les opérations simples

  ⚠️ Point de congestion unique
  ⚠️ Complexité dès le départ
  ⚠️ Couplage fort
end note
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Dalam pendekatan ini, bahkan operasi-operasi paling sederhana (membaca artikel, membuat komentar) harus melewati backend Anda. Anda membayar biaya kompleksitas sejak hari pertama.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_arsitektur_baas_semua_delegat&amp;quot;&amp;gt;Arsitektur BaaS: Semua Delegat&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle

package &amp;quot;Arsitektur BaaS Murni&amp;quot; {
  actor Utilisateur as user
  component &amp;quot;Frontend&amp;quot; as frontend #LightBlue
  cloud &amp;quot;Supabase / Firebase&amp;quot; as baas #LightGreen {
    [Auth]
    [Database]
    [Storage]
    [Edge Functions]
  }

  user --&amp;amp;gt; frontend
  frontend --&amp;amp;gt; baas : Toutes les opérations
}

note right of baas
  Développement rapide
  Pas de serveur à gérer

  ⚠️ Limité pour logique complexe
  ⚠️ Vendor lock-in
  ⚠️ Edge Functions contraintes
end note
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Di sisi lain, mendelegasikan semuanya ke BaaS menarik di awal, tetapi menunjukkan batasnya dengan cepat begitu logika bisnis menjadi kompleks.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_arsitektur_hibrid_pemisahan_tanggung_jawab&amp;quot;&amp;gt;Arsitektur Hibrid : Pemisahan Tanggung Jawab&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle

actor Utilisateur as user #LightBlue

package &amp;quot;Lapisan Frontend&amp;quot; {
  component &amp;quot;Situs Statis JBake&amp;quot; as frontend #SkyBlue
}

package &amp;quot;Lapisan Sederhana (Supabase)&amp;quot; #LightGreen {
  component &amp;quot;Authentifikasi&amp;quot; as auth
  component &amp;quot;CRUD Dasar&amp;quot; as crud
  component &amp;quot;Penyimpanan File&amp;quot; as storage
  component &amp;quot;Waktu nyata&amp;quot; as realtime
}

package &amp;quot;Lapisan Bisnis (Spring Boot)&amp;quot; #Coral {
  component &amp;quot;Logika Kompleks&amp;quot; as logic
  component &amp;quot;Orkestrasi&amp;quot; as orchestration
  component &amp;quot;Pekerjaan Terjadwal&amp;quot; as jobs
  component &amp;quot;Integrasi&amp;quot; as integrations
}

database &amp;quot;PostgreSQL Dibagikan&amp;quot; as db #LightGray

user --&amp;amp;gt; frontend

frontend --&amp;amp;gt; auth : OAuth2, JWT
frontend --&amp;amp;gt; crud : Articles, Profils
frontend --&amp;amp;gt; storage : Images, Fichiers
frontend --&amp;amp;gt; realtime : Notifications

frontend --&amp;amp;gt; logic : Calculs, Rapports
frontend --&amp;amp;gt; orchestration : Workflows
logic --&amp;amp;gt; integrations : Stripe, APIs
orchestration --&amp;amp;gt; db
jobs --&amp;amp;gt; db

crud --&amp;amp;gt; db
auth --&amp;amp;gt; db

note bottom of db
  **Une seule source de vérité**
  Partagée entre Supabase et Spring Boot
end note

note right of auth
  **Gratuit jusqu&amp;#39;à 50k users**
  Configuration en quelques clics
  OAuth2 multi-providers
end note

note right of logic
  **Activé seulement si nécessaire**
  Kotlin + Coroutines
  Transactions complexes
end note
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Arsitektur hibrid dengan jelas memisahkan tanggung jawab berdasarkan kompleksitas dan sifat operasi.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_prinsip_desain&amp;quot;&amp;gt;Prinsip Desain&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_prinsip_1_mulai_sederhana_berkembang_secara_cerdas&amp;quot;&amp;gt;Prinsip 1: Mulai Sederhana, Berkembang Secara Cerdas&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE

rectangle &amp;quot;**Tahap 1 : MVP**&amp;quot; #LightGreen {
  component &amp;quot;JBake + Supabase&amp;quot; as mvp
  note right
    ✓ Développement en jours
    ✓ Coût : 0€
    ✓ Validation concept
  end note
}

rectangle &amp;quot;Fase 2 : Pertumbuhan&amp;quot; #LightYellow {
  component &amp;quot;JBake + Supabase\n+ Spring Boot (opsional)&amp;quot; as growth
  note right
    ✓ Logique métier émerge
    ✓ Premiers jobs planifiés
    ✓ Intégrations tierces
  end note
}

rectangle &amp;quot;Fase 3 : Skala&amp;quot; #LightCoral {
  component &amp;quot;Arsitektur lengkap&amp;quot; as scale
  note right
    ✓ Orchestration complexe
    ✓ Performances optimisées
    ✓ Cache distribué
  end note
}

mvp -down-&amp;amp;gt; growth : Quand la complexité\nle justifie
growth -down-&amp;amp;gt; scale : Optimisation\ncontinue
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Jangan membangun Spring Boot jika Anda tidak membutuhkannya.&amp;lt;/strong&amp;gt;Mulai dengan Supabase, tambahkan Spring Boot ketika kompleksitas membenarkannya.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_prinsip_2_pemisahan_berdasarkan_jenis_operasi&amp;quot;&amp;gt;Prinsip 2 : Pemisahan Berdasarkan Jenis Operasi&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startmindmap
skinparam backgroundColor #FEFEFE
* Opérations\nApplicatives

left side

** Opérations Simples\n(Supabase)
***_ CRUD Standard
****_ Lecture
****_ Création
****_ Mise à jour
****_ Suppression
***_ Authentification
****_ Login/Logout
****_ OAuth2
****_ Gestion session
***_ Stockage
****_ Upload fichiers
****_ CDN automatique
***_ Temps Réel
****_ WebSocket
****_ Notifications

right side

** Opérations Complexes\n(Spring Boot)
***_ Logique Métier
****_ Calculs complexes
****_ Règles métier
****_ Validations avancées
***_ Orchestration
****_ Multi-services
****_ Transactions distribuées
****_ Saga patterns
***_ Tâches Planifiées
****_ Rapports périodiques
****_ Synchronisations
****_ Nettoyages
***_ Intégrations
****_ Paiements (Stripe)
****_ APIs tierces
****_ Services ML/AI

@endmindmap&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_prinsip_3_satu_sumber_kebenaran&amp;quot;&amp;gt;Prinsip 3: Satu Sumber Kebenaran&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE

database &amp;quot;PostgreSQL&amp;quot; as db #LightGray {
  package &amp;quot;Skema: auth (Supabase)&amp;quot; #LightGreen {
    [users]
    [sessions]
    [identities]
  }

  package &amp;quot;Skema: Publik (Dibagikan)&amp;quot; #SkyBlue {
    [articles]
    [comments]
    [profiles]
  }

  package &amp;quot;Schema: business (Spring Boot)&amp;quot; #Coral {
    [orders]
    [payments]
    [reports]
  }
}

component &amp;quot;Supabase&amp;quot; as supabase #LightGreen
component &amp;quot;Spring Boot&amp;quot; as spring #Coral

supabase -down-&amp;amp;gt; users : Gère
supabase -down-&amp;amp;gt; sessions : Gère
supabase -down-&amp;amp;gt; articles : Accède
supabase -down-&amp;amp;gt; comments : Accède
supabase -down-&amp;amp;gt; profiles : Accède

spring -down-&amp;amp;gt; articles : Accède
spring -down-&amp;amp;gt; profiles : Accède
spring -down-&amp;amp;gt; orders : Gère
spring -down-&amp;amp;gt; payments : Gère
spring -down-&amp;amp;gt; reports : Gère

note right of db
  **Avantages :**
  • Pas de synchronisation complexe
  • JOINs possibles entre tables
  • Cohérence garantie
  • Migrations unifiées
end note
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Dengan berbagi instance PostgreSQL yang sama, Supabase dan Spring Boot bekerja pada data yang sama tanpa sinkronisasi kompleks.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_orchestrasi_aliran&amp;quot;&amp;gt;Orchestrasi aliran&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_aliran_1_otentikasi_terpusat&amp;quot;&amp;gt;Aliran 1 : Otentikasi Terpusat&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE
autonumber

actor &amp;quot;pengguna&amp;quot; as user
participant &amp;quot;JBake&amp;quot; as jbake
participant &amp;quot;Supabase Auth&amp;quot; as auth
participant &amp;quot;Spring Boot&amp;quot; as spring
database &amp;quot;PostgreSQL&amp;quot; as db

== Authentification ==
user -&amp;amp;gt; jbake : Clique &amp;quot;Masuk&amp;quot;
jbake -&amp;amp;gt; auth : Redirect OAuth2 (Google/GitHub)
auth -&amp;amp;gt; user : Consent screen
user -&amp;amp;gt; auth : Autorise
auth -&amp;amp;gt; auth : Génère JWT
auth -&amp;amp;gt; jbake : Retourne JWT + Refresh Token
jbake -&amp;amp;gt; jbake : Stocke tokens (sessionStorage)

== Accès aux Ressources ==
group Opération Simple (CRUD)
  user -&amp;amp;gt; jbake : Crée un article
  jbake -&amp;amp;gt; db : POST avec JWT
  note right: Supabase Row Level Security\nvérifie le JWT automatiquement
  db -&amp;amp;gt; jbake : Article créé
end

group Opération Complexe (Métier)
  user -&amp;amp;gt; jbake : Génère un rapport
  jbake -&amp;amp;gt; spring : POST /api/reports\navec JWT
  spring -&amp;amp;gt; spring : Valide JWT Supabase
  spring -&amp;amp;gt; spring : Extrait claims (user_id, roles)
  spring -&amp;amp;gt; db : Requêtes complexes
  spring -&amp;amp;gt; jbake : Rapport généré
end

note over auth, spring
  **Le JWT est universel**
  Valide par Supabase ET Spring Boot
  Pas de double authentification
end note
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Poin kunci&amp;lt;/strong&amp;gt;: Satu proses otentikasi, satu JWT, dapat digunakan di mana pun.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_alur_2_orkesstraksi_proses_bisnis_kompleks&amp;quot;&amp;gt;Alur 2: Orkesstraksi Proses Bisnis Kompleks&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE
autonumber

actor &amp;quot;Klien&amp;quot; as user
participant &amp;quot;JBake&amp;quot; as jbake
participant &amp;quot;Spring Boot&amp;quot; as spring
participant &amp;quot;Supabase DB&amp;quot; as supadb
participant &amp;quot;Stripe API&amp;quot; as stripe
participant &amp;quot;Layanan Persediaan&amp;quot; as inventory
participant &amp;quot;Layanan Email&amp;quot; as email
database &amp;quot;PostgreSQL&amp;quot; as db

user -&amp;amp;gt; jbake : Passe commande
jbake -&amp;amp;gt; spring : POST /api/orders/create

== Orchestration Spring Boot ==
spring -&amp;amp;gt; db : BEGIN TRANSACTION

par Vérifications Parallèles
  spring -&amp;amp;gt; inventory : Vérifier stock
  inventory -&amp;amp;gt; spring : Stock OK
else
  spring -&amp;amp;gt; db : Vérifier profil client
  db -&amp;amp;gt; spring : Client vérifié
end

spring -&amp;amp;gt; stripe : Créer Payment Intent
stripe -&amp;amp;gt; spring : Intent créé (ID: pi_xxx)

spring -&amp;amp;gt; db : Créer Order (status: PENDING)
db -&amp;amp;gt; spring : Order créé

spring -&amp;amp;gt; stripe : Confirmer paiement
stripe -&amp;amp;gt; spring : Paiement confirmé

spring -&amp;amp;gt; db : Update Order (status: PAID)
spring -&amp;amp;gt; inventory : Décrémenter stock
spring -&amp;amp;gt; db : COMMIT TRANSACTION

par Notifications Asynchrones
  spring -&amp;amp;gt; email : Email confirmation
else
  spring -&amp;amp;gt; supadb : Insert notification
  note right: Notification temps réel\nvia Supabase Realtime
end

spring -&amp;amp;gt; jbake : Order confirmé
jbake -&amp;amp;gt; user : Affiche confirmation

note over spring
  **Responsabilités Spring Boot :**
  • Transaction ACID
  • Orchestration multi-services
  • Rollback automatique si erreur
  • Gestion des cas limites
end note
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Apa yang Spring Boot bawa&amp;lt;/strong&amp;gt;: Orkestrasi yang andal dari proses kompleks yang melibatkan beberapa sistem dengan jaminan transaksional.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_flux_3_tugas_terjadwal_dan_sinkronisasi&amp;quot;&amp;gt;Flux 3 : Tugas Terjadwal dan Sinkronisasi&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE

participant &amp;quot;Penjadwal
(Spring Boot)&amp;quot; as scheduler
participant &amp;quot;layanan laporan&amp;quot; as report
participant &amp;quot;PostgreSQL&amp;quot; as db
participant &amp;quot;API eksternal&amp;quot; as api
participant &amp;quot;Layanan Email&amp;quot; as email
participant &amp;quot;Supabase Storage&amp;quot; as storage

== Job Quotidien : 2h00 ==
activate scheduler
scheduler -&amp;amp;gt; report : Déclenche génération rapport

activate report
report -&amp;amp;gt; db : Récupère données\ndes dernières 24h
db -&amp;amp;gt; report : Dataset

report -&amp;amp;gt; report : Calculs &amp;amp;amp; Agrégations
report -&amp;amp;gt; report : Génération PDF

report -&amp;amp;gt; storage : Upload PDF
storage -&amp;amp;gt; report : URL publique

report -&amp;amp;gt; email : Envoie aux admins\navec lien PDF
deactivate report

== Job Hebdomadaire : Dimanche 3h00 ==
scheduler -&amp;amp;gt; report : Synchronisation externe

activate report
report -&amp;amp;gt; api : Fetch données externes
api -&amp;amp;gt; report : Nouvelles données

report -&amp;amp;gt; db : Mise à jour tables
report -&amp;amp;gt; db : Nettoyage données obsolètes
deactivate report

== Job Mensuel : 1er du mois ==
scheduler -&amp;amp;gt; report : Facturation

activate report
report -&amp;amp;gt; db : Liste clients actifs
report -&amp;amp;gt; report : Calcul montants
report -&amp;amp;gt; api : Génération factures (Stripe)
report -&amp;amp;gt; email : Envoi factures
deactivate report

deactivate scheduler

note right of scheduler
  **Impossible avec Supabase seul**
  Edge Functions non adaptées
  pour jobs longs et récurrents
end note
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Apa yang dibawa Spring Boot&amp;lt;/strong&amp;gt;: Pekerjaan terjadwal yang andal dengan manajemen status, percobaan otomatis, dan eksekusi terjamin.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_matriks_keputusan&amp;quot;&amp;gt;Matriks Keputusan&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_kapan_menggunakan_supabase&amp;quot;&amp;gt;Kapan Menggunakan Supabase ?&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE

start

:Nouvelle fonctionnalité à implémenter;

if (CRUD simple ?) then (oui)
  :✅ **Supabase**;
  stop
else (non)
  if (Authentification ?) then (oui)
    :✅ **Supabase Auth**;
    stop
  else (non)
    if (Upload fichiers ?) then (oui)
      :✅ **Supabase Storage**;
      stop
    else (non)
      if (Temps réel ?) then (oui)
        :✅ **Supabase Realtime**;
        stop
      else (non)
        if (Row Level Security\nsuffisante ?) then (oui)
          :✅ **Supabase + Policies**;
          stop
        else (non)
          :⚠️ **Considérer Spring Boot**;
          #Coral:Passer à l&amp;#39;arbre\nde décision Spring Boot;
          stop
        endif
      endif
    endif
  endif
endif

@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_kapan_harus_menggunakan_spring_boot&amp;quot;&amp;gt;Kapan harus menggunakan Spring Boot ?&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE

start

:Nouvelle fonctionnalité complexe;

if (Orchestration\nmulti-services ?) then (oui)
  :✅ **Spring Boot**;
  stop
else (non)
  if (Transaction ACID\ncritique ?) then (oui)
    :✅ **Spring Boot**;
    stop
  else (non)
    if (Job planifié ?) then (oui)
      :✅ **Spring Boot Scheduler**;
      stop
    else (non)
      if (Intégration API\ntierce complexe ?) then (oui)
        :✅ **Spring Boot**;
        stop
      else (non)
        if (Calculs lourds\n&amp;amp;gt; 10 secondes ?) then (oui)
          :✅ **Spring Boot**;
          stop
        else (non)
          if (Cache distribué\nnécessaire ?) then (oui)
            :✅ **Spring Boot + Redis**;
            stop
          else (non)
            :⚠️ **Probablement Supabase**;
            #LightGreen:Revenir à l&amp;#39;arbre\nde décision Supabase;
            stop
          endif
        endif
      endif
    endif
  endif
endif

@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_contoh_arsitektur_berdasarkan_tipe_proyek&amp;quot;&amp;gt;Contoh Arsitektur berdasarkan Tipe Proyek&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_blog_portofolio&amp;quot;&amp;gt;Blog / Portofolio&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle

package &amp;quot;Blog Pribadi - 95% Supabase&amp;quot; {
  component &amp;quot;JBake&amp;quot; as jbake #SkyBlue

  package &amp;quot;Supabase (Gratis)&amp;quot; #LightGreen {
    [Auth: OAuth2 Google]
    [Articles (CRUD)]
    [Commentaires]
    [Images (Storage)]
    [Notifications Realtime]
  }

  package &amp;quot;Spring Boot (Opsional)&amp;quot; #LightCoral {
    [Newsletter Mensuelle]
    note bottom: Job planifié uniquement
  }

  database &amp;quot;PostgreSQL&amp;quot; as db

  jbake --&amp;amp;gt; [Auth: OAuth2 Google]
  jbake --&amp;amp;gt; [Articles (CRUD)]
  jbake --&amp;amp;gt; [Commentaires]
  jbake --&amp;amp;gt; [Images (Storage)]
  jbake --&amp;amp;gt; [Notifications Realtime]

  [Articles (CRUD)] --&amp;amp;gt; db
  [Commentaires] --&amp;amp;gt; db
  [Newsletter Mensuelle] --&amp;amp;gt; db
}

note bottom of &amp;quot;Spring Boot (Opsional)&amp;quot;
  **Spring Boot pas obligatoire**
  Newsletter peut être gérée
  par un service tiers (Mailchimp)
end note
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Putusan&amp;lt;/strong&amp;gt;: 100% Supabase lebih-than cukup.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_aplikasi_saas&amp;quot;&amp;gt;Aplikasi SaaS&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle

package &amp;quot;Aplikasi SaaS - Arsitektur Hibrid&amp;quot; {
  component &amp;quot;JBake / React&amp;quot; as frontend #SkyBlue

  package &amp;quot;Supabase&amp;quot; #LightGreen {
    [Auth Multi-tenant]
    [Profils Utilisateurs]
    [Données Simples]
    [Storage Documents]
  }

  package &amp;quot;Spring Boot&amp;quot; #Coral {
    [Facturation (Stripe)]
    [Analytics Avancées]
    [Rapports Mensuels]
    [Quotas &amp;amp;amp; Limites]
    [Webhooks Stripe]
  }

  cloud &amp;quot;Layanan Eksternal&amp;quot; {
    [Stripe API]
    [SendGrid Email]
  }

  database &amp;quot;PostgreSQL Dibagikan&amp;quot; as db

  frontend --&amp;amp;gt; [Auth Multi-tenant]
  frontend --&amp;amp;gt; [Profils Utilisateurs]
  frontend --&amp;amp;gt; [Données Simples]
  frontend --&amp;amp;gt; [Facturation (Stripe)]
  frontend --&amp;amp;gt; [Analytics Avancées]

  [Auth Multi-tenant] --&amp;amp;gt; db
  [Profils Utilisateurs] --&amp;amp;gt; db
  [Données Simples] --&amp;amp;gt; db

  [Facturation (Stripe)] --&amp;amp;gt; db
  [Facturation (Stripe)] --&amp;amp;gt; [Stripe API]
  [Analytics Avancées] --&amp;amp;gt; db
  [Rapports Mensuels] --&amp;amp;gt; db
  [Rapports Mensuels] --&amp;amp;gt; [SendGrid Email]
  [Webhooks Stripe] ..&amp;amp;gt; [Stripe API] : Écoute
}

note right of &amp;quot;Spring Boot&amp;quot;
  **Nécessaire pour :**
  • Gestion abonnements
  • Facturation récurrente
  • Quotas dynamiques
  • Analytics complexes
  • Webhooks fiables
end note
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Putusan&amp;lt;/strong&amp;gt;: Arsitektur hibrid diperlukan. Supabase untuk inti, Spring Boot untuk bagian kritis (pembayaran, penagihan).&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_perdagangan_elektronik&amp;quot;&amp;gt;perdagangan elektronik&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle

package &amp;quot;E-komers - 70% Spring Boot&amp;quot; {
  component &amp;quot;Storefront (JBake/React)&amp;quot; as frontend #SkyBlue

  package &amp;quot;Supabase&amp;quot; #LightGreen {
    [Auth Clients]
    [Catalogue Produits\n(lecture seule)]
    [Images Produits]
  }

  package &amp;quot;Spring Boot (Inti Bisnis)&amp;quot; #Coral {
    [Gestion Commandes]
    [Paiements Stripe]
    [Gestion Stock]
    [Calcul Frais Port]
    [Génération Factures]
    [Synchronisation ERP]
    [Emails Transactionnels]
  }

  cloud &amp;quot;Integrasi&amp;quot; {
    [Stripe]
    [Transporteur API]
    [ERP Externe]
  }

  database &amp;quot;PostgreSQL&amp;quot; as db

  frontend --&amp;amp;gt; [Auth Clients]
  frontend --&amp;amp;gt; [Catalogue Produits\n(lecture seule)]
  frontend --&amp;amp;gt; [Gestion Commandes]

  [Auth Clients] --&amp;amp;gt; db
  [Catalogue Produits\n(lecture seule)] --&amp;amp;gt; db

  [Gestion Commandes] --&amp;amp;gt; db
  [Paiements Stripe] --&amp;amp;gt; [Stripe]
  [Gestion Stock] --&amp;amp;gt; db
  [Calcul Frais Port] --&amp;amp;gt; [Transporteur API]
  [Génération Factures] --&amp;amp;gt; db
  [Synchronisation ERP] --&amp;amp;gt; [ERP Externe]
  [Synchronisation ERP] --&amp;amp;gt; db
}

note right of &amp;quot;Spring Boot (Inti Bisnis)&amp;quot;
  **Spring Boot indispensable :**
  • Transactions critiques
  • Orchestration complexe
  • Intégrations multiples
  • Conformité légale
  • Jobs de synchronisation
end note
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Putusan&amp;lt;/strong&amp;gt;: Spring Boot penting. Supabase hanya untuk otentikasi dan katalog.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_strategi_migrasi_progresif&amp;quot;&amp;gt;Strategi Migrasi Progresif&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE

|Semaine 1-2|
start
:Créer projet Supabase;
:Configurer Auth (Google, GitHub);
:Définir schéma PostgreSQL initial;
:Implémenter CRUD de base sur JBake;
:Déployer MVP sur GitHub Pages;
note right
  **Livrable :** Application fonctionnelle
  **Coût :** 0€
  **Temps :** 1-2 semaines
end note

|Semaine 3-4|
:Observer utilisation réelle;
:Identifier limitations;

|Décision|
if (Logique complexe\némerge ?) then (non)
  |Supabase|
  :Continuer avec Supabase seul;
  :Ajouter Edge Functions si besoin;
  stop
else (oui)
  |Spring Boot|
  :Créer projet Spring Boot;
  :Configurer validation JWT Supabase;
  :Migrer logique complexe vers Spring Boot;
  note right
    **Migration sélective**
    Seulement ce qui est complexe
  end note
endif

|Semaines suivantes|
:Déployer Spring Boot (Cloud Run, EC2);
:Connecter à PostgreSQL Supabase;
:Implémenter premiers jobs planifiés;
:Intégrer APIs tierces;

stop

note right
  **Architecture finale hybride**
  Supabase + Spring Boot
  en parfaite harmonie
end note
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_biaya_dan_pertimbangan_operasional&amp;quot;&amp;gt;Biaya dan Pertimbangan Operasional&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_struktur_biaya&amp;quot;&amp;gt;Struktur Biaya&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE

package &amp;quot;Perkiraan Biaya Bulanan&amp;quot; {

  rectangle &amp;quot;Tahap MVP\n(0-1000 pengguna)&amp;quot; #LightGreen {
    [JBake (GitHub Pages): 0€]
    [Supabase: 0€]
    [Spring Boot: N/A]
    note bottom: **Total: 0€/mois**
  }

  rectangle &amp;quot;Fase Pertumbuhan\n(1k-10k pengguna)&amp;quot; #LightYellow {
    [JBake (GitHub Pages): 0€]
    [Supabase: 0€]
    [Spring Boot (Cloud Run): 20-50€]
    [PostgreSQL (si séparé): 0-30€]
    note bottom: **Total: 20-80€/mois**
  }

  rectangle &amp;quot;Skala Fase
(10k-100k pengguna)&amp;quot; #LightCoral {
    [JBake (Netlify Pro): 19€]
    [Supabase Pro: 25€]
    [Spring Boot (instances multiples): 100-300€]
    [Redis Cache: 20-50€]
    [Monitoring: 20€]
    note bottom: **Total: 184-414€/mois**
  }
}

note right of &amp;quot;Skala Fase
(10k-100k pengguna)&amp;quot;
  À ce stade, les revenus
  justifient largement les coûts
end note
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_tanggung_jawab_operasional&amp;quot;&amp;gt;Tanggung jawab operasional&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE

package &amp;quot;Siapa yang mengelola apa?&amp;quot; {

  rectangle &amp;quot;Dikelola oleh Supabase (0 effort)&amp;quot; #LightGreen {
    [Authentification]
    [Base de données]
    [Backups automatiques]
    [Scaling automatique]
    [Monitoring DB]
    [Sécurité DB]
  }

  rectangle &amp;quot;Dikelola oleh GitHub (0 upaya)&amp;quot; #SkyBlue {
    [Hébergement site statique]
    [HTTPS/SSL]
    [CDN global]
    [Déploiement automatique]
  }

  rectangle &amp;quot;Mengelola (Spring Boot)&amp;quot; #LightCoral {
    [Déploiement application]
    [Monitoring APM]
    [Logs centralisés]
    [Scaling horizontal]
    [Mises à jour sécurité]
  }
}

note bottom of &amp;quot;Mengelola (Spring Boot)&amp;quot;
  **Minimisable avec :**
  • Cloud Run (serverless)
  • Kubernetes (si nécessaire)
  • Services managés (RDS, etc.)
end note
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_kesimpulan_keseimbangan_sempurna&amp;quot;&amp;gt;Kesimpulan : Keseimbangan Sempurna&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Arsitektur hibrid Supabase + Spring Boot bukanlah sebuah kompromi, ini adalah&amp;lt;strong&amp;gt;sinergi&amp;lt;/strong&amp;gt;.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_prinsip_prinsip_yang_harus_diingat&amp;quot;&amp;gt;Prinsip-prinsip yang Harus Diingat&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startmindmap
skinparam backgroundColor #FEFEFE
* Architecture\nHybride

** Commencer Simple
***_ MVP avec Supabase
***_ 0€ de coût initial
***_ Validation rapide

** Évoluer Intelligemment
***_ Ajouter Spring Boot\nsi nécessaire
***_ Migration progressive
***_ Pas de refonte totale

** Exploiter les Forces
***_ Supabase: Simplicité
***_ Spring Boot: Puissance
***_ PostgreSQL: Source unique

** Minimiser la Complexité
***_ Pas de synchronisation
***_ Pas de duplication
***_ Architecture claire

** Optimiser les Coûts
***_ Gratuit au départ
***_ Scaling progressif
***_ ROI évident

@endmindmap&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_kapan_memilih_arsitektur_ini&amp;quot;&amp;gt;Kapan Memilih Arsitektur Ini?&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;✅ Arsitektur ini ideal jika :&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Anda ingin memulai dengan cepat (MVP dalam hari, bukan dalam bulan)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Anda mengantisipasi pertumbuhan kompleksitas bisnis&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Anda ingin meminimalkan biaya awal&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Anda menyukai PostgreSQL dan ingin memiliki satu sumber kebenaran tunggal.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Anda menyukai Kotlin dan Coroutine untuk kode bisnis&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Anda ingin menghindari vendor lock-in total&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;❌ Arsitektur ini TIDAK cocok jika :&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Proyek Anda sederhana dan akan tetap demikian (blog pribadi → 100% Supabase cukup)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Anda sudah memiliki stack backend yang sudah terbentuk yang Anda kuasai&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Anda lebih suka sebuah monolitik tradisional&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Anda membutuhkan .NET, Python, atau bahasa backend lain&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_visi_keseluruhan_final&amp;quot;&amp;gt;Visi Keseluruhan Final&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE

package &amp;quot;Arsitektur Hibrid Lengkap&amp;quot; {

  actor &amp;quot;Pengguna&amp;quot; as users

  rectangle &amp;quot;Frontend (Statik)&amp;quot; #SkyBlue {
    [JBake / GitHub Pages]
    note right: Gratuit, CDN global
  }

  rectangle &amp;quot;Backend Sederhana (Supabase)&amp;quot; #LightGreen {
    [Auth OAuth2]
    [CRUD APIs]
    [Storage]
    [Realtime]
    note right
      Gratuit jusqu&amp;#39;à 50k users
      Zéro configuration serveur
    end note
  }

  rectangle &amp;quot;Backend Bisnis (Spring Boot)&amp;quot; #Coral {
    [Orchestration]
    [Jobs Planifiés]
    [Intégrations]
    [Transactions]
    note right
      Activé uniquement
      si nécessaire
    end note
  }

  database &amp;quot;PostgreSQL
Sumber unik kebenaran&amp;quot; as db #LightGray

  users --&amp;amp;gt; [JBake / GitHub Pages]

  [JBake / GitHub Pages] --&amp;amp;gt; [Auth OAuth2]
  [JBake / GitHub Pages] --&amp;amp;gt; [CRUD APIs]
  [JBake / GitHub Pages] --&amp;amp;gt; [Storage]
  [JBake / GitHub Pages] --&amp;amp;gt; [Realtime]
  [JBake / GitHub Pages] --&amp;amp;gt; [Orchestration]

  [Auth OAuth2] --&amp;amp;gt; db
  [CRUD APIs] --&amp;amp;gt; db
  [Orchestration] --&amp;amp;gt; db
  [Jobs Planifiés] --&amp;amp;gt; db
  [Intégrations] --&amp;amp;gt; db
}

note bottom of db
  **Une seule base, deux consommateurs**
  • Supabase pour le simple
  • Spring Boot pour le complexe
  • Aucune synchronisation requise
end note

@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_terbaik_dari_kedua_dunia&amp;quot;&amp;gt;Terbaik dari kedua dunia&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Arsitektur ini memberi Anda :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;🚀 Kecepatan Supabase&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Memulai dalam jam, bukan minggu&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Otentikasi OAuth2 dalam beberapa klik&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;APIs REST yang dihasilkan secara otomatis&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Konfigurasi server nol&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;💪 Kekuatan Spring Boot&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Kotlin dan Coroutines untuk kode asinkron yang elegan&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Orkestrasi proses bisnis kompleks&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Pekerjaan terencana yang andal&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Transaksi ACID terjamin&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Ekosistem Spring lengkap&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;💰 Ekonomi Progresif&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;0€ untuk memulai dan memvalidasi&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Biaya yang mengikuti pertumbuhan&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Tidak ada over-engineering yang dilakukan terlalu dini&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;🎯 Fleksibilitas Arsitektur&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Migrasi progresif tanpa rekonstruksi&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Penambahan Spring Boot hanya jika diperlukan&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Tidak ada vendor lock-in total&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Arsitektur yang dapat berevolusi&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_untuk_pergi_lebih_jauh&amp;quot;&amp;gt;Untuk pergi lebih jauh&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_sumber_daya_teknis&amp;quot;&amp;gt;Sumber Daya Teknis&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Dokumentasi Supabase :&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://supabase.com/docs/guides/auth&amp;quot;&amp;gt;Panduan Otentikasi&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://supabase.com/docs/guides/database/postgres/row-level-security&amp;quot;&amp;gt;Keamanan Tingkat Baris&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://supabase.com/docs/guides/api&amp;quot;&amp;gt;API yang dihasilkan otomatis&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Dokumentasi Spring Boot :&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://spring.io/guides/tutorials/spring-boot-kotlin/&amp;quot;&amp;gt;Spring Boot dengan Kotlin&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://spring.io/blog/2019/04/12/going-reactive-with-spring-coroutines-and-kotlin-flow&amp;quot;&amp;gt;Coroutines dan Spring&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://docs.spring.io/spring-security/reference/servlet/oauth2/resource-server/jwt.html&amp;quot;&amp;gt;JWT Resource Server&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Artikel Tambahan :&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Keamanan API dengan JWT&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Optimasi performa PostgreSQL&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Pola migrasi progresif&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Manajemen kesalahan dalam arsitektur terdistribusi&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_kasus_penggunaan_nyata&amp;quot;&amp;gt;Kasus penggunaan nyata&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Arsitektur hibrid ini digunakan dengan sukses di :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;SaaS B2B&amp;lt;/strong&amp;gt;: Autentikasi Supabase, penagihan Spring Boot&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Pasar&amp;lt;/strong&amp;gt;: Katalog Supabase, transaksi Spring Boot&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Platform konten&amp;lt;/strong&amp;gt;: Artikel Supabase, analitik Spring Boot&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Alat internal&amp;lt;/strong&amp;gt;: CRUD Supabase, alur kerja Spring Boot&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_checklist_pemulai&amp;quot;&amp;gt;Checklist Pemulai&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Tahap 1 - Fondasi (Minggu 1)&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist checklist&amp;quot;&amp;gt;
&amp;lt;ul class=&amp;quot;checklist&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;i class=&amp;quot;fa fa-square-o&amp;quot;&amp;gt;&amp;lt;/i&amp;gt; Buat akun Supabase - [ ] Mengonfigurasi proyek PostgreSQL - [ ] Aktifkan Auth (Google, GitHub) - [ ] Menentukan skema awal - [ ] Mengatur Row Level Security - [ ] Menguji API dari JBake&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Phase 2 - MVP (Minggu 2-3)&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist checklist&amp;quot;&amp;gt;
&amp;lt;ul class=&amp;quot;checklist&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;i class=&amp;quot;fa fa-square-o&amp;quot;&amp;gt;&amp;lt;/i&amp;gt; Mengimplementasikan halaman utama - [ ] Mengintegrasikan otentikasi OAuth2 - [ ] Buat formulir CRUD - [ ] Mengatur Penyimpanan untuk file - [ ] Men-deploy ke GitHub Pages - [ ] Uji dalam kondisi nyata&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Fase 3 - evolusi (sesuai kebutuhan)&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist checklist&amp;quot;&amp;gt;
&amp;lt;ul class=&amp;quot;checklist&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;i class=&amp;quot;fa fa-square-o&amp;quot;&amp;gt;&amp;lt;/i&amp;gt; Identifikasi kebutuhan logika kompleks - [ ] Membuat proyek Spring Boot jika diperlukan - [ ] Konfigurasi validasi JWT Supabase - [ ] Menghubungkan ke PostgreSQL Supabase - [ ] Memigrasikan fitur-fitur kompleks - [ ] Menerapkan pekerjaan terjadwal - [ ] Men-deploy Spring Boot (Cloud Run, dst.)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_anti_patterns_yang_harus_dihindari&amp;quot;&amp;gt;Anti-Patterns yang harus dihindari&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;❌ Jangan lakukan :&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Duplikasi data antara Supabase dan Spring Boot&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Buat dua basis data PostgreSQL terpisah&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Mengodek otentikasi sendiri&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Menggunakan Spring Boot untuk CRUD sederhana&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Menglebih-lebihan arsitektur sejak awal&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Mengabaikan keamanan tingkat baris Supabase&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;✅ Lebih baik :&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Membagikan satu basis data PostgreSQL&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Biarkan Supabase mengelola autentikasi&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Menggunakan Spring Boot hanya untuk kompleksitas&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Mulai sederhana, berkembang secara bertahap&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Manfaatkan kekuatan setiap teknologi&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Mengamankan dengan RLS pada tingkat basis data&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_prospek_evolusi&amp;quot;&amp;gt;Prospek Evolusi&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_penambahan_kemampuan_baru&amp;quot;&amp;gt;Penambahan Kemampuan Baru&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE

rectangle &amp;quot;Arsitektur Saat Ini&amp;quot; #SkyBlue {
  [JBake]
  [Supabase]
  [Spring Boot]
}

rectangle &amp;quot;Evolusi yang Mungkin&amp;quot; #LightYellow {

  package &amp;quot;Kinerja&amp;quot; {
    [Redis Cache]
    [CDN pour Assets]
    [PostgreSQL Replicas]
  }

  package &amp;quot;Pemantauan&amp;quot; {
    [Grafana]
    [Prometheus]
    [Sentry]
  }

  package &amp;quot;CI/CD&amp;quot; {
    [GitHub Actions]
    [Tests Automatisés]
    [Déploiement Continu]
  }

  package &amp;quot;Fitur&amp;quot; {
    [Search (ElasticSearch)]
    [Queue (RabbitMQ)]
    [Event Sourcing]
  }
}

[Spring Boot] .down.&amp;amp;gt; [Redis Cache] : Peut ajouter
[Spring Boot] .down.&amp;amp;gt; [Queue (RabbitMQ)] : Peut ajouter
[Spring Boot] .down.&amp;amp;gt; [Search (ElasticSearch)] : Peut ajouter

[Supabase] .down.&amp;amp;gt; [PostgreSQL Replicas] : Peut ajouter

note bottom
  **Architecture extensible**
  Chaque ajout est indépendant
  Pas de refonte globale nécessaire
end note
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_penskalaan_horizontal&amp;quot;&amp;gt;Penskalaan Horizontal&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Ketika aplikasi Anda tumbuh, arsitektur hybrid diskalakan secara alami :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Supabase :&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Skala otomatis hingga jutaan operasi&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Replikasi baca untuk performa baca&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Pemulihan poin waktu untuk keamanan&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Spring Boot :&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;beberapa instance di belakang load balancer&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Desain stateless memfasilitasi skala horizontal&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Kubernetes untuk orkestrasi jika diperlukan&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;PostgreSQL :&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Partitioning untuk tabel yang berukuran besar&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;pooling koneksi (PgBouncer)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Sharding memang diperlukan (jarang)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_saksi_saksi_arkitektonik&amp;quot;&amp;gt;Saksi-saksi Arkitektonik&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_startup_saas_50k_pengguna&amp;quot;&amp;gt;Startup SaaS (50k pengguna)&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;amp;lt;/think&amp;amp;gt; Kami memulai dengan 100% Supabase. Pada 5.000 pengguna, kami menambahkan Spring Boot hanya untuk penagihan Stripe dan laporan bulanan. Satu tahun kemudian, 80% dari operasi kami masih melewati Supabase. Spring Boot hanya mengelola bagian kritis. Pemisahan ini memungkinkan kami untuk menskalakan tanpa rekonstruksi. &amp;lt;em&amp;gt;__&amp;lt;/em&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_platform_e_learning_10k_pengguna&amp;quot;&amp;gt;Platform e-learning (10k pengguna)&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;em&amp;gt;&amp;lt;em&amp;gt;_ Autentikasi OAuth2 Supabase telah menghemat tiga minggu pengembangan kami. API yang dihasilkan secara otomatis mengelola seluruh katalog kursus kami. Spring Boot hanya digunakan untuk sertifikat PDF dan email progres. Arsitektur sederhana, mudah dipelihara, dan dapat diskalakan. &amp;lt;/em&amp;gt;_&amp;lt;/em&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_pasar_b2b_3k_pengguna&amp;quot;&amp;gt;Pasar B2B (3k pengguna)&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;em&amp;gt;&amp;lt;em&amp;gt;_ Spring Boot mengelola transaksi antara pembeli dan penjual (krusial). Supabase mengelola semua yang lain: profil, pesan realtime, dokumen. Faktanya mereka berbagi basis data PostgreSQL yang sama menghindarkan kita dari sinkronisasi kompleks. Pilihan arsitektur terbaik yang pernah kita buat. &amp;lt;/em&amp;gt;_&amp;lt;/em&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_sintesis_paradigma_arsitektur_modern&amp;quot;&amp;gt;Sintesis: Paradigma Arsitektur Modern&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Arsitektur hibrid Supabase + Spring Boot merupakan paradigma baru:&amp;lt;strong&amp;gt;spesialisasi arsitektur&amp;lt;/strong&amp;gt;.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE

rectangle &amp;quot;Paradigma Lama&amp;quot; #LightCoral {
  card &amp;quot;Semua di Backend&amp;quot; {
    [Auth]
    [CRUD]
    [Logique Métier]
    [Jobs]
    [Storage]
    [Realtime]
  }
  note bottom
    Complexité maximale
    dès le jour 1
  end note
}

rectangle &amp;quot;Paradigma Baru&amp;quot; #LightGreen {
  card &amp;quot;Supabase
(kemudahan)&amp;quot; {
    [Auth]
    [CRUD Simple]
    [Storage]
    [Realtime]
  }

  card &amp;quot;Spring Boot
(Diferensiasi)&amp;quot; {
    [Logique Métier]
    [Jobs]
    [Intégrations]
  }

  note bottom
    Complexité progressive
    ajoutée seulement si nécessaire
  end note
}

[Ancien Paradigme] -right-&amp;amp;gt; [Nouveau Paradigme] : Évolution

@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Prinsip dasar:&amp;lt;/strong&amp;gt;Jangan membangun hal-hal yang sudah ada dalam bentuk layanan. Fokuslah pada yang membedakan aplikasi Anda.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_aturan_8020&amp;quot;&amp;gt;Aturan 80/20&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Di sebagian besar aplikasi:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;80% operasi&amp;lt;/strong&amp;gt;adalah CRUD standar → Supabase&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;20% dari operasi&amp;lt;/strong&amp;gt;memerlukan logika bisnis → Spring Boot&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Aturan alami ini membenarkan arsitektur hibrida.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE

package &amp;quot;Distribusi Operasi&amp;quot; {

  rectangle &amp;quot;80% - Operasi Standar&amp;quot; #LightGreen {
    [Lecture de données]
    [Création d&amp;#39;entités]
    [Mise à jour simple]
    [Suppression]
    [Upload fichiers]
    [Authentification]

    note bottom: **Géré par Supabase**
  }

  rectangle &amp;quot;20% - Logika bisnis&amp;quot; #Coral {
    [Calculs complexes]
    [Workflows multi-étapes]
    [Transactions critiques]
    [Intégrations tierces]
    [Jobs planifiés]

    note bottom: **Géré par Spring Boot**
  }
}

note right
  **Focus optimal :**
  Votre temps de développement
  concentré sur les 20% qui
  différencient votre produit
end note
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_kesimpulan_akhir&amp;quot;&amp;gt;Kesimpulan Akhir&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Arsitektur hibrid bukanlah kompromi teknis, ini adalah&amp;lt;strong&amp;gt;keputusan strategis&amp;lt;/strong&amp;gt;.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Ini memungkinkan Anda:&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;✅ Mulai cepat dengan MVP yang berfungsi&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;✅ Memvalidasi pasar Anda tanpa investasi berat&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;✅ Bertahap berkembang ketika kompleksitas menuntutnya&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;✅ Mengendalikan biaya Anda pada setiap tahap&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;✅ Memanfaatkan yang terbaik dari setiap teknologi&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;✅ Menghindari over-engineering yang terlalu dini&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;✅ Pertahankan fleksibilitas untuk masa depan&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Dia mewakili :&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;🎯&amp;lt;strong&amp;gt;Pragmatisme&amp;lt;/strong&amp;gt;: Setiap teknologi di mana dia unggul&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;🚀&amp;lt;strong&amp;gt;kecepatan&amp;lt;/strong&amp;gt;: Waktu untuk pasar minimal&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;💰&amp;lt;strong&amp;gt;Ekonomi&amp;lt;/strong&amp;gt;: Biaya yang selaras dengan nilai&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;🔮&amp;lt;strong&amp;gt;Skalabilitas&amp;lt;/strong&amp;gt;: Arsitektur yang tumbuh bersama Anda&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;🛡️&amp;lt;strong&amp;gt;Ketahanan&amp;lt;/strong&amp;gt;Servis yang terbukti dan andal&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_langkah_selanjutnya_anda&amp;quot;&amp;gt;Langkah Selanjutnya Anda&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Jika Anda menyukai arsitektur ini, berikut ini cara memulai:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;olist arabic&amp;quot;&amp;gt;
&amp;lt;ol class=&amp;quot;arabic&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Buat akun Supabase&amp;lt;/strong&amp;gt;(gratis)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Tentukan skema data minimal Anda&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Konfigurasikan autentikasi OAuth2&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Buat halaman pertama JBake Anda yang menggunakan API&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Terapkan di GitHub Pages&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Anda akan memiliki MVP yang fungsional di&amp;lt;strong&amp;gt;beberapa hari&amp;lt;/strong&amp;gt;.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Spring Boot akan datang secara alami saat Anda membutuhkannya. Belum sebelumnya.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;hr&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Arsitektur hibrid adalah seni membangun tepat apa yang diperlukan, pada saat yang tepat.&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;em&amp;gt;Semoga berkembang dengan baik! 🚀&amp;lt;/em&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;hr&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;em&amp;gt;Bagikan artikel ini jika Anda berpikir bahwa ia dapat membantu pengembang lain untuk membuat pilihan arsitektur yang tepat.&amp;lt;/em&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>Arsitektur Hibrid : Yang Terbaik dari Kedua Dunia dengan Supabase dan Spring Boot</title>
            <link >https://cheroliv.com/blog/2026/0100_jbake_supabase_springboot_hybride_post.html</link>
            <pubDate>Wed, 14 Jan 2026 00:00:00 +0000</pubDate>
            <guid isPermaLink="false">blog/2026/0100_jbake_supabase_springboot_hybride_post.html</guid>
            <description>&amp;lt;div id=&amp;quot;preamble&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;waktu membaca : 12 minutes&amp;lt;/div&amp;gt;
&amp;lt;p&amp;gt;Alih-alih memilih antara kesederhanaan Supabase dan kekuatan Spring Boot, mengapa tidak menggabungkan keduanya? Temukan arsitektur hibrida yang memanfaatkan yang terbaik dari setiap teknologi.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;dlist&amp;quot;&amp;gt;
&amp;lt;dl&amp;gt;
&amp;lt;dt class=&amp;quot;hdlist1&amp;quot;&amp;gt;tok&amp;lt;/dt&amp;gt;
&amp;lt;dd&amp;gt;
&amp;lt;p&amp;gt;[]&amp;lt;/p&amp;gt;
&amp;lt;/dd&amp;gt;
&amp;lt;/dl&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_dilema_palsu_arsitektur_modern&amp;quot;&amp;gt;Dilema Palsu Arsitektur Modern&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Saat membangun aplikasi modern dengan frontend statis, kita sering kali menghadapi pilihan biner:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Menaruh semua harapan pada solusi Backend-as-a-Service&amp;lt;/strong&amp;gt;(Supabase, Firebase) - sederhana tetapi terbatas&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Membangun backend lengkap&amp;lt;/strong&amp;gt;(Spring Boot, Node.js) - kuat namun kompleks&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Pilihan ini adalah dilema palsu. Arsitektur hibrid menawarkan jalan ketiga: &amp;lt;strong&amp;gt;menggunakan setiap teknologi tempat ia mengungguli&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_visi_arsitektur&amp;quot;&amp;gt;Visi Arsitektur&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_arsitektur_tradisional_monolitik&amp;quot;&amp;gt;Arsitektur Tradisional: Monolitik&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle

package &amp;quot;Arsitektur Tradisional&amp;quot; {
  actor Utilisateur as user
  component &amp;quot;frontend&amp;quot; as frontend #LightBlue
  component &amp;quot;backend monolitik&amp;quot; as backend #LightCoral {
    [Authentification]
    [CRUD]
    [Logique Métier]
    [Jobs]
    [APIs Tierces]
  }
  database &amp;quot;Basis data&amp;quot; as db #LightGray

  user --&amp;amp;gt; frontend
  frontend --&amp;amp;gt; backend : Toutes les requêtes
  backend --&amp;amp;gt; db
}

note right of backend
  Tout passe par le backend
  même les opérations simples

  ⚠️ Point de congestion unique
  ⚠️ Complexité dès le départ
  ⚠️ Couplage fort
end note
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Pendekatan ini, bahkan operasi paling sederhana (membaca artikel, membuat komentar) harus melalui backend Anda. Anda membayar biaya kompleksitas sejak hari pertama.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_arsitektur_baas_semua_delegasi&amp;quot;&amp;gt;Arsitektur BaaS: Semua Delegasi&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle

package &amp;quot;Arsitektur BaaS Murni&amp;quot; {
  actor Utilisateur as user
  component &amp;quot;Frontend&amp;quot; as frontend #LightBlue
  cloud &amp;quot;Supabase / Firebase&amp;quot; as baas #LightGreen {
    [Auth]
    [Database]
    [Storage]
    [Edge Functions]
  }

  user --&amp;amp;gt; frontend
  frontend --&amp;amp;gt; baas : Toutes les opérations
}

note right of baas
  Développement rapide
  Pas de serveur à gérer

  ⚠️ Limité pour logique complexe
  ⚠️ Vendor lock-in
  ⚠️ Edge Functions contraintes
end note
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Sebaliknya, mendelegasikan semuanya ke BaaS menarik di awal tetapi menunjukkan batasnya dengan cepat saat logika bisnis menjadi kompleks.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_arsitektur_hibrid_pemisahan_tanggung_jawab&amp;quot;&amp;gt;Arsitektur Hibrid: Pemisahan Tanggung Jawab&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle

actor Utilisateur as user #LightBlue

package &amp;quot;Lapisan Frontend&amp;quot; {
  component &amp;quot;Situs Statis JBake&amp;quot; as frontend #SkyBlue
}

package &amp;quot;Lapisan Sederhana (Supabase)&amp;quot; #LightGreen {
  component &amp;quot;Otentikasi&amp;quot; as auth
  component &amp;quot;CRUD Dasar&amp;quot; as crud
  component &amp;quot;Penyimpanan File&amp;quot; as storage
  component &amp;quot;Waktu nyata&amp;quot; as realtime
}

package &amp;quot;Lapisan Bisnis (Spring Boot)&amp;quot; #Coral {
  component &amp;quot;Logika Kompleks&amp;quot; as logic
  component &amp;quot;Orkestrasi&amp;quot; as orchestration
  component &amp;quot;Jobs Jadwal&amp;quot; as jobs
  component &amp;quot;Integrasi&amp;quot; as integrations
}

database &amp;quot;PostgreSQL yang dibagikan&amp;quot; as db #LightGray

user --&amp;amp;gt; frontend

frontend --&amp;amp;gt; auth : OAuth2, JWT
frontend --&amp;amp;gt; crud : Articles, Profils
frontend --&amp;amp;gt; storage : Images, Fichiers
frontend --&amp;amp;gt; realtime : Notifications

frontend --&amp;amp;gt; logic : Calculs, Rapports
frontend --&amp;amp;gt; orchestration : Workflows
logic --&amp;amp;gt; integrations : Stripe, APIs
orchestration --&amp;amp;gt; db
jobs --&amp;amp;gt; db

crud --&amp;amp;gt; db
auth --&amp;amp;gt; db

note bottom of db
  **Une seule source de vérité**
  Partagée entre Supabase et Spring Boot
end note

note right of auth
  **Gratuit jusqu&amp;#39;à 50k users**
  Configuration en quelques clics
  OAuth2 multi-providers
end note

note right of logic
  **Activé seulement si nécessaire**
  Kotlin + Coroutines
  Transactions complexes
end note
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Arsitektur hybrida memisahkan dengan jelas tanggung jawab berdasarkan kompleksitas dan sifat operasi.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_prinsip_desain&amp;quot;&amp;gt;Prinsip Desain&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_prinsip_1_mulai_sederhana_berkembang_cerdas&amp;quot;&amp;gt;Prinsip 1 : Mulai Sederhana, Berkembang Cerdas&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE

rectangle &amp;quot;Tahap 1 : MVP&amp;quot; #LightGreen {
  component &amp;quot;JBake + Supabase&amp;quot; as mvp
  note right
    ✓ Développement en jours
    ✓ Coût : 0€
    ✓ Validation concept
  end note
}

rectangle &amp;quot;**Tahap 2 : Pertumbuhan**&amp;quot; #LightYellow {
  component &amp;quot;JBake + Supabase
+ Spring Boot (opsional)&amp;quot; as growth
  note right
    ✓ Logique métier émerge
    ✓ Premiers jobs planifiés
    ✓ Intégrations tierces
  end note
}

rectangle &amp;quot;Tahap 3: Skala&amp;quot; #LightCoral {
  component &amp;quot;Arsitektur Lengkap&amp;quot; as scale
  note right
    ✓ Orchestration complexe
    ✓ Performances optimisées
    ✓ Cache distribué
  end note
}

mvp -down-&amp;amp;gt; growth : Quand la complexité\nle justifie
growth -down-&amp;amp;gt; scale : Optimisation\ncontinue
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Jangan membangun Spring Boot jika Anda tidak membutuhkannya.&amp;lt;/strong&amp;gt;Mulai dengan Supabase, tambahkan Spring Boot bila kompleksitas membenarkannya.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_prinsip_2_pemisahan_berdasarkan_jenis_operasi&amp;quot;&amp;gt;Prinsip 2 : Pemisahan berdasarkan Jenis Operasi&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startmindmap
skinparam backgroundColor #FEFEFE
* Opérations\nApplicatives

left side

** Opérations Simples\n(Supabase)
***_ CRUD Standard
****_ Lecture
****_ Création
****_ Mise à jour
****_ Suppression
***_ Authentification
****_ Login/Logout
****_ OAuth2
****_ Gestion session
***_ Stockage
****_ Upload fichiers
****_ CDN automatique
***_ Temps Réel
****_ WebSocket
****_ Notifications

right side

** Opérations Complexes\n(Spring Boot)
***_ Logique Métier
****_ Calculs complexes
****_ Règles métier
****_ Validations avancées
***_ Orchestration
****_ Multi-services
****_ Transactions distribuées
****_ Saga patterns
***_ Tâches Planifiées
****_ Rapports périodiques
****_ Synchronisations
****_ Nettoyages
***_ Intégrations
****_ Paiements (Stripe)
****_ APIs tierces
****_ Services ML/AI

@endmindmap&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_prinsip_3_satu_sumber_kebenaran&amp;quot;&amp;gt;Prinsip 3 : Satu Sumber Kebenaran&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE

database &amp;quot;PostgreSQL&amp;quot; as db #LightGray {
  package &amp;quot;skema: auth (Supabase)&amp;quot; #LightGreen {
    [users]
    [sessions]
    [identities]
  }

  package &amp;quot;Skema: publik (dibagikan)&amp;quot; #SkyBlue {
    [articles]
    [comments]
    [profiles]
  }

  package &amp;quot;Skema: bisnis (Spring Boot)&amp;quot; #Coral {
    [orders]
    [payments]
    [reports]
  }
}

component &amp;quot;Supabase&amp;quot; as supabase #LightGreen
component &amp;quot;Spring Boot&amp;quot; as spring #Coral

supabase -down-&amp;amp;gt; users : Gère
supabase -down-&amp;amp;gt; sessions : Gère
supabase -down-&amp;amp;gt; articles : Accède
supabase -down-&amp;amp;gt; comments : Accède
supabase -down-&amp;amp;gt; profiles : Accède

spring -down-&amp;amp;gt; articles : Accède
spring -down-&amp;amp;gt; profiles : Accède
spring -down-&amp;amp;gt; orders : Gère
spring -down-&amp;amp;gt; payments : Gère
spring -down-&amp;amp;gt; reports : Gère

note right of db
  **Avantages :**
  • Pas de synchronisation complexe
  • JOINs possibles entre tables
  • Cohérence garantie
  • Migrations unifiées
end note
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Dengan berbagi instance PostgreSQL yang sama, Supabase dan Spring Boot bekerja pada data yang sama tanpa sinkronisasi yang kompleks.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_orkestrasi_aliran&amp;quot;&amp;gt;Orkestrasi Aliran&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_alur_1_otentikasi_terpusat&amp;quot;&amp;gt;Alur 1 : Otentikasi Terpusat&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE
autonumber

actor &amp;quot;Pengguna&amp;quot; as user
participant &amp;quot;JBake&amp;quot; as jbake
participant &amp;quot;Supabase Auth&amp;quot; as auth
participant &amp;quot;Spring Boot&amp;quot; as spring
database &amp;quot;PostgreSQL&amp;quot; as db

== Authentification ==
user -&amp;amp;gt; jbake : Clique &amp;quot;Masuk&amp;quot;
jbake -&amp;amp;gt; auth : Redirect OAuth2 (Google/GitHub)
auth -&amp;amp;gt; user : Consent screen
user -&amp;amp;gt; auth : Autorise
auth -&amp;amp;gt; auth : Génère JWT
auth -&amp;amp;gt; jbake : Retourne JWT + Refresh Token
jbake -&amp;amp;gt; jbake : Stocke tokens (sessionStorage)

== Accès aux Ressources ==
group Opération Simple (CRUD)
  user -&amp;amp;gt; jbake : Crée un article
  jbake -&amp;amp;gt; db : POST avec JWT
  note right: Supabase Row Level Security\nvérifie le JWT automatiquement
  db -&amp;amp;gt; jbake : Article créé
end

group Opération Complexe (Métier)
  user -&amp;amp;gt; jbake : Génère un rapport
  jbake -&amp;amp;gt; spring : POST /api/reports\navec JWT
  spring -&amp;amp;gt; spring : Valide JWT Supabase
  spring -&amp;amp;gt; spring : Extrait claims (user_id, roles)
  spring -&amp;amp;gt; db : Requêtes complexes
  spring -&amp;amp;gt; jbake : Rapport généré
end

note over auth, spring
  **Le JWT est universel**
  Valide par Supabase ET Spring Boot
  Pas de double authentification
end note
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Poin penting&amp;lt;/strong&amp;gt;: Sebuah proses otentikasi tunggal, satu JWT, dapat digunakan di mana saja.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_aliran_2_orkeshtrasi_sebuah_proses_bisnis_kompleks&amp;quot;&amp;gt;Aliran 2 : Orkeshtrasi sebuah Proses Bisnis Kompleks&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE
autonumber

actor &amp;quot;Klien&amp;quot; as user
participant &amp;quot;JBake&amp;quot; as jbake
participant &amp;quot;Spring Boot&amp;quot; as spring
participant &amp;quot;Supabase DB&amp;quot; as supadb
participant &amp;quot;Stripe API&amp;quot; as stripe
participant &amp;quot;layanan persediaan&amp;quot; as inventory
participant &amp;quot;Layanan Email&amp;quot; as email
database &amp;quot;PostgreSQL&amp;quot; as db

user -&amp;amp;gt; jbake : Passe commande
jbake -&amp;amp;gt; spring : POST /api/orders/create

== Orchestration Spring Boot ==
spring -&amp;amp;gt; db : BEGIN TRANSACTION

par Vérifications Parallèles
  spring -&amp;amp;gt; inventory : Vérifier stock
  inventory -&amp;amp;gt; spring : Stock OK
else
  spring -&amp;amp;gt; db : Vérifier profil client
  db -&amp;amp;gt; spring : Client vérifié
end

spring -&amp;amp;gt; stripe : Créer Payment Intent
stripe -&amp;amp;gt; spring : Intent créé (ID: pi_xxx)

spring -&amp;amp;gt; db : Créer Order (status: PENDING)
db -&amp;amp;gt; spring : Order créé

spring -&amp;amp;gt; stripe : Confirmer paiement
stripe -&amp;amp;gt; spring : Paiement confirmé

spring -&amp;amp;gt; db : Update Order (status: PAID)
spring -&amp;amp;gt; inventory : Décrémenter stock
spring -&amp;amp;gt; db : COMMIT TRANSACTION

par Notifications Asynchrones
  spring -&amp;amp;gt; email : Email confirmation
else
  spring -&amp;amp;gt; supadb : Insert notification
  note right: Notification temps réel\nvia Supabase Realtime
end

spring -&amp;amp;gt; jbake : Order confirmé
jbake -&amp;amp;gt; user : Affiche confirmation

note over spring
  **Responsabilités Spring Boot :**
  • Transaction ACID
  • Orchestration multi-services
  • Rollback automatique si erreur
  • Gestion des cas limites
end note
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Apa yang diberikan Spring Boot&amp;lt;/strong&amp;gt;Orchestrasi yang andal dari proses kompleks yang melibatkan beberapa sistem dengan jaminan transaksional.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_aliran_3_pekerjaan_terjadwal_dan_sinkronisasi&amp;quot;&amp;gt;Aliran 3: Pekerjaan terjadwal dan sinkronisasi&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE

participant &amp;quot;Penjadwal
(Spring Boot)&amp;quot; as scheduler
participant &amp;quot;layanan pelaporan
layanan pelaporan&amp;quot; as report
participant &amp;quot;PostgreSQL&amp;quot; as db
participant &amp;quot;API eksternal&amp;quot; as api
participant &amp;quot;Layanan Email&amp;quot; as email
participant &amp;quot;Supabase Storage&amp;quot; as storage

== Job Quotidien : 2h00 ==
activate scheduler
scheduler -&amp;amp;gt; report : Déclenche génération rapport

activate report
report -&amp;amp;gt; db : Récupère données\ndes dernières 24h
db -&amp;amp;gt; report : Dataset

report -&amp;amp;gt; report : Calculs &amp;amp;amp; Agrégations
report -&amp;amp;gt; report : Génération PDF

report -&amp;amp;gt; storage : Upload PDF
storage -&amp;amp;gt; report : URL publique

report -&amp;amp;gt; email : Envoie aux admins\navec lien PDF
deactivate report

== Job Hebdomadaire : Dimanche 3h00 ==
scheduler -&amp;amp;gt; report : Synchronisation externe

activate report
report -&amp;amp;gt; api : Fetch données externes
api -&amp;amp;gt; report : Nouvelles données

report -&amp;amp;gt; db : Mise à jour tables
report -&amp;amp;gt; db : Nettoyage données obsolètes
deactivate report

== Job Mensuel : 1er du mois ==
scheduler -&amp;amp;gt; report : Facturation

activate report
report -&amp;amp;gt; db : Liste clients actifs
report -&amp;amp;gt; report : Calcul montants
report -&amp;amp;gt; api : Génération factures (Stripe)
report -&amp;amp;gt; email : Envoi factures
deactivate report

deactivate scheduler

note right of scheduler
  **Impossible avec Supabase seul**
  Edge Functions non adaptées
  pour jobs longs et récurrents
end note
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Apa yang Spring Boot bawa&amp;lt;/strong&amp;gt; : Pekerjaan terjadwal yang andal dengan pengelolaan status, retry otomatis, dan eksekusi terjamin.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_matriks_keputusan&amp;quot;&amp;gt;Matriks Keputusan&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_kapan_menggunakan_supabase&amp;quot;&amp;gt;Kapan menggunakan Supabase?&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE

start

:Nouvelle fonctionnalité à implémenter;

if (CRUD simple ?) then (oui)
  :✅ **Supabase**;
  stop
else (non)
  if (Authentification ?) then (oui)
    :✅ **Supabase Auth**;
    stop
  else (non)
    if (Upload fichiers ?) then (oui)
      :✅ **Supabase Storage**;
      stop
    else (non)
      if (Temps réel ?) then (oui)
        :✅ **Supabase Realtime**;
        stop
      else (non)
        if (Row Level Security\nsuffisante ?) then (oui)
          :✅ **Supabase + Policies**;
          stop
        else (non)
          :⚠️ **Considérer Spring Boot**;
          #Coral:Passer à l&amp;#39;arbre\nde décision Spring Boot;
          stop
        endif
      endif
    endif
  endif
endif

@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_kapan_menggunakan_spring_boot&amp;quot;&amp;gt;Kapan Menggunakan Spring Boot ?&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE

start

:Nouvelle fonctionnalité complexe;

if (Orchestration\nmulti-services ?) then (oui)
  :✅ **Spring Boot**;
  stop
else (non)
  if (Transaction ACID\ncritique ?) then (oui)
    :✅ **Spring Boot**;
    stop
  else (non)
    if (Job planifié ?) then (oui)
      :✅ **Spring Boot Scheduler**;
      stop
    else (non)
      if (Intégration API\ntierce complexe ?) then (oui)
        :✅ **Spring Boot**;
        stop
      else (non)
        if (Calculs lourds\n&amp;amp;gt; 10 secondes ?) then (oui)
          :✅ **Spring Boot**;
          stop
        else (non)
          if (Cache distribué\nnécessaire ?) then (oui)
            :✅ **Spring Boot + Redis**;
            stop
          else (non)
            :⚠️ **Probablement Supabase**;
            #LightGreen:Revenir à l&amp;#39;arbre\nde décision Supabase;
            stop
          endif
        endif
      endif
    endif
  endif
endif

@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_contoh_arsitektur_per_tipe_proyek&amp;quot;&amp;gt;Contoh Arsitektur per Tipe Proyek&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_blog_portofolio&amp;quot;&amp;gt;Blog / Portofolio&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle

package &amp;quot;Blog Pribadi - 95% Supabase&amp;quot; {
  component &amp;quot;JBake&amp;quot; as jbake #SkyBlue

  package &amp;quot;Supabase (Gratis)&amp;quot; #LightGreen {
    [Auth: OAuth2 Google]
    [Articles (CRUD)]
    [Commentaires]
    [Images (Storage)]
    [Notifications Realtime]
  }

  package &amp;quot;Spring Boot (Opsional)&amp;quot; #LightCoral {
    [Newsletter Mensuelle]
    note bottom: Job planifié uniquement
  }

  database &amp;quot;PostgreSQL&amp;quot; as db

  jbake --&amp;amp;gt; [Auth: OAuth2 Google]
  jbake --&amp;amp;gt; [Articles (CRUD)]
  jbake --&amp;amp;gt; [Commentaires]
  jbake --&amp;amp;gt; [Images (Storage)]
  jbake --&amp;amp;gt; [Notifications Realtime]

  [Articles (CRUD)] --&amp;amp;gt; db
  [Commentaires] --&amp;amp;gt; db
  [Newsletter Mensuelle] --&amp;amp;gt; db
}

note bottom of &amp;quot;Spring Boot (Opsional)&amp;quot;
  **Spring Boot pas obligatoire**
  Newsletter peut être gérée
  par un service tiers (Mailchimp)
end note
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;putusan&amp;lt;/strong&amp;gt;: 100% Supabase sudah cukup.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_aplikasi_saas&amp;quot;&amp;gt;Aplikasi SaaS&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle

package &amp;quot;Aplikasi SaaS - Arsitektur Hibrid&amp;quot; {
  component &amp;quot;JBake / React&amp;quot; as frontend #SkyBlue

  package &amp;quot;Supabase&amp;quot; #LightGreen {
    [Auth Multi-tenant]
    [Profils Utilisateurs]
    [Données Simples]
    [Storage Documents]
  }

  package &amp;quot;Spring Boot&amp;quot; #Coral {
    [Facturation (Stripe)]
    [Analytics Avancées]
    [Rapports Mensuels]
    [Quotas &amp;amp;amp; Limites]
    [Webhooks Stripe]
  }

  cloud &amp;quot;Layanan Eksternal&amp;quot; {
    [Stripe API]
    [SendGrid Email]
  }

  database &amp;quot;PostgreSQL Dibagikan&amp;quot; as db

  frontend --&amp;amp;gt; [Auth Multi-tenant]
  frontend --&amp;amp;gt; [Profils Utilisateurs]
  frontend --&amp;amp;gt; [Données Simples]
  frontend --&amp;amp;gt; [Facturation (Stripe)]
  frontend --&amp;amp;gt; [Analytics Avancées]

  [Auth Multi-tenant] --&amp;amp;gt; db
  [Profils Utilisateurs] --&amp;amp;gt; db
  [Données Simples] --&amp;amp;gt; db

  [Facturation (Stripe)] --&amp;amp;gt; db
  [Facturation (Stripe)] --&amp;amp;gt; [Stripe API]
  [Analytics Avancées] --&amp;amp;gt; db
  [Rapports Mensuels] --&amp;amp;gt; db
  [Rapports Mensuels] --&amp;amp;gt; [SendGrid Email]
  [Webhooks Stripe] ..&amp;amp;gt; [Stripe API] : Écoute
}

note right of &amp;quot;Spring Boot&amp;quot;
  **Nécessaire pour :**
  • Gestion abonnements
  • Facturation récurrente
  • Quotas dynamiques
  • Analytics complexes
  • Webhooks fiables
end note
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;putusan&amp;lt;/strong&amp;gt;Arsitektur hibrid diperlukan. Supabase untuk bagian inti, Spring Boot untuk bagian kritis (pembayaran, penagihan).&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_perdagangan_elektronik&amp;quot;&amp;gt;perdagangan elektronik&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle

package &amp;quot;E-niaga - 70% Spring Boot&amp;quot; {
  component &amp;quot;Storefront (JBake/React)&amp;quot; as frontend #SkyBlue

  package &amp;quot;Supabase&amp;quot; #LightGreen {
    [Auth Clients]
    [Catalogue Produits\n(lecture seule)]
    [Images Produits]
  }

  package &amp;quot;Spring Boot (Inti Bisnis)&amp;quot; #Coral {
    [Gestion Commandes]
    [Paiements Stripe]
    [Gestion Stock]
    [Calcul Frais Port]
    [Génération Factures]
    [Synchronisation ERP]
    [Emails Transactionnels]
  }

  cloud &amp;quot;Integrasi&amp;quot; {
    [Stripe]
    [Transporteur API]
    [ERP Externe]
  }

  database &amp;quot;PostgreSQL&amp;quot; as db

  frontend --&amp;amp;gt; [Auth Clients]
  frontend --&amp;amp;gt; [Catalogue Produits\n(lecture seule)]
  frontend --&amp;amp;gt; [Gestion Commandes]

  [Auth Clients] --&amp;amp;gt; db
  [Catalogue Produits\n(lecture seule)] --&amp;amp;gt; db

  [Gestion Commandes] --&amp;amp;gt; db
  [Paiements Stripe] --&amp;amp;gt; [Stripe]
  [Gestion Stock] --&amp;amp;gt; db
  [Calcul Frais Port] --&amp;amp;gt; [Transporteur API]
  [Génération Factures] --&amp;amp;gt; db
  [Synchronisation ERP] --&amp;amp;gt; [ERP Externe]
  [Synchronisation ERP] --&amp;amp;gt; db
}

note right of &amp;quot;Spring Boot (Inti Bisnis)&amp;quot;
  **Spring Boot indispensable :**
  • Transactions critiques
  • Orchestration complexe
  • Intégrations multiples
  • Conformité légale
  • Jobs de synchronisation
end note
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Putusan&amp;lt;/strong&amp;gt;Spring Boot diperlukan. Supabase hanya untuk auth dan katalog.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_strategi_migrasi_bertahap&amp;quot;&amp;gt;Strategi migrasi bertahap&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE

|Semaine 1-2|
start
:Créer projet Supabase;
:Configurer Auth (Google, GitHub);
:Définir schéma PostgreSQL initial;
:Implémenter CRUD de base sur JBake;
:Déployer MVP sur GitHub Pages;
note right
  **Livrable :** Application fonctionnelle
  **Coût :** 0€
  **Temps :** 1-2 semaines
end note

|Semaine 3-4|
:Observer utilisation réelle;
:Identifier limitations;

|Décision|
if (Logique complexe\némerge ?) then (non)
  |Supabase|
  :Continuer avec Supabase seul;
  :Ajouter Edge Functions si besoin;
  stop
else (oui)
  |Spring Boot|
  :Créer projet Spring Boot;
  :Configurer validation JWT Supabase;
  :Migrer logique complexe vers Spring Boot;
  note right
    **Migration sélective**
    Seulement ce qui est complexe
  end note
endif

|Semaines suivantes|
:Déployer Spring Boot (Cloud Run, EC2);
:Connecter à PostgreSQL Supabase;
:Implémenter premiers jobs planifiés;
:Intégrer APIs tierces;

stop

note right
  **Architecture finale hybride**
  Supabase + Spring Boot
  en parfaite harmonie
end note
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_biaya_dan_pertimbangan_operasional&amp;quot;&amp;gt;Biaya dan Pertimbangan Operasional&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_struktur_biaya&amp;quot;&amp;gt;Struktur Biaya&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE

package &amp;quot;Perkiraan Biaya Bulanan&amp;quot; {

  rectangle &amp;quot;Tahap MVP\n(0-1000 pengguna)&amp;quot; #LightGreen {
    [JBake (GitHub Pages): 0€]
    [Supabase: 0€]
    [Spring Boot: N/A]
    note bottom: **Total: 0€/mois**
  }

  rectangle &amp;quot;Fase Pertumbuhan\n(1k-10k users)&amp;quot; #LightYellow {
    [JBake (GitHub Pages): 0€]
    [Supabase: 0€]
    [Spring Boot (Cloud Run): 20-50€]
    [PostgreSQL (si séparé): 0-30€]
    note bottom: **Total: 20-80€/mois**
  }

  rectangle &amp;quot;Skala Fase
(10k-100k pengguna)&amp;quot; #LightCoral {
    [JBake (Netlify Pro): 19€]
    [Supabase Pro: 25€]
    [Spring Boot (instances multiples): 100-300€]
    [Redis Cache: 20-50€]
    [Monitoring: 20€]
    note bottom: **Total: 184-414€/mois**
  }
}

note right of &amp;quot;Skala Fase
(10k-100k pengguna)&amp;quot;
  À ce stade, les revenus
  justifient largement les coûts
end note
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_tanggung_jawab_operasional&amp;quot;&amp;gt;Tanggung Jawab Operasional&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE

package &amp;quot;Siapa yang mengelola apa?&amp;quot; {

  rectangle &amp;quot;Dikelola oleh Supabase (0 upaya)&amp;quot; #LightGreen {
    [Authentification]
    [Base de données]
    [Backups automatiques]
    [Scaling automatique]
    [Monitoring DB]
    [Sécurité DB]
  }

  rectangle &amp;quot;Dikelola oleh GitHub (0 upaya)&amp;quot; #SkyBlue {
    [Hébergement site statique]
    [HTTPS/SSL]
    [CDN global]
    [Déploiement automatique]
  }

  rectangle &amp;quot;Untuk Mengelola (Spring Boot)&amp;quot; #LightCoral {
    [Déploiement application]
    [Monitoring APM]
    [Logs centralisés]
    [Scaling horizontal]
    [Mises à jour sécurité]
  }
}

note bottom of &amp;quot;Untuk Mengelola (Spring Boot)&amp;quot;
  **Minimisable avec :**
  • Cloud Run (serverless)
  • Kubernetes (si nécessaire)
  • Services managés (RDS, etc.)
end note
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_kesimpulan_keseimbangan_sempurna&amp;quot;&amp;gt;Kesimpulan: Keseimbangan Sempurna&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Arsitektur hibrid Supabase + Spring Boot bukanlah sebuah kompromi, ini adalah&amp;lt;strong&amp;gt;sinergi&amp;lt;/strong&amp;gt;.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_prinsip_prinsip_yang_harus_diingat&amp;quot;&amp;gt;Prinsip-prinsip yang harus diingat&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startmindmap
skinparam backgroundColor #FEFEFE
* Architecture\nHybride

** Commencer Simple
***_ MVP avec Supabase
***_ 0€ de coût initial
***_ Validation rapide

** Évoluer Intelligemment
***_ Ajouter Spring Boot\nsi nécessaire
***_ Migration progressive
***_ Pas de refonte totale

** Exploiter les Forces
***_ Supabase: Simplicité
***_ Spring Boot: Puissance
***_ PostgreSQL: Source unique

** Minimiser la Complexité
***_ Pas de synchronisation
***_ Pas de duplication
***_ Architecture claire

** Optimiser les Coûts
***_ Gratuit au départ
***_ Scaling progressif
***_ ROI évident

@endmindmap&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_kapan_memilih_arsitektur_ini&amp;quot;&amp;gt;Kapan Memilih Arsitektur Ini?&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;✅ Arsitektur ini ideal jika :&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Anda ingin memulai dengan cepat (MVP dalam hari, bukan dalam bulan)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Kamu mengantisipasi pertumbuhan kompleksitas bisnis&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Anda ingin meminimalkan biaya awal&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Anda menyukai PostgreSQL dan ingin memiliki satu sumber kebenaran yang unik&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Anda menyukai Kotlin dan coroutine untuk kode bisnis&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Anda ingin menghindari vendor lock-in total&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;❌ Arsitektur ini TIDAK cocok jika :&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Proyek Anda sederhana dan akan tetap sederhana (blog pribadi → 100% Supabase cukup)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Anda sudah memiliki sebuah stack backend yang ter-establish yang Anda menguasai&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Anda lebih suka sebuah monolit tradisional&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Anda membutuhkan .NET, Python, atau bahasa pemrograman lain di sisi backend&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_visions_keseluruhan_akhir&amp;quot;&amp;gt;Visions Keseluruhan Akhir&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE

package &amp;quot;Arsitektur Hibrid Lengkap&amp;quot; {

  actor &amp;quot;Pengguna&amp;quot; as users

  rectangle &amp;quot;frontend (statis)&amp;quot; #SkyBlue {
    [JBake / GitHub Pages]
    note right: Gratuit, CDN global
  }

  rectangle &amp;quot;Backend sederhana (Supabase)&amp;quot; #LightGreen {
    [Auth OAuth2]
    [CRUD APIs]
    [Storage]
    [Realtime]
    note right
      Gratuit jusqu&amp;#39;à 50k users
      Zéro configuration serveur
    end note
  }

  rectangle &amp;quot;Backend Bisnis (Spring Boot)&amp;quot; #Coral {
    [Orchestration]
    [Jobs Planifiés]
    [Intégrations]
    [Transactions]
    note right
      Activé uniquement
      si nécessaire
    end note
  }

  database &amp;quot;PostgreSQL\nSumber Tunggal Kebenaran&amp;quot; as db #LightGray

  users --&amp;amp;gt; [JBake / GitHub Pages]

  [JBake / GitHub Pages] --&amp;amp;gt; [Auth OAuth2]
  [JBake / GitHub Pages] --&amp;amp;gt; [CRUD APIs]
  [JBake / GitHub Pages] --&amp;amp;gt; [Storage]
  [JBake / GitHub Pages] --&amp;amp;gt; [Realtime]
  [JBake / GitHub Pages] --&amp;amp;gt; [Orchestration]

  [Auth OAuth2] --&amp;amp;gt; db
  [CRUD APIs] --&amp;amp;gt; db
  [Orchestration] --&amp;amp;gt; db
  [Jobs Planifiés] --&amp;amp;gt; db
  [Intégrations] --&amp;amp;gt; db
}

note bottom of db
  **Une seule base, deux consommateurs**
  • Supabase pour le simple
  • Spring Boot pour le complexe
  • Aucune synchronisation requise
end note

@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_terbaik_dari_kedua_dunia&amp;quot;&amp;gt;Terbaik dari kedua dunia&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Arsitektur ini memberi Anda :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;🚀 Kecepatan Supabase&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Mulai dalam jam, bukan dalam minggu&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Autentikasi OAuth2 dalam beberapa klik&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;APIs REST yang dihasilkan secara otomatis&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Konfigurasi server nol&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;💪 Kekuatan Spring Boot&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Kotlin dan Coroutines untuk kode asinkron yang elegan&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Orkestrasi proses bisnis kompleks&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Pekerjaan terjadwal yang andal&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Transaksi ACID terjamin&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Ekosistem Spring lengkap&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;💰 Ekonomi Progresif&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;0€ untuk mulai dan validasi&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Biaya yang mengikuti pertumbuhan&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Tanpa over-engineering yang dini&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;🎯 Fleksibilitas Arsitektur&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Migrasi progresif tanpa rekonstruksi&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Penambahan Spring Boot hanya jika diperlukan&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Tidak ada vendor lock-in total&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Arsitektur evolutif&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_untuk_pergi_lebih_jauh&amp;quot;&amp;gt;Untuk Pergi Lebih Jauh&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_sumber_daya_teknis&amp;quot;&amp;gt;Sumber Daya Teknis&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Dokumentasi Supabase :&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://supabase.com/docs/guides/auth&amp;quot;&amp;gt;Panduan Otentikasi&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://supabase.com/docs/guides/database/postgres/row-level-security&amp;quot;&amp;gt;Keamanan Tingkat Baris&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://supabase.com/docs/guides/api&amp;quot;&amp;gt;API yang dibuat otomatis&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Documentasi Spring Boot :&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://spring.io/guides/tutorials/spring-boot-kotlin/&amp;quot;&amp;gt;Spring Boot dengan Kotlin&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://spring.io/blog/2019/04/12/going-reactive-with-spring-coroutines-and-kotlin-flow&amp;quot;&amp;gt;Korutina dan Spring&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://docs.spring.io/spring-security/reference/servlet/oauth2/resource-server/jwt.html&amp;quot;&amp;gt;Server Sumber Daya JWT&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;artikel tambahan :&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Pengamanan API dengan JWT&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Optimalisasi performa PostgreSQL&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;pola migrasi progresif&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Penanganan kesalahan dalam arsitektur terdistribusi&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_kasus_penggunaan_nyata&amp;quot;&amp;gt;Kasus Penggunaan Nyata&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Arsitektur hibrid ini digunakan dengan sukses di :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;SaaS B2B&amp;lt;/strong&amp;gt;: Autentikasi Supabase, penagihan Spring Boot&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;pasar-pasar&amp;lt;/strong&amp;gt;: Katalog Supabase, transaksi Spring Boot&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Platform konten&amp;lt;/strong&amp;gt;: Artikel Supabase, analitik Spring Boot&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Alat internal&amp;lt;/strong&amp;gt;CRUD Supabase, workflows Spring Boot&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_daftar_periksa_awal&amp;quot;&amp;gt;Daftar Periksa Awal&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Phase 1 - Fondasi (Minggu 1)&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist checklist&amp;quot;&amp;gt;
&amp;lt;ul class=&amp;quot;checklist&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;i class=&amp;quot;fa fa-square-o&amp;quot;&amp;gt;&amp;lt;/i&amp;gt; Membuat akun Supabase - [ ] Mengonfigurasi proyek PostgreSQL - [ ] Mengaktifkan Auth (Google, GitHub) - [ ] Definisikan skema awal - [ ] Mengatur Row Level Security - [ ] Menguji API sejak JBake&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Tahap 2 - MVP (Minggu 2-3)&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist checklist&amp;quot;&amp;gt;
&amp;lt;ul class=&amp;quot;checklist&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;i class=&amp;quot;fa fa-square-o&amp;quot;&amp;gt;&amp;lt;/i&amp;gt; mengimplementasikan halaman utama - [ ] Mengintegrasikan autentikasi OAuth2 - [ ] Membuat formulir CRUD - [ ] Mengkonfigurasi Storage untuk file - [ ] Men-deploy ke GitHub Pages - [ ] Uji dalam kondisi nyata&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Tahap 3 - Perkembangan (Sesuai kebutuhan)&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist checklist&amp;quot;&amp;gt;
&amp;lt;ul class=&amp;quot;checklist&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;i class=&amp;quot;fa fa-square-o&amp;quot;&amp;gt;&amp;lt;/i&amp;gt; Identifikasi kebutuhan logika kompleks - [ ] Membuat proyek Spring Boot jika diperlukan - [ ] Konfigurasi validasi JWT Supabase - [ ] Menghubungkan ke PostgreSQL Supabase - [ ] Migrasikan fitur kompleks - [ ] Implementasikan pekerjaan terjadwal - [ ] Mendeploy Spring Boot (Cloud Run, etc.)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_anti_patterns_yang_harus_dihindari&amp;quot;&amp;gt;Anti-Patterns yang harus dihindari&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;❌ Jangan melakukan :&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Menduplikasi data antara Supabase dan Spring Boot&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Buat dua basis data PostgreSQL terpisah&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Kodekan otentikasi sendiri&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Menggunakan Spring Boot untuk CRUD sederhana&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Melakukan arsitektur berlebihan sejak awal&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Abaikan Keamanan Level Baris Supabase&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;✅ Lebih baik melakukan:&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Membagikan satu basis data PostgreSQL&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Biarkan Supabase mengelola otentikasi&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Menggunakan Spring Boot hanya untuk kompleksitas&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Memulai sederhana, berkembang secara bertahap&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Memanfaatkan kekuatan setiap teknologi&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Mengamankan dengan RLS pada tingkat basis data&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_perspektif_perkembangan&amp;quot;&amp;gt;Perspektif perkembangan&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_penambahan_kemampuan_baru&amp;quot;&amp;gt;Penambahan Kemampuan Baru&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE

rectangle &amp;quot;Arsitektur Saat Ini&amp;quot; #SkyBlue {
  [JBake]
  [Supabase]
  [Spring Boot]
}

rectangle &amp;quot;Perkembangan yang Mungkin&amp;quot; #LightYellow {

  package &amp;quot;Kinerja&amp;quot; {
    [Redis Cache]
    [CDN pour Assets]
    [PostgreSQL Replicas]
  }

  package &amp;quot;pemantauan&amp;quot; {
    [Grafana]
    [Prometheus]
    [Sentry]
  }

  package &amp;quot;CI/CD&amp;quot; {
    [GitHub Actions]
    [Tests Automatisés]
    [Déploiement Continu]
  }

  package &amp;quot;Fitur&amp;quot; {
    [Search (ElasticSearch)]
    [Queue (RabbitMQ)]
    [Event Sourcing]
  }
}

[Spring Boot] .down.&amp;amp;gt; [Redis Cache] : Peut ajouter
[Spring Boot] .down.&amp;amp;gt; [Queue (RabbitMQ)] : Peut ajouter
[Spring Boot] .down.&amp;amp;gt; [Search (ElasticSearch)] : Peut ajouter

[Supabase] .down.&amp;amp;gt; [PostgreSQL Replicas] : Peut ajouter

note bottom
  **Architecture extensible**
  Chaque ajout est indépendant
  Pas de refonte globale nécessaire
end note
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_penskalaan_horizontal&amp;quot;&amp;gt;Penskalaan Horizontal&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Saat aplikasi Anda tumbuh, arsitektur hybridnya diskalakan secara alami:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Supabase :&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Menskala otomatis hingga jutaan operasi&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Read replicas untuk kinerja pembacaan&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Pemulihan poin waktu untuk keamanan&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Spring Boot :&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Beberapa instance di belakang load balancer&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Desain tanpa keadaan memudahkan penskalaan horizontal&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Kubernetes untuk orkestrasi jika diperlukan&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;PostgreSQL :&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Partisi untuk tabel besar&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Pooling koneksi (PgBouncer)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Sharding benar-benar diperlukan (jarang)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_bukti_arkitektonik&amp;quot;&amp;gt;Bukti Arkitektonik&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_startup_saas_50k_pengguna&amp;quot;&amp;gt;Startup SaaS (50k pengguna)&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;em&amp;gt;&amp;lt;em&amp;gt;_ Kami memulai 100% Supabase. Pada 5 ribu pengguna, kami menambahkan Spring Boot hanya untuk penagihan Stripe dan laporan bulanan. Satu tahun kemudian, 80% dari operasi kami masih melewati Supabase. Spring Boot hanya mengelola bagian kritis. Pemisahan ini memungkinkan kami menskalakan tanpa perlu merancang ulang. &amp;lt;/em&amp;gt;_&amp;lt;/em&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_platform_e_learning_10_000_pengguna&amp;quot;&amp;gt;Platform E-learning (10.000 pengguna)&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;em&amp;gt;&amp;lt;em&amp;gt;_ Otentikasi OAuth2 Supabase telah menghemat tiga minggu pengembangan kami. API yang otomatis dihasilkan mengelola seluruh katalog kursus kami. Spring Boot hanya digunakan untuk sertifikat PDF dan email progres. Arsitektur sederhana, mudah dipelihara, dan mudah dikembangkan. &amp;lt;/em&amp;gt;_&amp;lt;/em&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_marketplace_b2b_3k_pengguna&amp;quot;&amp;gt;Marketplace B2B (3k pengguna)&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;amp;lt;/think&amp;amp;gt; (Empty output) Spring Boot mengelola transaksi antara pembeli dan penjual (kritik). Supabase mengelola seluruh hal lain: profil, pesan realtime, dokumen. Fakta bahwa mereka berbagi basis data PostgreSQL yang sama menghindarkan kita dari sinkronisasi yang kompleks. Pilihan arsitektur terbaik yang telah kita buat. &amp;lt;em&amp;gt;__&amp;lt;/em&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_sintesis_sebuah_paradigma_arsitektur_modern&amp;quot;&amp;gt;Sintesis : Sebuah Paradigma Arsitektur Modern&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Arsitektur hibrid Supabase + Spring Boot merepresentasikan sebuah paradigma baru :&amp;lt;strong&amp;gt;spesialisasi arsitektur&amp;lt;/strong&amp;gt;.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE

rectangle &amp;quot;Paradigma lama&amp;quot; #LightCoral {
  card &amp;quot;Semua di Backend&amp;quot; {
    [Auth]
    [CRUD]
    [Logique Métier]
    [Jobs]
    [Storage]
    [Realtime]
  }
  note bottom
    Complexité maximale
    dès le jour 1
  end note
}

rectangle &amp;quot;Paradigma Baru&amp;quot; #LightGreen {
  card &amp;quot;Supabase
(Kemudahan)&amp;quot; {
    [Auth]
    [CRUD Simple]
    [Storage]
    [Realtime]
  }

  card &amp;quot;Spring Boot\n(Diferensiasi)&amp;quot; {
    [Logique Métier]
    [Jobs]
    [Intégrations]
  }

  note bottom
    Complexité progressive
    ajoutée seulement si nécessaire
  end note
}

[Ancien Paradigme] -right-&amp;amp;gt; [Nouveau Paradigme] : Évolution

@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Prinsip fundamental:&amp;lt;/strong&amp;gt;Jangan membangun apa yang sudah ada dalam bentuk layanan. Fokuslah pada apa yang membedakan aplikasi Anda.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_prinsip_8020&amp;quot;&amp;gt;prinsip 80/20&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Dalam sebagian besar aplikasi:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;80% dari operasi&amp;lt;/strong&amp;gt;adalah CRUD standar → Supabase&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;20% dari operasi&amp;lt;/strong&amp;gt;memerlukan logika bisnis → Spring Boot&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Aturan alami ini membenarkan arsitektur hibrid.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam backgroundColor #FEFEFE

package &amp;quot;Distribusi Operasi&amp;quot; {

  rectangle &amp;quot;80% - Operasi Standar&amp;quot; #LightGreen {
    [Lecture de données]
    [Création d&amp;#39;entités]
    [Mise à jour simple]
    [Suppression]
    [Upload fichiers]
    [Authentification]

    note bottom: **Géré par Supabase**
  }

  rectangle &amp;quot;20% - Logika Bisnis&amp;quot; #Coral {
    [Calculs complexes]
    [Workflows multi-étapes]
    [Transactions critiques]
    [Intégrations tierces]
    [Jobs planifiés]

    note bottom: **Géré par Spring Boot**
  }
}

note right
  **Focus optimal :**
  Votre temps de développement
  concentré sur les 20% qui
  différencient votre produit
end note
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_kesimpulan_akhir&amp;quot;&amp;gt;Kesimpulan Akhir&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Arsitektur hibrid bukanlah sebuah kompromi teknis, ini adalah&amp;lt;strong&amp;gt;keputusan strategis&amp;lt;/strong&amp;gt;.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Dia memungkinkan Anda untuk :&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;✅ Memulai dengan cepat dengan MVP yang berfungsi&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;✅ Memvalidasi pasar Anda tanpa investasi yang besar&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;✅ Berkembang secara bertahap ketika kompleksitas mengharuskannya&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;✅ Mengendalikan biaya Anda pada setiap tahap&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;✅ Memanfaatkan yang terbaik dari setiap teknologi&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;✅ Menghindari over-engineering yang terlalu dini&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;✅ Menjaga fleksibilitas untuk masa depan&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Dia mewakili :&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;🎯 &amp;lt;strong&amp;gt;Pragmatisme&amp;lt;/strong&amp;gt;: Setiap teknologi di mana ia unggul&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;🚀&amp;lt;strong&amp;gt;kecepatan&amp;lt;/strong&amp;gt;: Time-to-market minimal&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;💰&amp;lt;strong&amp;gt;ekonomi&amp;lt;/strong&amp;gt;: Biaya yang selaras dengan nilai&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;🔮&amp;lt;strong&amp;gt;skalabilitas&amp;lt;/strong&amp;gt;: Arsitektur yang tumbuh dengan anda&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;🛡️&amp;lt;strong&amp;gt;ketangguhan&amp;lt;/strong&amp;gt;: Layanan yang terbukti dan andal&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_langkah_selanjutnya_anda&amp;quot;&amp;gt;Langkah selanjutnya Anda&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Jika arsitektur ini Anda rasakan, ini adalah tempat untuk memulai:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;olist arabic&amp;quot;&amp;gt;
&amp;lt;ol class=&amp;quot;arabic&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Buat akun Supabase&amp;lt;/strong&amp;gt;(gratis)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Tentukan skema data minimal Anda&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Konfigurasikan otentikasi OAuth2&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Buat halaman JBake pertama Anda yang mengkonsumsi API&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Deploy ke GitHub Pages&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Anda akan memiliki MVP yang fungsional di&amp;lt;strong&amp;gt;beberapa hari&amp;lt;/strong&amp;gt;.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Spring Boot akan datang secara alami ketika Anda membutuhkannya. Belum sebelum.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;hr&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Arsitektur hibrid adalah seni membangun tepat apa yang diperlukan, ketika diperlukan.&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;em&amp;gt;Semoga sukses dalam pengembangan! 🚀&amp;lt;/em&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;hr&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;em&amp;gt;Bagikan artikel ini jika Anda berpikir bahwa artikel ini dapat membantu pengembang lain untuk membuat pilihan arsitektur yang tepat.&amp;lt;/em&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>Seni Pra-muatan: Menghilangkan &#39;Flash&#39; Tema dengan JavaScript dan CSS</title>
            <link >https://cheroliv.com/blog/2025/0098_preloading_css_variable_post.html</link>
            <pubDate>Sat, 15 Nov 2025 00:00:00 +0000</pubDate>
            <guid isPermaLink="false">blog/2025/0098_preloading_css_variable_post.html</guid>
            <description>&amp;lt;div id=&amp;quot;toc&amp;quot; class=&amp;quot;toc&amp;quot;&amp;gt;
&amp;lt;div id=&amp;quot;toctitle&amp;quot;&amp;gt;Table des matières&amp;lt;/div&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel1&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_ringkasan&amp;quot;&amp;gt;Ringkasan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_masalah_kedipan_tema_yang_sial&amp;quot;&amp;gt;Masalah: Kedipan Tema yang Sial&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_sebuah_mimpi_buruk_untuk_pengalaman_pengguna&amp;quot;&amp;gt;Sebuah mimpi buruk untuk pengalaman pengguna&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_fouc_masalah_web_kuno&amp;quot;&amp;gt;FOUC: Masalah Web Kuno&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_dampak_pada_persepsi_kualitas&amp;quot;&amp;gt;Dampak pada Persepsi Kualitas&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_analisis_teknis_masalah&amp;quot;&amp;gt;Analisis Teknis Masalah&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_mengapa_script_klasik_tidak_cukup&amp;quot;&amp;gt;Mengapa script klasik tidak cukup?&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_pendekatan_intuitif_tetapi_tidak_efektif&amp;quot;&amp;gt;Pendekatan intuitif tetapi tidak efektif&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_memahami_acara_domcontentloaded&amp;quot;&amp;gt;Memahami Acara DOMContentLoaded&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_masalah_render_blocking&amp;quot;&amp;gt;Masalah Render Blocking&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_visualisasi_masalah&amp;quot;&amp;gt;Visualisasi Masalah&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_upaya_penyelesaian_yang_tidak_efektif&amp;quot;&amp;gt;Upaya Penyelesaian yang Tidak Efektif&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_solusi_yang_benar_bertindak_lebih_awal&amp;quot;&amp;gt;Solusi yang Benar : Bertindak Lebih Awal&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_solusi_pemuatan_dini_early_loading&amp;quot;&amp;gt;Solusi: Pemuatan Dini (Early Loading)&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_prinsip_dasar&amp;quot;&amp;gt;Prinsip Dasar&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_mengapa_head_adalah_tempat_yang_ideal&amp;quot;&amp;gt;Mengapa &amp;lt;code&amp;gt;&amp;amp;lt;head&amp;amp;gt;&amp;lt;/code&amp;gt; adalah tempat yang ideal&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_arsitektur_solusi_dalam_tiga_lapisan&amp;quot;&amp;gt;Arsitektur Solusi dalam Tiga Lapisan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_langkah_1_menyimpan_pilihan_pengguna&amp;quot;&amp;gt;Langkah 1: Menyimpan Pilihan Pengguna&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_localstorage_memori_persisten_anda&amp;quot;&amp;gt;localStorage : Memori Persisten Anda&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_implementasi_cadangan_tema&amp;quot;&amp;gt;Implementasi Cadangan Tema&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_manajemen_kasus_kesalahan&amp;quot;&amp;gt;Manajemen Kasus Kesalahan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_strategi_persistensi_lanjutan&amp;quot;&amp;gt;Strategi Persistensi Lanjutan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_langkah_2_skrip_preload_di_head&amp;quot;&amp;gt;Langkah 2: Skrip Preload di `&amp;amp;lt;head&amp;amp;gt;&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_inti_solusi&amp;quot;&amp;gt;Inti Solusi&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_anatomi_skrip_setiap_baris_penting&amp;quot;&amp;gt;Anatomi Skrip : Setiap Baris Penting&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_deteksi_preferensi_sistem_bonus&amp;quot;&amp;gt;Deteksi Preferensi Sistem (Bonus)&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_kinerja_mengapa_skrip_ini_cepat&amp;quot;&amp;gt;Kinerja : Mengapa skrip ini cepat&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_penempatan_optimal_di_head&amp;quot;&amp;gt;Penempatan optimal di `&amp;amp;lt;head&amp;amp;gt;&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_langkah_3_kekuatan_selektor_atribut_css&amp;quot;&amp;gt;Langkah 3: Kekuatan Selektor Atribut CSS&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_sistem_tema_bootstrap_5&amp;quot;&amp;gt;Sistem Tema Bootstrap 5&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_struktur_css_untuk_sebuah_sistem_tema&amp;quot;&amp;gt;Struktur CSS untuk sebuah Sistem Tema&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_ringkasan&amp;quot;&amp;gt;Ringkasan&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Dalam artikel teknis mendalam ini, kita mengeksplorasi cara mengimplementasikan pemilih tema (light/dark) tanpa efek berkedip yang tidak menyenangkan yang terjadi saat memuat halaman. Kami menganalisis secara detail siklus rendering browser, penyebab mendalam FOUC (Flash of Unstyled Content), dan menawarkan solusi kuat berbasis pre-chargement sinkron dalam`&amp;amp;lt;head&amp;amp;gt;`. Teknik ini menjamin pengalaman pengguna yang lancar dan profesional.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_masalah_kedipan_tema_yang_sial&amp;quot;&amp;gt;Masalah: Kedipan Tema yang Sial&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_sebuah_mimpi_buruk_untuk_pengalaman_pengguna&amp;quot;&amp;gt;Sebuah mimpi buruk untuk pengalaman pengguna&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Bayangkan adegan ini: Anda telah menghabiskan jam-jam untuk merancang tema gelap yang indah untuk aplikasi web Anda. Warna-warna seimbang sempurna, kontras optimal, dan pengguna Anda menyukai opsi ini. Tapi ada masalah memalukan: setiap kali halaman dimuat ulang, dalam sekejap, tema terang default muncul sebelum tema gelap menggantikannya.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Flash visual ini, meskipun singkat (kadang kurang dari 100ms), langsung terasa oleh mata manusia dan menciptakan pengalaman yang tidak menyenangkan. Untuk pengguna yang telah memilih tema gelap karena alasan kenyamanan visual atau aksesibilitas, kedipan ini bahkan dapat menyakitkan, terutama dalam lingkungan yang tidak terang.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_fouc_masalah_web_kuno&amp;quot;&amp;gt;FOUC: Masalah Web Kuno&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Fenomena ini adalah varian dari yang disebut &amp;quot;Flash of Unstyled Content&amp;quot; (FOUC), sebuah masalah klasik dalam pengembangan web yang bermula sejak awal era CSS. FOUC terjadi ketika peramban menampilkan sementara konten HTML tanpa gaya CSS yang diterapkan, menciptakan kilat konten yang tidak terdahului.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Dalam kasus spesifik kita, kita tidak berbicara tentang konten yang tidak diberi gaya sepenuhnya, melainkan tentang sebuah&amp;lt;strong&amp;gt;Kilat tema salah&amp;lt;/strong&amp;gt;(FOWT) - Konten tersebut telah diberi gaya, tetapi dengan tema yang salah. Ini sangat menyebalkan karena ini menunjukkan bahwa aplikasi kita &amp;quot;melupakan&amp;quot; preferensi pengguna setiap kali halaman dimuat.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_dampak_pada_persepsi_kualitas&amp;quot;&amp;gt;Dampak pada Persepsi Kualitas&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Masalah ini, meskipun bersifat teknis, memiliki dampak yang signifikan terhadap persepsi kualitas aplikasi Anda :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Kurang halus&amp;lt;/strong&amp;gt;: Kedipan memberi kesan bahwa aplikasi belum selesai atau tidak dioptimalkan dengan baik. Pengguna sering mengaitkan cacat visual kecil ini dengan kurangnya profesionalisme secara keseluruhan.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Kehilangan koherensi&amp;lt;/strong&amp;gt;Aplikasi sepertinya &amp;quot;melupakan&amp;quot; preferensi pengguna, menciptakan perasaan tidak sinkron antara antarmuka dan harapan.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Kelelahan visual&amp;lt;/strong&amp;gt;Untuk pengguna yang sensitif terhadap cahaya atau yang mengalami migrain, cahaya kilat ini bisa menjadi lebih dari sekadar gangguan estetika.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Kinerja yang dirasakan&amp;lt;/strong&amp;gt;Ironis, meski situs Anda dimuat dengan cepat, ketipukan ini dapat memberi kesan aplikasi lambat atau kurang responsif.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_analisis_teknis_masalah&amp;quot;&amp;gt;Analisis Teknis Masalah&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Untuk memahami cara menyelesaikan masalah ini, pertama-tama perlu memahami mengapa hal tersebut terjadi. Kedipan terjadi karena adanya keterlambatan waktu antara tiga peristiwa kritikal dalam siklus hidup sebuah halaman web :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;olist arabic&amp;quot;&amp;gt;
&amp;lt;ol class=&amp;quot;arabic&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Parsing awal dari HTML&amp;lt;/strong&amp;gt;Peramban membaca dan menganalisis struktur halaman Anda.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Penerapan gaya CSS&amp;lt;/strong&amp;gt;Peramban menerapkan aturan CSS dan menghitung rendering visual&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Eksekusi JavaScript&amp;lt;/strong&amp;gt;: Kode Anda yang mengubah tema berjalan&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Masalah terjadi ketika peristiwa no.3 (eksekusi JavaScript) tiba setelah browser sudah memulai atau menyelesaikan peristiwa no.2 (penerapan gaya). Saat itu, browser sudah membuat keputusan tentang tema mana yang akan ditampilkan, dan kode Anda terlambat untuk memengaruhinya sebelum rendering pertama.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_mengapa_script_klasik_tidak_cukup&amp;quot;&amp;gt;Mengapa script klasik tidak cukup?&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_pendekatan_intuitif_tetapi_tidak_efektif&amp;quot;&amp;gt;Pendekatan intuitif tetapi tidak efektif&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Pendekatan paling natural untuk seorang pengembang akan menempatkan skrip di akhir dari kita.`&amp;amp;lt;body&amp;amp;gt;`yang memeriksa tema favorit pengguna dan menerapkannya. Pendekatan ini mengikuti praktik terbaik tradisional web yang merekomendasikan untuk memuat skrip di akhir halaman agar tidak memblokir rendering.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-javascript hljs&amp;quot; data-lang=&amp;quot;javascript&amp;quot;&amp;gt;// À la fin de &amp;amp;lt;body&amp;amp;gt; - L&amp;#39;APPROCHE INSUFFISANTE
document.addEventListener(&amp;#39;DOMContentLoaded&amp;#39;, () =&amp;amp;gt; {
  const theme = localStorage.getItem(&amp;#39;preferred-theme&amp;#39;);
  if (theme === &amp;#39;dark&amp;#39;) {
    document.documentElement.setAttribute(&amp;#39;data-bs-theme&amp;#39;, &amp;#39;dark&amp;#39;);
  }
});&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Pendekatan ini tampak logis pada pandangan pertama. Kami menunggu hingga DOM siap, lalu kami menerapkan tema. Sederhana, bukan? Sayangnya, kesederhanaan ini menyembunyikan cacat fundamental yang terkait dengan timing siklus rendering peramban.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_memahami_acara_domcontentloaded&amp;quot;&amp;gt;Memahami Acara DOMContentLoaded&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Kejadian`DOMContentLoaded`terpicu ketika dokumen HTML awal telah sepenuhnya dimuat dan dianalisis oleh peramban,&amp;lt;strong&amp;gt;tanpa menunggu&amp;lt;/strong&amp;gt;akhir pemuatan stylesheets, gambar, dan subframe. Ini adalah poin penting untuk dipahami.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Berikut adalah urutan tipikal peristiwa:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;olist arabic&amp;quot;&amp;gt;
&amp;lt;ol class=&amp;quot;arabic&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Peramban mulai mengunduh HTML&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Dia menganalisis HTML seiring diterimaannya&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Dia menemukan tag`&amp;amp;lt;link&amp;amp;gt;`untuk CSS dan mulai mengunduhnya&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Dia menemukan tag`&amp;amp;lt;script&amp;amp;gt;`dan mengeksekusi mereka (sesuai tipe dan atribut mereka)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Dia membangun DOM (Document Object Model)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Peristiwa &amp;lt;code&amp;gt;DOMContentLoaded&amp;lt;/code&amp;gt; terpicu&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Dia terus menerapkan gaya dan melakukan layout&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Pengecatan pertama (tampilan) terjadi&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Acara`load`terjadi ketika semua resource telah dimuat&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Masalah? Di antara langkah 6 (DOMContentLoaded) dan langkah 8 (paint pertama), peramban telah membuat keputusan tentang cara menampilkan halaman. Jika skrip perubahan tema Anda berjalan di langkah 6, sudah terlambat untuk menghindari tampilan pertama dengan gaya default.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_masalah_render_blocking&amp;quot;&amp;gt;Masalah Render Blocking&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Sebenarnya, timing-nya masih lebih kompleks. Penyelancar modern menggunakan teknik optimisasi yang canggih untuk meningkatkan performa yang dirasakan. Mereka mencoba membuat first paint (First Contentful Paint) secepat mungkin agar pengguna melihat sesuatu di layar.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;CSS secara default bersifat &amp;quot;render-blocking&amp;quot;, artinya browser menunggu sampai stylesheet terunduh dan diparse sebelum melakukan first paint. Itu masuk akal: kita tidak ingin menampilkan konten yang belum diberi gaya.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Tapi ini adalah jebakannya: ketika peramban menerapkan gaya CSS ini untuk pertama kalinya, ia melakukannya berdasarkan keadaan saat ini dari DOM. Jika atribut`data-bs-theme`belum didefinisikan pada tag`&amp;amp;lt;html&amp;amp;gt;`, perambar akan menerapkan gaya default (biasanya tema terang).&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Kemudian, ketika skrip Anda berjalan dan mengubah atribut ini, peramban harus:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;olist arabic&amp;quot;&amp;gt;
&amp;lt;ol class=&amp;quot;arabic&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Menghitung ulang semua gaya yang dipengaruhi oleh perubahan ini&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Buat ulang tata letak jika diperlukan&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Mengulang cat elemen yang terpengaruh&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Proses penghitungan ulang dan penarikan ulang ini yang menyebabkan kedipan yang terlihat.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_visualisasi_masalah&amp;quot;&amp;gt;Visualisasi Masalah&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Untuk lebih memahami urutan masalah ini, mari kita periksa diagram urutan terperinci:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div id=&amp;quot;diag-flux-incorrect&amp;quot; class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
actor Utilisateur
participant Navigateur
participant &amp;quot;Parser HTML&amp;quot; as Parser
participant &amp;quot;CSSOM&amp;quot; as CSS
participant &amp;quot;Script.js (akhir body)&amp;quot; as Script
participant &amp;quot;Mesin render&amp;quot; as Render

Utilisateur -&amp;amp;gt; Navigateur: Demande la page
activate Navigateur

Navigateur -&amp;amp;gt; Parser: Parse le HTML
activate Parser
Parser -&amp;amp;gt; CSS: Découvre et charge les CSS
activate CSS
Parser -&amp;amp;gt; Parser: Construit le DOM
Parser -&amp;amp;gt; Navigateur: DOM Ready (DOMContentLoaded)
deactivate Parser

CSS -&amp;amp;gt; CSS: Parse les feuilles de style
CSS -&amp;amp;gt; Render: CSSOM Ready
deactivate CSS

activate Render
Render -&amp;amp;gt; Render: Calcule les styles avec data-bs-theme=&amp;quot;&amp;quot;note right: Tema default (light)
Render -&amp;amp;gt; Navigateur: Premier Paint (Tema Terang)
note right: Pengguna melihat tema terang
deactivate Render

Navigateur -&amp;amp;gt; Script: Menjalankan skrip
activate Script
Script -&amp;amp;gt; Script: Membaca localStorage
Script -&amp;amp;gt; Navigateur: setAttribute(&amp;#39;data-bs-theme&amp;#39;, &amp;#39;dark&amp;#39;)
deactivate Script

activate Render
Render -&amp;amp;gt; Render: Menghitung ulang gaya dengan data-bs-theme=&amp;quot;dark&amp;quot;
note right: Thème sombre
Render -&amp;amp;gt; Navigateur: Repaint (Thème Sombre)
note right: Clignotement visible
deactivate Render

deactivate Navigateur
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Diagram ini menggambarkan dengan jelas masalahnya: pertama kali paint terjadi sebelum skrip kami berempatan untuk menentukan tema yang tepat. Repaint yang kemudian menghasilkan berkedip yang terlihat.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_upaya_penyelesaian_yang_tidak_efektif&amp;quot;&amp;gt;Upaya Penyelesaian yang Tidak Efektif&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Berbagai pendekatan telah dicoba untuk menyelesaikan masalah ini, tetapi kebanyakan memiliki kelemahan masing-masing :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Pendekatan 1: Menyembunyikan konten sampai pemuatan selesai&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-css hljs&amp;quot; data-lang=&amp;quot;css&amp;quot;&amp;gt;body {
  opacity: 0;
  transition: opacity 0.3s;
}

body.loaded {
  opacity: 1;
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Pendekatan ini menyembunyikan semua konten sampai JavaScript menentukan tema yang tepat. Masalah? Ini menunda secara buatan penampilan konten, memberi kesan situs lebih lambat. Selain itu, jika JavaScript dinonaktifkan, pengguna tidak akan melihat apa-apa sama sekali!&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Pendekatan 2 : Menggunakan loader/spinner&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Serupa dengan pendekatan 1, tetapi dengan spinner pemuat. Ini menyembunyikan masalah tetapi tidak meningkatkan performa aktual dan menambahkan penundaan yang dirasa tidak perlu.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Pendekatan 3 : Default ke tema gelap&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Beberapa developer mengatur tema gelap sebagai default dalam CSS. Ini mencegah kedipan bagi pengguna tema gelap, tetapi menciptakan masalah yang berlawanan bagi pengguna tema terang!&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Tidak ada pendekatan ini yang memuaskan karena mereka mengatasi gejala alih-alih akar masalah.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_solusi_yang_benar_bertindak_lebih_awal&amp;quot;&amp;gt;Solusi yang Benar : Bertindak Lebih Awal&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Kunci untuk menyelesaikan masalah ini adalah menyadari bahwa kita harus menentukan atribut`data-bs-theme` &amp;lt;strong&amp;gt;sebelum&amp;lt;/strong&amp;gt;bahwa peramban belum mulai menerapkan gaya CSS. Ini berarti bahwa skrip kami harus dieksekusi lebih awal dalam siklus hidup halaman, dan ini persis apa yang akan kita jelajahi di bagian berikutnya.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_solusi_pemuatan_dini_early_loading&amp;quot;&amp;gt;Solusi: Pemuatan Dini (Early Loading)&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_prinsip_dasar&amp;quot;&amp;gt;Prinsip Dasar&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Solusi elegan untuk masalah berkedip kita didasarkan pada prinsip yang sederhana tetapi kuat:&amp;lt;strong&amp;gt;Mengsinkronkan status aplikasi dengan proses rendering peramban&amp;lt;/strong&amp;gt;. Sebagai gantinya menunggu hingga halaman dimuat untuk menentukan tema, kita harus menetapkannya&amp;lt;strong&amp;gt;saat&amp;lt;/strong&amp;gt;pemuatan, bahkan sebelum gaya CSS diterapkan.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Pendekatan ini disebut &amp;quot;Early Loading&amp;quot; atau &amp;quot;Synchronous Preloading&amp;quot; dalam jargon pengembangan web. Ideanya adalah menjalankan logika deteksi tema kita secepat mungkin dalam siklus hidup halaman, idealnya dalam tag`&amp;amp;lt;head&amp;amp;gt;`, bahkan sebelum peramban mulai mengunduh file CSS.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_mengapa_head_adalah_tempat_yang_ideal&amp;quot;&amp;gt;Mengapa &amp;lt;code&amp;gt;&amp;amp;lt;head&amp;amp;gt;&amp;lt;/code&amp;gt; adalah tempat yang ideal&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Le `&amp;amp;lt;head&amp;amp;gt;`Sebuah elemen dari dokumen HTML diproses secara berurutan oleh peramban, dari atas ke bawah. Setiap elemen diproses dalam urutan tempat ia muncul. Karakteristik ini krusial untuk solusi kita.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Ketika peramban menemui sebuah tag`&amp;amp;lt;script&amp;amp;gt;`dalam`&amp;amp;lt;head&amp;amp;gt;`tanpa atribut-atribut`async` ou &amp;lt;code&amp;gt;defer&amp;lt;/code&amp;gt;, il :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;olist arabic&amp;quot;&amp;gt;
&amp;lt;ol class=&amp;quot;arabic&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Menginterupsi parsing HTML&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Unduh skrip&amp;lt;/strong&amp;gt;(jika eksternal) atau tempat tidur (jika inline)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Jalankan skrip segera&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Melanjutkan parsing HTML&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Perilaku ini, yang sering dianggap sebagai masalah performa (sebab itu rekomendasi biasa adalah meletakkan script di akhir halaman), menjadi sekutu kita dalam kasus spesifik ini. Dengan menempatkan script deteksi tema kita di awal du`&amp;amp;lt;head&amp;amp;gt;`, kami menjamin bahwa dieksekusi sebelum browser bertemu dengan tag`&amp;amp;lt;link&amp;amp;gt;`dari lembar gaya kami.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_arsitektur_solusi_dalam_tiga_lapisan&amp;quot;&amp;gt;Arsitektur Solusi dalam Tiga Lapisan&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Solusi lengkap kami terdiri dari tiga lapisan yang saling terkait, masing-masing berperan secara spesifik :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Lapisan 1 : Persistensi (localStorage)&amp;lt;/strong&amp;gt; Lapisan ini bertanggung jawab atas penyimpanan dan pemulihan pilihan pengguna antar sesi&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Lapisan 2 : Sinkronisasi Dini (script inline dalam &amp;lt;code&amp;gt;&amp;amp;lt;head&amp;amp;gt;&amp;lt;/code&amp;gt;)&amp;lt;/strong&amp;gt; Lapisan ini mensinkronkan keadaan aplikasi dengan DOM sebelum rendering awal&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Lapisan 3 : Gaya Responsif (CSS dengan selektor atribut)&amp;lt;/strong&amp;gt; Lapisan ini menentukan gaya visual berdasarkan keadaan yang ditentukan oleh lapisan 2.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Mari kita jelajahi setiap lapisan secara detail sekarang&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_langkah_1_menyimpan_pilihan_pengguna&amp;quot;&amp;gt;Langkah 1: Menyimpan Pilihan Pengguna&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_localstorage_memori_persisten_anda&amp;quot;&amp;gt;localStorage : Memori Persisten Anda&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Le &amp;lt;code&amp;gt;localStorage`adalah API Web Storage yang memungkinkan penyimpanan pasangan kunci-nilai dalam browser secara persisten. Berbeda dengan cookies, data`localStorage&amp;lt;/code&amp;gt;:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Tidak pernah dikirim ke server secara otomatis&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Mempunyai kapasitas penyimpanan yang lebih besar (biasanya 5-10 MB)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Tidak memiliki tanggal kedaluwarsa (bertahan sampai dihapus secara eksplisit)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Dibatasi pada protokol dan domain (Same-Origin Policy)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Untuk kasus penggunaan kita, yang`localStorage`adalah sempurna karena :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;olist arabic&amp;quot;&amp;gt;
&amp;lt;ol class=&amp;quot;arabic&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Kami tidak perlu berbagi informasi ini dengan server&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Kami ingin preferensi tersebut tetap bertahan selamanya&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Ukuran penyimpanan yang diperlukan sangat kecil (beberapa byte)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_implementasi_cadangan_tema&amp;quot;&amp;gt;Implementasi Cadangan Tema&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Berikut cara kami menyimpan pilihan pengguna saat ia mengubah tema :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-javascript hljs&amp;quot; data-lang=&amp;quot;javascript&amp;quot;&amp;gt;// Fonction complète pour changer le thème
function setTheme(newTheme) {
  // Validation de l&amp;#39;entrée
  if (![&amp;#39;light&amp;#39;, &amp;#39;dark&amp;#39;, &amp;#39;auto&amp;#39;].includes(newTheme)) {
    console.error(&amp;#39;Thème invalide:&amp;#39;, newTheme);
    return;
  }

  try {
    // Sauvegarde dans localStorage
    localStorage.setItem(&amp;#39;preferred-theme&amp;#39;, newTheme);

    // Application immédiate dans le DOM
    document.documentElement.setAttribute(&amp;#39;data-bs-theme&amp;#39;, newTheme);

    // Dispatch d&amp;#39;un événement personnalisé pour notifier d&amp;#39;autres composants
    window.dispatchEvent(new CustomEvent(&amp;#39;theme-changed&amp;#39;, {
      detail: { theme: newTheme }
    }));

    console.log(&amp;#39;Thème changé:&amp;#39;, newTheme);
  } catch (error) {
    console.error(&amp;#39;Erreur lors de la sauvegarde du thème:&amp;#39;, error);
    // Fallback : on applique quand même le thème visuellement
    document.documentElement.setAttribute(&amp;#39;data-bs-theme&amp;#39;, newTheme);
  }
}

// Exemple d&amp;#39;utilisation avec un bouton
document.getElementById(&amp;#39;theme-toggle&amp;#39;).addEventListener(&amp;#39;click&amp;#39;, () =&amp;amp;gt; {
  const currentTheme = document.documentElement.getAttribute(&amp;#39;data-bs-theme&amp;#39;) || &amp;#39;light&amp;#39;;
  const newTheme = currentTheme === &amp;#39;light&amp;#39; ? &amp;#39;dark&amp;#39; : &amp;#39;light&amp;#39;;
  setTheme(newTheme);
});&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_manajemen_kasus_kesalahan&amp;quot;&amp;gt;Manajemen Kasus Kesalahan&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Penting untuk mengelola kasus di mana`localStorage`Tidak tersedia atau tidak dapat diakses. Beberapa skenario dapat mencegah akses ke`localStorage` :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Navigasi pribadi ketat&amp;lt;/strong&amp;gt;Safari dalam mode penelusuran pribadi menimbulkan pengecualian`QuotaExceededError`saat percobaan menulis dalam`localStorage`.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Pengaturan privasi&amp;lt;/strong&amp;gt;: Beberapa peramban atau ekstensi privasi dapat memblokir akses ke`localStorage`.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Keterbatasan domain&amp;lt;/strong&amp;gt; : Le `localStorage`tidak dapat diakses pada protokol`file://`di beberapa peramban.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Ruang penyimpanan penuh&amp;lt;/strong&amp;gt;Meskipun jarang, ruang penyimpanan dapat sepenuhnya terisi.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Karena itu kode kita menggunakan sebuah blok`try&amp;amp;#8230;&amp;amp;#8203;catch`Untuk menangani kasus-kasus ini dengan anggun, sambil terus menawarkan fungsionalitas perubahan tema meskipun persistensi tidak tersedia.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_strategi_persistensi_lanjutan&amp;quot;&amp;gt;Strategi Persistensi Lanjutan&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Untuk aplikasi yang lebih canggih, Anda dapat mempertimbangkan strategi tambahan:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Sinkronisasi server (opsional)&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-javascript hljs&amp;quot; data-lang=&amp;quot;javascript&amp;quot;&amp;gt;async function setTheme(newTheme) {
  // Sauvegarde locale immédiate
  localStorage.setItem(&amp;#39;preferred-theme&amp;#39;, newTheme);
  document.documentElement.setAttribute(&amp;#39;data-bs-theme&amp;#39;, newTheme);

  // Synchronisation serveur en arrière-plan (si l&amp;#39;utilisateur est connecté)
  if (userIsAuthenticated()) {
    try {
      await fetch(&amp;#39;/api/user/preferences&amp;#39;, {
        method: &amp;#39;POST&amp;#39;,
        headers: { &amp;#39;Content-Type&amp;#39;: &amp;#39;application/json&amp;#39; },
        body: JSON.stringify({ theme: newTheme })
      });
    } catch (error) {
      console.warn(&amp;#39;Échec de la synchronisation serveur:&amp;#39;, error);
      // L&amp;#39;échec n&amp;#39;est pas critique car la préférence est déjà sauvegardée localement
    }
  }
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Pendekatan ini memungkinkan sinkronisasi preferensi antar perangkat untuk pengguna yang terhubung, sambil tetap menjaga responsivitas lokal yang langsung.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_langkah_2_skrip_preload_di_head&amp;quot;&amp;gt;Langkah 2: Skrip Preload di `&amp;amp;lt;head&amp;amp;gt;&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_inti_solusi&amp;quot;&amp;gt;Inti Solusi&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Ini adalah tempat di mana sihir tersebut benar-benar berfungsi. Kami akan menempatkan sebuah skrip kecil.&amp;lt;strong&amp;gt;secara langsung&amp;lt;/strong&amp;gt;langsung di dalam kita`&amp;amp;lt;head&amp;amp;gt;`, sebelum semua tag kami`&amp;amp;lt;link&amp;amp;gt;`de lembar gaya. Skrip ini sengaja dibuat minimalis, mandiri, dan dirancang untuk berjalan secepat mungkin.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-html hljs&amp;quot; data-lang=&amp;quot;html&amp;quot;&amp;gt;&amp;amp;lt;!DOCTYPE html&amp;amp;gt;
&amp;amp;lt;html lang=&amp;quot;fr&amp;quot;&amp;amp;gt;
&amp;amp;lt;head&amp;amp;gt;
    &amp;amp;lt;meta charset=&amp;quot;UTF-8&amp;quot;&amp;amp;gt;
    &amp;amp;lt;meta name=&amp;quot;viewport&amp;quot; content=&amp;quot;width=device-width, initial-scale=1.0&amp;quot;&amp;amp;gt;
    &amp;amp;lt;title&amp;amp;gt;Mon Site Incroyable&amp;amp;lt;/title&amp;amp;gt;

    &amp;amp;lt;!-- ==========================================
         NOTRE SCRIPT MAGIQUE DE PRÉ-CHARGEMENT
         Ce script DOIT être le premier élément
         dans le &amp;amp;lt;head&amp;amp;gt; après les meta tags
         ========================================== --&amp;amp;gt;
    &amp;amp;lt;script&amp;amp;gt;
      // IIFE pour ne pas polluer le scope global
      (function() {
        &amp;#39;use strict&amp;#39;;

        try {
          // Lecture de la préférence sauvegardée
          const savedTheme = localStorage.getItem(&amp;#39;preferred-theme&amp;#39;);

          // Si une préférence existe, on l&amp;#39;applique immédiatement
          if (savedTheme) {
            document.documentElement.setAttribute(&amp;#39;data-bs-theme&amp;#39;, savedTheme);
          }
          // Optionnel : Détecter la préférence système si aucune sauvegarde
          else if (window.matchMedia &amp;amp;amp;&amp;amp;amp; window.matchMedia(&amp;#39;(prefers-color-scheme: dark)&amp;#39;).matches) {
            document.documentElement.setAttribute(&amp;#39;data-bs-theme&amp;#39;, &amp;#39;dark&amp;#39;);
          }
          // Sinon, le thème par défaut du CSS sera utilisé (généralement &amp;#39;light&amp;#39;)

        } catch (error) {
          // En cas d&amp;#39;erreur (localStorage bloqué, etc.), on log discrètement
          // et on laisse le thème par défaut s&amp;#39;appliquer
          console.warn(&amp;#39;Impossible de charger la préférence de thème:&amp;#39;, error);
        }
      })();
    &amp;amp;lt;/script&amp;amp;gt;
    &amp;amp;lt;!-- FIN DU SCRIPT MAGIQUE --&amp;amp;gt;

    &amp;amp;lt;!-- Les feuilles de style sont chargées APRÈS le script --&amp;amp;gt;
    &amp;amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;css/bootstrap.min.css&amp;quot;&amp;amp;gt;
    &amp;amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;css/styles.css&amp;quot;&amp;amp;gt;

    &amp;amp;lt;!-- Autres ressources du head --&amp;amp;gt;
    &amp;amp;lt;link rel=&amp;quot;icon&amp;quot; href=&amp;quot;favicon.ico&amp;quot;&amp;amp;gt;
&amp;amp;lt;/head&amp;amp;gt;
&amp;amp;lt;body&amp;amp;gt;
    &amp;amp;lt;!-- Contenu de la page --&amp;amp;gt;
&amp;amp;lt;/body&amp;amp;gt;
&amp;amp;lt;/html&amp;amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_anatomi_skrip_setiap_baris_penting&amp;quot;&amp;gt;Anatomi Skrip : Setiap Baris Penting&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Mari kita pecah skrip ini baris demi baris untuk memahami setiap keputusan desain:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;IIFE (fungsi yang segera dipanggil)&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-javascript hljs&amp;quot; data-lang=&amp;quot;javascript&amp;quot;&amp;gt;(function() {
  // ...
})();&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Struktur ini membuat sebuah fungsi yang dieksekusi segera. Mengapa? Untuk mengisolasi variabel kita dalam cakupan lokal dan menghindari pencemaran cakupan global. Meskipun kita hanya menggunakan`const`(yang memiliki scope blok), IIFE adalah praktik yang baik yang membuat niat kita jelas dan melindungi dari konflik nama yang mungkin.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Mode Ketat&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-javascript hljs&amp;quot; data-lang=&amp;quot;javascript&amp;quot;&amp;gt;&amp;#39;use strict&amp;#39;;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Directive ini mengaktifkan mode ketat JavaScript, yang : - Melarang penggunaan variabel yang tidak dideklarasikan - Menghasilkan kesalahan untuk operasi berbahaya - Meningkatkan performa di beberapa mesin JavaScript&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Untuk skrip kritis seperti ini, kami ingin keamanan maksimal.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Blok try&amp;amp;#8230;&amp;amp;#8203;catch&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-javascript hljs&amp;quot; data-lang=&amp;quot;javascript&amp;quot;&amp;gt;try {
  // Code principal
} catch (error) {
  console.warn(&amp;#39;Impossible de charger la préférence de thème:&amp;#39;, error);
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Blok ini sangat penting. Ia menjamin bahwa jika terjadi sesuatu yang salah (localStorage terblokir, kesalahan sintaks improbable, dsb.), skrip kita tidak akan menghambat pemuatan seluruh halaman. Penggunaan`console.warn`daripada`console.error`mengindikasikan bahwa ini bukan masalah kritis&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Pembacaan localStorage&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-javascript hljs&amp;quot; data-lang=&amp;quot;javascript&amp;quot;&amp;gt;const savedTheme = localStorage.getItem(&amp;#39;preferred-theme&amp;#39;);&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Baris ini dapat menimbulkan pengecualian dalam beberapa konteks (privatisasi Safari yang ketat). Itu sebabnya ia berada dalam blok try&amp;amp;#8230;&amp;amp;#8203;catch.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Aplikasi bersyarat&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-javascript hljs&amp;quot; data-lang=&amp;quot;javascript&amp;quot;&amp;gt;if (savedTheme) {
  document.documentElement.setAttribute(&amp;#39;data-bs-theme&amp;#39;, savedTheme);
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Kami menerapkan tema hanya jika kami menemukan tema yang telah disimpan. Jika tidak, kita membiarkan CSS menggunakan tema default-nya. Pendekatan ini lebih robust daripada nilai default yang di-hardcode dalam JavaScript.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_deteksi_preferensi_sistem_bonus&amp;quot;&amp;gt;Deteksi Preferensi Sistem (Bonus)&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Penambahan yang opsional namun elegan adalah mendeteksi preferensi tema sistem operasi pengguna jika mereka belum membuat pilihan eksplisit dalam aplikasi Anda :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-javascript hljs&amp;quot; data-lang=&amp;quot;javascript&amp;quot;&amp;gt;else if (window.matchMedia &amp;amp;amp;&amp;amp;amp; window.matchMedia(&amp;#39;(prefers-color-scheme: dark)&amp;#39;).matches) {
  document.documentElement.setAttribute(&amp;#39;data-bs-theme&amp;#39;, &amp;#39;dark&amp;#39;);
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Fitur ini menggunakan Media Query`prefers-color-scheme`untuk menanyakan sistem. Pada macOS, Windows 10+, iOS, dan Android modern, permintaan ini mengembalikan preferensi sistem pengguna.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Keuntungan :&amp;lt;/strong&amp;gt; - Pengalaman yang dipersonalisasi sejak pertama kali berkunjung - Konsistensi dengan lingkungan sistem pengguna - Tidak perlu penyimpanan untuk kunjungan pertama&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Pertimbangan:&amp;lt;/strong&amp;gt; - Tidak semua browser mendukung fitur ini (tapi dukungannya sangat baik sejak 2020) - Verifikasi`window.matchMedia`memastikan kompatibilitas - Pengguna selalu dapat mengganti pilihan ini&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_kinerja_mengapa_skrip_ini_cepat&amp;quot;&amp;gt;Kinerja : Mengapa skrip ini cepat&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Skrip pre-load kami dirancang untuk sangat cepat:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Ukuran minimum&amp;lt;/strong&amp;gt;Kira-kira 300 byte yang tidak dipersempit, 200 byte yang dipersempit. Itu dianggap tidak signifikan dibandingkan dengan gambar atau pustaka JavaScript apa pun.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;secara langsung&amp;lt;/strong&amp;gt;Tidak ada permintaan HTTP tambahan. Skrip ada di dalam HTML, sehingga ia langsung tersedia.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Operasi sinkron sederhana&amp;lt;/strong&amp;gt;: Membaca sebuah kunci dalam localStorage (operasi sangat cepat) dan modifikasi sebuah atribut DOM (operasi bawaan peramban).&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Tidak ada ketergantungan&amp;lt;/strong&amp;gt;: Tidak ada framework, tidak ada perpustakaan, hanya JavaScript vanilla. Tidak ada waktu pemuatan, tidak ada parsing dependensi.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Eksekusi tunggal&amp;lt;/strong&amp;gt;: Skrip ini dijalankan hanya sekali saat dimuat. Tidak ada listener peristiwa, tidak ada perulangan, tidak ada perhitungan kompleks.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Dalam praktiknya, pada perangkat modern, skrip ini berjalan dalam kurang dari 1 milidetik, waktu yang tidak terasa yang tidak memiliki dampak apa pun pada kinerja pemuatan halaman.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_penempatan_optimal_di_head&amp;quot;&amp;gt;Penempatan optimal di `&amp;amp;lt;head&amp;amp;gt;&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Urutan elemen di dalam`&amp;amp;lt;head&amp;amp;gt;`penting. Berikut urutan yang disarankan :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-html hljs&amp;quot; data-lang=&amp;quot;html&amp;quot;&amp;gt;&amp;amp;lt;head&amp;amp;gt;
    &amp;amp;lt;!-- 1. Métadonnées critiques --&amp;amp;gt;
    &amp;amp;lt;meta charset=&amp;quot;UTF-8&amp;quot;&amp;amp;gt;
    &amp;amp;lt;meta name=&amp;quot;viewport&amp;quot; content=&amp;quot;width=device-width, initial-scale=1.0&amp;quot;&amp;amp;gt;

    &amp;amp;lt;!-- 2. Notre script de pré-chargement (IMMÉDIATEMENT après les meta) --&amp;amp;gt;
    &amp;amp;lt;script&amp;amp;gt;
      (function() { /* notre code */ })();
    &amp;amp;lt;/script&amp;amp;gt;

    &amp;amp;lt;!-- 3. Titre de la page --&amp;amp;gt;
    &amp;amp;lt;title&amp;amp;gt;Mon Site&amp;amp;lt;/title&amp;amp;gt;

    &amp;amp;lt;!-- 4. Feuilles de style --&amp;amp;gt;
    &amp;amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;styles.css&amp;quot;&amp;amp;gt;

    &amp;amp;lt;!-- 5. Autres ressources (fonts, favicons, etc.) --&amp;amp;gt;
    &amp;amp;lt;link rel=&amp;quot;preconnect&amp;quot; href=&amp;quot;https://fonts.googleapis.com&amp;quot;&amp;amp;gt;
    &amp;amp;lt;link rel=&amp;quot;icon&amp;quot; href=&amp;quot;favicon.ico&amp;quot;&amp;amp;gt;

    &amp;amp;lt;!-- 6. Autres scripts avec defer ou async --&amp;amp;gt;
    &amp;amp;lt;script src=&amp;quot;app.js&amp;quot; defer&amp;amp;gt;&amp;amp;lt;/script&amp;amp;gt;
&amp;amp;lt;/head&amp;amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Perintah ini memastikan bahwa :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;olist arabic&amp;quot;&amp;gt;
&amp;lt;ol class=&amp;quot;arabic&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Charset ditentukan sebelum apa pun pemrosesan teks&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Skrip kita dieksekusi sebelum CSS dimuat&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;CSS kemudian dimuat dan langsung menerapkan tema yang tepat&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Sumber daya non-kritis lainnya dimuat terakhir&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_langkah_3_kekuatan_selektor_atribut_css&amp;quot;&amp;gt;Langkah 3: Kekuatan Selektor Atribut CSS&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_sistem_tema_bootstrap_5&amp;quot;&amp;gt;Sistem Tema Bootstrap 5&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Bootstrap 5 telah memperkenalkan sistem tema yang elegan berbasis custom properties CSS (variabel CSS) dan selector atribut. Sistem ini menggunakan atribut`data-bs-theme`pada elemen`&amp;amp;lt;html&amp;amp;gt;`untuk menentukan kumpulan variabel warna yang akan diterapkan.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Keindahan sistem ini terletak pada kesederhanaannya: alih-alih memuat berbagai stylesheet atau mengalihkan kelas pada ribuan elemen, kita hanya mengubah satu atribut pada satu elemen, dan CSS melakukan sisa dengan menggunakan kaskad.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_struktur_css_untuk_sebuah_sistem_tema&amp;quot;&amp;gt;Struktur CSS untuk sebuah Sistem Tema&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Berikut adalah struktur CSS lengkap untuk mengimplementasikan sistem tema yang kuat :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-css hljs&amp;quot; data-lang=&amp;quot;css&amp;quot;&amp;gt;/**
 * SYSTÈME DE THÈME COMPLET
 * Utilise les Custom Properties CSS pour une maintenance facile
 */

/* ============================================
   THÈME PAR DÉFAUT (LIGHT)
   Défini sur :root pour être le fallback
   ============================================ */
:root {
  /* Couleurs de base */
  --color-primary: #0d6efd;
  --color-secondary: #6c757d;
  --color-success: #198754;
  --color-danger: #dc3545;
  --color-warning: #ffc107;
  --color-info: #0dcaf0;

  /* Couleurs de fond */
  --bg-primary: #ffffff;
  --bg-secondary: #f8f9fa;
  --bg-tertiary: #e9ecef;

  /* Couleurs de texte */
  --text-primary: #212529;
  --text-secondary: #6c757d;
  --text-tertiary: #adb5bd;

  /* Couleurs de bordure */
  --border-color: #dee2e6;
  --border-color-subtle: #e9ecef;

  /* Couleurs d&amp;#39;ombre */
  --shadow-sm: rgba(0, 0, 0, 0.075);
  --shadow-md: rgba(0, 0, 0, 0.15);
  --shadow-lg: rgba(0, 0, 0, 0.25&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>Logging Efektif dalam Pengujian Fungsional Plugin Gradle</title>
            <link >https://cheroliv.com/blog/2025/0097_gradle_plugin_dev_func_test_logger_post.html</link>
            <pubDate>Fri, 7 Nov 2025 00:00:00 +0000</pubDate>
            <guid isPermaLink="false">blog/2025/0097_gradle_plugin_dev_func_test_logger_post.html</guid>
            <description>&amp;lt;div id=&amp;quot;toc&amp;quot; class=&amp;quot;toc&amp;quot;&amp;gt;
&amp;lt;div id=&amp;quot;toctitle&amp;quot;&amp;gt;Table of Contents&amp;lt;/div&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel1&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_pendahuluan&amp;quot;&amp;gt;Pendahuluan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_masalah_dependensi_pada_gradlerunner&amp;quot;&amp;gt;Masalah: Dependensi pada GradleRunner&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_konteks_pengembangan_plugin&amp;quot;&amp;gt;Konteks Pengembangan Plugin&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_dilemma_logging&amp;quot;&amp;gt;Dilemma Logging&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_ide_ide_yang_salah&amp;quot;&amp;gt;Ide-ide yang salah&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_solusi_logger_independen&amp;quot;&amp;gt;Solusi : Logger Independen&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_arsitektur_yang_disarankan&amp;quot;&amp;gt;Arsitektur yang disarankan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_alur_eksekusi&amp;quot;&amp;gt;Alur eksekusi&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_implementasi_lengkap&amp;quot;&amp;gt;Implementasi lengkap&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_dependensi_gradle&amp;quot;&amp;gt;Dependensi Gradle&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_konfigurasi_logback&amp;quot;&amp;gt;Konfigurasi Logback&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_kelas_test_lengkap&amp;quot;&amp;gt;Kelas Test Lengkap&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_skema_logging_multi_tingkat&amp;quot;&amp;gt;Skema Logging Multi-Tingkat&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_praktik_terbaik&amp;quot;&amp;gt;Praktik Terbaik&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_struktur_logging_yang_direkomendasikan&amp;quot;&amp;gt;Struktur logging yang direkomendasikan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_simbol_visual_untuk_keterbacaan&amp;quot;&amp;gt;simbol visual untuk keterbacaan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_ekstraksi_informasi_dari_keluaran&amp;quot;&amp;gt;Ekstraksi Informasi dari Keluaran&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_perbandingan_sebelumsesudah&amp;quot;&amp;gt;Perbandingan Sebelum/Sesudah&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_sebelum_pendekatan_bermasalah&amp;quot;&amp;gt;Sebelum : Pendekatan Bermasalah&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_setelah_pendekatan_robust&amp;quot;&amp;gt;Setelah: Pendekatan Robust&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_diagram_rekapitulatif&amp;quot;&amp;gt;Diagram Rekapitulatif&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_jebakan_yang_harus_dihindari&amp;quot;&amp;gt;Jebakan yang Harus Dihindari&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_jebakan_no_1_melupakan_forwardoutput&amp;quot;&amp;gt;❌ Jebakan No. 1: Melupakan `.forwardOutput()&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_jebakan_nomor_2_logger_belum_diinisialisasi&amp;quot;&amp;gt;❌ Jebakan nomor 2: Logger belum diinisialisasi&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_jebakan_nomor_3_logging_berlebih&amp;quot;&amp;gt;❌ Jebakan nomor 3 : Logging Berlebih&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_hasil_dan_metrik&amp;quot;&amp;gt;Hasil dan Metrik&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_kesimpulan&amp;quot;&amp;gt;Kesimpulan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_sumber_daya&amp;quot;&amp;gt;Sumber daya&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_pergi_lebih_jauh&amp;quot;&amp;gt;Pergi Lebih Jauh&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_pendahuluan&amp;quot;&amp;gt;Pendahuluan&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Selama mengembangkan plugin Gradle, pengujian fungsional sangat penting untuk memvalidasi perilaku plugin dalam lingkungan Gradle yang sebenarnya. Namun, pertanyaan penting ini muncul dengan cepat :&amp;lt;strong&amp;gt;bagaimana cara menyiapkan sistem pencatatan yang efektif tanpa membuat dependensi yang bermasalah dengan &amp;lt;code&amp;gt;GradleRunner&amp;lt;/code&amp;gt; ?&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Dalam artikel ini, kita akan menjelajahi :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Masalah logging dalam tes fungsional Gradle&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Arsitektur solusi yang kuat dengan SLF4J dan Logback&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Implementasi lengkap dengan contoh konkret&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Praktik terbaik dan jebakan yang harus dihindari&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_masalah_dependensi_pada_gradlerunner&amp;quot;&amp;gt;Masalah: Dependensi pada GradleRunner&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_konteks_pengembangan_plugin&amp;quot;&amp;gt;Konteks Pengembangan Plugin&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Saat kita mengembangkan sebuah plugin Gradle, kita biasanya menggunakan`GradleRunner`Gradle TestKit untuk menjalankan build pengujian:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;@Test
fun `test my plugin`() {
    val result = GradleRunner.create()
        .withProjectDir(projectDir)
        .withArguments(&amp;quot;myTask&amp;quot;)
        .build()

    // Comment logger efficacement ici ?
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_dilemma_logging&amp;quot;&amp;gt;Dilemma Logging&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Masalah utamanya adalah sebagai berikut :&amp;lt;strong&amp;gt;logging tidak boleh bergantung pada objek yang tidak yakin tentang inisialisasinya&amp;lt;/strong&amp;gt;.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
!define RECTANGLE class

skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle

package &amp;quot;Arsitektur Bermasalah&amp;quot; {
    RECTANGLE Test {
        + runTest()
    }

    RECTANGLE GradleRunner {
        + build()
        + getLogger() ❌
    }

    RECTANGLE Logger {
        + info()
        + debug()
    }

    Test --&amp;amp;gt; GradleRunner : dépend de
    Logger ..&amp;amp;gt; GradleRunner : dépend de ❌
}

note right of Logger
  Problème: Le logger dépend
  du GradleRunner qui peut
  ne pas être initialisé
end note

@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_ide_ide_yang_salah&amp;quot;&amp;gt;Ide-ide yang salah&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;admonitionblock warning&amp;quot;&amp;gt;
&amp;lt;table&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;icon&amp;quot;&amp;gt;
&amp;lt;i class=&amp;quot;fa icon-warning&amp;quot; title=&amp;quot;Warning&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Jangan lakukan ini :&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;// ❌ MAUVAIS: Tentative d&amp;#39;obtenir un logger du runner
val runner = GradleRunner.create()
val logger = runner.logger // N&amp;#39;existe pas !
// ❌ MAUVAIS: Logger partagé initialisé tardivement
lateinit var logger: Logger
@BeforeEach
fun setup() {
    logger = LoggerFactory.getLogger(...)
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_solusi_logger_independen&amp;quot;&amp;gt;Solusi : Logger Independen&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_arsitektur_yang_disarankan&amp;quot;&amp;gt;Arsitektur yang disarankan&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Solusi adalah menggunakan logger&amp;lt;strong&amp;gt;sepenuhnya mandiri&amp;lt;/strong&amp;gt; du &amp;lt;code&amp;gt;GradleRunner&amp;lt;/code&amp;gt;, diinisialisasikan pada tingkat kelas pengujian.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
!define RECTANGLE class

skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle

package &amp;quot;Arsitektur yang benar&amp;quot; {
    RECTANGLE TestClass {
        + {static} logger: Logger ✓
        + runTest()
    }

    RECTANGLE SLF4J {
        + LoggerFactory
    }

    RECTANGLE GradleRunner {
        + build()
        + forwardOutput()
    }

    RECTANGLE &amp;quot;Output Konsol&amp;quot; as Output {
        + captured text
    }

    TestClass --&amp;amp;gt; SLF4J : utilise ✓
    TestClass --&amp;amp;gt; GradleRunner : utilise
    GradleRunner --&amp;amp;gt; Output : génère
    TestClass --&amp;amp;gt; Output : analyse
}

note top of TestClass
  Le logger est initialisé
  indépendamment du GradleRunner
end note

note bottom of GradleRunner
  .forwardOutput() permet
  de capturer la sortie
  pour analyse
end note

@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_alur_eksekusi&amp;quot;&amp;gt;Alur eksekusi&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
!theme plain
skinparam backgroundColor #FEFEFE

participant &amp;quot;JUnit&amp;quot; as junit
participant &amp;quot;KelasUji&amp;quot; as test
participant &amp;quot;Pencatat (SLF4J)&amp;quot; as logger
participant &amp;quot;GradleRunner&amp;quot; as runner
participant &amp;quot;Membangun Gradle&amp;quot; as gradle

activate junit
junit -&amp;amp;gt; test : load class
activate test

test -&amp;amp;gt; logger : initialize (companion)
activate logger
logger --&amp;amp;gt; test : logger ready ✓
deactivate logger

junit -&amp;amp;gt; test : @BeforeAll
test -&amp;amp;gt; logger : info(&amp;quot;Initialisation...&amp;quot;)

junit -&amp;amp;gt; test : @BeforeEach
test -&amp;amp;gt; logger : debug(&amp;quot;Uji coba...&amp;quot;)

junit -&amp;amp;gt; test : @Test
test -&amp;amp;gt; logger : info(&amp;quot;TEST: Memulai...&amp;quot;)

test -&amp;amp;gt; runner : create()
activate runner
test -&amp;amp;gt; runner : .forwardOutput()
test -&amp;amp;gt; runner : .withArguments(...)
test -&amp;amp;gt; runner : .build()

runner -&amp;amp;gt; gradle : execute build
activate gradle
gradle --&amp;amp;gt; runner : output text
deactivate gradle

runner --&amp;amp;gt; test : BuildResult + output
deactivate runner

test -&amp;amp;gt; logger : debug(&amp;quot;Menganalisis keluaran...&amp;quot;)
test -&amp;amp;gt; test : analyze output
test -&amp;amp;gt; logger : info(&amp;quot;✓ Tes berhasil&amp;quot;)

test --&amp;amp;gt; junit : success
deactivate test
deactivate junit

@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_implementasi_lengkap&amp;quot;&amp;gt;Implementasi lengkap&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_dependensi_gradle&amp;quot;&amp;gt;Dependensi Gradle&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Tambahkan dependensi yang diperlukan pada Anda`build.gradle.kts`&amp;amp;lt;/think&amp;amp;gt; (Empty output)&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;dependencies {
    // Pour les tests fonctionnels
    testImplementation(gradleTestKit())
    testImplementation(&amp;quot;org.junit.jupiter:junit-jupiter:5.10.2&amp;quot;)

    // Pour le logging
    testImplementation(&amp;quot;org.slf4j:slf4j-api:2.0.17&amp;quot;)
    testRuntimeOnly(&amp;quot;ch.qos.logback:logback-classic:1.4.14&amp;quot;)

    // Pour les assertions
    testImplementation(&amp;quot;org.assertj:assertj-core:3.27.6&amp;quot;)
}

tasks.test {
    useJUnitPlatform()
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_konfigurasi_logback&amp;quot;&amp;gt;Konfigurasi Logback&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Buat file`src/test/resources/logback-test.xml`:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-xml hljs&amp;quot; data-lang=&amp;quot;xml&amp;quot;&amp;gt;&amp;amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;amp;gt;
&amp;amp;lt;configuration&amp;amp;gt;
    &amp;amp;lt;!-- Appender console avec format lisible --&amp;amp;gt;
    &amp;amp;lt;appender name=&amp;quot;STDOUT&amp;quot; class=&amp;quot;ch.qos.logback.core.ConsoleAppender&amp;quot;&amp;amp;gt;
        &amp;amp;lt;encoder&amp;amp;gt;
            &amp;amp;lt;pattern&amp;amp;gt;%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n&amp;amp;lt;/pattern&amp;amp;gt;
        &amp;amp;lt;/encoder&amp;amp;gt;
    &amp;amp;lt;/appender&amp;amp;gt;

    &amp;amp;lt;!-- Appender fichier pour conservation --&amp;amp;gt;
    &amp;amp;lt;appender name=&amp;quot;FILE&amp;quot; class=&amp;quot;ch.qos.logback.core.FileAppender&amp;quot;&amp;amp;gt;
        &amp;amp;lt;file&amp;amp;gt;build/test-results/functional-tests.log&amp;amp;lt;/file&amp;amp;gt;
        &amp;amp;lt;encoder&amp;amp;gt;
            &amp;amp;lt;pattern&amp;amp;gt;%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n&amp;amp;lt;/pattern&amp;amp;gt;
        &amp;amp;lt;/encoder&amp;amp;gt;
    &amp;amp;lt;/appender&amp;amp;gt;

    &amp;amp;lt;!-- Logger pour vos tests --&amp;amp;gt;
    &amp;amp;lt;logger name=&amp;quot;com.cheroliv.bakery&amp;quot; level=&amp;quot;DEBUG&amp;quot;/&amp;amp;gt;

    &amp;amp;lt;!-- Réduire le bruit de Gradle --&amp;amp;gt;
    &amp;amp;lt;logger name=&amp;quot;org.gradle&amp;quot; level=&amp;quot;WARN&amp;quot;/&amp;amp;gt;

    &amp;amp;lt;!-- Configuration racine --&amp;amp;gt;
    &amp;amp;lt;root level=&amp;quot;INFO&amp;quot;&amp;amp;gt;
        &amp;amp;lt;appender-ref ref=&amp;quot;STDOUT&amp;quot;/&amp;amp;gt;
        &amp;amp;lt;appender-ref ref=&amp;quot;FILE&amp;quot;/&amp;amp;gt;
    &amp;amp;lt;/root&amp;amp;gt;
&amp;amp;lt;/configuration&amp;amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_kelas_test_lengkap&amp;quot;&amp;gt;Kelas Test Lengkap&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;package com.cheroliv.bakery

import org.gradle.testkit.runner.GradleRunner
import org.gradle.testkit.runner.TaskOutcome
import org.junit.jupiter.api.*
import org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS
import org.junit.jupiter.api.io.TempDir
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import java.io.File
import kotlin.test.assertEquals
import kotlin.test.assertTrue

@TestInstance(PER_CLASS)
class BakeryPluginFunctionalTest {

    @field:TempDir
    lateinit var projectDir: File

    private val buildFile by lazy { projectDir.resolve(&amp;quot;build.gradle.kts&amp;quot;) }
    private val settingsFile by lazy { projectDir.resolve(&amp;quot;settings.gradle.kts&amp;quot;) }

    companion object {
        // ✓ Logger indépendant, initialisé au chargement de la classe
        private val logger: Logger = LoggerFactory.getLogger(
            BakeryPluginFunctionalTest::class.java
        )

        @JvmStatic
        @BeforeAll
        fun globalSetup() {
            logger.info(&amp;quot;═&amp;quot;.repeat(60))
            logger.info(&amp;quot;DÉMARRAGE DE LA SUITE DE TESTS FONCTIONNELS&amp;quot;)
            logger.info(&amp;quot;═&amp;quot;.repeat(60))
        }

        @JvmStatic
        @AfterAll
        fun globalTeardown() {
            logger.info(&amp;quot;═&amp;quot;.repeat(60))
            logger.info(&amp;quot;FIN DE LA SUITE DE TESTS&amp;quot;)
            logger.info(&amp;quot;═&amp;quot;.repeat(60))
        }
    }

    @BeforeEach
    fun setup() {
        logger.info(&amp;quot;─&amp;quot;.repeat(60))
        logger.info(&amp;quot;Préparation de l&amp;#39;environnement de test&amp;quot;)
        logger.debug(&amp;quot;Répertoire de test: ${projectDir.absolutePath}&amp;quot;)

        settingsFile.writeText(&amp;quot;&amp;quot;&amp;quot;
            rootProject.name = &amp;quot;test-bakery-project&amp;quot;
        &amp;quot;&amp;quot;&amp;quot;.trimIndent())

        logger.debug(&amp;quot;✓ settings.gradle.kts créé&amp;quot;)
    }

    @AfterEach
    fun teardown(testInfo: TestInfo) {
        logger.info(&amp;quot;✓ Test terminé: ${testInfo.displayName}&amp;quot;)
        logger.info(&amp;quot;─&amp;quot;.repeat(60))
    }

    @Test
    fun `plugin can be applied successfully`() {
        logger.info(&amp;quot;TEST: Application du plugin Bakery&amp;quot;)

        buildFile.writeText(&amp;quot;&amp;quot;&amp;quot;
            plugins {
                id(&amp;quot;com.cheroliv.bakery&amp;quot;)
            }
        &amp;quot;&amp;quot;&amp;quot;.trimIndent())

        logger.debug(&amp;quot;Fichier build.gradle.kts créé&amp;quot;)
        logger.debug(&amp;quot;Lancement du build Gradle...&amp;quot;)

        val startTime = System.currentTimeMillis()

        val result = GradleRunner.create()
            .forwardOutput() // ← Important : capture la sortie
            .withPluginClasspath()
            .withArguments(&amp;quot;tasks&amp;quot;, &amp;quot;--group=bakery&amp;quot;, &amp;quot;--stacktrace&amp;quot;)
            .withProjectDir(projectDir)
            .build()

        val duration = System.currentTimeMillis() - startTime
        logger.info(&amp;quot;Build terminé en ${duration}ms&amp;quot;)

        // Analyse de la sortie
        logger.debug(&amp;quot;Analyse de la sortie Gradle...&amp;quot;)
        val outputLines = result.output.lines()
        logger.debug(&amp;quot;Nombre de lignes capturées: ${outputLines.size}&amp;quot;)

        // Validation
        val expectedTasks = listOf(&amp;quot;bake&amp;quot;, &amp;quot;printConfigPath&amp;quot;, &amp;quot;printJBakeClasspath&amp;quot;)
        logger.info(&amp;quot;Vérification des tâches attendues:&amp;quot;)

        expectedTasks.forEach { task -&amp;amp;gt;
            val found = result.output.contains(task)
            logger.debug(&amp;quot;  ${if (found) &amp;quot;✓&amp;quot; else &amp;quot;✗&amp;quot;} $task&amp;quot;)
            assertTrue(found, &amp;quot;Task &amp;#39;$task&amp;#39; should be available&amp;quot;)
        }

        logger.info(&amp;quot;✓ Plugin appliqué avec succès - ${expectedTasks.size} tâches trouvées&amp;quot;)
    }

    @Test
    fun `bake task executes and prints version`() {
        logger.info(&amp;quot;TEST: Exécution de la tâche &amp;#39;bake&amp;#39;&amp;quot;)

        buildFile.writeText(&amp;quot;&amp;quot;&amp;quot;
            plugins {
                id(&amp;quot;com.cheroliv.bakery&amp;quot;)
            }
        &amp;quot;&amp;quot;&amp;quot;.trimIndent())

        logger.debug(&amp;quot;Exécution de &amp;#39;gradle bake&amp;#39;...&amp;quot;)

        val result = GradleRunner.create()
            .forwardOutput()
            .withPluginClasspath()
            .withArguments(&amp;quot;bake&amp;quot;, &amp;quot;--info&amp;quot;) // --info pour plus de détails
            .withProjectDir(projectDir)
            .build()

        // Extraction d&amp;#39;informations depuis la sortie
        logger.debug(&amp;quot;Recherche du message &amp;#39;Baking site&amp;#39;...&amp;quot;)
        val hasBakingMessage = result.output.contains(&amp;quot;Baking site with Bakery Plugin!&amp;quot;)

        logger.debug(&amp;quot;Recherche de la version JBake...&amp;quot;)
        val hasVersionMessage = result.output.contains(&amp;quot;Using JBake version:&amp;quot;)

        // Extraction de la version (si présente)
        val versionRegex = Regex(&amp;quot;Using JBake version: (.+)&amp;quot;)
        val versionMatch = versionRegex.find(result.output)
        if (versionMatch != null) {
            val version = versionMatch.groupValues[1]
            logger.info(&amp;quot;Version JBake détectée: $version&amp;quot;)
        }

        // Validation
        assertTrue(hasBakingMessage, &amp;quot;Message &amp;#39;Baking site&amp;#39; attendu&amp;quot;)
        assertTrue(hasVersionMessage, &amp;quot;Message de version attendu&amp;quot;)
        assertEquals(TaskOutcome.SUCCESS, result.task(&amp;quot;:bake&amp;quot;)?.outcome)

        logger.info(&amp;quot;✓ Tâche &amp;#39;bake&amp;#39; exécutée avec succès&amp;quot;)
    }

    @Test
    fun `analyze output with structured logging`() {
        logger.info(&amp;quot;TEST: Analyse structurée de la sortie&amp;quot;)

        buildFile.writeText(&amp;quot;&amp;quot;&amp;quot;
            plugins {
                id(&amp;quot;com.cheroliv.bakery&amp;quot;)
            }

            task(&amp;quot;diagnostics&amp;quot;) {
                doLast {
                    println(&amp;quot;[DIAG] Project: ${&amp;#39;$&amp;#39;}{project.name}&amp;quot;)
                    println(&amp;quot;[DIAG] Build dir: ${&amp;#39;$&amp;#39;}{project.buildDir}&amp;quot;)
                    println(&amp;quot;[DIAG] Plugin applied: true&amp;quot;)

                    val config = configurations.findByName(&amp;quot;jbakeRuntime&amp;quot;)
                    println(&amp;quot;[DIAG] JBake config exists: ${&amp;#39;$&amp;#39;}{config != null}&amp;quot;)

                    if (config != null) {
                        println(&amp;quot;[DIAG] Dependencies count: ${&amp;#39;$&amp;#39;}{config.dependencies.size}&amp;quot;)
                    }
                }
            }
        &amp;quot;&amp;quot;&amp;quot;.trimIndent())

        logger.debug(&amp;quot;Exécution de la tâche diagnostics...&amp;quot;)

        val result = GradleRunner.create()
            .forwardOutput()
            .withPluginClasspath()
            .withArguments(&amp;quot;diagnostics&amp;quot;, &amp;quot;--quiet&amp;quot;)
            .withProjectDir(projectDir)
            .build()

        // Analyse structurée
        logger.info(&amp;quot;Extraction des informations de diagnostic:&amp;quot;)

        val diagLines = result.output.lines()
            .filter { it.startsWith(&amp;quot;[DIAG]&amp;quot;) }

        logger.debug(&amp;quot;Nombre de lignes de diagnostic: ${diagLines.size}&amp;quot;)

        diagLines.forEach { line -&amp;amp;gt;
            logger.info(&amp;quot;  $line&amp;quot;)

            // Analyse spécifique par type de ligne
            when {
                line.contains(&amp;quot;Project:&amp;quot;) -&amp;amp;gt; {
                    val projectName = line.substringAfter(&amp;quot;Project:&amp;quot;).trim()
                    logger.debug(&amp;quot;    → Nom du projet extrait: &amp;#39;$projectName&amp;#39;&amp;quot;)
                }
                line.contains(&amp;quot;Dependencies count:&amp;quot;) -&amp;amp;gt; {
                    val count = line.substringAfter(&amp;quot;count:&amp;quot;).trim()
                    logger.debug(&amp;quot;    → Nombre de dépendances: $count&amp;quot;)
                }
            }
        }

        assertTrue(diagLines.isNotEmpty(), &amp;quot;Des lignes de diagnostic devraient être présentes&amp;quot;)
        logger.info(&amp;quot;✓ Analyse structurée terminée - ${diagLines.size} lignes analysées&amp;quot;)
    }

    @Test
    fun `test with error handling`() {
        logger.info(&amp;quot;TEST: Gestion d&amp;#39;erreurs avec logging&amp;quot;)

        buildFile.writeText(&amp;quot;&amp;quot;&amp;quot;
            plugins {
                id(&amp;quot;com.cheroliv.bakery&amp;quot;)
            }
        &amp;quot;&amp;quot;&amp;quot;.trimIndent())

        try {
            logger.debug(&amp;quot;Tentative d&amp;#39;exécution...&amp;quot;)

            val result = GradleRunner.create()
                .forwardOutput()
                .withPluginClasspath()
                .withArguments(&amp;quot;printJBakeClasspath&amp;quot;)
                .withProjectDir(projectDir)
                .build()

            val outcome = result.task(&amp;quot;:printJBakeClasspath&amp;quot;)?.outcome
            logger.info(&amp;quot;Outcome: $outcome&amp;quot;)

            if (outcome == TaskOutcome.SUCCESS) {
                logger.info(&amp;quot;✓ Exécution réussie&amp;quot;)
            } else {
                logger.warn(&amp;quot;⚠ Outcome inattendu: $outcome&amp;quot;)
            }

            assertEquals(TaskOutcome.SUCCESS, outcome)

        } catch (e: Exception) {
            logger.error(&amp;quot;✗ Échec du test&amp;quot;, e)
            logger.error(&amp;quot;Type d&amp;#39;exception: ${e.javaClass.simpleName}&amp;quot;)
            logger.error(&amp;quot;Message: ${e.message}&amp;quot;)

            // Log de la stack trace si nécessaire
            if (logger.isDebugEnabled) {
                logger.debug(&amp;quot;Stack trace complète:&amp;quot;, e)
            }

            throw e
        }
    }
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_skema_logging_multi_tingkat&amp;quot;&amp;gt;Skema Logging Multi-Tingkat&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
!theme plain
skinparam backgroundColor #FEFEFE

package &amp;quot;Tingkat log&amp;quot; {

    rectangle &amp;quot;jejak&amp;quot; #E8E8E8 {
        note
            Détails d&amp;#39;exécution très fins
            • Valeurs de variables
            • Appels de méthodes internes
        end note
    }

    rectangle &amp;quot;DEBUG&amp;quot; #D0E8FF {
        note
            Informations de débogage
            • Étapes intermédiaires
            • Contenu des fichiers créés
            • Analyse de la sortie Gradle
        end note
    }

    rectangle &amp;quot;informasi&amp;quot; #C8FFC8 {
        note
            Informations principales
            • Début/fin de test
            • Résultats de validation
            • Durée d&amp;#39;exécution
        end note
    }

    rectangle &amp;quot;WARN&amp;quot; #FFFFC8 {
        note
            Avertissements
            • Comportements inattendus
            • Outcomes inhabituels
        end note
    }

    rectangle &amp;quot;kesalahan&amp;quot; #FFC8C8 {
        note
            Erreurs
            • Échecs de test
            • Exceptions
            • Stack traces
        end note
    }
}

@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_praktik_terbaik&amp;quot;&amp;gt;Praktik Terbaik&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_struktur_logging_yang_direkomendasikan&amp;quot;&amp;gt;Struktur logging yang direkomendasikan&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;@Test
fun `mon test`() {
    // 1. LOG: Début du test
    logger.info(&amp;quot;TEST: Description du test&amp;quot;)

    // 2. LOG: Préparation
    logger.debug(&amp;quot;Préparation des fichiers...&amp;quot;)
    buildFile.writeText(&amp;quot;...&amp;quot;)
    logger.debug(&amp;quot;✓ Fichiers préparés&amp;quot;)

    // 3. LOG: Exécution
    logger.debug(&amp;quot;Lancement du build...&amp;quot;)
    val startTime = System.currentTimeMillis()

    val result = GradleRunner.create()
        .forwardOutput()
        .withArguments(&amp;quot;task&amp;quot;)
        .build()

    val duration = System.currentTimeMillis() - startTime
    logger.info(&amp;quot;Build terminé en ${duration}ms&amp;quot;)

    // 4. LOG: Analyse
    logger.debug(&amp;quot;Analyse des résultats...&amp;quot;)
    // ... validations ...

    // 5. LOG: Conclusion
    logger.info(&amp;quot;✓ Test réussi&amp;quot;)
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_simbol_visual_untuk_keterbacaan&amp;quot;&amp;gt;simbol visual untuk keterbacaan&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Gunakan simbol Unicode untuk meningkatkan keterbacaan log:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;logger.info(&amp;quot;✓ Succès&amp;quot;)
logger.info(&amp;quot;✗ Échec&amp;quot;)
logger.info(&amp;quot;⚠ Attention&amp;quot;)
logger.info(&amp;quot;→ Étape suivante&amp;quot;)
logger.info(&amp;quot;═&amp;quot;.repeat(60)) // Séparateur principal
logger.info(&amp;quot;─&amp;quot;.repeat(60)) // Séparateur secondaire&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_ekstraksi_informasi_dari_keluaran&amp;quot;&amp;gt;Ekstraksi Informasi dari Keluaran&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;// Recherche simple
val hasMessage = result.output.contains(&amp;quot;Expected message&amp;quot;)

// Extraction avec regex
val versionRegex = Regex(&amp;quot;Version: (.+)&amp;quot;)
val version = versionRegex.find(result.output)?.groupValues?.get(1)

// Filtrage de lignes
val errorLines = result.output.lines()
    .filter { it.contains(&amp;quot;ERROR&amp;quot;) }

errorLines.forEach { logger.error(&amp;quot;Gradle error: $it&amp;quot;) }&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_perbandingan_sebelumsesudah&amp;quot;&amp;gt;Perbandingan Sebelum/Sesudah&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_sebelum_pendekatan_bermasalah&amp;quot;&amp;gt;Sebelum : Pendekatan Bermasalah&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;class MyTest {
    lateinit var logger: Logger // ❌ Initialisé tardivement

    @BeforeEach
    fun setup() {
        logger = LoggerFactory.getLogger(...) // ❌ Dépendance
    }

    @Test
    fun test() {
        val runner = GradleRunner.create()
        logger.info(&amp;quot;Testing...&amp;quot;) // ⚠ Peut échouer
    }
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_setelah_pendekatan_robust&amp;quot;&amp;gt;Setelah: Pendekatan Robust&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;class MyTest {
    companion object {
        private val logger = LoggerFactory.getLogger(...) // ✓ Indépendant
    }

    @Test
    fun test() {
        logger.info(&amp;quot;TEST: Starting...&amp;quot;) // ✓ Toujours disponible

        val result = GradleRunner.create()
            .forwardOutput() // ✓ Capture la sortie
            .build()

        logger.debug(&amp;quot;Output: ${result.output}&amp;quot;) // ✓ Analyse
    }
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_diagram_rekapitulatif&amp;quot;&amp;gt;Diagram Rekapitulatif&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
!theme plain
skinparam backgroundColor #FEFEFE

title Alur lengkap logging dalam pengujian fungsional
actor &amp;quot;Pengembang&amp;quot; as dev
participant &amp;quot;JUnit&amp;quot; as junit
participant &amp;quot;TestClass&amp;quot; as test
participant &amp;quot;Pencatat
(SLF4J)&amp;quot; as logger
participant &amp;quot;Logback&amp;quot; as logback
participant &amp;quot;GradleRunner&amp;quot; as runner
participant &amp;quot;Konsol/File&amp;quot; as output

dev -&amp;amp;gt; junit : lance les tests

activate junit
junit -&amp;amp;gt; test : charge la classe
activate test

test -&amp;amp;gt; logger : initialize (companion)
activate logger
logger -&amp;amp;gt; logback : configure
activate logback
logback --&amp;amp;gt; logger : ready
deactivate logback
logger --&amp;amp;gt; test : ✓
deactivate logger

== Phase de Setup ==

junit -&amp;amp;gt; test : @BeforeAll
test -&amp;amp;gt; logger : info(&amp;quot;Inisialisasi&amp;quot;)
logger -&amp;amp;gt; output : [INFO] Initialisation

junit -&amp;amp;gt; test : @BeforeEach
test -&amp;amp;gt; logger : debug(&amp;quot;tes setup&amp;quot;)
logger -&amp;amp;gt; output : [DEBUG] Setup test

== Phase de Test ==

junit -&amp;amp;gt; test : @Test
test -&amp;amp;gt; logger : info(&amp;quot;Starting&amp;quot;)
logger -&amp;amp;gt; output : [INFO] TEST: Starting

test -&amp;amp;gt; runner : create()
activate runner

test -&amp;amp;gt; runner : .forwardOutput()
test -&amp;amp;gt; runner : .build()

runner -&amp;amp;gt; runner : execute Gradle
runner --&amp;amp;gt; test : BuildResult + output text
deactivate runner

test -&amp;amp;gt; logger : debug(&amp;quot;Menganalisis output&amp;quot;)
logger -&amp;amp;gt; output : [DEBUG] Analyzing output

test -&amp;amp;gt; test : assertions
test -&amp;amp;gt; logger : info(&amp;quot;✓ Test berhasil&amp;quot;)
logger -&amp;amp;gt; output : [INFO] ✓ Test passed

== Phase de Cleanup ==

junit -&amp;amp;gt; test : @AfterEach
test -&amp;amp;gt; logger : debug(&amp;quot;pembersihan&amp;quot;)
logger -&amp;amp;gt; output : [DEBUG] Cleanup

junit -&amp;amp;gt; test : @AfterAll
test -&amp;amp;gt; logger : info(&amp;quot;Selesai pengujian&amp;quot;)
logger -&amp;amp;gt; output : [INFO] Fin des tests

test --&amp;amp;gt; junit : ✓ all tests passed
deactivate test
deactivate junit

dev &amp;amp;lt;-- junit : rapport de tests

@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_jebakan_yang_harus_dihindari&amp;quot;&amp;gt;Jebakan yang Harus Dihindari&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_jebakan_no_1_melupakan_forwardoutput&amp;quot;&amp;gt;❌ Jebakan No. 1: Melupakan `.forwardOutput()&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;// MAUVAIS: La sortie Gradle n&amp;#39;est pas capturée
val result = GradleRunner.create()
    .withArguments(&amp;quot;task&amp;quot;)
    .build()

// result.output sera vide !&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;// BON: La sortie est capturée
val result = GradleRunner.create()
    .forwardOutput() // ✓
    .withArguments(&amp;quot;task&amp;quot;)
    .build()&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_jebakan_nomor_2_logger_belum_diinisialisasi&amp;quot;&amp;gt;❌ Jebakan nomor 2: Logger belum diinisialisasi&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;// MAUVAIS
class Test {
    lateinit var logger: Logger // Peut ne pas être initialisé
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;// BON
class Test {
    companion object {
        private val logger = LoggerFactory.getLogger(...) // Toujours initialisé
    }
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_jebakan_nomor_3_logging_berlebih&amp;quot;&amp;gt;❌ Jebakan nomor 3 : Logging Berlebih&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;// MAUVAIS: Trop de logs DEBUG en production
logger.debug(&amp;quot;Variable a = $a&amp;quot;)
logger.debug(&amp;quot;Variable b = $b&amp;quot;)
logger.debug(&amp;quot;Variable c = $c&amp;quot;)
// ... 100 lignes de logs&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;// BON: Logging ciblé
logger.info(&amp;quot;Traitement de ${items.size} éléments&amp;quot;)
logger.debug(&amp;quot;Détails: ${items.take(5)}...&amp;quot;) // Échantillon seulement&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_hasil_dan_metrik&amp;quot;&amp;gt;Hasil dan Metrik&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Dengan pendekatan ini, Anda mendapatkan :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;table class=&amp;quot;tableblock frame-all grid-all stretch&amp;quot;&amp;gt;
&amp;lt;colgroup&amp;gt;
&amp;lt;col style=&amp;quot;width: 28.5714%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 42.8571%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 28.5715%;&amp;quot;&amp;gt;
&amp;lt;/colgroup&amp;gt;
&amp;lt;thead&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;Metrik&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;sebelum&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;setelah&amp;lt;/th&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/thead&amp;gt;
&amp;lt;tbody&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Baris kode&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;~50 baris&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;~80 baris (+60%)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;waktu debugging&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;~30 menit&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;~5 menit (-83%)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Kesalahan terdeteksi&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;dasar&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;rinci&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;perawatan&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Sulit&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;sederhana&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;kemerdekaan&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;❌ Bersatu&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;✓ mandiri&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_kesimpulan&amp;quot;&amp;gt;Kesimpulan&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Pembuatan sistem logging yang kuat dalam tes fungsional plugin Gradle didasarkan pada prinsip sederhana :&amp;lt;strong&amp;gt;kemerdekaan&amp;lt;/strong&amp;gt;.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Poin-poin penting yang harus diingat :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;olist arabic&amp;quot;&amp;gt;
&amp;lt;ol class=&amp;quot;arabic&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Logger independen&amp;lt;/strong&amp;gt;: Diinisialisasi dalam`companion object`&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;SLF4J + Logback&amp;lt;/strong&amp;gt;: Stack yang terbukti dan dapat dikonfigurasi&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;.forwardOutput()&amp;lt;/strong&amp;gt;: Penangkapan output Gradle&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Pencatatan terstruktur&amp;lt;/strong&amp;gt;: INFO untuk langkah, DEBUG untuk detail&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Analisis output&amp;lt;/strong&amp;gt;Ekstraksi informasi sejak`result.output`&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Pendekatan ini menjamin tes&amp;lt;strong&amp;gt;dapat dipelihara&amp;lt;/strong&amp;gt;, &amp;lt;strong&amp;gt;bisa di-debug&amp;lt;/strong&amp;gt; et &amp;lt;strong&amp;gt;kuat&amp;lt;/strong&amp;gt;, sambil tetap mematuhi prinsip-prinsip desain yang kuat.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_sumber_daya&amp;quot;&amp;gt;Sumber daya&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://docs.gradle.org/current/userguide/test_kit.html&amp;quot;&amp;gt;Gradle TestKit dokumentasi&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://www.slf4j.org/manual.html&amp;quot;&amp;gt;Panduan SLF4J&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://logback.qos.ch/manual/configuration.html&amp;quot;&amp;gt;Konfigurasi Logback&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://github.com/gradle/gradle/tree/master/subprojects/test-kit&amp;quot;&amp;gt;Kode Sumber Gradle TestKit&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_pergi_lebih_jauh&amp;quot;&amp;gt;Pergi Lebih Jauh&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Dalam artikel berikutnya, kami akan menjelajahi:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Integrasi berkelanjutan dengan logging terstruktur&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Laporan HTML pengujian dengan log terintegrasi&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Debugging lanjutan dengan JVM attach&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Kinerja dan optimasi logging&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;em&amp;gt;Artikel ini merupakan bagian dari seri \&amp;quot;Pengembangan Plugin Gradle\&amp;quot; di blog teknis saya.&amp;lt;/em&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>Menampilkan kotak berwarna dalam AsciiDoc : Panduan Praktis</title>
            <link >https://cheroliv.com/blog/2025/0096_afficher-couleurs-asciidoc_post.html</link>
            <pubDate>Sat, 1 Nov 2025 00:00:00 +0000</pubDate>
            <guid isPermaLink="false">blog/2025/0096_afficher-couleurs-asciidoc_post.html</guid>
            <description>&amp;lt;div id=&amp;quot;toc&amp;quot; class=&amp;quot;toc&amp;quot;&amp;gt;
&amp;lt;div id=&amp;quot;toctitle&amp;quot;&amp;gt;Table of Contents&amp;lt;/div&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel1&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_pendahuluan&amp;quot;&amp;gt;Pendahuluan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_masalah&amp;quot;&amp;gt;Masalah&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_konteks_penggunaan&amp;quot;&amp;gt;Konteks penggunaan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_pendekatan_yang_mungkin&amp;quot;&amp;gt;Pendekatan yang mungkin&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel3&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_pilihan_1_karakter_unicode&amp;quot;&amp;gt;Pilihan 1 : Karakter Unicode&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_opsi_2_gambar_eksternal&amp;quot;&amp;gt;Opsi 2 : Gambar Eksternal&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_opsi_3_peran_css_kustom&amp;quot;&amp;gt;Opsi 3 : Peran CSS Kustom&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_opsi_4_injeksi_html_solusi_yang_dipilih&amp;quot;&amp;gt;Opsi 4 : Injeksi HTML (solusi yang dipilih)&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_solusi_injeksi_html_dengan&amp;quot;&amp;gt;Solusi : Injeksi HTML dengan &amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_prinsip_kerja&amp;quot;&amp;gt;Prinsip kerja&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_implementasi&amp;quot;&amp;gt;Implementasi&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel3&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_penjelasan_properti_css&amp;quot;&amp;gt;Penjelasan properti CSS&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_contoh_lengkap&amp;quot;&amp;gt;Contoh lengkap&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_optimisasi_dan_praktik_terbaik&amp;quot;&amp;gt;Optimisasi dan praktik terbaik&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_mengelola_warna_terang&amp;quot;&amp;gt;Mengelola warna terang&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_mengatur_ukuran_persegi&amp;quot;&amp;gt;Mengatur ukuran persegi&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_membuat_varian&amp;quot;&amp;gt;Membuat varian&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel3&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_lingkaran_berwarna&amp;quot;&amp;gt;lingkaran berwarna&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_persegi_dengan_batas_berwarna&amp;quot;&amp;gt;Persegi dengan batas berwarna&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_arsitektur_solusi&amp;quot;&amp;gt;Arsitektur solusi&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_manfaat_dan_batasan&amp;quot;&amp;gt;Manfaat dan batasan&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_kelebihan&amp;quot;&amp;gt;kelebihan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_keterbatasan&amp;quot;&amp;gt;Keterbatasan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_peningkatan_aksesibilitas&amp;quot;&amp;gt;Peningkatan aksesibilitas&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_contoh_konkret_dokumentasi_tema&amp;quot;&amp;gt;Contoh konkret: Dokumentasi tema&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_kesimpulan&amp;quot;&amp;gt;Kesimpulan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_sumber_daya&amp;quot;&amp;gt;Sumber daya&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_pendahuluan&amp;quot;&amp;gt;Pendahuluan&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Saat menyusun dokumentasi teknis, terutama untuk panduan gaya atau spesifikasi antarmuka pengguna, seringkali diperlukan untuk menampilkan palet warna. AsciiDoc, meski sangat kuat untuk dokumentasi, tidak menyediakan sintaksis bawaan untuk menampilkan sampel warna. Artikel ini mengeksplorasi masalah dan menawarkan solusi yang elegan berbasis injeksi HTML.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_masalah&amp;quot;&amp;gt;Masalah&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_konteks_penggunaan&amp;quot;&amp;gt;Konteks penggunaan&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Bayangkan bahwa Anda mendokumentasikan variabel CSS dari tema aplikasi web. Anda membutuhkan :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;olist arabic&amp;quot;&amp;gt;
&amp;lt;ol class=&amp;quot;arabic&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Daftar kode warna heksadesimal&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Menampilkan pratinjau visual setiap warna&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Mempertahankan keterbacaan dokumen sumber&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Memastikan kompatibilitas dengan generator situs statis seperti JBake&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
left to right direction
skinparam packageStyle rectangle

actor &amp;quot;Penulis Teknis&amp;quot; as writer
actor &amp;quot;Pembaca&amp;quot; as reader

rectangle &amp;quot;Dokumentasi AsciiDoc&amp;quot; {
  usecase &amp;quot;dokumentasikan palet warna&amp;quot; as UC1
  usecase &amp;quot;Tampilkan kode heksadesimal&amp;quot; as UC2
  usecase &amp;quot;Lihat warna&amp;quot; as UC3
  usecase &amp;quot;Menghasilkan situs statis&amp;quot; as UC4
  usecase &amp;quot;Melihat dokumentasi&amp;quot; as UC5
}

writer --&amp;amp;gt; UC1
UC1 ..&amp;amp;gt; UC2 : include
UC1 ..&amp;amp;gt; UC3 : include
UC1 --&amp;amp;gt; UC4
UC4 --&amp;amp;gt; UC5
UC5 &amp;amp;lt;-- reader
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_pendekatan_yang_mungkin&amp;quot;&amp;gt;Pendekatan yang mungkin&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Beberapa solusi dapat dipertimbangkan untuk menampilkan warna dalam dokumen AsciiDoc :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam componentStyle rectangle

package &amp;quot;Solusi mungkin&amp;quot; {
  component &amp;quot;Karakter Unicode&amp;quot; as unicode
  component &amp;quot;Gambar eksternal&amp;quot; as images
  component &amp;quot;Peran CSS khusus&amp;quot; as css
  component &amp;quot;Injeksi HTML&amp;quot; as html
}

package &amp;quot;Kriteria evaluasi&amp;quot; {
  component &amp;quot;kesederhanaan&amp;quot; as simple
  component &amp;quot;portabilitas&amp;quot; as portable
  component &amp;quot;Personalisasi&amp;quot; as custom
  component &amp;quot;Pemeliharaan&amp;quot; as maintain
}

unicode -down-&amp;amp;gt; simple : ✓
unicode -down-&amp;amp;gt; portable : ✓
unicode -down-&amp;amp;gt; custom : ✗

images -down-&amp;amp;gt; simple : ✗
images -down-&amp;amp;gt; portable : ✗
images -down-&amp;amp;gt; custom : ✓

css -down-&amp;amp;gt; simple : ~
css -down-&amp;amp;gt; portable : ✗
css -down-&amp;amp;gt; custom : ✓

html -down-&amp;amp;gt; simple : ✓
html -down-&amp;amp;gt; portable : ✓
html -down-&amp;amp;gt; custom : ✓✓
html -down-&amp;amp;gt; maintain : ✓

note right of html
  Solution recommandée
end note
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect3&amp;quot;&amp;gt;
&amp;lt;h4 id=&amp;quot;_pilihan_1_karakter_unicode&amp;quot;&amp;gt;Pilihan 1 : Karakter Unicode&amp;lt;/h4&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Penggunaan karakter Unicode seperti`&amp;amp;#9632;`(■) adalah sederhana tetapi terbatas:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-asciidoc hljs&amp;quot; data-lang=&amp;quot;asciidoc&amp;quot;&amp;gt;* ■ --accent-color: #7952b3 (violet Bootstrap)&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Keterbatasan&amp;lt;/strong&amp;gt; : Tidak ada kontrol atas warna, bergantung pada font sistem.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect3&amp;quot;&amp;gt;
&amp;lt;h4 id=&amp;quot;_opsi_2_gambar_eksternal&amp;quot;&amp;gt;Opsi 2 : Gambar Eksternal&amp;lt;/h4&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Buat gambar PNG atau SVG untuk setiap warna:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-asciidoc hljs&amp;quot; data-lang=&amp;quot;asciidoc&amp;quot;&amp;gt;* image:colors/violet.svg[width=16] --accent-color: #7952b3&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Keterbatasan&amp;lt;/strong&amp;gt; : Pemeliharaan yang berat, perbantasan file, tidak ada sinkronisasi otomatis.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect3&amp;quot;&amp;gt;
&amp;lt;h4 id=&amp;quot;_opsi_3_peran_css_kustom&amp;quot;&amp;gt;Opsi 3 : Peran CSS Kustom&amp;lt;/h4&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Menentukan kelas CSS dan menerapkannya melalui peran AsciiDoc :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-asciidoc hljs&amp;quot; data-lang=&amp;quot;asciidoc&amp;quot;&amp;gt;* [.color-violet]##■## --accent-color: #7952b3&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Keterbatasan&amp;lt;/strong&amp;gt; : Memerlukan lembar gaya eksternal, definisi sebelumnya semua warna yang mungkin.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect3&amp;quot;&amp;gt;
&amp;lt;h4 id=&amp;quot;_opsi_4_injeksi_html_solusi_yang_dipilih&amp;quot;&amp;gt;Opsi 4 : Injeksi HTML (solusi yang dipilih)&amp;lt;/h4&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Menggunakan makro``untuk menyisipkan HTML dengan gaya inline :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-asciidoc hljs&amp;quot; data-lang=&amp;quot;asciidoc&amp;quot;&amp;gt;* pass:[&amp;amp;lt;span style=&amp;quot;display:inline-block;width:1em;height:1em;
background:#7952b3;vertical-align:middle;margin-right:0.5em&amp;quot;&amp;amp;gt;&amp;amp;lt;/span&amp;amp;gt;]
--accent-color: #7952b3 (violet Bootstrap)&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_solusi_injeksi_html_dengan&amp;quot;&amp;gt;Solusi : Injeksi HTML dengan &amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_prinsip_kerja&amp;quot;&amp;gt;Prinsip kerja&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;makro``d&amp;amp;#8217;AsciiDoc memungkinkan untuk menyisipkan konten mentah yang tidak akan ditafsirkan oleh prosesor AsciiDoc. Hal ini memungkinkan kita untuk langsung menyisipkan HTML dengan gaya CSS inline.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
participant &amp;quot;Dokumen
AsciiDoc&amp;quot; as doc
participant &amp;quot;Prosesor\nAsciiDoc&amp;quot; as processor
participant &amp;quot;pass:[] macro&amp;quot; as pass
participant &amp;quot;HTML
akhir&amp;quot; as html

doc -&amp;amp;gt; processor : Analyse document
processor -&amp;amp;gt; pass : Détecte pass:[]
pass -&amp;amp;gt; processor : Retourne HTML brut
processor -&amp;amp;gt; html : Insère sans transformation
html -&amp;amp;gt; html : Navigateur applique styles
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_implementasi&amp;quot;&amp;gt;Implementasi&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Berikut struktur HTML untuk disisipkan:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-html hljs&amp;quot; data-lang=&amp;quot;html&amp;quot;&amp;gt;&amp;amp;lt;span style=&amp;quot;display:inline-block;
             width:1em;
             height:1em;
             background:#7952b3;
             vertical-align:middle;
             margin-right:0.5em&amp;quot;&amp;amp;gt;
&amp;amp;lt;/span&amp;amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect3&amp;quot;&amp;gt;
&amp;lt;h4 id=&amp;quot;_penjelasan_properti_css&amp;quot;&amp;gt;Penjelasan properti CSS&amp;lt;/h4&amp;gt;
&amp;lt;table class=&amp;quot;tableblock frame-all grid-all stretch&amp;quot;&amp;gt;
&amp;lt;colgroup&amp;gt;
&amp;lt;col style=&amp;quot;width: 25%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 75%;&amp;quot;&amp;gt;
&amp;lt;/colgroup&amp;gt;
&amp;lt;thead&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;Properti&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;fungsi&amp;lt;/th&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/thead&amp;gt;
&amp;lt;tbody&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;display:inline-block&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Memungkinkan pengaturan lebar/tinggi sambil tetap berada dalam alur inline&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;width:1em; height:1em&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Ukuran persegi relatif terhadap ukuran font (responsif)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;background:#7952b3&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Warna yang ditampilkan (variabel sesuai kebutuhan Anda)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;vertical-align:middle&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Ratakan persegi dengan teks yang bersebelahan&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;margin-right:0.5em&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Jarak antara kotak dan teks&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_contoh_lengkap&amp;quot;&amp;gt;Contoh lengkap&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Berikut adalah contoh yang mendokumentasikan palet tema :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-asciidoc hljs&amp;quot; data-lang=&amp;quot;asciidoc&amp;quot;&amp;gt;== Thème Clair

* pass:[&amp;amp;lt;span style=&amp;quot;display:inline-block;width:1em;height:1em;background:#f8f9fa;border:1px solid #ccc;vertical-align:middle;margin-right:0.5em&amp;quot;&amp;amp;gt;&amp;amp;lt;/span&amp;amp;gt;] --bg-primary: `#f8f9fa` (blanc cassé)
* pass:[&amp;amp;lt;span style=&amp;quot;display:inline-block;width:1em;height:1em;background:#212529;vertical-align:middle;margin-right:0.5em&amp;quot;&amp;amp;gt;&amp;amp;lt;/span&amp;amp;gt;] --text-primary: `#212529` (noir très foncé)
* pass:[&amp;amp;lt;span style=&amp;quot;display:inline-block;width:1em;height:1em;background:#7952b3;vertical-align:middle;margin-right:0.5em&amp;quot;&amp;amp;gt;&amp;amp;lt;/span&amp;amp;gt;] --accent-color: `#7952b3` (violet Bootstrap)
* pass:[&amp;amp;lt;span style=&amp;quot;display:inline-block;width:1em;height:1em;background:#61428f;vertical-align:middle;margin-right:0.5em&amp;quot;&amp;amp;gt;&amp;amp;lt;/span&amp;amp;gt;] --accent-hover: `#61428f` (violet plus foncé)
* pass:[&amp;amp;lt;span style=&amp;quot;display:inline-block;width:1em;height:1em;background:#ffffff;border:1px solid #ccc;vertical-align:middle;margin-right:0.5em&amp;quot;&amp;amp;gt;&amp;amp;lt;/span&amp;amp;gt;] --card-bg: `#ffffff` (blanc)&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Yang menghasilkan rendering berikut:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Tema Terang&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;span style=&amp;quot;display:inline-block;width:1em;height:1em;background:#f8f9fa;border:1px solid #ccc;vertical-align:middle;margin-right:0.5em&amp;quot;&amp;gt;&amp;lt;/span&amp;gt; --bg-primary:`#f8f9fa`(putih krem)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;span style=&amp;quot;display:inline-block;width:1em;height:1em;background:#212529;vertical-align:middle;margin-right:0.5em&amp;quot;&amp;gt;&amp;lt;/span&amp;gt; --text-primary:`#212529`(hitam sangat gelap)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;span style=&amp;quot;display:inline-block;width:1em;height:1em;background:#7952b3;vertical-align:middle;margin-right:0.5em&amp;quot;&amp;gt;&amp;lt;/span&amp;gt; --accent-color:`#7952b3`(ungu Bootstrap)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;span style=&amp;quot;display:inline-block;width:1em;height:1em;background:#61428f;vertical-align:middle;margin-right:0.5em&amp;quot;&amp;gt;&amp;lt;/span&amp;gt; --accent-hover:`#61428f`(ungu lebih gelap)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;span style=&amp;quot;display:inline-block;width:1em;height:1em;background:#ffffff;border:1px solid #ccc;vertical-align:middle;margin-right:0.5em&amp;quot;&amp;gt;&amp;lt;/span&amp;gt; --card-bg:`#ffffff`(putih)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_optimisasi_dan_praktik_terbaik&amp;quot;&amp;gt;Optimisasi dan praktik terbaik&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_mengelola_warna_terang&amp;quot;&amp;gt;Mengelola warna terang&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Untuk warna-warni yang sangat terang (putih, abu-abu sangat terang), tambahkan garis tepi agar terlihat pada latar belakang putih:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-html hljs&amp;quot; data-lang=&amp;quot;html&amp;quot;&amp;gt;&amp;amp;lt;span style=&amp;quot;display:inline-block;width:1em;height:1em;
background:#ffffff;border:1px solid #ccc;
vertical-align:middle;margin-right:0.5em&amp;quot;&amp;amp;gt;&amp;amp;lt;/span&amp;amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;batas kelabu`border:1px solid #ccc`) memungkinkan untuk membatasi kotak putih di latar belakang putih.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_mengatur_ukuran_persegi&amp;quot;&amp;gt;Mengatur ukuran persegi&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Anda dapat menyesuaikan ukuran kotak sesuai kebutuhan Anda :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-asciidoc hljs&amp;quot; data-lang=&amp;quot;asciidoc&amp;quot;&amp;gt;* pass:[&amp;amp;lt;span style=&amp;quot;display:inline-block;width:1.5em;height:1.5em;background:#7952b3;vertical-align:middle;margin-right:0.5em&amp;quot;&amp;amp;gt;&amp;amp;lt;/span&amp;amp;gt;] Grand carré
* pass:[&amp;amp;lt;span style=&amp;quot;display:inline-block;width:0.8em;height:0.8em;background:#7952b3;vertical-align:middle;margin-right:0.5em&amp;quot;&amp;amp;gt;&amp;amp;lt;/span&amp;amp;gt;] Petit carré&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Penggunaan satuan`em`memastikan bahwa kotak menyesuaikan ukuran dengan ukuran huruf&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_membuat_varian&amp;quot;&amp;gt;Membuat varian&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Untuk kebutuhan spesifik, Anda dapat membuat bentuk-bentuk yang berbeda:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect3&amp;quot;&amp;gt;
&amp;lt;h4 id=&amp;quot;_lingkaran_berwarna&amp;quot;&amp;gt;lingkaran berwarna&amp;lt;/h4&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-asciidoc hljs&amp;quot; data-lang=&amp;quot;asciidoc&amp;quot;&amp;gt;* pass:[&amp;amp;lt;span style=&amp;quot;display:inline-block;width:1em;height:1em;background:#7952b3;border-radius:50%;vertical-align:middle;margin-right:0.5em&amp;quot;&amp;amp;gt;&amp;amp;lt;/span&amp;amp;gt;] Cercle violet&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect3&amp;quot;&amp;gt;
&amp;lt;h4 id=&amp;quot;_persegi_dengan_batas_berwarna&amp;quot;&amp;gt;Persegi dengan batas berwarna&amp;lt;/h4&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-asciidoc hljs&amp;quot; data-lang=&amp;quot;asciidoc&amp;quot;&amp;gt;* pass:[&amp;amp;lt;span style=&amp;quot;display:inline-block;width:1em;height:1em;background:#ffffff;border:3px solid #7952b3;vertical-align:middle;margin-right:0.5em&amp;quot;&amp;amp;gt;&amp;amp;lt;/span&amp;amp;gt;] Bordure violette&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_arsitektur_solusi&amp;quot;&amp;gt;Arsitektur solusi&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
package &amp;quot;Alur kerja dokumentasi&amp;quot; {
  [Document AsciiDoc\navec pass macro] as asciidoc
  [Processeur JBake] as jbake
  [Site HTML statique] as html
  [Navigateur Web] as browser
}

asciidoc --&amp;amp;gt; jbake : 1. Compilation
jbake --&amp;amp;gt; html : 2. Génération
html --&amp;amp;gt; browser : 3. Affichage
browser --&amp;amp;gt; browser : 4. Rendu CSS

note right of asciidoc
  Contient les balises
  pass:[] avec HTML inline
end note

note right of jbake
  Préserve le HTML
  dans pass:[]
end note

note right of browser
  Applique les styles
  inline CSS
end note
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_manfaat_dan_batasan&amp;quot;&amp;gt;Manfaat dan batasan&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_kelebihan&amp;quot;&amp;gt;kelebihan&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;✓ &amp;lt;strong&amp;gt;Kemandirian&amp;lt;/strong&amp;gt; : Tidak ada ketergantungan pada file eksternal&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;✓ &amp;lt;strong&amp;gt;Portabilitas&amp;lt;/strong&amp;gt; : Berfungsi dengan semua prosesor AsciiDoc&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;✓ &amp;lt;strong&amp;gt;Synchronisation&amp;lt;/strong&amp;gt; : Kode warnanya berada langsung di HTML&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;✓ &amp;lt;strong&amp;gt;Fleksibilitas&amp;lt;/strong&amp;gt; : Penyesuaian lengkap gaya&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;✓ &amp;lt;strong&amp;gt;Maintenance&amp;lt;/strong&amp;gt; : Modifikasi sederhana kode heksadesimal&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;✓ &amp;lt;strong&amp;gt;Kompatibilitas JBake&amp;lt;/strong&amp;gt; : Berfungsi dengan sempurna dengan generator situs statis&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_keterbatasan&amp;quot;&amp;gt;Keterbatasan&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;✗ &amp;lt;strong&amp;gt;Verbosity&amp;lt;/strong&amp;gt; : Repetitive HTML code in the AsciiDoc source&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;✗ &amp;lt;strong&amp;gt;Keterbacaan sumber&amp;lt;/strong&amp;gt; : Dokumen sumber kurang rapi&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;✗ &amp;lt;strong&amp;gt;Aksesibilitas&amp;lt;/strong&amp;gt; : Tidak ada teks alternatif bawaan (yang harus ditambahkan secara manual)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_peningkatan_aksesibilitas&amp;quot;&amp;gt;Peningkatan aksesibilitas&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Untuk membuat kotak dapat diakses oleh pembaca layar :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-asciidoc hljs&amp;quot; data-lang=&amp;quot;asciidoc&amp;quot;&amp;gt;* pass:[&amp;amp;lt;span role=&amp;quot;img&amp;quot; aria-label=&amp;quot;Couleur violet Bootstrap&amp;quot;
style=&amp;quot;display:inline-block;width:1em;height:1em;background:#7952b3;
vertical-align:middle;margin-right:0.5em&amp;quot;&amp;amp;gt;&amp;amp;lt;/span&amp;amp;gt;]
--accent-color: `#7952b3` (violet Bootstrap)&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Atribut`role=&amp;quot;img&amp;quot;` et `aria-label`Memungkinkan teknologi asistif untuk memahami dan menggambarkan konten visual.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_contoh_konkret_dokumentasi_tema&amp;quot;&amp;gt;Contoh konkret: Dokumentasi tema&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Berikut contoh lengkap yang mendokumentasikan beberapa tema:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-asciidoc hljs&amp;quot; data-lang=&amp;quot;asciidoc&amp;quot;&amp;gt;= Guide des couleurs de l&amp;#39;application

== Thème Clair (`:root`)

* pass:[&amp;amp;lt;span style=&amp;quot;display:inline-block;width:1em;height:1em;background:#f8f9fa;border:1px solid #ccc;vertical-align:middle;margin-right:0.5em&amp;quot;&amp;amp;gt;&amp;amp;lt;/span&amp;amp;gt;] --bg-primary: `#f8f9fa` (blanc cassé)
* pass:[&amp;amp;lt;span style=&amp;quot;display:inline-block;width:1em;height:1em;background:#212529;vertical-align:middle;margin-right:0.5em&amp;quot;&amp;amp;gt;&amp;amp;lt;/span&amp;amp;gt;] --text-primary: `#212529` (noir très foncé)
* pass:[&amp;amp;lt;span style=&amp;quot;display:inline-block;width:1em;height:1em;background:#7952b3;vertical-align:middle;margin-right:0.5em&amp;quot;&amp;amp;gt;&amp;amp;lt;/span&amp;amp;gt;] --accent-color: `#7952b3` (violet Bootstrap)

== Thème Sombre (`[data-bs-theme=&amp;quot;dark&amp;quot;]`)

* pass:[&amp;amp;lt;span style=&amp;quot;display:inline-block;width:1em;height:1em;background:#212529;vertical-align:middle;margin-right:0.5em&amp;quot;&amp;amp;gt;&amp;amp;lt;/span&amp;amp;gt;] --bg-primary: `#212529` (noir très foncé)
* pass:[&amp;amp;lt;span style=&amp;quot;display:inline-block;width:1em;height:1em;background:#ffffff;border:1px solid #999;vertical-align:middle;margin-right:0.5em&amp;quot;&amp;amp;gt;&amp;amp;lt;/span&amp;amp;gt;] --text-primary: `#ffffff` (blanc)
* pass:[&amp;amp;lt;span style=&amp;quot;display:inline-block;width:1em;height:1em;background:#0d6efd;vertical-align:middle;margin-right:0.5em&amp;quot;&amp;amp;gt;&amp;amp;lt;/span&amp;amp;gt;] --accent-color: `#0d6efd` (bleu Bootstrap)

== Thème Contraste Élevé (`[data-bs-theme=&amp;quot;high-contrast&amp;quot;]`)

* pass:[&amp;amp;lt;span style=&amp;quot;display:inline-block;width:1em;height:1em;background:#000000;vertical-align:middle;margin-right:0.5em&amp;quot;&amp;amp;gt;&amp;amp;lt;/span&amp;amp;gt;] --bg-primary: `#000000` (noir)
* pass:[&amp;amp;lt;span style=&amp;quot;display:inline-block;width:1em;height:1em;background:#ffffff;border:1px solid #000;vertical-align:middle;margin-right:0.5em&amp;quot;&amp;amp;gt;&amp;amp;lt;/span&amp;amp;gt;] --text-primary: `#ffffff` (blanc)
* pass:[&amp;amp;lt;span style=&amp;quot;display:inline-block;width:1em;height:1em;background:#00ff00;vertical-align:middle;margin-right:0.5em&amp;quot;&amp;amp;gt;&amp;amp;lt;/span&amp;amp;gt;] --accent-color: `#00ff00` (vert vif)&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_kesimpulan&amp;quot;&amp;gt;Kesimpulan&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Injeksi HTML melalui makro``d&amp;amp;#8217;AsciiDoc menawarkan solusi yang elegan dan pragmatis untuk menampilkan sampel warna dalam dokumentasi teknis. Meskipun sedikit bertele-tele, pendekatan ini menjamin portabilitas maksimal dan pemeliharaan yang disederhanakan.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Teknik ini tidak memerlukan konfigurasi eksternal, tidak memerlukan lembar gaya tambahan, dan berfungsi segera dengan JBake dan semua prosesor AsciiDoc standar.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Setelah Anda membuat kotak berwarna pertama Anda, cukup menyalin dan menempelkan kode HTML dan mengubah kode heksadesimal untuk dengan cepat membuat palet lengkap.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Teknik ini dapat diperluas ke kasus penggunaan lain yang memerlukan rendering visual khusus: ikon, badge, grafik sederhana, atau elemen visual apa pun yang memerlukan kontrol gaya yang tepat.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_sumber_daya&amp;quot;&amp;gt;Sumber daya&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://docs.asciidoctor.org/asciidoc/latest/pass/pass-macro/&amp;quot; target=&amp;quot;_blank&amp;quot; rel=&amp;quot;noopener&amp;quot;&amp;gt;Dokumentasi AsciiDoc : pass macro&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://jbake.org/docs/2.6.7/&amp;quot; target=&amp;quot;_blank&amp;quot; rel=&amp;quot;noopener&amp;quot;&amp;gt;Dokumentasi JBake&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://developer.mozilla.org/fr/docs/Web/CSS/color_value&amp;quot; target=&amp;quot;_blank&amp;quot; rel=&amp;quot;noopener&amp;quot;&amp;gt;MDN : Nilai warna CSS&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;em&amp;gt;Artikel dipublikasikan pada 2025-11-01&amp;lt;/em&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>Internasionalisasi situs statis JBake dengan Thymeleaf</title>
            <link >https://cheroliv.com/blog/2025/0095_i18n-site-statique-jbake-thymeleaf_post.html</link>
            <pubDate>Mon, 20 Oct 2025 00:00:00 +0000</pubDate>
            <guid isPermaLink="false">blog/2025/0095_i18n-site-statique-jbake-thymeleaf_post.html</guid>
            <description>&amp;lt;div id=&amp;quot;toc&amp;quot; class=&amp;quot;toc&amp;quot;&amp;gt;
&amp;lt;div id=&amp;quot;toctitle&amp;quot;&amp;gt;Table of Contents&amp;lt;/div&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel1&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_pendahuluan&amp;quot;&amp;gt;Pendahuluan&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_diagram_kasus_penggunaan_use_case&amp;quot;&amp;gt;Diagram kasus penggunaan (Use Case)&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_arsitektur_internasionalisasi&amp;quot;&amp;gt;Arsitektur internasionalisasi&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_diagram_struktur_organisasi_file&amp;quot;&amp;gt;Diagram struktur (Organisasi file)&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_mengapa_pendekatan_ini&amp;quot;&amp;gt;Mengapa pendekatan ini?&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_diagram_komponen&amp;quot;&amp;gt;Diagram komponen&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_i18n_templating_dengan_thymeleaf&amp;quot;&amp;gt;I18n templating dengan Thymeleaf&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_struktur_file_pesan&amp;quot;&amp;gt;Struktur file pesan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_isi_file_pesan&amp;quot;&amp;gt;Isi file pesan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_penggunaan_dalam_templat&amp;quot;&amp;gt;Penggunaan dalam templat&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_diagram_urutan_resolusi_i18n&amp;quot;&amp;gt;Diagram urutan - resolusi i18n&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_konfigurasi_lokal_di_jbake&amp;quot;&amp;gt;Konfigurasi lokal di JBake&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_internasionalisasi_artikel_organisasi_berdasarkan_folder&amp;quot;&amp;gt;Internasionalisasi artikel : organisasi berdasarkan folder&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_diagram_alir_flow_generasi&amp;quot;&amp;gt;Diagram alir (Flow) - Generasi&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_struktur_folder&amp;quot;&amp;gt;Struktur folder&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_keuntungan_dari_pendekatan_ini&amp;quot;&amp;gt;Keuntungan dari pendekatan ini&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_metadata_artikel&amp;quot;&amp;gt;Metadata artikel&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_konfigurasi_url&amp;quot;&amp;gt;Konfigurasi URL&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_templates_untuk_tampilan_multibahasa&amp;quot;&amp;gt;Templates untuk tampilan multibahasa&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_template_artikel_dengan_pemilih_bahasa&amp;quot;&amp;gt;Template artikel dengan pemilih bahasa&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_indeks_difilter_berdasarkan_bahasa&amp;quot;&amp;gt;Indeks difilter berdasarkan bahasa&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_navigasi_antara_bahasa&amp;quot;&amp;gt;Navigasi antara bahasa&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_pemilih_bahasa_global&amp;quot;&amp;gt;Pemilih bahasa global&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_diagram_alur_pembacaan_pengguna&amp;quot;&amp;gt;Diagram alur - Pembacaan pengguna&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_gaya_css_untuk_penilih&amp;quot;&amp;gt;Gaya CSS untuk penilih&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_umpan_rss_per_bahasa&amp;quot;&amp;gt;Umpan RSS per bahasa&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_praktik_terbaik_dan_tips&amp;quot;&amp;gt;Praktik terbaik dan tips&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_1_konsistensi_id_artikel&amp;quot;&amp;gt;1. Konsistensi ID artikel&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_2_tanggal_konsisten&amp;quot;&amp;gt;2. Tanggal konsisten&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_3_tags_multibahasa&amp;quot;&amp;gt;3. Tags multibahasa&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_4_pengelolaan_artikel_yang_tidak_diterjemahkan&amp;quot;&amp;gt;4. Pengelolaan artikel yang tidak diterjemahkan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_5_peta_situs_multibahasa&amp;quot;&amp;gt;5. Peta situs multibahasa&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_kesimpulan&amp;quot;&amp;gt;Kesimpulan&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_diagram_penerapan&amp;quot;&amp;gt;Diagram penerapan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_pendahuluan&amp;quot;&amp;gt;Pendahuluan&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Internasionalisasi (i18n) situs statis dapat terlihat kompleks pada pertama kalinya, tetapi JBake yang dikombinasikan dengan Thymeleaf menawarkan solusi yang anggun untuk membuat situs multilingual. Dalam artikel ini, saya akan menunjukkan bagaimana saya telah mengimplementasikan i18n di blog saya, mencakup baik templating maupun pengelolaan artikel dalam beberapa bahasa.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_diagram_kasus_penggunaan_use_case&amp;quot;&amp;gt;Diagram kasus penggunaan (Use Case)&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
left to right direction
skinparam packageStyle rectangle

actor &amp;quot;Penulis&amp;quot; as author
actor &amp;quot;Pembaca FR&amp;quot; as reader_fr
actor &amp;quot;Pembaca EN&amp;quot; as reader_en

rectangle &amp;quot;Situs JBake Multibahasa&amp;quot; {
  usecase &amp;quot;Tulis artikel FR&amp;quot; as UC1
  usecase &amp;quot;Menulis artikel EN&amp;quot; as UC2
  usecase &amp;quot;Mengatur pesan i18n&amp;quot; as UC3
  usecase &amp;quot;Buat situs&amp;quot; as UC4
  usecase &amp;quot;Membaca artikel FR&amp;quot; as UC5
  usecase &amp;quot;Baca artikel EN&amp;quot; as UC6
  usecase &amp;quot;Ubah bahasa&amp;quot; as UC7
  usecase &amp;quot;Lihat terjemahan yang tersedia&amp;quot; as UC8
}

author --&amp;amp;gt; UC1
author --&amp;amp;gt; UC2
author --&amp;amp;gt; UC3
author --&amp;amp;gt; UC4

reader_fr --&amp;amp;gt; UC5
reader_fr --&amp;amp;gt; UC7
reader_fr --&amp;amp;gt; UC8

reader_en --&amp;amp;gt; UC6
reader_en --&amp;amp;gt; UC7
reader_en --&amp;amp;gt; UC8

UC1 ..&amp;amp;gt; UC4 : &amp;amp;lt;&amp;amp;lt;include&amp;amp;gt;&amp;amp;gt;
UC2 ..&amp;amp;gt; UC4 : &amp;amp;lt;&amp;amp;lt;include&amp;amp;gt;&amp;amp;gt;
UC3 ..&amp;amp;gt; UC4 : &amp;amp;lt;&amp;amp;lt;include&amp;amp;gt;&amp;amp;gt;
UC5 ..&amp;amp;gt; UC8 : &amp;amp;lt;&amp;amp;lt;extend&amp;amp;gt;&amp;amp;gt;
UC6 ..&amp;amp;gt; UC8 : &amp;amp;lt;&amp;amp;lt;extend&amp;amp;gt;&amp;amp;gt;
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_arsitektur_internasionalisasi&amp;quot;&amp;gt;Arsitektur internasionalisasi&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Pendekatan kami didasarkan pada dua pilar :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;olist arabic&amp;quot;&amp;gt;
&amp;lt;ol class=&amp;quot;arabic&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;internasionalisasi templating&amp;lt;/strong&amp;gt;penggunaan file pesan Thymeleaf untuk elemen antarmuka&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;internasionalisasi konten&amp;lt;/strong&amp;gt;: pengaturan artikel menurut bahasa dalam struktur folder khusus&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_diagram_struktur_organisasi_file&amp;quot;&amp;gt;Diagram struktur (Organisasi file)&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
@startsalt
{
{T
+ projet-jbake
++ content
+++ blog
++++ 2024
+++++ fr
++++++ article1.adoc
++++++ article2.adoc
+++++ en
++++++ article1.adoc
++++++ article2.adoc
++++ 2025
+++++ fr
++++++ guide-i18n.adoc
+++++ en
++++++ i18n-guide.adoc
++ templates
+++ post.html
+++ index.html
+++ index_en.html
+++ messages.properties
+++ messages_fr.properties
+++ messages_en.properties
++ assets
+++ css
+++ js
+++ img
++ jbake.properties
++ output
+++ fr
++++ blog
+++++ 2025
++++++ guide-i18n.html
+++ en
++++ blog
+++++ 2025
++++++ i18n-guide.html
}
}
@endsalt
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_mengapa_pendekatan_ini&amp;quot;&amp;gt;Mengapa pendekatan ini?&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Pemisahan ini memungkinkan :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Pertahankan konsistensi antarmuka apa pun bahasa&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Mengelola secara independen konten dan terjemahan artikel&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Memudahkan penambahan bahasa baru tanpa refactoring utama&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Mengizinkan artikel yang hanya tersedia dalam beberapa bahasa&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_diagram_komponen&amp;quot;&amp;gt;Diagram komponen&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
skinparam componentStyle rectangle

package &amp;quot;Sumber&amp;quot; {
  folder &amp;quot;content/blog/2025/&amp;quot; {
    folder &amp;quot;fr/&amp;quot; as content_fr {
      [article1.adoc]
      [article2.adoc]
    }
    folder &amp;quot;en/&amp;quot; as content_en {
      [article1.adoc] as article1_en
      [article2.adoc] as article2_en
    }
  }

  folder &amp;quot;templates/&amp;quot; {
    [post.html]
    [index.html]
    [index_en.html]
  }

  folder &amp;quot;pesan/&amp;quot; {
    [messages_fr.properties]
    [messages_en.properties]
  }

  [jbake.properties]
}

package &amp;quot;JBake Engine&amp;quot; {
  [Parser AsciiDoc] as parser
  [Template Engine\nThymeleaf] as thymeleaf
  [Generator] as generator
  [I18n Resolver] as i18n
}

package &amp;quot;Situs yang dihasilkan&amp;quot; {
  folder &amp;quot;fr/&amp;quot; {
    folder &amp;quot;blog/2025/&amp;quot; as blog_fr {
      [article1.html]
      [article2.html]
    }
  }

  folder &amp;quot;en/&amp;quot; {
    folder &amp;quot;blog/2025/&amp;quot; as blog_en {
      [article1.html] as article1_html_en
      [article2.html] as article2_html_en
    }
  }
}

content_fr --&amp;amp;gt; parser
content_en --&amp;amp;gt; parser
parser --&amp;amp;gt; generator

templates --&amp;amp;gt; thymeleaf
messages --&amp;amp;gt; i18n
i18n --&amp;amp;gt; thymeleaf
thymeleaf --&amp;amp;gt; generator
jbake.properties --&amp;amp;gt; generator

generator --&amp;amp;gt; blog_fr
generator --&amp;amp;gt; blog_en
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_i18n_templating_dengan_thymeleaf&amp;quot;&amp;gt;I18n templating dengan Thymeleaf&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_struktur_file_pesan&amp;quot;&amp;gt;Struktur file pesan&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Langkah pertama adalah membuat file properti untuk setiap bahasa yang didukung:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-text hljs&amp;quot; data-lang=&amp;quot;text&amp;quot;&amp;gt;src/jbake/templates/
├── messages.properties        # Fallback par défaut
├── messages_fr.properties     # Français
├── messages_en.properties     # Anglais
└── messages_de.properties     # Allemand&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_isi_file_pesan&amp;quot;&amp;gt;Isi file pesan&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Berikut contoh file`messages_fr.properties` :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-properties hljs&amp;quot; data-lang=&amp;quot;properties&amp;quot;&amp;gt;# Navigation
nav.home=Accueil
nav.blog=Blog
nav.about=À propos
nav.contact=Contact

# Articles
article.readmore=Lire la suite
article.published=Publié le
article.tags=Étiquettes
article.also.available=Également disponible en

# Interface
site.title=Mon Blog Technique
site.description=Partage de connaissances et expériences
footer.copyright=© 2025 Tous droits réservés
search.placeholder=Rechercher un article...&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Dan setaraannya dalam bahasa Inggris`messages_en.properties`:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-properties hljs&amp;quot; data-lang=&amp;quot;properties&amp;quot;&amp;gt;# Navigation
nav.home=Home
nav.blog=Blog
nav.about=About
nav.contact=Contact

# Articles
article.readmore=Read more
article.published=Published on
article.tags=Tags
article.also.available=Also available in

# Interface
site.title=My Tech Blog
site.description=Sharing knowledge and experiences
footer.copyright=© 2025 All rights reserved
search.placeholder=Search articles...&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_penggunaan_dalam_templat&amp;quot;&amp;gt;Penggunaan dalam templat&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Dalam template Thymeleaf Anda, gunakan sintaksis`#{}`untuk mengakses pesan :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-html hljs&amp;quot; data-lang=&amp;quot;html&amp;quot;&amp;gt;&amp;amp;lt;!DOCTYPE html&amp;amp;gt;
&amp;amp;lt;html xmlns:th=&amp;quot;http://www.thymeleaf.org&amp;quot;&amp;amp;gt;
&amp;amp;lt;head&amp;amp;gt;
    &amp;amp;lt;title th:text=&amp;quot;#{site.title}&amp;quot;&amp;amp;gt;Mon Blog&amp;amp;lt;/title&amp;amp;gt;
    &amp;amp;lt;meta name=&amp;quot;description&amp;quot; th:content=&amp;quot;#{site.description}&amp;quot; /&amp;amp;gt;
&amp;amp;lt;/head&amp;amp;gt;
&amp;amp;lt;body&amp;amp;gt;
    &amp;amp;lt;nav&amp;amp;gt;
        &amp;amp;lt;a th:href=&amp;quot;@{/}&amp;quot; th:text=&amp;quot;#{nav.home}&amp;quot;&amp;amp;gt;Accueil&amp;amp;lt;/a&amp;amp;gt;
        &amp;amp;lt;a th:href=&amp;quot;@{/blog/}&amp;quot; th:text=&amp;quot;#{nav.blog}&amp;quot;&amp;amp;gt;Blog&amp;amp;lt;/a&amp;amp;gt;
        &amp;amp;lt;a th:href=&amp;quot;@{/about.html}&amp;quot; th:text=&amp;quot;#{nav.about}&amp;quot;&amp;amp;gt;À propos&amp;amp;lt;/a&amp;amp;gt;
    &amp;amp;lt;/nav&amp;amp;gt;

    &amp;amp;lt;footer&amp;amp;gt;
        &amp;amp;lt;p th:text=&amp;quot;#{footer.copyright}&amp;quot;&amp;amp;gt;Copyright&amp;amp;lt;/p&amp;amp;gt;
    &amp;amp;lt;/footer&amp;amp;gt;
&amp;amp;lt;/body&amp;amp;gt;
&amp;amp;lt;/html&amp;amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_diagram_urutan_resolusi_i18n&amp;quot;&amp;gt;Diagram urutan - resolusi i18n&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
actor Auteur
participant JBake
participant &amp;quot;AsciiDoc\nParser&amp;quot; as parser
participant &amp;quot;Thymeleaf
Engine&amp;quot; as thymeleaf
participant &amp;quot;I18n\nResolver&amp;quot; as i18n
database &amp;quot;messages_*.properties&amp;quot; as messages

Auteur -&amp;amp;gt; JBake: jbake -b
activate JBake

JBake -&amp;amp;gt; parser: Lire article.fr.adoc
activate parser
parser --&amp;amp;gt; JBake: Contenu + métadonnées\n(lang=fr, article-id=xyz)
deactivate parser

JBake -&amp;amp;gt; parser: Lire article.en.adoc
activate parser
parser --&amp;amp;gt; JBake: Contenu + métadonnées\n(lang=en, article-id=xyz)
deactivate parser

JBake -&amp;amp;gt; thymeleaf: Générer page FR
activate thymeleaf

thymeleaf -&amp;amp;gt; i18n: Résoudre #{nav.home}
activate i18n
i18n -&amp;amp;gt; messages: Lire messages_fr.properties
messages --&amp;amp;gt; i18n: &amp;quot;Beranda&amp;quot;
i18n --&amp;amp;gt; thymeleaf: &amp;quot;Beranda&amp;quot;
deactivate i18n

thymeleaf -&amp;amp;gt; JBake: Rechercher traductions\n(article-id=xyz, lang!=fr)
JBake --&amp;amp;gt; thymeleaf: article.en.html trouvé

thymeleaf --&amp;amp;gt; JBake: /fr/blog/article.html
deactivate thymeleaf

JBake -&amp;amp;gt; thymeleaf: Générer page EN
activate thymeleaf

thymeleaf -&amp;amp;gt; i18n: Résoudre #{nav.home}
activate i18n
i18n -&amp;amp;gt; messages: Lire messages_en.properties
messages --&amp;amp;gt; i18n: &amp;quot;Rumah&amp;quot;
i18n --&amp;amp;gt; thymeleaf: &amp;quot;Rumah&amp;quot;
deactivate i18n

thymeleaf -&amp;amp;gt; JBake: Rechercher traductions\n(article-id=xyz, lang!=en)
JBake --&amp;amp;gt; thymeleaf: article.fr.html trouvé

thymeleaf --&amp;amp;gt; JBake: /en/blog/article.html
deactivate thymeleaf

JBake --&amp;amp;gt; Auteur: Site généré
deactivate JBake
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_konfigurasi_lokal_di_jbake&amp;quot;&amp;gt;Konfigurasi lokal di JBake&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Di file Anda`jbake.properties`, tetapkan locale default :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-properties hljs&amp;quot; data-lang=&amp;quot;properties&amp;quot;&amp;gt;# Locale par défaut
thymeleaf.locale=fr

# Encodage
template.encoding=UTF-8&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_internasionalisasi_artikel_organisasi_berdasarkan_folder&amp;quot;&amp;gt;Internasionalisasi artikel : organisasi berdasarkan folder&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_diagram_alir_flow_generasi&amp;quot;&amp;gt;Diagram alir (Flow) - Generasi&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
start

:Lire jbake.properties;
:Charger configuration i18n;

partition &amp;quot;Untuk setiap file AsciiDoc&amp;quot; {
  :Lire métadonnées\n(:jbake-lang:, :jbake-article-id:);
  :Parser contenu AsciiDoc;
  :Stocker en mémoire avec langue;
}

partition &amp;quot;Untuk setiap template&amp;quot; {
  :Charger template Thymeleaf;
  :Identifier langue cible;
  :Charger messages_{lang}.properties;

  partition &amp;quot;Penyaringan konten&amp;quot; {
    :Filtrer articles par langue;
    :Grouper traductions\npar article-id;
  }

  :Appliquer template avec i18n;
  :Générer HTML dans /{lang}/;
}

:Copier assets statiques;
:Générer sitemap multilingue;
:Générer flux RSS par langue;

stop
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_struktur_folder&amp;quot;&amp;gt;Struktur folder&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Alih-alih menggunakan ekstensi dalam nama file, saya memilih organisasi berdasarkan folder yang memberikan lebih banyak kejelasan dan kemudahan pemeliharaan:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-text hljs&amp;quot; data-lang=&amp;quot;text&amp;quot;&amp;gt;content/blog/
├── 2024/
│   ├── fr/
│   │   ├── introduction-jbake.adoc
│   │   ├── guide-thymeleaf.adoc
│   │   └── astuces-asciidoc.adoc
│   └── en/
│       ├── introduction-jbake.adoc
│       ├── thymeleaf-guide.adoc
│       └── asciidoc-tips.adoc
└── 2025/
    ├── fr/
    │   └── internationalisation-jbake.adoc
    └── en/
        └── jbake-internationalization.adoc&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_keuntungan_dari_pendekatan_ini&amp;quot;&amp;gt;Keuntungan dari pendekatan ini&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Struktur ini memiliki beberapa keuntungan :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;pemisahan yang jelas&amp;lt;/strong&amp;gt;: setiap bahasa memiliki ruang sendiri&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;penamaan fleksibel&amp;lt;/strong&amp;gt;: file dapat memiliki nama yang berbeda tergantung pada bahasa&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Skalabilitas&amp;lt;/strong&amp;gt;: mudah menambahkan bahasa baru&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Organisasi alamiah&amp;lt;/strong&amp;gt;: mengikuti logika waktu JBake&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_metadata_artikel&amp;quot;&amp;gt;Metadata artikel&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Setiap artikel harus mengandung metadata untuk memungkinkan hubungan antara terjemahan. Ini adalah contoh :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Versi Prancis&amp;lt;/strong&amp;gt;(&amp;lt;code&amp;gt;2025/fr/internationalisation-jbake.adoc&amp;lt;/code&amp;gt;) :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-asciidoc hljs&amp;quot; data-lang=&amp;quot;asciidoc&amp;quot;&amp;gt;= Internationalisation d&amp;#39;un site statique JBake
:jbake-type: post
:jbake-status: published
:jbake-date: 2025-10-20
:jbake-lang: fr
:jbake-article-id: jbake-i18n-thymeleaf
:jbake-tags: jbake, thymeleaf, i18n
:jbake-description: Guide pour mettre en place l&amp;#39;i18n avec JBake&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Versi bahasa Inggris&amp;lt;/strong&amp;gt; (&amp;lt;code&amp;gt;2025/en/jbake-internationalization.adoc&amp;lt;/code&amp;gt;) :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-asciidoc hljs&amp;quot; data-lang=&amp;quot;asciidoc&amp;quot;&amp;gt;= Internationalizing a JBake Static Site
:jbake-type: post
:jbake-status: published
:jbake-date: 2025-10-20
:jbake-lang: en
:jbake-article-id: jbake-i18n-thymeleaf
:jbake-tags: jbake, thymeleaf, i18n
:jbake-description: Guide to implement i18n with JBake&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;CATATAN: Atribut`:jbake-article-id:`est penting : ia memungkinkan untuk menghubungkan berbagai terjemahan artikel yang sama.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_konfigurasi_url&amp;quot;&amp;gt;Konfigurasi URL&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;di dalam`jbake.properties`, atur pola URL untuk mencakup bahasa :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-properties hljs&amp;quot; data-lang=&amp;quot;properties&amp;quot;&amp;gt;# Pattern d&amp;#39;URL avec langue
post.permalink.pattern=:lang/blog/:year/:name.html

# Langue par défaut
site.default.lang=fr&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Akan menghasilkan URL bertipe :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;code&amp;gt;/fr/blog/2025/internationalisation-jbake.html&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;code&amp;gt;/en/blog/2025/jbake-internationalization.html&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_templates_untuk_tampilan_multibahasa&amp;quot;&amp;gt;Templates untuk tampilan multibahasa&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_template_artikel_dengan_pemilih_bahasa&amp;quot;&amp;gt;Template artikel dengan pemilih bahasa&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Buat template`post.html`yang menampilkan terjemahan yang tersedia :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-html hljs&amp;quot; data-lang=&amp;quot;html&amp;quot;&amp;gt;&amp;amp;lt;!DOCTYPE html&amp;amp;gt;
&amp;amp;lt;html xmlns:th=&amp;quot;http://www.thymeleaf.org&amp;quot;&amp;amp;gt;
&amp;amp;lt;head&amp;amp;gt;
    &amp;amp;lt;title th:text=&amp;quot;${content.title}&amp;quot;&amp;amp;gt;Article&amp;amp;lt;/title&amp;amp;gt;
&amp;amp;lt;/head&amp;amp;gt;
&amp;amp;lt;body&amp;amp;gt;
    &amp;amp;lt;article&amp;amp;gt;
        &amp;amp;lt;header&amp;amp;gt;
            &amp;amp;lt;h1 th:text=&amp;quot;${content.title}&amp;quot;&amp;amp;gt;Titre&amp;amp;lt;/h1&amp;amp;gt;

            &amp;amp;lt;div class=&amp;quot;article-meta&amp;quot;&amp;amp;gt;
                &amp;amp;lt;time th:text=&amp;quot;${#dates.format(content.date, &amp;#39;dd MMMM yyyy&amp;#39;)}&amp;quot;
                      th:attr=&amp;quot;datetime=${#dates.format(content.date, &amp;#39;yyyy-MM-dd&amp;#39;)}&amp;quot;&amp;amp;gt;
                    Date
                &amp;amp;lt;/time&amp;amp;gt;

                &amp;amp;lt;!-- Sélecteur de traductions --&amp;amp;gt;
                &amp;amp;lt;div class=&amp;quot;translations&amp;quot; th:if=&amp;quot;${content[&amp;#39;article-id&amp;#39;]}&amp;quot;&amp;amp;gt;
                    &amp;amp;lt;span th:text=&amp;quot;#{article.also.available}&amp;quot;&amp;amp;gt;Aussi disponible en :&amp;amp;lt;/span&amp;amp;gt;
                    &amp;amp;lt;ul class=&amp;quot;language-list&amp;quot;&amp;amp;gt;
                        &amp;amp;lt;li th:each=&amp;quot;post : ${published_posts}&amp;quot;
                            th:if=&amp;quot;${post[&amp;#39;article-id&amp;#39;] == content[&amp;#39;article-id&amp;#39;] and post.lang != content.lang}&amp;quot;&amp;amp;gt;
                            &amp;amp;lt;a th:href=&amp;quot;${post.uri}&amp;quot;
                               th:text=&amp;quot;${post.lang.toUpperCase()}&amp;quot;&amp;amp;gt;
                                LANG
                            &amp;amp;lt;/a&amp;amp;gt;
                        &amp;amp;lt;/li&amp;amp;gt;
                    &amp;amp;lt;/ul&amp;amp;gt;
                &amp;amp;lt;/div&amp;amp;gt;
            &amp;amp;lt;/div&amp;amp;gt;
        &amp;amp;lt;/header&amp;amp;gt;

        &amp;amp;lt;div class=&amp;quot;content&amp;quot; th:utext=&amp;quot;${content.body}&amp;quot;&amp;amp;gt;
            Contenu de l&amp;#39;article
        &amp;amp;lt;/div&amp;amp;gt;

        &amp;amp;lt;footer class=&amp;quot;article-footer&amp;quot;&amp;amp;gt;
            &amp;amp;lt;div class=&amp;quot;tags&amp;quot; th:if=&amp;quot;${content.tags}&amp;quot;&amp;amp;gt;
                &amp;amp;lt;span th:text=&amp;quot;#{article.tags}&amp;quot;&amp;amp;gt;Étiquettes :&amp;amp;lt;/span&amp;amp;gt;
                &amp;amp;lt;span th:each=&amp;quot;tag : ${content.tags}&amp;quot;&amp;amp;gt;
                    &amp;amp;lt;a th:href=&amp;quot;@{/tags/{tag}.html(tag=${tag})}&amp;quot;
                       th:text=&amp;quot;${tag}&amp;quot;&amp;amp;gt;tag&amp;amp;lt;/a&amp;amp;gt;
                &amp;amp;lt;/span&amp;amp;gt;
            &amp;amp;lt;/div&amp;amp;gt;
        &amp;amp;lt;/footer&amp;amp;gt;
    &amp;amp;lt;/article&amp;amp;gt;
&amp;amp;lt;/body&amp;amp;gt;
&amp;amp;lt;/html&amp;amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_indeks_difilter_berdasarkan_bahasa&amp;quot;&amp;gt;Indeks difilter berdasarkan bahasa&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Buat templat indeks untuk setiap bahasa :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;index.html&amp;lt;/strong&amp;gt;(indeks Perancis) :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-html hljs&amp;quot; data-lang=&amp;quot;html&amp;quot;&amp;gt;&amp;amp;lt;!DOCTYPE html&amp;amp;gt;
&amp;amp;lt;html xmlns:th=&amp;quot;http://www.thymeleaf.org&amp;quot;&amp;amp;gt;
&amp;amp;lt;head&amp;amp;gt;
    &amp;amp;lt;title th:text=&amp;quot;#{site.title}&amp;quot;&amp;amp;gt;Mon Blog&amp;amp;lt;/title&amp;amp;gt;
&amp;amp;lt;/head&amp;amp;gt;
&amp;amp;lt;body&amp;amp;gt;
    &amp;amp;lt;main&amp;amp;gt;
        &amp;amp;lt;h1 th:text=&amp;quot;#{nav.blog}&amp;quot;&amp;amp;gt;Blog&amp;amp;lt;/h1&amp;amp;gt;

        &amp;amp;lt;div class=&amp;quot;articles-list&amp;quot;&amp;amp;gt;
            &amp;amp;lt;article th:each=&amp;quot;post : ${published_posts}&amp;quot;
                     th:if=&amp;quot;${post.lang == &amp;#39;fr&amp;#39;}&amp;quot;&amp;amp;gt;
                &amp;amp;lt;h2&amp;amp;gt;
                    &amp;amp;lt;a th:href=&amp;quot;${post.uri}&amp;quot; th:text=&amp;quot;${post.title}&amp;quot;&amp;amp;gt;Titre&amp;amp;lt;/a&amp;amp;gt;
                &amp;amp;lt;/h2&amp;amp;gt;
                &amp;amp;lt;time th:text=&amp;quot;${#dates.format(post.date, &amp;#39;dd MMMM yyyy&amp;#39;)}&amp;quot;&amp;amp;gt;
                    Date
                &amp;amp;lt;/time&amp;amp;gt;
                &amp;amp;lt;p th:text=&amp;quot;${post.description}&amp;quot;&amp;amp;gt;Description&amp;amp;lt;/p&amp;amp;gt;
                &amp;amp;lt;a th:href=&amp;quot;${post.uri}&amp;quot; th:text=&amp;quot;#{article.readmore}&amp;quot;&amp;amp;gt;
                    Lire la suite
                &amp;amp;lt;/a&amp;amp;gt;
            &amp;amp;lt;/article&amp;amp;gt;
        &amp;amp;lt;/div&amp;amp;gt;
    &amp;amp;lt;/main&amp;amp;gt;
&amp;amp;lt;/body&amp;amp;gt;
&amp;amp;lt;/html&amp;amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;index_en.html&amp;lt;/strong&amp;gt;(indeks bahasa Inggris) :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-html hljs&amp;quot; data-lang=&amp;quot;html&amp;quot;&amp;gt;&amp;amp;lt;!DOCTYPE html&amp;amp;gt;
&amp;amp;lt;html xmlns:th=&amp;quot;http://www.thymeleaf.org&amp;quot;&amp;amp;gt;
&amp;amp;lt;head&amp;amp;gt;
    &amp;amp;lt;title th:text=&amp;quot;#{site.title}&amp;quot;&amp;amp;gt;My Blog&amp;amp;lt;/title&amp;amp;gt;
&amp;amp;lt;/head&amp;amp;gt;
&amp;amp;lt;body&amp;amp;gt;
    &amp;amp;lt;main&amp;amp;gt;
        &amp;amp;lt;h1 th:text=&amp;quot;#{nav.blog}&amp;quot;&amp;amp;gt;Blog&amp;amp;lt;/h1&amp;amp;gt;

        &amp;amp;lt;div class=&amp;quot;articles-list&amp;quot;&amp;amp;gt;
            &amp;amp;lt;article th:each=&amp;quot;post : ${published_posts}&amp;quot;
                     th:if=&amp;quot;${post.lang == &amp;#39;en&amp;#39;}&amp;quot;&amp;amp;gt;
                &amp;amp;lt;h2&amp;amp;gt;
                    &amp;amp;lt;a th:href=&amp;quot;${post.uri}&amp;quot; th:text=&amp;quot;${post.title}&amp;quot;&amp;amp;gt;Title&amp;amp;lt;/a&amp;amp;gt;
                &amp;amp;lt;/h2&amp;amp;gt;
                &amp;amp;lt;time th:text=&amp;quot;${#dates.format(post.date, &amp;#39;dd MMMM yyyy&amp;#39;)}&amp;quot;&amp;amp;gt;
                    Date
                &amp;amp;lt;/time&amp;amp;gt;
                &amp;amp;lt;p th:text=&amp;quot;${post.description}&amp;quot;&amp;amp;gt;Description&amp;amp;lt;/p&amp;amp;gt;
                &amp;amp;lt;a th:href=&amp;quot;${post.uri}&amp;quot; th:text=&amp;quot;#{article.readmore}&amp;quot;&amp;amp;gt;
                    Read more
                &amp;amp;lt;/a&amp;amp;gt;
            &amp;amp;lt;/article&amp;amp;gt;
        &amp;amp;lt;/div&amp;amp;gt;
    &amp;amp;lt;/main&amp;amp;gt;
&amp;amp;lt;/body&amp;amp;gt;
&amp;amp;lt;/html&amp;amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_navigasi_antara_bahasa&amp;quot;&amp;gt;Navigasi antara bahasa&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_pemilih_bahasa_global&amp;quot;&amp;gt;Pemilih bahasa global&amp;lt;/h3&amp;gt;

&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_diagram_alur_pembacaan_pengguna&amp;quot;&amp;gt;Diagram alur - Pembacaan pengguna&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
start

:Utilisateur accède au site;

if (Langue préférée ?) then (FR)
  :Afficher /index.html;
  :Lister articles FR;
else (EN)
  :Afficher /en/index.html;
  :Lister articles EN;
endif

:Utilisateur clique sur article;

:Afficher article\navec métadonnées;

if (Traductions disponibles ?) then (oui)
  :Afficher sélecteur\nde traductions;

  if (Changement de langue ?) then (oui)
    :Rediriger vers\ntraduction;
    :Afficher article\ndans nouvelle langue;
  else (non)
    :Continuer lecture;
  endif
else (non)
  :Continuer lecture;
endif

stop
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Tambahkan pemilih bahasa di template utama Anda:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-html hljs&amp;quot; data-lang=&amp;quot;html&amp;quot;&amp;gt;&amp;amp;lt;nav class=&amp;quot;language-switcher&amp;quot;&amp;amp;gt;
    &amp;amp;lt;a href=&amp;quot;/index.html&amp;quot;
       th:classappend=&amp;quot;${content.lang == &amp;#39;fr&amp;#39;} ? &amp;#39;active&amp;#39;&amp;quot;
       title=&amp;quot;Français&amp;quot;&amp;amp;gt;
        🇫🇷 FR
    &amp;amp;lt;/a&amp;amp;gt;
    &amp;amp;lt;a href=&amp;quot;/en/index.html&amp;quot;
       th:classappend=&amp;quot;${content.lang == &amp;#39;en&amp;#39;} ? &amp;#39;active&amp;#39;&amp;quot;
       title=&amp;quot;English&amp;quot;&amp;amp;gt;
        🇬🇧 EN
    &amp;amp;lt;/a&amp;amp;gt;
&amp;amp;lt;/nav&amp;amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_gaya_css_untuk_penilih&amp;quot;&amp;gt;Gaya CSS untuk penilih&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-css hljs&amp;quot; data-lang=&amp;quot;css&amp;quot;&amp;gt;.language-switcher {
    display: flex;
    gap: 1rem;
    padding: 0.5rem;
    background: #f5f5f5;
    border-radius: 4px;
}

.language-switcher a {
    padding: 0.5rem 1rem;
    text-decoration: none;
    color: #333;
    border-radius: 4px;
    transition: background 0.2s;
}

.language-switcher a:hover {
    background: #e0e0e0;
}

.language-switcher a.active {
    background: #007bff;
    color: white;
}

.translations {
    margin: 1rem 0;
    padding: 1rem;
    background: #f8f9fa;
    border-left: 4px solid #007bff;
}

.language-list {
    display: inline-flex;
    gap: 0.5rem;
    list-style: none;
    padding: 0;
    margin: 0;
}

.language-list li::after {
    content: &amp;quot;•&amp;quot;;
    margin-left: 0.5rem;
}

.language-list li:last-child::after {
    content: &amp;quot;&amp;quot;;
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_umpan_rss_per_bahasa&amp;quot;&amp;gt;Umpan RSS per bahasa&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Untuk memiliki feed RSS terpisah per bahasa, buat template yang terpisah :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;feed.xml&amp;lt;/strong&amp;gt;(aliran Prancis) :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-xml hljs&amp;quot; data-lang=&amp;quot;xml&amp;quot;&amp;gt;&amp;amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;amp;gt;
&amp;amp;lt;rss version=&amp;quot;2.0&amp;quot; xmlns:th=&amp;quot;http://www.thymeleaf.org&amp;quot;&amp;amp;gt;
    &amp;amp;lt;channel&amp;amp;gt;
        &amp;amp;lt;title th:text=&amp;quot;#{site.title}&amp;quot;&amp;amp;gt;Mon Blog&amp;amp;lt;/title&amp;amp;gt;
        &amp;amp;lt;link th:text=&amp;quot;${config.site_host}&amp;quot;&amp;amp;gt;http://example.com&amp;amp;lt;/link&amp;amp;gt;
        &amp;amp;lt;description th:text=&amp;quot;#{site.description}&amp;quot;&amp;amp;gt;Description&amp;amp;lt;/description&amp;amp;gt;
        &amp;amp;lt;language&amp;amp;gt;fr&amp;amp;lt;/language&amp;amp;gt;

        &amp;amp;lt;item th:each=&amp;quot;post : ${published_posts}&amp;quot;
              th:if=&amp;quot;${post.lang == &amp;#39;fr&amp;#39;}&amp;quot;&amp;amp;gt;
            &amp;amp;lt;title th:text=&amp;quot;${post.title}&amp;quot;&amp;amp;gt;Titre&amp;amp;lt;/title&amp;amp;gt;
            &amp;amp;lt;link th:text=&amp;quot;${config.site_host + post.uri}&amp;quot;&amp;amp;gt;Lien&amp;amp;lt;/link&amp;amp;gt;
            &amp;amp;lt;pubDate th:text=&amp;quot;${#dates.format(post.date, &amp;#39;EEE, dd MMM yyyy HH:mm:ss Z&amp;#39;)}&amp;quot;&amp;amp;gt;
                Date
            &amp;amp;lt;/pubDate&amp;amp;gt;
            &amp;amp;lt;description th:text=&amp;quot;${post.description}&amp;quot;&amp;amp;gt;Description&amp;amp;lt;/description&amp;amp;gt;
        &amp;amp;lt;/item&amp;amp;gt;
    &amp;amp;lt;/channel&amp;amp;gt;
&amp;amp;lt;/rss&amp;amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_praktik_terbaik_dan_tips&amp;quot;&amp;gt;Praktik terbaik dan tips&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_1_konsistensi_id_artikel&amp;quot;&amp;gt;1. Konsistensi ID artikel&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Pastikan bahwa`:jbake-article-id:`adalah sama untuk semua terjemahan dari artikel yang sama. Gunakan format yang konsisten :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Pilih identifier dalam bahasa Inggris untuk universalitas&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Gunakan tanda hubung untuk memisahkan kata&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Hindari karakter khusus&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_2_tanggal_konsisten&amp;quot;&amp;gt;2. Tanggal konsisten&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Semua terjemahan suatu artikel harus memiliki tanggal publikasi yang sama (&amp;lt;code&amp;gt;:jbake-date:&amp;lt;/code&amp;gt;). Ini memudahkan pengurutan dan penampilan kronologis.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_3_tags_multibahasa&amp;quot;&amp;gt;3. Tags multibahasa&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Untuk tag, Anda memiliki dua opsi :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Option 1: tag universal bahasa Inggris&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-asciidoc hljs&amp;quot; data-lang=&amp;quot;asciidoc&amp;quot;&amp;gt;:jbake-tags: java, spring-boot, microservices&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Opsi 2 : Tag diterjemahkan dengan pemetaan&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-asciidoc hljs&amp;quot; data-lang=&amp;quot;asciidoc&amp;quot;&amp;gt;# Version française
:jbake-tags: java, spring-boot, microservices

# Version anglaise
:jbake-tags: java, spring-boot, microservices&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_4_pengelolaan_artikel_yang_tidak_diterjemahkan&amp;quot;&amp;gt;4. Pengelolaan artikel yang tidak diterjemahkan&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Tidak wajib untuk menerjemahkan semua artikel. Jika sebuah artikel hanya ada dalam satu bahasa, artikel tersebut tidak akan muncul dalam daftar bahasa lain.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_5_peta_situs_multibahasa&amp;quot;&amp;gt;5. Peta situs multibahasa&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Buat sitemap yang mencakup semua bahasa&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-xml hljs&amp;quot; data-lang=&amp;quot;xml&amp;quot;&amp;gt;&amp;amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;amp;gt;
&amp;amp;lt;urlset xmlns=&amp;quot;http://www.sitemaps.org/schemas/sitemap/0.9&amp;quot;
        xmlns:xhtml=&amp;quot;http://www.w3.org/1999/xhtml&amp;quot;
        xmlns:th=&amp;quot;http://www.thymeleaf.org&amp;quot;&amp;amp;gt;
    &amp;amp;lt;url th:each=&amp;quot;post : ${published_posts}&amp;quot;&amp;amp;gt;
        &amp;amp;lt;loc th:text=&amp;quot;${config.site_host + post.uri}&amp;quot;&amp;amp;gt;URL&amp;amp;lt;/loc&amp;amp;gt;
        &amp;amp;lt;lastmod th:text=&amp;quot;${#dates.format(post.date, &amp;#39;yyyy-MM-dd&amp;#39;)}&amp;quot;&amp;amp;gt;Date&amp;amp;lt;/lastmod&amp;amp;gt;

        &amp;amp;lt;!-- Liens alternatifs pour les traductions --&amp;amp;gt;
        &amp;amp;lt;xhtml:link th:each=&amp;quot;translation : ${published_posts}&amp;quot;
                    th:if=&amp;quot;${translation[&amp;#39;article-id&amp;#39;] == post[&amp;#39;article-id&amp;#39;] and translation.lang != post.lang}&amp;quot;
                    rel=&amp;quot;alternate&amp;quot;
                    th:attr=&amp;quot;hreflang=${translation.lang},href=${config.site_host + translation.uri}&amp;quot; /&amp;amp;gt;
    &amp;amp;lt;/url&amp;amp;gt;
&amp;amp;lt;/urlset&amp;amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_kesimpulan&amp;quot;&amp;gt;Kesimpulan&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Internasionalisasi situs JBake dengan Thymeleaf adalah pendekatan yang kuat dan dapat dipelihara. Dengan memisahkan i18n dari templating (melalui file pesan) dan i18n dari konten (melalui organisasi dalam folder), Anda mendapatkan sistem yang fleksibel yang dapat berkembang dengan mudah.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Poin-poin penting yang harus diingat:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;File pesan Thymeleaf&amp;lt;/strong&amp;gt;untuk antarmuka pengguna&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Organisasi berdasarkan folder&amp;lt;/strong&amp;gt;(tahun/bahasa) untuk artikel&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;ID artikel&amp;lt;/strong&amp;gt;untuk menautkan terjemahan&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;template khusus&amp;lt;/strong&amp;gt;untuk setiap bahasa&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;URL eksplisit&amp;lt;/strong&amp;gt;memasukkan kode bahasa&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_diagram_penerapan&amp;quot;&amp;gt;Diagram penerapan&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
node &amp;quot;Mesin pengembangan&amp;quot; {
  artifact &amp;quot;Sumber&amp;quot; {
    folder &amp;quot;isi/&amp;quot;
    folder &amp;quot;templates/&amp;quot;
    folder &amp;quot;aset/&amp;quot;
  }

  component &amp;quot;JBake CLI&amp;quot; as jbake

  Sources --&amp;amp;gt; jbake : jbake -b
}

node &amp;quot;Server build\n(CI/CD)&amp;quot; {
  component &amp;quot;GitHub Actions
atau GitLab CI&amp;quot; as ci

  jbake --&amp;amp;gt; ci : push
}

cloud &amp;quot;CDN / Penyediaan&amp;quot; {
  node &amp;quot;Server web statis&amp;quot; {
    artifact &amp;quot;situs yang dihasilkan&amp;quot; {
      folder &amp;quot;/id/blog/&amp;quot;
      folder &amp;quot;/en/blog/&amp;quot;
      folder &amp;quot;/assets/&amp;quot;
    }
  }
}

ci --&amp;amp;gt; &amp;quot;situs yang dihasilkan&amp;quot; : déploiement

actor &amp;quot;Pembaca&amp;quot; as users

users --&amp;amp;gt; &amp;quot;Server web statis&amp;quot; : HTTPS
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Arsitektur ini memungkinkan Anda untuk memulai dengan dua bahasa dengan mudah dan menambah bahasa lain tanpa refactoring yang besar. Semuanya tetap sepenuhnya statis dan berkinerja, setia kepada filosofi JBake.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Selamat pengembangan multilingue ! 🌍&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>Instal dan verifikasi Tor Browser di Ubuntu</title>
            <link >https://cheroliv.com/blog/2025/0094_tor_browser_install_post.html</link>
            <pubDate>Sun, 5 Oct 2025 00:00:00 +0000</pubDate>
            <guid isPermaLink="false">blog/2025/0094_tor_browser_install_post.html</guid>
            <description>&amp;lt;div id=&amp;quot;toc&amp;quot; class=&amp;quot;toc&amp;quot;&amp;gt;
&amp;lt;div id=&amp;quot;toctitle&amp;quot;&amp;gt;Table of Contents&amp;lt;/div&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel1&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_pendahuluan&amp;quot;&amp;gt;Pendahuluan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_unduhan_tor_dan_tanda_tandanya&amp;quot;&amp;gt;Unduhan Tor dan tanda tandanya&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_verifikasi_tanda_tangan_kriptografis&amp;quot;&amp;gt;Verifikasi tanda tangan kriptografis&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_1_mengimpor_kunci_publik_dari_proyek_tor&amp;quot;&amp;gt;1. Mengimpor kunci publik dari Proyek Tor&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_2_memeriksa_tanda_tangan&amp;quot;&amp;gt;2. Memeriksa tanda tangan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_contoh_praktis_tanda_tangan_valid_tetapi_kunci_tidak_bersertifikat&amp;quot;&amp;gt;Contoh praktis: Tanda tangan valid tetapi kunci tidak bersertifikat&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_mengapa_harus_memeriksa_tanda_tangan&amp;quot;&amp;gt;Mengapa harus memeriksa tanda tangan?&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_ekstraksi_dan_peluncuran_tor_browser&amp;quot;&amp;gt;Ekstraksi dan peluncuran Tor Browser&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_integrasi_ke_desktop_ubuntu&amp;quot;&amp;gt;Integrasi ke desktop Ubuntu&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_menjelajahi_sumber_daya_pendidikan_dengan_tor&amp;quot;&amp;gt;Menjelajahi sumber daya pendidikan dengan Tor&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_pembaruan_tor_browser&amp;quot;&amp;gt;Pembaruan Tor Browser&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_kesimpulan&amp;quot;&amp;gt;Kesimpulan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_referensi&amp;quot;&amp;gt;Referensi&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_pendahuluan&amp;quot;&amp;gt;Pendahuluan&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Di dalam dunia di mana keprivasian digital semakin terancam, &amp;lt;strong&amp;gt;Tor Browser&amp;lt;/strong&amp;gt; tetap merupakan alat penting untuk melindungi anonimnya dan kebebasan informasinya. Namun, sangat penting untuk tidak pernah menjalankan perangkat lunak yang diunduh tanpa memiliki&amp;lt;strong&amp;gt;memverifikasi keasliannya&amp;lt;/strong&amp;gt;. Artikel ini menyajikan metode lengkap untuk:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;olist arabic&amp;quot;&amp;gt;
&amp;lt;ol class=&amp;quot;arabic&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Unduh &amp;lt;strong&amp;gt;Tor Browser&amp;lt;/strong&amp;gt; dan tandatangannya`.asc`&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Memeriksa validitas file yang diunduh&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Pasang dan jalankan &amp;lt;strong&amp;gt;Tor Browser&amp;lt;/strong&amp;gt; di Ubuntu&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Menambahkan ke menu desktop untuk penggunaan harian&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Jelajahi sumber daya pendidikan dan OSINT etik melalui Tor&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_unduhan_tor_dan_tanda_tandanya&amp;quot;&amp;gt;Unduhan Tor dan tanda tandanya&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Buatlah folder khusus untuk browser Tor :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;mkdir -p ~/workspace/tipiak/tor
cd ~/workspace/tipiak/tor&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Unduh kemudian arsip peramban Tor dan tanda tandanya:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;wget https://www.torproject.org/dist/torbrowser/14.5.7/tor-browser-linux-x86_64-14.5.7.tar.xz
wget https://www.torproject.org/dist/torbrowser/14.5.7/tor-browser-linux-x86_64-14.5.7.tar.xz.asc&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_verifikasi_tanda_tangan_kriptografis&amp;quot;&amp;gt;Verifikasi tanda tangan kriptografis&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Tahap ini adalah&amp;lt;strong&amp;gt;diperlukan&amp;lt;/strong&amp;gt;agar memastikan bahwa file yang diunduh benar-benar berasal dari &amp;lt;strong&amp;gt;Tor Project&amp;lt;/strong&amp;gt; dan tidak telah dimodifikasi.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_1_mengimpor_kunci_publik_dari_proyek_tor&amp;quot;&amp;gt;1. Mengimpor kunci publik dari Proyek Tor&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;gpg --auto-key-locate nodefault,wkd --locate-keys torbrowser@torproject.org&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Pastikan kunci telah diimpor dengan baik :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;gpg --list-keys torbrowser@torproject.org&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_2_memeriksa_tanda_tangan&amp;quot;&amp;gt;2. Memeriksa tanda tangan&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;gpg --verify tor-browser-linux-x86_64-14.5.7.tar.xz.asc tor-browser-linux-x86_64-14.5.7.tar.xz&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Jika semuanya benar, pesan berikutnya akan ditampilkan:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-none hljs&amp;quot;&amp;gt;gpg: Good signature from &amp;quot;Tor Browser Developers (signing key)&amp;quot;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Jika ada peringatan kunci tidak bersertifikat, itu hanya berarti bahwa Anda belum menandatangani kunci kepercayaan proyek Tor, tapi tandatangannya tetap valid.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_contoh_praktis_tanda_tangan_valid_tetapi_kunci_tidak_bersertifikat&amp;quot;&amp;gt;Contoh praktis: Tanda tangan valid tetapi kunci tidak bersertifikat&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Seringkali menemukan pesan seperti ini saat verifikasi :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-none hljs&amp;quot;&amp;gt;gpg --verify tor-browser-linux-x86_64-14.5.7.tar.xz.asc tor-browser-linux-x86_64-14.5.7.tar.xz
gpg: Signature faite le mar. 16 sept. 2025 14:26:26 CEST
gpg:                avec la clef RSA CAAE408AEBE2288E96FC5D5E157432CF78A65729
gpg: Bonne signature de « Tor Browser Developers (signing key) &amp;amp;lt;torbrowser@torproject.org&amp;amp;gt; » [inconnu]
gpg: Attention : cette clef n&amp;#39;est pas certifiée avec une signature de confiance.
gpg:          Rien n&amp;#39;indique que la signature appartient à son propriétaire.
Empreinte de clef principale : EF6E 286D DA85 EA2A 4BA7  DE68 4E2C 6E87 9329 8290
     Empreinte de la sous-clef : CAAE 408A EBE2 288E 96FC  5D5E 1574 32CF 78A6 5729&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Pesan ini menunjukkan bahwa tanda tangan secara teknis &amp;quot;baik&amp;quot; (file tidak diubah), tetapi sistem GPG Anda tidak dapat &amp;lt;strong&amp;gt;mencertifikasi kepercayaan&amp;lt;/strong&amp;gt; pada kunci itu sendiri. Untuk menghilangkan keraguan ini, sangat penting untuk membandingkan sidik jari kunci utama (&amp;lt;code&amp;gt;EF6E 286D DA85 EA2A 4BA7 DE68 4E2C 6E87 9329 8290&amp;lt;/code&amp;gt;) dengan yang dipublikasikan pada situs resmi Tor Project.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Menurut situs resmi, jejak yang diharapkan adalah :`EF6E286DDA85EA2A4BA7DE684E2C6E8793298290`.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Karena jejak yang ditampilkan oleh`gpg`correspond &amp;lt;strong&amp;gt;tepat&amp;lt;/strong&amp;gt; dengan yang ada di situs resmi, Anda dapat yakin bahwa kunci tersebut sah dan file tersebut aman. Peringatan &amp;#39;kunci tidak bersertifikat&amp;#39; kemudian merupakan informasi tentang konfigurasi GPG lokal Anda, dan bukan tentang validitas tanda tangan atau keaslian file.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_mengapa_harus_memeriksa_tanda_tangan&amp;quot;&amp;gt;Mengapa harus memeriksa tanda tangan?&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Verifikasi tanda tangan GPG adalah tindakan fundamental keamanan digital. Memungkinkan untuk :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;memastikan&amp;lt;strong&amp;gt;keaslian&amp;lt;/strong&amp;gt;perangkat lunak yang diunduh&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Mencegah instalasi file yang dikompromikan oleh aktor berbahaya&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Mengikuti&amp;lt;strong&amp;gt;kekuasaan digital&amp;lt;/strong&amp;gt;individual&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Mendukung budaya digital yang bertanggung jawab dan etis&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
left to right direction
actor &amp;quot;Pengguna&amp;quot; as U
actor &amp;quot;server TorProject&amp;quot; as S

usecase &amp;quot;Unduh Tor dan tandatangannya&amp;quot; as D
usecase &amp;quot;Impor kunci publik&amp;quot; as K
usecase &amp;quot;Verifikasi tanda tangan GPG&amp;quot; as V
usecase &amp;quot;Instal dan jalankan Tor&amp;quot; as I

U --&amp;amp;gt; D
U --&amp;amp;gt; K
U --&amp;amp;gt; V
U --&amp;amp;gt; I
S --&amp;amp;gt; D

@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_ekstraksi_dan_peluncuran_tor_browser&amp;quot;&amp;gt;Ekstraksi dan peluncuran Tor Browser&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Ekstrak arsip yang diunduh:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;tar -xvf tor-browser-linux-x86_64-14.5.7.tar.xz
cd tor-browser&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Jalankan lalu peramban:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;./start-tor-browser.desktop&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Sa saat peluncuran pertama, jendela konfigurasi akan terbuka untuk membangun koneksi ke jaringan Tor.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_integrasi_ke_desktop_ubuntu&amp;quot;&amp;gt;Integrasi ke desktop Ubuntu&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Untuk menambahkan &amp;lt;strong&amp;gt;Tor Browser&amp;lt;/strong&amp;gt; dalam menu aplikasi :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;./start-tor-browser.desktop --register-app&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Anda kemudian :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Mulai &amp;lt;strong&amp;gt;Tor Browser&amp;lt;/strong&amp;gt; dari menu aplikasi&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Tambahkan ke favorit dock&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Buat pintasan keyboard jika diinginkan&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
start
:Téléchargement de Tor;
:Importation de la clé GPG;
:Vérification de la signature;
if (Signature valide ?) then (Oui)
:Extraction du navigateur;
:Lancement du navigateur Tor;
:Enregistrement dans le bureau;
else (Non)
:Supprimer le fichier compromis;
endif
stop
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_menjelajahi_sumber_daya_pendidikan_dengan_tor&amp;quot;&amp;gt;Menjelajahi sumber daya pendidikan dengan Tor&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Tor bukan hanya sebuah alat untuk anonimitas. Ini merupakan sebuah portal akses ke sumber daya pendidikan dan penelitian, terutama dalam bidang-bidang :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;OSINT (Inteligensi Sumber Terbuka)&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://inteltechniques.com&amp;quot;&amp;gt;https://inteltechniques.com&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://osintframework.com&amp;quot;&amp;gt;https://osintframework.com&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Berbagi edukatif dan open source&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://archive.org&amp;quot;&amp;gt;https://archive.org&amp;lt;/a&amp;gt;(akses melalui Tor untuk privasi lebih)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://github.com/&amp;quot;&amp;gt;https://github.com/&amp;lt;/a&amp;gt;(dapat diakses melalui Tor)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://librarygenesis.pro&amp;quot;&amp;gt;https://librarygenesis.pro&amp;lt;/a&amp;gt;(akses ke literatur ilmiah bebas)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Penyaluran pengetahuan dan pemahaman tentang manusia dalam masyarakat digital&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Sumber daya tentang budaya digital, kebebasan berekspresi, dan akses ke pengetahuan&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Dokumentasi bebas tentang keamanan siber dan etika teknologi&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_pembaruan_tor_browser&amp;quot;&amp;gt;Pembaruan Tor Browser&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Untuk menjamin keamanan optimal, sangat penting untuk memastikan &amp;lt;strong&amp;gt;Tor Browser&amp;lt;/strong&amp;gt; selalu diperbarui. Peramban memiliki mekanisme pembaruan otomatis yang dapat dipicu lewat baris perintah.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Dari direktori instalasi Tor Browser, cukup menjalankan ulang skrip pemulai :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;cd ~/workspace/tipiak/tor/tor-browser
./start-tor-browser.desktop&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Pada peluncuran, &amp;lt;strong&amp;gt;Tor Browser&amp;lt;/strong&amp;gt; akan otomatis memeriksa apakah ada versi baru. Jika ya, ia akan mengunduh dan menginstalnya untuk Anda.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Metode sederhana ini memastikan bahwa Anda selalu memiliki patch keamanan terbaru dan perbaikan terkini, yang sangat penting untuk telusur yang aman.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_kesimpulan&amp;quot;&amp;gt;Kesimpulan&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Memeriksa unduhan adalah refleks penting bagi siapa pun yang ingin berkembang dalam lingkungan digital yang aman. Menginstal Tor dengan cara terverifikasi, itu&amp;lt;strong&amp;gt;melindungi integritas digitalnya&amp;lt;/strong&amp;gt;, &amp;lt;strong&amp;gt;mempertahankan kepercayaan dalam perangkat lunak bebas&amp;lt;/strong&amp;gt;, et &amp;lt;strong&amp;gt;memperkuat autonomi informasinya&amp;lt;/strong&amp;gt;.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Keamanan digital adalah tindakan kesadaran, bukan paranoia.&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
title Tampilan ringkas penggunaan Tor
actor Utilisateur
rectangle &amp;quot;Tor Browser&amp;quot; {
rectangle &amp;quot;anonimitas&amp;quot; as A
rectangle &amp;quot;Pencarian OSINT&amp;quot; as O
rectangle &amp;quot;Berbagi pendidikan&amp;quot; as E
}
Utilisateur --&amp;amp;gt; A
Utilisateur --&amp;amp;gt; O
Utilisateur --&amp;amp;gt; E
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_referensi&amp;quot;&amp;gt;Referensi&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Situs resmi Tor Project:https://www.torproject.org/download/[&amp;lt;a href=&amp;quot;https://www.torproject.org/download/&amp;quot; class=&amp;quot;bare&amp;quot;&amp;gt;https://www.torproject.org/download/&amp;lt;/a&amp;gt;]&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Dokumentasi verifikasi tanda tangan :https://support.torproject.org/tbb/how-to-verify-signature/[&amp;lt;a href=&amp;quot;https://support.torproject.org/tbb/how-to-verify-signature/&amp;quot; class=&amp;quot;bare&amp;quot;&amp;gt;https://support.torproject.org/tbb/how-to-verify-signature/&amp;lt;/a&amp;gt;]&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Dokumentasi Ubuntu :https://ubuntu.com[&amp;lt;a href=&amp;quot;https://ubuntu.com&amp;quot; class=&amp;quot;bare&amp;quot;&amp;gt;https://ubuntu.com&amp;lt;/a&amp;gt;]&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Arsip edukatif :https://archive.org/details/opensource[&amp;lt;a href=&amp;quot;https://archive.org/details/opensource&amp;quot; class=&amp;quot;bare&amp;quot;&amp;gt;https://archive.org/details/opensource&amp;lt;/a&amp;gt;]&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;admonitionblock note&amp;quot;&amp;gt;
&amp;lt;table&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;icon&amp;quot;&amp;gt;
&amp;lt;i class=&amp;quot;fa icon-note&amp;quot; title=&amp;quot;Note&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Artikel ini adalah bagian dari seri &amp;lt;strong&amp;gt;Keselamatan dan Kesadaran Digital&amp;lt;/strong&amp;gt; bertujuan untuk mempromosikan praktik yang bertanggung jawab dan berbasis pendidikan mengenai alat bebas&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>Artikel 5 : Petualangan libs.versions.toml yang dibagikan</title>
            <link >https://cheroliv.com/blog/2025/0093_gradle_plugin_toml_composite_build_post.html</link>
            <pubDate>Sat, 27 Sep 2025 00:00:00 +0000</pubDate>
            <guid isPermaLink="false">blog/2025/0093_gradle_plugin_toml_composite_build_post.html</guid>
            <description>&amp;lt;div id=&amp;quot;toc&amp;quot; class=&amp;quot;toc&amp;quot;&amp;gt;
&amp;lt;div id=&amp;quot;toctitle&amp;quot;&amp;gt;Sommaire&amp;lt;/div&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel1&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_pengenalan&amp;quot;&amp;gt;Pengenalan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_1_daftar_versi_libs_versions_toml_sebuah_pengingat&amp;quot;&amp;gt;1. Daftar Versi (&amp;lt;code&amp;gt;libs.versions.toml&amp;lt;/code&amp;gt;) : Sebuah pengingat&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_2_masalah_sebuah_build_komposit_dan_dua_dunia_terpisah&amp;quot;&amp;gt;2. Masalah: Sebuah Build Komposit dan Dua Dunia Terpisah&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_3_solusi_membagikan_resolusi_dependensi&amp;quot;&amp;gt;3. Solusi: Membagikan Resolusi Dependensi&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_4_kesimpulan_kekuatan_dari_builds_komposit_yang_dikelola_dengan_baik&amp;quot;&amp;gt;4. Kesimpulan : Kekuatan dari Builds Komposit yang dikelola dengan baik&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_pengenalan&amp;quot;&amp;gt;Pengenalan&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;Target audience: Gradle developers facing dependency management in multi-build projects.&amp;lt;/div&amp;gt;
&amp;lt;p&amp;gt;Dalam pencarian kita untuk membuat plugin`site-baker`, kami berusaha menjaga basis kode yang bersih dan terpusat. Salah satu praktik terbaik dalam ekosistem Gradle modern adalah penggunaan&amp;lt;strong&amp;gt;katalog versi&amp;lt;/strong&amp;gt;(&amp;lt;code&amp;gt;libs.versions.toml&amp;lt;/code&amp;gt;) untuk mengelola dependensi. Namun apa yang terjadi ketika proyek Anda menjadi lebih kompleks, seperti&amp;lt;strong&amp;gt;membangun komposit&amp;lt;/strong&amp;gt;Di mana sebuah build independen (plugin kami) harus dimasukkan ke dalam satu yang lain?&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Itulah tempat petualangan kita mengambil wending yang tidak terduga. Kami ingin bahwa plugin kami`site-baker`, yang merupakan proyek Gradle itu sendiri, menggunakan katalog versi yang sama dengan proyek utama kami. Artikel ini, yang merupakan kelima dari seri kami, menceritakan bagaimana kami telah mengatasi tantangan ini.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_1_daftar_versi_libs_versions_toml_sebuah_pengingat&amp;quot;&amp;gt;1. Daftar Versi (&amp;lt;code&amp;gt;libs.versions.toml&amp;lt;/code&amp;gt;) : Sebuah pengingat&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;File`gradle/libs.versions.toml`adalah suatu fitur Gradle yang memungkinkan Anda untuk memusatkan versi dan koordinat dependensi proyek Anda. Umumnya diatur dalam empat bagian :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;code&amp;gt;[versions]&amp;lt;/code&amp;gt;: Mendefinisikan alias untuk nomor versi (contoh:`kotlin = &amp;quot;1.9.20&amp;quot;`).&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;code&amp;gt;[libraries]&amp;lt;/code&amp;gt;: Menetapkan alias untuk dependensi lengkap, menggunakan versi yang ditentukan di atas (contoh:`kotlin-stdlib = { module = &amp;quot;org.jetbrains.kotlin:kotlin-stdlib&amp;quot;, version.ref = &amp;quot;kotlin&amp;quot; }`).&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;code&amp;gt;[bundles]`Mengelompokkan beberapa perpustakaan di bawah satu alias (contoh:`jackson = [&amp;quot;jackson-core&amp;quot;, &amp;quot;jackson-databind&amp;quot;]&amp;lt;/code&amp;gt;)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;code&amp;gt;[plugins]&amp;lt;/code&amp;gt;: Menetapkan alias untuk plugin Gradle.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Setelah ditentukan, Gradle secara otomatis menghasilkan aksesor bertipe, yang membuat Anda`build.gradle.kts`lebih mudah dibaca :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;dependencies {
    // Au lieu de : implementation(&amp;quot;org.jetbrains.kotlin:kotlin-stdlib:1.9.20&amp;quot;)
    implementation(libs.kotlin.stdlib)
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;aliran data dari katalog versi&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
!theme plain
file &amp;quot;gradle/libs.versions.toml&amp;quot; as Toml
node &amp;quot;Gradle&amp;quot; as G
file &amp;quot;build.gradle.kts&amp;quot; as BuildScript

Toml --&amp;amp;gt; G : &amp;quot;dibaca oleh&amp;quot;
G --&amp;amp;gt; BuildScript : &amp;quot;menghasilkan getter `libs&amp;quot;
BuildScript -&amp;amp;gt; G : &amp;quot;menggunakan `libs` untuk mendeklarasikan dependensi&amp;quot;
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_2_masalah_sebuah_build_komposit_dan_dua_dunia_terpisah&amp;quot;&amp;gt;2. Masalah: Sebuah Build Komposit dan Dua Dunia Terpisah&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Struktur proyek kami adalah sebuah&amp;lt;strong&amp;gt;membangun komposit&amp;lt;/strong&amp;gt;python print(&amp;quot;. Proyek utama (&amp;quot;) &amp;lt;code&amp;gt;thymeleaf.cheroliv.com&amp;lt;/code&amp;gt;) mengandung proyek plugin (&amp;lt;code&amp;gt;site-baker&amp;lt;/code&amp;gt;) melalui perintah`includeBuild(&amp;quot;site-baker&amp;quot;)&amp;lt;code&amp;gt;dalam file`settings.gradle.kts&amp;lt;/code&amp;gt;.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Struktur build komposit&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
!theme plain
package &amp;quot;Proyek Utama&amp;quot; {
  file &amp;quot;settings.gradle.kts&amp;quot; as MainSettings
  folder &amp;quot;gradle&amp;quot; {
    file &amp;quot;libs.versions.toml&amp;quot; as MainToml
  }
}

package &amp;quot;Plugin `site-baker` (Build Termasuk)&amp;quot; {
  file &amp;quot;settings.gradle.kts&amp;quot; as PluginSettings
  file &amp;quot;build.gradle.kts&amp;quot; as PluginBuild
}

MainSettings -&amp;amp;gt; PluginSettings : `includeBuild(&amp;quot;site-baker&amp;quot;)`
PluginBuild ..&amp;amp;gt; MainToml : **Voulait accéder à...**
note right on link: ...mais ne pouvait pas !
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Masalahnya adalah bahwa, secara default, sebuah build yang termasuk adalah sebuah&amp;lt;strong&amp;gt;dunia terpisah&amp;lt;/strong&amp;gt;. Dia memiliki konfigurasi sendiri, siklus hidup sendiri, dan dia tidak melihat file`libs.versions.toml`dari build utama. Kami`plugin/build.gradle.kts`mencoba menggunakannya`libs.plugins.kotlin.jvm`, tapi objek`libs`tidak dihasilkan dari file TOML yang benar, yang menyebabkan kesalahan build.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_3_solusi_membagikan_resolusi_dependensi&amp;quot;&amp;gt;3. Solusi: Membagikan Resolusi Dependensi&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Setelah beberapa penelitian, ternyata solusinya adalah fitur Gradle yang dirancang khusus untuk kasus ini :`dependencyResolutionManagement`.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;dalam file`settings.gradle.kts` du &amp;lt;strong&amp;gt;membangun inklusif&amp;lt;/strong&amp;gt;(&amp;lt;code&amp;gt;site-baker/settings.gradle.kts&amp;lt;/code&amp;gt;), kita dapat memberitahu Gradle tempat menemukan katalog versi yang akan digunakan.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Ini adalah konfigurasi yang kami tambahkan ke`site-baker/settings.gradle.kts`Saya adalah code dan saya berbicara bahasa Prancis.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;// site-baker/settings.gradle.kts

dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        gradlePluginPortal()
        mavenCentral()
    }
    versionCatalogs {
        create(&amp;quot;libs&amp;quot;) {
            from(files(&amp;quot;../gradle/libs.versions.toml&amp;quot;))
        }
    }
}

rootProject.name = &amp;quot;site-baker&amp;quot;
include(&amp;quot;plugin&amp;quot;)&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Mari kita analisis bagian krusial:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;versionCatalogs {
    create(&amp;quot;libs&amp;quot;) { // Crée un catalogue nommé &amp;#39;libs&amp;#39;
        from(files(&amp;quot;../gradle/libs.versions.toml&amp;quot;)) // En utilisant ce fichier
    }
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;code&amp;gt;create(&amp;quot;libs&amp;quot;)`Kami mendeklarasikan sebuah katalog versi yang akan dapat diakses melalui alias`libs&amp;lt;/code&amp;gt;.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;code&amp;gt;from(files(&amp;quot;../gradle/libs.versions.toml&amp;quot;))&amp;lt;/code&amp;gt;: Ini adalah keajaiban. Kami memberi tahu Gradle bahwa sumber katalog ini adalah file TOML yang berada di dalam direktori`gradle` du &amp;lt;strong&amp;gt;proyek induk&amp;lt;/strong&amp;gt;(&amp;lt;code&amp;gt;../&amp;lt;/code&amp;gt;).&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Dengan konfigurasi ini, build`site-baker`Sekarang dia tahu bahwa ia harus menggunakan katalog versi dari proyek utama. Objek`libs`dibuat dengan benar, dan dependensi telah dipecahkan seperti yang diharapkan.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_4_kesimpulan_kekuatan_dari_builds_komposit_yang_dikelola_dengan_baik&amp;quot;&amp;gt;4. Kesimpulan : Kekuatan dari Builds Komposit yang dikelola dengan baik&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Pengalaman ini kaya akan pelajaran. Katalog versi adalah alat yang luar biasa untuk kemudahan pemeliharaan, tetapi perilakunya dalam skenario build komposit tidak selalu intuitif.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Kunci adalah mengingat bahwa build yang termasuk tetap merupakan build independen. Untuk membagikan konfigurasi seperti katalog versi, harus menggunakan mekanisme eksplisit yang disediakan oleh Gradle, seperti blok`dependencyResolutionManagement`di`settings.gradle.kts`.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Dengan menyelesaikan masalah ini, kami tidak hanya membuat build kami lebih bersih, tetapi kami juga memperkuat koherensi proyek kami dengan memastikan bahwa plugin dan proyek utama berbagi satu sumber kebenaran tunggal untuk dependensi mereka.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Dalam artikel berikutnya, kita akan terus memperkaya plugin kami dengan menambahkan tugas-tugas yang lebih kompleks, mengingat manajemen dependensi kami sudah kuat dan terpusat.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>Pasal 4: Jebakan Cache Konfigurasi Gradle</title>
            <link >https://cheroliv.com/blog/2025/0092_gradle_configuration_cache_post.html</link>
            <pubDate>Fri, 26 Sep 2025 00:00:00 +0000</pubDate>
            <guid isPermaLink="false">blog/2025/0092_gradle_configuration_cache_post.html</guid>
            <description>&amp;lt;div id=&amp;quot;toc&amp;quot; class=&amp;quot;toc&amp;quot;&amp;gt;
&amp;lt;div id=&amp;quot;toctitle&amp;quot;&amp;gt;Sommaire&amp;lt;/div&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel1&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_pendahuluan&amp;quot;&amp;gt;Pendahuluan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_gejala_build_hantu&amp;quot;&amp;gt;Gejala: Build Hantu&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_survei_apa_itu_cache_konfigurasi&amp;quot;&amp;gt;Survei: Apa itu Cache Konfigurasi ?&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_penyebab_masalah_plugin_tidak_sesuai&amp;quot;&amp;gt;Penyebab Masalah: Plugin Tidak Sesuai&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_solusi_sementara_nonaktifkan_cache&amp;quot;&amp;gt;Solusi sementara: nonaktifkan cache&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_solusi_yang_benar_membuat_plugin_kompatibel&amp;quot;&amp;gt;Solusi yang Benar : Membuat Plugin Kompatibel&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_pendahuluan&amp;quot;&amp;gt;Pendahuluan&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;Sasaran : Pengembang Gradle yang pernah mengalami perilaku build &amp;quot;magis&amp;quot; atau tidak terduga.&amp;lt;/div&amp;gt;
&amp;lt;p&amp;gt;Dalam petualangan kami dalam pembuatan plugin`site-baker`, kami telah mengikuti pendekatan TDD yang ketat. Setiap fitur diuji, divalidasi, dan kami maju dengan kepercayaan. Dan kemudian, suatu hari, hal yang tidak terduga terjadi. Build mulai berperilaku dengan cara yang tidak teratur. Perubahan dalam logika plugin atau dalam file konfigurasi tampak diabaikan, dan tes fungsional kami yang sebelumnya dapat diandalkan gagal tanpa alasan yang jelas.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Masalah seperti ini bisa sangat memicu frustrasi. Ini menimbulkan pertanyaan tentang keandalan alat dan kevaliditas kode kita. Sesudah sesi debugging yang intensif, penyalah telah diidentifikasi:&amp;lt;strong&amp;gt;cache konfigurasi Gradle&amp;lt;/strong&amp;gt;.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_gejala_build_hantu&amp;quot;&amp;gt;Gejala: Build Hantu&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Masalah ini muncul dengan beberapa cara :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;olist arabic&amp;quot;&amp;gt;
&amp;lt;ol class=&amp;quot;arabic&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Saya sedang memodifikasi sebuah rantai karakter dalam sebuah tugas`println`, tetapi string lama terus ditampilkan saat eksekusi.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Saya sedang mengubah sebuah nilai dalam file saya`managed-jbake-context.yml`, tetapi plugin berperilaku seolah-olah file tersebut tidak telah dimodifikasi.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Uji fungsional, yang membuat proyek uji secara langsung, gagal karena plugin tidak terlihat mendeteksi file konfigurasi yang baru dibuat.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Semua terasa seolah-olah Gradle menjalankan \&amp;quot;versi fantom\&amp;quot; dari build kita, mengabaikan perubahan terbaru kita.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_survei_apa_itu_cache_konfigurasi&amp;quot;&amp;gt;Survei: Apa itu Cache Konfigurasi ?&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Cache konfigurasi adalah fitur yang relatif modern dan sangat kuat dari Gradle, yang diaktifkan secara default di versi baru. Tujuannya adalah membuat build lebih cepat.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;olist arabic&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;Prinsipnya sederhana :&amp;lt;/div&amp;gt;
&amp;lt;ol class=&amp;quot;arabic&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Selama pelaksanaan pertama, Gradle mengeksekusi fase dari&amp;lt;strong&amp;gt;Konfigurasi&amp;lt;/strong&amp;gt;(membaca`build.gradle.kts`, pembuatan tugas, penyelesaian dependensi) dan membangun grafik tugas.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Di akhir fase ini, Gradle&amp;lt;strong&amp;gt;Serialisasikan graf tugas ini&amp;lt;/strong&amp;gt;dan menyimpannya dalam cache.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Selama eksekusi berikutnya, jika tidak ada yang berubah (skrip build,&amp;lt;code&amp;gt;gradle.properties&amp;lt;/code&amp;gt;, etc.), Gradle&amp;lt;strong&amp;gt;melewati sepenuhnya fase konfigurasi&amp;lt;/strong&amp;gt;dan menggunakan kembali grafik tugas yang disimpan di cache.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Penghematan waktu ini spektakuler pada proyek-proyek besar. Namun, performa ini memiliki harga: ia menegakkan ketentuan yang ketat tentang bagaimana plugin harus ditulis.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;siklus hidup cache konfigurasi&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
start
:Première exécution;
:Phase de Configuration;
:Création du graphe de tâches;
:Mise en cache du graphe;
:Phase d&amp;#39;Exécution;
end

start
:Exécution suivante;
if (Cache valide ?) then (oui)
  :Restauration du graphe depuis le cache;
  note right
    La phase de configuration
    est sautée !
  end note
else (non)
  :Phase de Configuration;
  :Mise à jour du cache;
endif
:Phase d&amp;#39;Exécution;
stop
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_penyebab_masalah_plugin_tidak_sesuai&amp;quot;&amp;gt;Penyebab Masalah: Plugin Tidak Sesuai&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Plugin kami`site-baker`melanggar, tanpa mengetahuinya, beberapa aturan cache konfigurasi. Agar grafik tugas dapat diserialkan, tugas tidak boleh mengandung referensi ke objek kompleks seperti objek`Project`atau membaca file secara sembarangan selama tahap eksekusi.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Kesalahan utama kami adalah membaca konten file YAML langsung di dalam logika eksekusi tugas, dengan menggunakan referensi ke jalur yang disimpan di ekstensi kami. Pendekatan ini tidak kompatibel dengan cache karena Gradle tidak dapat mengetahui apakah konten file telah berubah jika pembacaan ini tidak dimodelkan sebagai sebuah&amp;lt;strong&amp;gt;masukan tugas&amp;lt;/strong&amp;gt;(Task Input)&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_solusi_sementara_nonaktifkan_cache&amp;quot;&amp;gt;Solusi sementara: nonaktifkan cache&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Untuk membebaskan kita dan mengembalikan perilaku build yang dapat diprediksi, solusi tercepat adalah menonaktifkan cache konfigurasi. Cukup menambahkan baris berikut ke dalam file`gradle.properties`proyek yang menggunakan plugin (atau dalam kasus kami, proyek uji)&amp;lt;code&amp;gt;site-baker&amp;lt;/code&amp;gt;).&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-properties hljs&amp;quot; data-lang=&amp;quot;properties&amp;quot;&amp;gt;# site-baker/gradle.properties
org.gradle.configuration-cache=false&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Segera, builds telah kembali berperilaku normal. Setiap eksekusi memulai kembali fase konfigurasi dan perubahan kami diperhitungkan.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Namun, ini adalah solusi sementara, bukan solusi permanen. Ia mengorbankan kinerja dan tidak menyelesaikan masalah inti plugin kami.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_solusi_yang_benar_membuat_plugin_kompatibel&amp;quot;&amp;gt;Solusi yang Benar : Membuat Plugin Kompatibel&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Agar suatu plugin menjadi warga yang baik dalam ekosistem Gradle modern, ia harus kompatibel dengan cache konfigurasi. Hal ini membutuhkan pemikiran ulang tentang cara data mengalir ke tugas kita.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Kunci adalah untuk menggunakan&amp;lt;strong&amp;gt;API Penyedia&amp;lt;/strong&amp;gt;Gradle. Alih-alih meneruskan nilai langsung (seperti satu`String` ou un &amp;lt;code&amp;gt;File&amp;lt;/code&amp;gt;) untuk tugas-tugas kita, kita harus menghabiskan beberapa`Property&amp;amp;lt;T&amp;amp;gt;`atau beberapa`Provider&amp;amp;lt;T&amp;amp;gt;`.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;olist arabic&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;Ini rencana refactoring :&amp;lt;/div&amp;gt;
&amp;lt;ol class=&amp;quot;arabic&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Mendeklarasikan entri tugas:&amp;lt;/strong&amp;gt;Tugas yang memparsing file YAML harus menyatakan file tersebut sebagai masukan. Untuk itu, kita menggunakan anotasi.&amp;lt;code&amp;gt;@InputFile&amp;lt;/code&amp;gt;.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;[source,kotlin] ---- @get:InputFile abstract val configFile: RegularFileProperty Halo dunia&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>Artikel 3 : Integrasi Jackson untuk Parsing YAML dengan TDD dalam Plugin Gradle</title>
            <link >https://cheroliv.com/blog/2025/0091_jackson_yaml_tdd_post.html</link>
            <pubDate>Thu, 25 Sep 2025 00:00:00 +0000</pubDate>
            <guid isPermaLink="false">blog/2025/0091_jackson_yaml_tdd_post.html</guid>
            <description>&amp;lt;div id=&amp;quot;toc&amp;quot; class=&amp;quot;toc&amp;quot;&amp;gt;
&amp;lt;div id=&amp;quot;toctitle&amp;quot;&amp;gt;Sommaire&amp;lt;/div&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel1&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_pendahuluan&amp;quot;&amp;gt;Pendahuluan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_1_konteks_plugin_kami_site_baker&amp;quot;&amp;gt;1. Konteks: Plugin kami `site-baker&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_2_pemodelan_konfigurasi_dalam_kotlin&amp;quot;&amp;gt;2. Pemodelan Konfigurasi dalam Kotlin&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_3_integrasi_jackson_dalam_build_gradle_kts&amp;quot;&amp;gt;3. Integrasi Jackson dalam `build.gradle.kts&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_4_pendekatan_tdd_menguji_parsing_yaml&amp;quot;&amp;gt;4. Pendekatan TDD: Menguji Parsing YAML&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_4_1_uji_unit_memetakan_yaml_menjadi_objek_kotlin&amp;quot;&amp;gt;4.1. Uji Unit : Memetakan YAML menjadi Objek Kotlin&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_4_2_test_fungsional_memvalidasi_pembacaan_file_konfigurasi&amp;quot;&amp;gt;4.2. Test Fungsional: Memvalidasi pembacaan file konfigurasi&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_5_visualisasi_alur_parsing&amp;quot;&amp;gt;5. Visualisasi Alur Parsing&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_kesimpulan&amp;quot;&amp;gt;Kesimpulan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_pendahuluan&amp;quot;&amp;gt;Pendahuluan&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;Target audience: Pengembang Gradle tingkat menengah yang ingin mengelola konfigurasi kompleks.&amp;lt;/div&amp;gt;
&amp;lt;p&amp;gt;Dalam pengembangan plugin Gradle, pengelolaan konfigurasi kompleks adalah tugas yang umum. Alih-alih membebani DSL dengan ratusan properti, sering kali lebih bersih dan lebih mudah dipelihara untuk mendefinisikan konfigurasi dalam file eksternal, seperti YAML. Artikel ini memandu Anda melalui integrasi pustaka Jackson yang kuat untuk mengurai file YAML menjadi objek Kotlin, dengan menerapkan pendekatan TDD yang ketat untuk memastikan keandalan plugin kami.&amp;lt;code&amp;gt;site-baker&amp;lt;/code&amp;gt;.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_1_konteks_plugin_kami_site_baker&amp;quot;&amp;gt;1. Konteks: Plugin kami `site-baker&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Plugin kami`site-baker`dirancang untuk mengotomatiskan pembuatan dan penyebaran situs statis. Perlu membaca berkas konfigurasi YAML (&amp;lt;code&amp;gt;managed-jbake-context.yml&amp;lt;/code&amp;gt;) untuk mendapatkan informasi seperti jalur sumber, tujuan penyebaran, dan identitas Git.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Konfigurasi YAML yang ingin kita parse mirip seperti ini :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-yaml hljs&amp;quot; data-lang=&amp;quot;yaml&amp;quot;&amp;gt;bake:
  srcPath: &amp;quot;./site/jbake&amp;quot;
  destDirPath: &amp;quot;bake&amp;quot;
  cname: &amp;quot;cheroliv.com&amp;quot;
pushPage:
  from: &amp;quot;bake&amp;quot;
  to: &amp;quot;cvs&amp;quot;
  repo:
    name: &amp;quot;trainings&amp;quot;
    repository: &amp;quot;https://github.com/pages-content/pages-content.github.io.git&amp;quot;
    credentials:
      username: &amp;quot;USERNAME&amp;quot;
      password: &amp;quot;SECRET_TOKEN&amp;quot;
  branch: &amp;quot;main&amp;quot;
  message: &amp;quot;cheroliv.com&amp;quot;
# ... autres configurations (pushMaquette, supabase, etc.)&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_2_pemodelan_konfigurasi_dalam_kotlin&amp;quot;&amp;gt;2. Pemodelan Konfigurasi dalam Kotlin&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Sebelum mem-parse, kita harus menentukan struktur data kita dalam Kotlin. Jackson akan menggunakan kelas ini untuk secara otomatis memetakan konten YAML.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;// plugin/src/main/kotlin/com/cheroliv/site/baker/data/SiteConfiguration.kt
package com.cheroliv.site.baker.data

import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory
import com.fasterxml.jackson.module.kotlin.readValue
import com.fasterxml.jackson.module.kotlin.registerKotlinModule

fun parseSiteConfiguration(yaml: String): SiteConfiguration {
    val mapper = ObjectMapper(YAMLFactory()).registerKotlinModule()
    return mapper.readValue(yaml)
}

data class GitPushConfiguration(
    val from: String = &amp;quot;&amp;quot;,
    val to: String = &amp;quot;&amp;quot;,
    val repo: RepositoryConfiguration = RepositoryConfiguration(),
    val branch: String = &amp;quot;&amp;quot;,
    val message: String = &amp;quot;&amp;quot;,
)

data class RepositoryConfiguration(
    val name: String = &amp;quot;&amp;quot;,
    val repository: String = &amp;quot;&amp;quot;,
    val credentials: RepositoryCredentials = RepositoryCredentials(),
) {
    companion object {
        const val ORIGIN = &amp;quot;origin&amp;quot;
        const val CNAME = &amp;quot;CNAME&amp;quot;
        const val REMOTE = &amp;quot;remote&amp;quot;
    }
}

data class RepositoryCredentials(val username: String = &amp;quot;&amp;quot;, val password: String = &amp;quot;&amp;quot;)

data class SiteConfiguration(
    val bake: BakeConfiguration = BakeConfiguration(),
    val pushPage: GitPushConfiguration = GitPushConfiguration(),
    val pushMaquette: GitPushConfiguration = GitPushConfiguration(),
    val pushSource: GitPushConfiguration? = null,
    val pushTemplate: GitPushConfiguration? = null,
    val supabase: SupabaseContactFormConfig? = null
)

data class BakeConfiguration(
    val srcPath: String = &amp;quot;&amp;quot;,
    val destDirPath: String = &amp;quot;&amp;quot;,
    val cname: String? = null,
)

// ... (autres data classes pour Supabase, si nécessaire)&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Fungsi`parseSiteConfiguration`adalah titik masuk kami untuk deserialisasi. Dia menggunakan`ObjectMapper`Jackson, dikonfigurasi dengan`YAMLFactory`untuk format YAML dan`registerKotlinModule()`untuk dukungan spesifikasi Kotlin (seperti nilai default properti).&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_3_integrasi_jackson_dalam_build_gradle_kts&amp;quot;&amp;gt;3. Integrasi Jackson dalam `build.gradle.kts&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Untuk menggunakan Jackson, kita harus menambahkan dependensi yang diperlukan dalam`build.gradle.kts`modul kita`plugin`(Empty output)&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;// plugin/build.gradle.kts
dependencies {
    // Jackson for YAML parsing
    implementation(&amp;quot;com.fasterxml.jackson.module:jackson-module-kotlin:2.18.3&amp;quot;)
    implementation(&amp;quot;com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.18.3&amp;quot;)

    // ... autres dépendances
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Kami menggunakan`jackson-module-kotlin`untuk dukungan Kotlin dan`jackson-dataformat-yaml`untuk pengelolaan format YAML.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_4_pendekatan_tdd_menguji_parsing_yaml&amp;quot;&amp;gt;4. Pendekatan TDD: Menguji Parsing YAML&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Sekarang, mari kita terapkan TDD untuk memvalidasi logika parsing kita.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_4_1_uji_unit_memetakan_yaml_menjadi_objek_kotlin&amp;quot;&amp;gt;4.1. Uji Unit : Memetakan YAML menjadi Objek Kotlin&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Kami memulai dengan pengujian unit di`SiteBakerPluginTest.kt`untuk memastikan bahwa fungsi`parseSiteConfiguration`Dapat dengan benar mengonversi string YAML menjadi objek.&amp;lt;code&amp;gt;SiteConfiguration&amp;lt;/code&amp;gt;.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;// plugin/src/test/kotlin/com/cheroliv/site/baker/SiteBakerPluginTest.kt
@Test
fun `can map configuration text to SiteConfiguration object`() {
    val yamlString = &amp;quot;../../managed-jbake-context.yml&amp;quot;
        .run(::File)
        .readText()
        .trimIndent()

    val config: SiteConfiguration = parseSiteConfiguration(yamlString)

    assertEquals(&amp;quot;cheroliv.com&amp;quot;, config.bake.cname)
    assertEquals(&amp;quot;main&amp;quot;, config.pushPage.branch)
    assertEquals(&amp;quot;https://github.com/pages-content/pages-content.github.io.git&amp;quot;, config.pushPage.repo.repository)
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Tes ini membaca isi file`managed-jbake-context.yml`(yang harus ada agar uji tersebut valid) dan melakukan asersi pada objek`SiteConfiguration`Hasil. Ia memvalidasi bahwa nilai kunci YAML dipetakan dengan benar.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_4_2_test_fungsional_memvalidasi_pembacaan_file_konfigurasi&amp;quot;&amp;gt;4.2. Test Fungsional: Memvalidasi pembacaan file konfigurasi&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Untuk memastikan bahwa plugin dapat membaca file konfigurasi melalui DSL-nya dan bahwa parsing berfungsi dalam lingkungan Gradle nyata, kami menambahkan sebuah tes fungsional di`SiteBakerPluginFunctionalTest.kt`.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Uji ini sangat penting karena mensimulasikan eksekusi plugin dalam proyek nyata. Harus&amp;lt;strong&amp;gt;kedap udara&amp;lt;/strong&amp;gt;, yaitu dia harus membuat berkas itu sendiri`managed-jbake-context.yml`dengan konten yang terkontrol, untuk memastikan reprodukibilitas dan isolasi uji.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;// plugin/src/functionalTest/kotlin/com/cheroliv/site/baker/SiteBakerPluginFunctionalTest.kt
@Test
fun `config file contains good data`(){
    val configContent = configFile.readText(UTF_8)
    // Vérifications comme dans vos commentaires
    assertTrue(configContent.contains(&amp;quot;bake&amp;quot;))
    assertTrue(configContent.contains(&amp;quot;pushPage&amp;quot;))
    assertTrue(configContent.contains(&amp;quot;repository&amp;quot;))
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Tes fungsional ini memverifikasi bahwa file konfigurasi yang disalin ke direktori sementara tes mengandung data yang diharapkan. Meskipun tes ini tidak secara langsung mem-parse YAML menjadi objek Kotlin, ia memvalidasi keberadaan file dan isinya, yang merupakan langkah prasyarat penting untuk parsing oleh plugin.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_5_visualisasi_alur_parsing&amp;quot;&amp;gt;5. Visualisasi Alur Parsing&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Diagram berikut menggambarkan aliran data dan interaksi saat parsing konfigurasi YAML :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
actor &amp;quot;Pengguna Gradle&amp;quot; as User
participant &amp;quot;build.gradle.kts&amp;quot; as BuildScript
participant &amp;quot;SiteBakerPlugin&amp;quot; as Plugin
participant &amp;quot;ExtensiSitus&amp;quot; as Extension
participant &amp;quot;managed-jbake-context.yml&amp;quot; as ConfigFile
participant &amp;quot;parseSiteConfiguration()&amp;quot; as Parser
participant &amp;quot;SiteConfiguration (Objek Kotlin)&amp;quot; as KotlinObject

User -&amp;amp;gt; BuildScript : Applique le plugin et configure le DSL
BuildScript -&amp;amp;gt; Plugin : apply()
Plugin -&amp;amp;gt; Extension : Crée et enregistre l&amp;#39;extension &amp;#39;site&amp;#39;
User -&amp;amp;gt; BuildScript : site { configPath = &amp;quot;...&amp;quot; }
BuildScript -&amp;amp;gt; Extension : Définit configPath

Plugin -&amp;amp;gt; ConfigFile : Lit le fichier (via configPath)
ConfigFile --&amp;amp;gt; Plugin : Contenu YAML
Plugin -&amp;amp;gt; Parser : Appelle parseSiteConfiguration(yamlString)
Parser -&amp;amp;gt; KotlinObject : Mappe le YAML en objet SiteConfiguration
KotlinObject --&amp;amp;gt; Plugin : Retourne l&amp;#39;objet configuré

@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_kesimpulan&amp;quot;&amp;gt;Kesimpulan&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Dengan mengikuti pendekatan TDD, kami berhasil mengintegrasikan pustaka Jackson untuk menguraikan file konfigurasi YAML menjadi objek Kotlin dalam plugin Gradle kami. Pendekatan ini memungkinkan kami untuk :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Membuat model dengan jelas&amp;lt;/strong&amp;gt;konfigurasi kita dengan kelas data Kotlin.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Memvalidasi logika parsing&amp;lt;/strong&amp;gt;dengan tes unit terarah.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Memastikan integrasi&amp;lt;/strong&amp;gt;di lingkungan Gradle yang nyata berkat uji fungsional yang ermetis.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Dasar solid ini memberi kita kepercayaan besar untuk memperluas plugin kami dengan fitur-fitur yang didasarkan pada konfigurasi ini, sekaligus menjamin kemudahan pemeliharaan dan ketahanan kode. Parsing YAML kini merupakan fitur yang andal dan teruji dari plugin kami.&amp;lt;code&amp;gt;site-baker&amp;lt;/code&amp;gt;.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>Bagian 2: Mengembangkan Plugin Gradle dengan pendekatan TDD</title>
            <link >https://cheroliv.com/blog/2025/0090_TDD_gradle_plugin_post.html</link>
            <pubDate>Wed, 24 Sep 2025 00:00:00 +0000</pubDate>
            <guid isPermaLink="false">blog/2025/0090_TDD_gradle_plugin_post.html</guid>
            <description>&amp;lt;div id=&amp;quot;toc&amp;quot; class=&amp;quot;toc&amp;quot;&amp;gt;
&amp;lt;div id=&amp;quot;toctitle&amp;quot;&amp;gt;Table of Contents&amp;lt;/div&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel1&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_inisialisasi_proyek&amp;quot;&amp;gt;Inisialisasi Proyek&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_2_siklus_tdd_pertama_kami_mencatat_sebuah_tugas&amp;quot;&amp;gt;2. Siklus TDD Pertama Kami: Mencatat sebuah tugas&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_2_1_uji_coba_pertama_uji_coba_yang_gagal&amp;quot;&amp;gt;2.1. Uji Coba Pertama (Uji Coba yang Gagal)&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_2_2_kode_selanjutnya_menjalankan_uji&amp;quot;&amp;gt;2.2. Kode Selanjutnya (Menjalankan Uji)&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_3_siklus_kedua_tdd_menambahkan_ekstensi_dsl&amp;quot;&amp;gt;3. Siklus Kedua TDD: Menambahkan Ekstensi DSL&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_3_1_uji_coba_pertama&amp;quot;&amp;gt;3.1. Uji coba pertama&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_3_2_kode_berikutnya&amp;quot;&amp;gt;3.2. Kode Berikutnya&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_4_tes_fungsional_memvalidasi_integrasi&amp;quot;&amp;gt;4. Tes Fungsional: Memvalidasi Integrasi&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_4_1_pengujian_integrasi_membuat_lingkungan_terkontrol&amp;quot;&amp;gt;4.1. Pengujian Integrasi: Membuat Lingkungan Terkontrol&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_4_2_menyelesaikan_logika&amp;quot;&amp;gt;4.2. Menyelesaikan Logika&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_kesimpulan&amp;quot;&amp;gt;Kesimpulan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div id=&amp;quot;preamble&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Dalam artikel ini, kami akan menjelajahi cara menyiapkan dasar pengembangan yang kuat untuk plugin Gradle dengan menggunakan pendekatan Pengembangan Berbasis Tes (TDD - Test-Driven Development). Metode ini memastikan bahwa kode kita kuat, dapat dipelihara, dan memenuhi persyaratan secara tepat sejak awal.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_inisialisasi_proyek&amp;quot;&amp;gt;Inisialisasi Proyek&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Gradle sangat memudahkan pembuatan plugin baru melalui perintah`gradle init`. Memilih untuk membuat &amp;quot;Gradle Plugin&amp;quot; dengan Kotlin, kita mendapatkan struktur proyek yang siap digunakan, mencakup dua jenis tes yang penting:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Uji Unit :&amp;lt;/strong&amp;gt;Berada di`src/test`, mereka memungkinkan untuk memvalidasi komponen terisolasi dari plugin kami, seperti logika internal dari suatu tugas atau konfigurasi ekstensi. Mereka cepat dan tidak memerlukan eksekusi lengkap Gradle.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Uji Fungsional :&amp;lt;/strong&amp;gt;Berada di`src/functionalTest`, mereka menggunakan`GradleRunner`untuk menjalankan build Gradle lengkap dalam proyek uji sementara. Hal ini memungkinkan kami untuk memverifikasi perilaku nyata plugin dalam lingkungan yang terkendali.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_2_siklus_tdd_pertama_kami_mencatat_sebuah_tugas&amp;quot;&amp;gt;2. Siklus TDD Pertama Kami: Mencatat sebuah tugas&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Persyaratan pertama kami sederhana: plugin harus mencatat sebuah tugas bernama`printSiteConfig`.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_2_1_uji_coba_pertama_uji_coba_yang_gagal&amp;quot;&amp;gt;2.1. Uji Coba Pertama (Uji Coba yang Gagal)&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Sesuai dengan TDD, kita pertama kali menulis sebuah tes yang memverifikasi keberadaan tugas ini. Di`SiteBakerPluginTest.kt`(file tes unit kami), kami menambahkan :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;@Test
fun `plugin registers task`() {
    // Créer un projet de test en mémoire
    val project = ProjectBuilder.builder().build()
    project.plugins.apply(&amp;quot;com.cheroliv.site-baker&amp;quot;)

    // Vérifier que la tâche a bien été enregistrée
    assertNotNull(project.tasks.findByName(&amp;quot;printSiteConfig&amp;quot;))
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Uji coba ini gagal karena kami belum menulis kode apa pun dalam plugin kami.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_2_2_kode_selanjutnya_menjalankan_uji&amp;quot;&amp;gt;2.2. Kode Selanjutnya (Menjalankan Uji)&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Sekarang, kami menulis kode minimum yang diperlukan di`SiteBakerPlugin.kt`agar test lulus :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;class SiteBakerPlugin: Plugin&amp;amp;lt;Project&amp;amp;gt; {
    override fun apply(project: Project) {
        // Enregistrer une tâche simple
        project.tasks.register(&amp;quot;printSiteConfig&amp;quot;) { task -&amp;amp;gt;
            // ... la logique de la tâche viendra plus tard
        }
    }
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Kami memulai ulang tes, dan mereka lulus. Fitur pertama kami telah divalidasi.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_3_siklus_kedua_tdd_menambahkan_ekstensi_dsl&amp;quot;&amp;gt;3. Siklus Kedua TDD: Menambahkan Ekstensi DSL&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Persyaratan berikutnya adalah memungkinkan pengguna untuk mengonfigurasi plugin kami melalui blok DSL di`build.gradle.kts`. Kami ingin sebuah blok`site { &amp;amp;#8230;&amp;amp;#8203; }`di mana seseorang dapat menentukan jalur konfigurasi.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_3_1_uji_coba_pertama&amp;quot;&amp;gt;3.1. Uji coba pertama&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Kami menambahkan uji untuk memverifikasi bahwa ekstensi`site`Tercatat dengan baik dan nilai dapat diberikan kepadanya.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;@Test
fun `plugin registers extension`() {
    val project = ProjectBuilder.builder().build()
    project.plugins.apply(&amp;quot;com.cheroliv.site-baker&amp;quot;)

    // Récupérer l&amp;#39;extension et lui affecter une valeur
    project.extensions
        .findByType(SiteExtension::class.java)!!
        .configPath
        .set(&amp;quot;config.yml&amp;quot;)

    // Vérifier que la valeur a bien été prise en compte
    assertEquals(
        &amp;quot;config.yml&amp;quot;,
        project.extensions.findByType(SiteExtension::class.java)?.configPath?.get()
    )
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Uji ini gagal karena tidak kelas`SiteExtension`baik tidak ada pendaftaran ekstensi&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_3_2_kode_berikutnya&amp;quot;&amp;gt;3.2. Kode Berikutnya&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Kami membuat kelas`SiteBakerExtension.kt`dan kami memperbarui`SiteBakerPlugin.kt`:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;// SiteBakerExtension.kt
open class SiteExtension @Inject constructor(objects: ObjectFactory) {
    val configPath: Property&amp;amp;lt;String&amp;amp;gt; = objects.property(String::class.java)
}

// SiteBakerPlugin.kt
class SiteBakerPlugin: Plugin&amp;amp;lt;Project&amp;amp;gt; {
    override fun apply(project: Project) {
        // Enregistrer l&amp;#39;extension
        val extension = project.extensions.create(&amp;quot;site&amp;quot;, SiteExtension::class.java)

        project.tasks.register(&amp;quot;printSiteConfig&amp;quot;) { task -&amp;amp;gt;
            task.doLast {
                // On utilisera l&amp;#39;extension plus tard
            }
        }
    }
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Tes kembali berhasil.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_4_tes_fungsional_memvalidasi_integrasi&amp;quot;&amp;gt;4. Tes Fungsional: Memvalidasi Integrasi&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Sekarang setelah unit diuji, kita harus memastikan semuanya berfungsi bersama dalam build yang sebenarnya. Ini adalah peran dari pengujian fungsional dalam`SiteBakerPluginFunctionalTest.kt`.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_4_1_pengujian_integrasi_membuat_lingkungan_terkontrol&amp;quot;&amp;gt;4.1. Pengujian Integrasi: Membuat Lingkungan Terkontrol&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Tes ini akan mensimulasikan proyek nyata yang menggunakan plugin kita. Agar dapat dipercaya, ia harus&amp;lt;strong&amp;gt;hermetis&amp;lt;/strong&amp;gt;, artinya ia tidak boleh bergantung pada file yang ada di sistem. Dia harus menciptakan sendiri semua kondisi yang diperlukan untuk eksekusi.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Uji coba maka:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;olist arabic&amp;quot;&amp;gt;
&amp;lt;ol class=&amp;quot;arabic&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Membuat`build.gradle.kts`uji yang menggunakan plugin kami dan DSL-nya.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Membuat file konfigurasi&amp;lt;/strong&amp;gt; (&amp;lt;code&amp;gt;managed-jbake-context.yml&amp;lt;/code&amp;gt;) yang seharusnya dibaca oleh plugin. Ini adalah langkah krusial untuk ketangguhan uji.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Jalankan tugas`printSiteConfig`melalui`GradleRunner`.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Pastikan bahwa output build-nya benar.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Salin atau buat file konfigurasi ini setiap kali dijalankan, kami memastikan bahwa pengujian adalah&amp;lt;strong&amp;gt;dapat direproduksi&amp;lt;/strong&amp;gt;dan tidak bergantung pada keadaan eksternal. Ini mengamankan tes kita dari regresi yang mungkin terkait dengan pembacaan file.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;@Test fun `can run task with DSL`() {
    // 1. Créer un build.gradle.kts de test
    buildFile.writeText(&amp;quot;&amp;quot;&amp;quot;
        plugins { id(&amp;quot;com.cheroliv.site-baker&amp;quot;) }
        site { configPath = &amp;quot;managed-jbake-context.yml&amp;quot; }
    &amp;quot;&amp;quot;&amp;quot;.trimIndent())

    // 2. Créer le fichier de configuration pour un test contrôlé
    val configFile = File(projectDir, &amp;quot;managed-jbake-context.yml&amp;quot;)
    configFile.writeText(&amp;quot;site: { title: &amp;#39;Mon Site de Test&amp;#39; }&amp;quot;) // Contenu YAML simple

    // 3. Exécuter la build
    val runner = GradleRunner.create()
    runner.withPluginClasspath()
    runner.withArguments(&amp;quot;printSiteConfig&amp;quot;)
    runner.withProjectDir(projectDir) // Spécifier le répertoire du projet de test
    val result = runner.build()

    // 4. Vérifier la sortie
    assertTrue(result.output.contains(&amp;quot;Site config path: managed-jbake-context.yml&amp;quot;))
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Test ini akan gagal selama tugas`printSiteConfig`Tidak akan sebenarnya menggunakan nilai ekstensi.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_4_2_menyelesaikan_logika&amp;quot;&amp;gt;4.2. Menyelesaikan Logika&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Kami sedang memperbarui tugas di`SiteBakerPlugin.kt`supaya ia menampilkan nilai yang dikonfigurasi:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;project.tasks.register(&amp;quot;printSiteConfig&amp;quot;) { task -&amp;amp;gt;
    task.doLast {
        println(&amp;quot;Site config path: ${extension.configPath.get()}&amp;quot;)
    }
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Semua tes, unitari dan fungsional, lulus sekarang.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_kesimpulan&amp;quot;&amp;gt;Kesimpulan&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Dengan mengikuti pendekatan TDD, kami telah membangun sebuah plugin secara bertahap dan aman. Setiap fungsi kecil langsung divalidasi oleh sebuah tes, dari tes unit yang cepat hingga tes fungsional yang memvalidasi integrasi lengkap.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Dengan berhati-hati untuk membuat tes kami fungsional&amp;lt;strong&amp;gt;hermetis&amp;lt;/strong&amp;gt;— terutama dengan membuat secara programmatis file konfigurasi yang diperlukan — kami membangun jaring keamanan yang sangat kuat. Ketelitian ini melindungi kami secara efektif dari regresi dan memberi kami kepercayaan besar untuk menambahkan fitur-fitur yang lebih kompleks nantinya, seperti parsing file konfigurasi YAML.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Basis tes yang kuat ini adalah aset paling berharga untuk pemeliharaan dan perkembangan masa depan plugin.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>Pasal 1: Membuat Plugin Gradle dari A sampai Z dengan perintah gradle init</title>
            <link >https://cheroliv.com/blog/2025/0089_creation_projet_plugin_gradle_post.html</link>
            <pubDate>Tue, 23 Sep 2025 00:00:00 +0000</pubDate>
            <guid isPermaLink="false">blog/2025/0089_creation_projet_plugin_gradle_post.html</guid>
            <description>&amp;lt;div id=&amp;quot;toc&amp;quot; class=&amp;quot;toc&amp;quot;&amp;gt;
&amp;lt;div id=&amp;quot;toctitle&amp;quot;&amp;gt;Sommaire&amp;lt;/div&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel1&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_pendahuluan&amp;quot;&amp;gt;Pendahuluan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_langkah_1_mulai_asisten_proyek&amp;quot;&amp;gt;Langkah 1 : Mulai asisten proyek&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_langkah_2_memahami_struktur_yang_dihasilkan&amp;quot;&amp;gt;Langkah 2: Memahami struktur yang dihasilkan&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_file_dan_folder_kunci&amp;quot;&amp;gt;File dan folder kunci&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_pendahuluan&amp;quot;&amp;gt;Pendahuluan&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;Sasaran : Pengembang pemula hingga menengah dengan Gradle.&amp;lt;/div&amp;gt;
&amp;lt;p&amp;gt;Memulai proyek plugin Gradle baru bisa terasa mengancam. Untungnya, Gradle menyediakan asisten interaktif yang kuat, tugas`init`, yang menghasilkan struktur proyek lengkap, bersih, dan siap pakai. Panduan ini akan menunjukkan kepada Anda cara menggunakan alat ini untuk membuat dasar plugin sehat, menjelaskan struktur, dan menampilkan cara menjalankan tugas-build pertama.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_langkah_1_mulai_asisten_proyek&amp;quot;&amp;gt;Langkah 1 : Mulai asisten proyek&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Perintah`gradle init`adalah titik awal. Dia meluncurkan asisten baris perintah yang menanyakan serangkaian pertanyaan untuk mengonfigurasi proyek.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Buka terminal Anda di folder kosong dan jalankan :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;gradle init&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Asisten akan memandu Anda. Berikut pilihan yang harus dilakukan untuk plugin Gradle yang ditulis dalam Kotlin:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;olist arabic&amp;quot;&amp;gt;
&amp;lt;ol class=&amp;quot;arabic&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Pilih jenis proyek untuk dihasilkan:&amp;lt;/strong&amp;gt;Pilih`Gradle plugin`.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Pilih bahasa implementasi:&amp;lt;/strong&amp;gt;Pilih`Kotlin`.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Pilih DSL skrip build:&amp;lt;/strong&amp;gt;Pilih`Kotlin`.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Nama proyek:&amp;lt;/strong&amp;gt;Masukkan nama untuk proyek Anda (contoh:`site-baker`)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Id plugin:&amp;lt;/strong&amp;gt;Berikan ID unik untuk plugin Anda (contoh:`com.cheroliv.site-baker`)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Kelas plugin:&amp;lt;/strong&amp;gt;Tentukan nama kelas implementasi (contoh:`com.cheroliv.SiteBakerPlugin`).&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Setelah selesai, Gradle menghasilkan pohon file lengkap.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;Diagram proses inisialisasi&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
start
:Lancer `gradle init`;
:Choisir &amp;quot;Gradle plugin&amp;quot;;
:Choisir &amp;quot;Kotlin&amp;quot; (langage);
:Choisir &amp;quot;Kotlin&amp;quot; (DSL);
:Saisir nom, ID, et classe;
:Projet généré;
stop
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_langkah_2_memahami_struktur_yang_dihasilkan&amp;quot;&amp;gt;Langkah 2: Memahami struktur yang dihasilkan&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Asisten membuat struktur proyek multi-modul, memisahkan proyek root dari kode sumber plugin itu sendiri.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-text hljs&amp;quot; data-lang=&amp;quot;text&amp;quot;&amp;gt;.
├── gradle/
│   └── wrapper/
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── plugin/
│   ├── build.gradle.kts
│   └── src/
│       ├── main/
│       │   └── kotlin/
│       │       └── com/cheroliv/SiteBakerPlugin.kt
│       ├── test/
│       │   └── kotlin/
│       │       └── com/cheroliv/SiteBakerPluginTest.kt
│       └── functionalTest/
│           └── kotlin/
│               └── com/cheroliv/SiteBakerPluginFunctionalTest.kt
└── settings.gradle.kts&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_file_dan_folder_kunci&amp;quot;&amp;gt;File dan folder kunci&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;code&amp;gt;settings.gradle.kts&amp;lt;/code&amp;gt;: File ini di root menentukan modul yang termasuk dalam build. Di sini, ia mencakup sub-proyek`plugin`.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;[source,kotlin] ---- rootProject.name = &amp;quot;site-baker include(&amp;quot;plugin&amp;quot;) (empty)&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>Artikel 0 : Penyiapan Lingkungan untuk Membuat Plugin Gradle</title>
            <link >https://cheroliv.com/blog/2025/0088_environnement_plugin_gradle_post.html</link>
            <pubDate>Mon, 22 Sep 2025 00:00:00 +0000</pubDate>
            <guid isPermaLink="false">blog/2025/0088_environnement_plugin_gradle_post.html</guid>
            <description>&amp;lt;div id=&amp;quot;toc&amp;quot; class=&amp;quot;toc&amp;quot;&amp;gt;
&amp;lt;div id=&amp;quot;toctitle&amp;quot;&amp;gt;Sommaire&amp;lt;/div&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel1&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_pendahuluan&amp;quot;&amp;gt;Pendahuluan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_1_jdk_mesin_gradle&amp;quot;&amp;gt;1. JDK: Mesin Gradle&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_versi_mana_yang_harus_dipilih&amp;quot;&amp;gt;Versi mana yang harus dipilih?&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_installation_via_sdkman_direkomendasikan&amp;quot;&amp;gt;Installation via SDKMAN! (Direkomendasikan)&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel3&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_instal_sdkman&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;Instal SDKMAN!&amp;lt;/strong&amp;gt;&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_menginstal_versi_jdk&amp;quot;&amp;gt;Menginstal versi JDK&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_memeriksa_instalasi&amp;quot;&amp;gt;Memeriksa instalasi&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_2_ide_intellij_idea&amp;quot;&amp;gt;2. IDE : IntelliJ IDEA&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_3_gradle_alat_build&amp;quot;&amp;gt;3. Gradle: Alat build&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_instalasi_via_sdkman&amp;quot;&amp;gt;Instalasi via SDKMAN!&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel3&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_instal_gradle&amp;quot;&amp;gt;Instal Gradle&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_memverifikasi_instalasi&amp;quot;&amp;gt;Memverifikasi instalasi&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_4_jbake_generator_situs_statik&amp;quot;&amp;gt;4. JBake : Generator situs statik&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_instalasi_via_sdkman_2&amp;quot;&amp;gt;Instalasi via SDKMAN!&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel3&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_pasang_jbake&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;Pasang JBake&amp;lt;/strong&amp;gt;&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_memeriksa_instalasi_2&amp;quot;&amp;gt;Memeriksa instalasi&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_5_gradle_wrapper_praktik_terbaik_yang_mutlak&amp;quot;&amp;gt;5. Gradle Wrapper: Praktik terbaik yang mutlak&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_6_docker_dan_portainer_untuk_builds_yang_dapat_direproduksi_opsional&amp;quot;&amp;gt;6. Docker dan Portainer : Untuk Builds yang Dapat Direproduksi (Opsional)&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_instalasi_docker_engine&amp;quot;&amp;gt;Instalasi Docker Engine&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel3&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_menginstal_docker_melalui_skrip_resmi&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;Menginstal Docker melalui skrip resmi :&amp;lt;/strong&amp;gt;&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_mengelola_docker_tanpa_sudo_direkomendasikan&amp;quot;&amp;gt;Mengelola Docker tanpa &amp;lt;code&amp;gt;sudo&amp;lt;/code&amp;gt; (direkomendasikan) :&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_mengelola_docker_dengan_portainer_gui&amp;quot;&amp;gt;Mengelola Docker dengan Portainer (GUI)&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel3&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_buat_volume_untuk_data_portainer&amp;quot;&amp;gt;Buat volume untuk data Portainer:&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_mulai_kontainer_portainer&amp;quot;&amp;gt;Mulai kontainer Portainer :&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_kesimpulan&amp;quot;&amp;gt;Kesimpulan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_pendahuluan&amp;quot;&amp;gt;Pendahuluan&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;Sasaran: Pengembang yang ingin mulai membuat plugin Gradle.&amp;lt;/div&amp;gt;
&amp;lt;p&amp;gt;Untuk mengembangkan plugin Gradle secara efektif, lingkungan pengembangan yang terkonfigurasi dengan baik adalah diperlukan. Pengaturan yang baik akan menghemat waktu Anda, mengurangi frustrasi, dan menjamin konsistensi build Anda.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;olist arabic&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;Panduan ini fokus pada empat pilar penting :&amp;lt;/div&amp;gt;
&amp;lt;ol class=&amp;quot;arabic&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Le &amp;lt;strong&amp;gt;JDK&amp;lt;/strong&amp;gt;(Java Development Kit), karena Gradle dan JBake berjalan di JVM.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Un &amp;lt;strong&amp;gt;Lingkungan Pengembangan Terintegrasi&amp;lt;/strong&amp;gt;modern (IntelliJ IDEA) dengan dukungan yang baik untuk Kotlin dan Gradle.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Gradle&amp;lt;/strong&amp;gt;dia sendiri, untuk memulai proyek.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;JBake&amp;lt;/strong&amp;gt;, mesin situs statis kita.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;Komponen lingkungan pengembangan&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
!theme plain
node &amp;quot;Mesin Pengembang&amp;quot; {
  component &amp;quot;JDK (JVM)&amp;quot; as JDK
  component &amp;quot;IntelliJ IDEA&amp;quot; as IDE
  component &amp;quot;Gradle (global)&amp;quot; as Gradle
  component &amp;quot;JBake (global)&amp;quot; as JBake

  IDE --&amp;amp;gt; JDK : utilise
  Gradle --&amp;amp;gt; JDK : utilise
  JBake --&amp;amp;gt; JDK : utilise
}
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_1_jdk_mesin_gradle&amp;quot;&amp;gt;1. JDK: Mesin Gradle&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Gradle adalah aplikasi yang berjalan pada Java Virtual Machine (JVM). Oleh karena itu, instalasi JDK adalah prasyarat mutlak.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_versi_mana_yang_harus_dipilih&amp;quot;&amp;gt;Versi mana yang harus dipilih?&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Disarankan untuk menggunakan versi Java LTS (Long-Term Support). Saat ini,&amp;lt;strong&amp;gt;JDK 17&amp;lt;/strong&amp;gt; ou &amp;lt;strong&amp;gt;JDK 21&amp;lt;/strong&amp;gt;adalah pilihan yang sangat baik.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_installation_via_sdkman_direkomendasikan&amp;quot;&amp;gt;Installation via SDKMAN! (Direkomendasikan)&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;SDKMAN! adalah alat luar biasa untuk mengelola versi banyak SDK, termasuk Java dan Gradle.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect3&amp;quot;&amp;gt;
&amp;lt;h4 id=&amp;quot;_instal_sdkman&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;Instal SDKMAN!&amp;lt;/strong&amp;gt;&amp;lt;/h4&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;curl -s &amp;quot;https://get.sdkman.io&amp;quot; | bash source &amp;quot;$HOME/.sdkman/bin/sdkman-init.sh&amp;quot;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect3&amp;quot;&amp;gt;
&amp;lt;h4 id=&amp;quot;_menginstal_versi_jdk&amp;quot;&amp;gt;Menginstal versi JDK&amp;lt;/h4&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;# Pour lister les versions de Java disponibles sdk list java

# Pour installer Java 17 (par exemple, la distribution Temurin)
sdk install java 17.0.8-tem&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect3&amp;quot;&amp;gt;
&amp;lt;h4 id=&amp;quot;_memeriksa_instalasi&amp;quot;&amp;gt;Memeriksa instalasi&amp;lt;/h4&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;java -version&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Anda seharusnya melihat versi yang baru saja Anda instal ditampilkan.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_2_ide_intellij_idea&amp;quot;&amp;gt;2. IDE : IntelliJ IDEA&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Untuk pengembangan plugin Gradle dengan DSL Kotlin,&amp;lt;strong&amp;gt;IntelliJ IDEA&amp;lt;/strong&amp;gt;adalah alat pilihan. Integrasinya dengan Gradle dan Kotlin tak tertandingi.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Edisi Komunitas :&amp;lt;/strong&amp;gt;Gratis dan lebih dari cukup untuk memulai.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Edisi Ultimat :&amp;lt;/strong&amp;gt;Berbayar, ia menawarkan fitur canggih untuk pengembangan web dan basis data&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Unduh versi yang cocok untuk Anda dari situs JetBrains: &amp;lt;a href=&amp;quot;https://www.jetbrains.com/idea/download/&amp;quot; class=&amp;quot;bare&amp;quot;&amp;gt;https://www.jetbrains.com/idea/download/&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;IDE akan otomatis mendeteksi file Anda`build.gradle.kts` et &amp;lt;code&amp;gt;settings.gradle.kts&amp;lt;/code&amp;gt;, menawarkan autocompletion yang kuat, navigasi dalam kode, dan alat debugging yang terintegrasi.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_3_gradle_alat_build&amp;quot;&amp;gt;3. Gradle: Alat build&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Meskipun setiap proyek akan menggunakan versi Gradle-nya sendiri melalui Gradle Wrapper, memiliki pemasangan Gradle global berguna untuk tugas seperti inisialisasi proyek baru (&amp;lt;code&amp;gt;gradle init&amp;lt;/code&amp;gt;).&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_instalasi_via_sdkman&amp;quot;&amp;gt;Instalasi via SDKMAN!&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Seperti dengan JDK, SDKMAN! menyederhanakan instalasi Gradle.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect3&amp;quot;&amp;gt;
&amp;lt;h4 id=&amp;quot;_instal_gradle&amp;quot;&amp;gt;Instal Gradle&amp;lt;/h4&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;sdk install gradle&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect3&amp;quot;&amp;gt;
&amp;lt;h4 id=&amp;quot;_memverifikasi_instalasi&amp;quot;&amp;gt;Memverifikasi instalasi&amp;lt;/h4&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;gradle -v&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Perintah ini akan menampilkan versi Gradle, Kotlin, Groovy dan JDK yang digunakan.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_4_jbake_generator_situs_statik&amp;quot;&amp;gt;4. JBake : Generator situs statik&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;JBake adalah alat yang akan kita gunakan untuk mengubah file sumber kami (seperti ini) menjadi situs web statis. Memiliki instalasi lokal praktis untuk melihat pratinjau perubahan.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_instalasi_via_sdkman_2&amp;quot;&amp;gt;Instalasi via SDKMAN!&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Seperti dengan alat lainnya, SDKMAN! adalah metode yang paling sederhana.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect3&amp;quot;&amp;gt;
&amp;lt;h4 id=&amp;quot;_pasang_jbake&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;Pasang JBake&amp;lt;/strong&amp;gt;&amp;lt;/h4&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;sdk install jbake&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect3&amp;quot;&amp;gt;
&amp;lt;h4 id=&amp;quot;_memeriksa_instalasi_2&amp;quot;&amp;gt;Memeriksa instalasi&amp;lt;/h4&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;jbake -v&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Perintah ini akan menampilkan versi JBake dan informasi lingkungan Java yang ia gunakan.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_5_gradle_wrapper_praktik_terbaik_yang_mutlak&amp;quot;&amp;gt;5. Gradle Wrapper: Praktik terbaik yang mutlak&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Setelah proyek Anda dibuat (yang akan kita lihat dalam artikel berikutnya), Anda tidak sebaiknya lagi menggunakan perintah global`gradle`. Sebaliknya, Anda akan menggunakan&amp;lt;strong&amp;gt;Penyiap Gradle&amp;lt;/strong&amp;gt;(&amp;lt;code&amp;gt;./gradlew&amp;lt;/code&amp;gt;), sebuah skrip yang termasuk dalam proyek Anda.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Mengapa?&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Reproducibilitas :&amp;lt;/strong&amp;gt;Le Wrapper memastikan bahwa setiap orang yang bekerja pada proyek (termasuk server CI/CD Anda) menggunakan persis&amp;lt;strong&amp;gt;versi yang sama dari Gradle&amp;lt;/strong&amp;gt;, sehingga menghilangkan kesalahan build karena lingkungan yang berbeda.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Kesederhanaan :&amp;lt;/strong&amp;gt;Tidak perlu menginstal Gradle secara manual untuk berkontribusi pada proyek.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Cukup mengklon repositori dan menjalankan`./gradlew`.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Sebuah perintah khas akan terlihat seperti ini:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;# Ne pas faire : gradle build
# À faire :
./gradlew build&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_6_docker_dan_portainer_untuk_builds_yang_dapat_direproduksi_opsional&amp;quot;&amp;gt;6. Docker dan Portainer : Untuk Builds yang Dapat Direproduksi (Opsional)&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Meski tidak secara ketat diperlukan untuk menulis plugin, Docker dan Portainer adalah alat DevOps modern yang sangat memudahkan pembuatan build yang dapat direplikasi dan pengelolaan lingkungan Anda.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_instalasi_docker_engine&amp;quot;&amp;gt;Instalasi Docker Engine&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Docker memungkinkan Anda memaketkan aplikasi Anda dan dependensinya dalam sebuah kontainer.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect3&amp;quot;&amp;gt;
&amp;lt;h4 id=&amp;quot;_menginstal_docker_melalui_skrip_resmi&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;Menginstal Docker melalui skrip resmi :&amp;lt;/strong&amp;gt;&amp;lt;/h4&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;curl -fsSL https://get.docker.com -o get-docker.sh sudo sh get-docker.sh&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect3&amp;quot;&amp;gt;
&amp;lt;h4 id=&amp;quot;_mengelola_docker_tanpa_sudo_direkomendasikan&amp;quot;&amp;gt;Mengelola Docker tanpa &amp;lt;code&amp;gt;sudo&amp;lt;/code&amp;gt; (direkomendasikan) :&amp;lt;/h4&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Tambahkan pengguna Anda ke grup`docker`.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;sudo usod -aG docker $USER&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;admonitionblock important&amp;quot;&amp;gt;
&amp;lt;table&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;icon&amp;quot;&amp;gt;
&amp;lt;i class=&amp;quot;fa icon-important&amp;quot; title=&amp;quot;Important&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;content&amp;quot;&amp;gt;

&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;docker run hello-world&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_mengelola_docker_dengan_portainer_gui&amp;quot;&amp;gt;Mengelola Docker dengan Portainer (GUI)&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Portainer adalah antarmuka web ringan untuk mengelola kontainer Docker Anda.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect3&amp;quot;&amp;gt;
&amp;lt;h4 id=&amp;quot;_buat_volume_untuk_data_portainer&amp;quot;&amp;gt;Buat volume untuk data Portainer:&amp;lt;/h4&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;docker volume create portainer_data&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect3&amp;quot;&amp;gt;
&amp;lt;h4 id=&amp;quot;_mulai_kontainer_portainer&amp;quot;&amp;gt;Mulai kontainer Portainer :&amp;lt;/h4&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;docker run -d -p 8000:8000 -p 9443:9443 --name portainer --restart=always -v /var/run/docker.sock:/var/run/docker.sock -v portainer_data:/data portainer/portainer-ce:latest&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Anda sekarang dapat mengakses antarmuka Portainer di`https://localhost:9443`.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_kesimpulan&amp;quot;&amp;gt;Kesimpulan&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Lingkungan Anda sekarang siap ! Anda memiliki JDK, IDE yang paling sesuai, Gradle dan JBake. Dengan penambahan Docker, Anda juga siap untuk membuat build yang dikontainerkan dan dapat direproduksi. Dasar yang kuat ini akan memungkinkan Anda untuk fokus pada yang esensial: pengembangan plugin yang kuat dan berguna.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Dalam artikel berikutnya, kami akan menggunakan perintah`gradle init`untuk menghasilkan struktur dari proyek plugin pertama kami.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>CI/CD Python – Bagian 3 : Melanjutkan lebih jauh dengan Poetry, Docker dan publikasi lanjutan</title>
            <link >https://cheroliv.com/blog/2025/0087_pypi_cicd_part_3_post.html</link>
            <pubDate>Sat, 19 Jul 2025 00:00:00 +0000</pubDate>
            <guid isPermaLink="false">blog/2025/0087_pypi_cicd_part_3_post.html</guid>
            <description>&amp;lt;div id=&amp;quot;toc&amp;quot; class=&amp;quot;toc&amp;quot;&amp;gt;
&amp;lt;div id=&amp;quot;toctitle&amp;quot;&amp;gt;Table of Contents&amp;lt;/div&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel1&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_integrasi_dengan_poetry&amp;quot;&amp;gt;Integrasi dengan Poetry&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_instalasi_poetry&amp;quot;&amp;gt;Instalasi Poetry&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_inisialisasi_proyek&amp;quot;&amp;gt;Inisialisasi proyek&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_penambahan_dan_instalasi_dependensi&amp;quot;&amp;gt;Penambahan dan instalasi dependensi&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_publication_dengan_poetry&amp;quot;&amp;gt;Publication dengan Poetry&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_builds_direproduksi_dengan_docker&amp;quot;&amp;gt;Builds direproduksi dengan Docker&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_contoh_dockerfile&amp;quot;&amp;gt;Contoh Dockerfile&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_integrasi_di_github_actions&amp;quot;&amp;gt;Integrasi di GitHub Actions&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_publikasi_bersyarat&amp;quot;&amp;gt;Publikasi bersyarat&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_otomatisasi_dependensi_dengan_renovate_dan_dependabot&amp;quot;&amp;gt;Otomatisasi dependensi dengan Renovate dan Dependabot&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_dependabot&amp;quot;&amp;gt;Dependabot&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_meng_renovasi&amp;quot;&amp;gt;meng-renovasi&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_diagram_plantuml&amp;quot;&amp;gt;Diagram PlantUML&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_kasus_penggunaan_cicd_lanjutan&amp;quot;&amp;gt;Kasus Penggunaan – CI/CD Lanjutan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_urutan_publikasi_bersyarat&amp;quot;&amp;gt;Urutan – Publikasi bersyarat&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_negara_pipeline_cicd&amp;quot;&amp;gt;Negara – Pipeline CI/CD&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_penyebaran_arsitektur_cicd&amp;quot;&amp;gt;Penyebaran – Arsitektur CI/CD&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_kesimpulan&amp;quot;&amp;gt;Kesimpulan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div id=&amp;quot;preamble&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Dalam dua bagian pertama dari seri ini, kita memiliki : - Telah menyusun pipeline CI/CD yang fungsional untuk aplikasi Python dengan GitHub Actions dan PyPI. - Mengindustrialisasi pipeline ini dengan pengujian multi-versi, penerbitan bertahap melalui Test PyPI, dan alat-alat kualitas serta keamanan.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Dalam bagian ketiga ini, kita akan pergi&amp;lt;strong&amp;gt;di luar sekadar publikasi di PyPI&amp;lt;/strong&amp;gt;untuk membangun rantai CI/CD yang lengkap, kuat, dan profesional dengan : - &amp;lt;strong&amp;gt;Puisi&amp;lt;/strong&amp;gt;untuk packaging modern dan manajemen dependensi yang dioptimalkan. - &amp;lt;strong&amp;gt;Docker&amp;lt;/strong&amp;gt;untuk membuat build yang dapat direproduksi dan multi-platform - &amp;lt;strong&amp;gt;Publikasi bersyarat&amp;lt;/strong&amp;gt;untuk mengelola skenario seperti release candidates - &amp;lt;strong&amp;gt;Alat otomatisasi&amp;lt;/strong&amp;gt;(Renovate, Dependabot) untuk menjaga pipeline tetap aktual tanpa usaha.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_integrasi_dengan_poetry&amp;quot;&amp;gt;Integrasi dengan Poetry&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Poetry menggantikan alat-alat packaging lama (setup.py, requirements.txt) dengan mengkonsolidasikan pengelolaan dependensi dan build dalam`pyproject.toml`.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_instalasi_poetry&amp;quot;&amp;gt;Instalasi Poetry&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-shell hljs&amp;quot; data-lang=&amp;quot;shell&amp;quot;&amp;gt;# Installer Poetry
curl -sSL https://install.python-poetry.org | python3 -
# Vérifier la version
poetry --version&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_inisialisasi_proyek&amp;quot;&amp;gt;Inisialisasi proyek&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-shell hljs&amp;quot; data-lang=&amp;quot;shell&amp;quot;&amp;gt;# Initialiser un nouveau projet avec Poetry
poetry init
# Suivre l&amp;#39;assistant pour renseigner : nom, version, description, licence, dépendances.&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Ini menghasilkan sebuah file`pyproject.toml`:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-toml hljs&amp;quot; data-lang=&amp;quot;toml&amp;quot;&amp;gt;[tool.poetry]
name = &amp;quot;playlist-downloader&amp;quot;
version = &amp;quot;0.1.0&amp;quot;
description = &amp;quot;CLI tool for managing YouTube playlists&amp;quot;
authors = [&amp;quot;Christophe Hérolivier &amp;amp;lt;cheroliv@example.com&amp;amp;gt;&amp;quot;]

[tool.poetry.dependencies]
python = &amp;quot;&amp;amp;gt;=3.8&amp;quot;
typer = &amp;quot;^0.9.0&amp;quot;
yt-dlp = &amp;quot;^2023.7.6&amp;quot;
google-api-python-client = &amp;quot;^2.0.0&amp;quot;
google-auth-oauthlib = &amp;quot;^1.0.0&amp;quot;

[tool.poetry.group.dev.dependencies]
pytest = &amp;quot;^7.0&amp;quot;
mypy = &amp;quot;^1.0&amp;quot;
bandit = &amp;quot;^1.7&amp;quot;
safety = &amp;quot;^2.3&amp;quot;
black = &amp;quot;^23.0&amp;quot;
ruff = &amp;quot;^0.1&amp;quot;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_penambahan_dan_instalasi_dependensi&amp;quot;&amp;gt;Penambahan dan instalasi dependensi&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-shell hljs&amp;quot; data-lang=&amp;quot;shell&amp;quot;&amp;gt;poetry add typer yt-dlp google-api-python-client google-auth-oauthlib
poetry add --group dev pytest mypy black bandit safety ruff&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_publication_dengan_poetry&amp;quot;&amp;gt;Publication dengan Poetry&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Poetry mengelola publikasi secara bawaan:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-shell hljs&amp;quot; data-lang=&amp;quot;shell&amp;quot;&amp;gt;# Publication sur Test PyPI
poetry publish --build --repository test-pypi

# Publication sur PyPI
poetry publish --build&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Perintah ini secara otomatis menggunakan informasi yang ada di`pyproject.toml`.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_builds_direproduksi_dengan_docker&amp;quot;&amp;gt;Builds direproduksi dengan Docker&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Untuk memastikan eksekusi yang identik di pengembangan, CI/CD, dan produksi, Docker terintegrasi sempurna dengan Poetry.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_contoh_dockerfile&amp;quot;&amp;gt;Contoh Dockerfile&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-dockerfile hljs&amp;quot; data-lang=&amp;quot;dockerfile&amp;quot;&amp;gt;FROM python:3.11-slim

WORKDIR /app
COPY pyproject.toml poetry.lock ./
RUN pip install poetry
RUN poetry install --no-root --only main

COPY . .

CMD [&amp;quot;poetry&amp;quot;, &amp;quot;run&amp;quot;, &amp;quot;python&amp;quot;, &amp;quot;cli.py&amp;quot;]&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Ini menjamin : - Lingkungan Python yang beku. - Dependensi yang terkunci via`poetry.lock`. Gambar yang dapat dijalankan di semua sistem yang mendukung Docker.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_integrasi_di_github_actions&amp;quot;&amp;gt;Integrasi di GitHub Actions&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-yaml hljs&amp;quot; data-lang=&amp;quot;yaml&amp;quot;&amp;gt;name: Docker Build

on:
  push:
    branches: [main]

jobs:
  build-docker:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Build Docker image
        run: docker build -t ghcr.io/${{ github.repository }}:latest .
      - name: Push Docker image
        run: docker push ghcr.io/${{ github.repository }}:latest&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_publikasi_bersyarat&amp;quot;&amp;gt;Publikasi bersyarat&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Dalam pipeline profesional, harus dapat mempublikasikan hanya dalam beberapa kasus : - Release candidates ke Test PyPI. - Versi stabil ke PyPI. - Builds Docker yang dimemicu hanya untuk`main`.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-yaml hljs&amp;quot; data-lang=&amp;quot;yaml&amp;quot;&amp;gt;- name: Publish to Test PyPI
  if: contains(github.ref, &amp;#39;-rc&amp;#39;)
  run: poetry publish --build --repository test-pypi

- name: Publish to PyPI
  if: startsWith(github.ref, &amp;#39;refs/tags/v&amp;#39;)
  run: poetry publish --build&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Pendekatan ini menghindari publikasi yang tidak disengaja&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_otomatisasi_dependensi_dengan_renovate_dan_dependabot&amp;quot;&amp;gt;Otomatisasi dependensi dengan Renovate dan Dependabot&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Agar dependensi Anda tidak menjadi usang, integrasikan alat pembaruan otomatis.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_dependabot&amp;quot;&amp;gt;Dependabot&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-yaml hljs&amp;quot; data-lang=&amp;quot;yaml&amp;quot;&amp;gt;version: 2
updates:
  - package-ecosystem: &amp;quot;pip&amp;quot;
    directory: &amp;quot;/&amp;quot;
    schedule:
      interval: &amp;quot;weekly&amp;quot;
  - package-ecosystem: &amp;quot;github-actions&amp;quot;
    directory: &amp;quot;/&amp;quot;
    schedule:
      interval: &amp;quot;weekly&amp;quot;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Dependabot membuka PR setiap minggu untuk memperbarui dependensi Python dan GitHub Actions.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_meng_renovasi&amp;quot;&amp;gt;meng-renovasi&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-json hljs&amp;quot; data-lang=&amp;quot;json&amp;quot;&amp;gt;{
  &amp;quot;extends&amp;quot;: [&amp;quot;config:base&amp;quot;],
  &amp;quot;packageRules&amp;quot;: [
    {
      &amp;quot;matchManagers&amp;quot;: [&amp;quot;pip&amp;quot;],
      &amp;quot;groupName&amp;quot;: &amp;quot;python-dependencies&amp;quot;,
      &amp;quot;schedule&amp;quot;: [&amp;quot;before 6am on monday&amp;quot;]
    }
  ]
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Renovate memungkinkan kontrol yang lebih halus: pengelompokan dependensi, penjadwalan, dan aturan canggih.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_diagram_plantuml&amp;quot;&amp;gt;Diagram PlantUML&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_kasus_penggunaan_cicd_lanjutan&amp;quot;&amp;gt;Kasus Penggunaan – CI/CD Lanjutan&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
actor Developer
actor GitHub as &amp;quot;GitHub Actions&amp;quot;
actor PyPI
actor DockerRegistry as &amp;quot;Docker Registry&amp;quot;
actor Automation as &amp;quot;Renovate/Dependabot&amp;quot;

Developer --&amp;amp;gt; (Push code)
(Trigger CI) --&amp;amp;gt; GitHub
GitHub --&amp;amp;gt; (Run Tests &amp;amp;amp; Lint)
GitHub --&amp;amp;gt; (Build Docker Image)
GitHub --&amp;amp;gt; DockerRegistry
GitHub --&amp;amp;gt; (Publish to Test PyPI)
GitHub --&amp;amp;gt; (Promote to PyPI)
Automation --&amp;amp;gt; (Update Dependencies)
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_urutan_publikasi_bersyarat&amp;quot;&amp;gt;Urutan – Publikasi bersyarat&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
!theme vibrant
left to right direction
Developer -&amp;amp;gt; GitHub: Push tag v1.2.3-rc
GitHub -&amp;amp;gt; CI: Run tests
CI -&amp;amp;gt; CD: Check release type
CD -&amp;amp;gt; Test PyPI: Publish RC
Developer -&amp;amp;gt; GitHub: Push tag v1.2.3
GitHub -&amp;amp;gt; CD: Publish to PyPI
CD -&amp;amp;gt; Docker Registry: Push Docker image
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_negara_pipeline_cicd&amp;quot;&amp;gt;Negara – Pipeline CI/CD&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
[*] --&amp;amp;gt; Idle
Idle --&amp;amp;gt; CI_Running : push
CI_Running --&amp;amp;gt; CI_Success : tests ok
CI_Running --&amp;amp;gt; CI_Failed : tests fail
CI_Success --&amp;amp;gt; CD_Running : tag detected
CD_Running --&amp;amp;gt; CD_Success : publish ok
CD_Running --&amp;amp;gt; CD_Failed : error
CD_Success --&amp;amp;gt; [*]
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_penyebaran_arsitektur_cicd&amp;quot;&amp;gt;Penyebaran – Arsitektur CI/CD&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
node &amp;quot;Mesin Pengembang&amp;quot; {
  component &amp;quot;Klien Git&amp;quot;
}

node &amp;quot;GitHub Actions&amp;quot; {
  component &amp;quot;CI Alur Kerja&amp;quot;
  component &amp;quot;CD Alur kerja&amp;quot;
}

node &amp;quot;Repositori Paket&amp;quot; {
  artifact &amp;quot;Uji PyPI&amp;quot;
  artifact &amp;quot;PyPI&amp;quot;
  artifact &amp;quot;Docker Registry&amp;quot;
}

&amp;quot;Mesin Pengembang&amp;quot; --&amp;amp;gt; &amp;quot;GitHub Actions&amp;quot;
&amp;quot;GitHub Actions&amp;quot; --&amp;amp;gt; &amp;quot;Uji PyPI&amp;quot;
&amp;quot;GitHub Actions&amp;quot; --&amp;amp;gt; &amp;quot;PyPI&amp;quot;
&amp;quot;GitHub Actions&amp;quot; --&amp;amp;gt; &amp;quot;Docker Registry&amp;quot;
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_kesimpulan&amp;quot;&amp;gt;Kesimpulan&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Dalam bagian ketiga ini, kita telah melihat bagaimana: - Modernisasi packaging Python dengan Poetry - Memastikan build yang dapat direproduksi melalui Docker. - Menyiapkan publikasi bersyarat. - Otomatiskan pembaruan dependensi.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Anda kini memiliki pipeline CI/CD&amp;lt;strong&amp;gt;lengkap, industri, dan aman&amp;lt;/strong&amp;gt;, siap untuk berkembang bersama proyek Anda.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>Bagian 2 : Mengindustrialisasikan dan mengamankan pipeline CI/CD Python tingkat lanjut</title>
            <link >https://cheroliv.com/blog/2025/0086_pypi_cicd_part_2_post.html</link>
            <pubDate>Fri, 18 Jul 2025 00:00:00 +0000</pubDate>
            <guid isPermaLink="false">blog/2025/0086_pypi_cicd_part_2_post.html</guid>
            <description>&amp;lt;div id=&amp;quot;toc&amp;quot; class=&amp;quot;toc&amp;quot;&amp;gt;
&amp;lt;div id=&amp;quot;toctitle&amp;quot;&amp;gt;Sommaire&amp;lt;/div&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel1&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_pengantar&amp;quot;&amp;gt;Pengantar&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_arsitektur_pipeline_cicd&amp;quot;&amp;gt;Arsitektur Pipeline CI/CD&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_struktur_proyek&amp;quot;&amp;gt;Struktur Proyek&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_konfigurasi_paket_dengan_pyproject_toml&amp;quot;&amp;gt;Konfigurasi Paket dengan pyproject.toml&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_alur_kerja_cicd_pengujian_dan_kualitas&amp;quot;&amp;gt;Alur kerja CI/CD - Pengujian dan Kualitas&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_workflow_rilis_dan_penerapan&amp;quot;&amp;gt;Workflow Rilis dan Penerapan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_diagram_arsitektur&amp;quot;&amp;gt;diagram arsitektur&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_diagram_urutan_proses_rilis&amp;quot;&amp;gt;Diagram Urutan - Proses Rilis&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_diagram_kondisi_siklus_hidup_paket&amp;quot;&amp;gt;Diagram Kondisi - Siklus Hidup Paket&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_diagram_penyebaran_infrastruktur_cicd&amp;quot;&amp;gt;Diagram Penyebaran - Infrastruktur CI/CD&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_objek_dan_model_pipeline&amp;quot;&amp;gt;Objek dan Model Pipeline&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_diagram_kelas_model_model_cicd&amp;quot;&amp;gt;Diagram kelas - Model-model CI/CD&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_konfigurasi_rahasia&amp;quot;&amp;gt;Konfigurasi Rahasia&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_rahasia_github_actions&amp;quot;&amp;gt;Rahasia GitHub Actions&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_pembuatan_token_pypi&amp;quot;&amp;gt;Pembuatan Token PyPI&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_skrip_pengembangan_lokal&amp;quot;&amp;gt;Skrip Pengembangan Lokal&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_makefile&amp;quot;&amp;gt;Makefile&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_skrip_versi&amp;quot;&amp;gt;Skrip Versi&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_praktik_baik_dan_rekomendasi&amp;quot;&amp;gt;Praktik Baik dan Rekomendasi&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_versioning_semantik&amp;quot;&amp;gt;Versioning Semantik&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_strategi_pembranching&amp;quot;&amp;gt;Strategi Pembranching&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_pengujian_dan_cakupan&amp;quot;&amp;gt;Pengujian dan Cakupan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_keamanan&amp;quot;&amp;gt;Keamanan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_monitoring_dan_observability&amp;quot;&amp;gt;monitoring dan observability&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_metrik_pipeline&amp;quot;&amp;gt;Metrik Pipeline&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_notifikasi&amp;quot;&amp;gt;Notifikasi&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_kesimpulan&amp;quot;&amp;gt;Kesimpulan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_sumber_daya_tambahan&amp;quot;&amp;gt;Sumber Daya Tambahan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_pengantar&amp;quot;&amp;gt;Pengantar&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;Tujuan : Mengubah pipeline sederhana menjadi pipeline industri yang kuat, mematuhi standar modern dari packaging dan rekayasa perangkat lunak.&amp;lt;/div&amp;gt;
&amp;lt;p&amp;gt;Dalam bagian pertama dari seri ini, kita telah membangun pipeline CI/CD sederhana namun efektif untuk mengotomatiskan pengujian dan publikasi paket Python ke PyPI.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Saatnya untuk profesionalisasi pipeline ini. Dalam bagian kedua ini, kita akan :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Migrasikan ke standar modern pyproject.toml,&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Menambahkan alat kualitas dan keamanan (Black, Mypy, Bandit, Safety),&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Menyiapkan tes multi-versi,&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Mengintegrasikan peluncuran bertahap melalui Test PyPI,&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Otomatiskan versioning dan perbaiki pemantauan pipeline.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Bersiaplah untuk beralih dari pipeline fungsional ke infrastruktur CI/CD profesional.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Menerapkan aplikasi Python ke PyPI membutuhkan pipeline CI/CD yang kuat yang mengotomatiskan pengujian, pembangunan, dan publikasi paket. Artikel ini merinci penyiapan pipeline lengkap yang menggunakan GitHub Actions untuk aplikasi CLI Python, dengan mengandalkan praktik terbaik ekosistem Python modern.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_arsitektur_pipeline_cicd&amp;quot;&amp;gt;Arsitektur Pipeline CI/CD&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Pipeline yang akan kita bangun mengikuti pendekatan bertahap:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
!theme plain
title Alur CI/CD Python ke PyPI
skinparam backgroundColor #f8f9fa
skinparam componentStyle rectangle

rectangle &amp;quot;Pengembang&amp;quot; as dev
rectangle &amp;quot;Repositori GitHub&amp;quot; as repo {
  rectangle &amp;quot;.github/workflows/&amp;quot; as workflows
  rectangle &amp;quot;ujian/&amp;quot; as tests
  rectangle &amp;quot;setup.py / pyproject.toml&amp;quot; as setup
  rectangle &amp;quot;requirements.txt&amp;quot; as req
}

rectangle &amp;quot;GitHub Tindakan&amp;quot; as actions {
  rectangle &amp;quot;Pelaksana Pengujian&amp;quot; as test_runner
  rectangle &amp;quot;Bangun&amp;quot; as build
  rectangle &amp;quot;Pindai Keamanan&amp;quot; as security
  rectangle &amp;quot;Pemeriksaan Kualitas&amp;quot; as quality
}

rectangle &amp;quot;PyPI&amp;quot; as pypi {
  rectangle &amp;quot;Uji PyPI&amp;quot; as test_pypi
  rectangle &amp;quot;Produksi PyPI&amp;quot; as prod_pypi
}

rectangle &amp;quot;Pengguna&amp;quot; as users

dev --&amp;amp;gt; repo : push/PR
repo --&amp;amp;gt; actions : trigger workflow
actions --&amp;amp;gt; test_runner : run tests
actions --&amp;amp;gt; security : security checks
actions --&amp;amp;gt; quality : code quality
actions --&amp;amp;gt; build : build package
build --&amp;amp;gt; test_pypi : deploy (pre-release)
build --&amp;amp;gt; prod_pypi : deploy (release)
prod_pypi --&amp;amp;gt; users : install package

@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_struktur_proyek&amp;quot;&amp;gt;Struktur Proyek&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Sebuah aplikasi CLI Python yang siap untuk didistribusikan harus mematuhi struktur yang terstandar :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-text hljs&amp;quot; data-lang=&amp;quot;text&amp;quot;&amp;gt;playlist-downloader/
├── .github/
│   └── workflows/
│       ├── ci.yml
│       ├── release.yml
│       └── security.yml
├── src/
│   └── playlist_downloader/
│       ├── __init__.py
│       ├── cli.py
│       ├── core/
│       └── adapters/
├── tests/
│   ├── unit/
│   ├── integration/
│   └── conftest.py
├── docs/
├── pyproject.toml
├── requirements.txt
├── requirements-dev.txt
├── MANIFEST.in
├── README.md
├── LICENSE
└── CHANGELOG.md&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_konfigurasi_paket_dengan_pyproject_toml&amp;quot;&amp;gt;Konfigurasi Paket dengan pyproject.toml&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;File`pyproject.toml`adalah standar modern untuk mengonfigurasi paket Python:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-toml hljs&amp;quot; data-lang=&amp;quot;toml&amp;quot;&amp;gt;[build-system]
requires = [&amp;quot;setuptools&amp;amp;gt;=45&amp;quot;, &amp;quot;wheel&amp;quot;, &amp;quot;setuptools_scm&amp;amp;gt;=6.2&amp;quot;]
build-backend = &amp;quot;setuptools.build_meta&amp;quot;

[project]
name = &amp;quot;playlist-downloader&amp;quot;
authors = [
    {name = &amp;quot;Christophe Hérolivier&amp;quot;, email = &amp;quot;cheroliv@example.com&amp;quot;},
]
description = &amp;quot;CLI tool for YouTube playlist management&amp;quot;
readme = &amp;quot;README.md&amp;quot;
requires-python = &amp;quot;&amp;amp;gt;=3.8&amp;quot;
keywords = [&amp;quot;youtube&amp;quot;, &amp;quot;playlist&amp;quot;, &amp;quot;cli&amp;quot;, &amp;quot;downloader&amp;quot;]
license = {text = &amp;quot;MIT&amp;quot;}
classifiers = [
    &amp;quot;Development Status :: 4 - Beta&amp;quot;,
    &amp;quot;Environment :: Console&amp;quot;,
    &amp;quot;Intended Audience :: End Users/Desktop&amp;quot;,
    &amp;quot;License :: OSI Approved :: MIT License&amp;quot;,
    &amp;quot;Operating System :: OS Independent&amp;quot;,
    &amp;quot;Programming Language :: Python :: 3&amp;quot;,
    &amp;quot;Programming Language :: Python :: 3.8&amp;quot;,
    &amp;quot;Programming Language :: Python :: 3.9&amp;quot;,
    &amp;quot;Programming Language :: Python :: 3.10&amp;quot;,
    &amp;quot;Programming Language :: Python :: 3.11&amp;quot;,
    &amp;quot;Topic :: Multimedia :: Sound/Audio&amp;quot;,
    &amp;quot;Topic :: Utilities&amp;quot;,
]
dependencies = [
    &amp;quot;typer&amp;amp;gt;=0.9.0&amp;quot;,
    &amp;quot;yt-dlp&amp;amp;gt;=2023.7.6&amp;quot;,
    &amp;quot;google-api-python-client&amp;amp;gt;=2.0.0&amp;quot;,
    &amp;quot;google-auth-oauthlib&amp;amp;gt;=1.0.0&amp;quot;,
    &amp;quot;pyyaml&amp;amp;gt;=6.0&amp;quot;,
    &amp;quot;rich&amp;amp;gt;=13.0.0&amp;quot;,
]
dynamic = [&amp;quot;version&amp;quot;]

[project.optional-dependencies]
dev = [
    &amp;quot;pytest&amp;amp;gt;=7.0.0&amp;quot;,
    &amp;quot;pytest-cov&amp;amp;gt;=4.0.0&amp;quot;,
    &amp;quot;pytest-mock&amp;amp;gt;=3.10.0&amp;quot;,
    &amp;quot;black&amp;amp;gt;=23.0.0&amp;quot;,
    &amp;quot;flake8&amp;amp;gt;=6.0.0&amp;quot;,
    &amp;quot;mypy&amp;amp;gt;=1.0.0&amp;quot;,
    &amp;quot;pre-commit&amp;amp;gt;=3.0.0&amp;quot;,
    &amp;quot;tox&amp;amp;gt;=4.0.0&amp;quot;,
]
test = [
    &amp;quot;pytest&amp;amp;gt;=7.0.0&amp;quot;,
    &amp;quot;pytest-cov&amp;amp;gt;=4.0.0&amp;quot;,
    &amp;quot;pytest-mock&amp;amp;gt;=3.10.0&amp;quot;,
]

[project.urls]
Homepage = &amp;quot;https://github.com/cheroliv/playlist-downloader&amp;quot;
Documentation = &amp;quot;https://github.com/cheroliv/playlist-downloader#readme&amp;quot;
Repository = &amp;quot;https://github.com/cheroliv/playlist-downloader.git&amp;quot;
&amp;quot;Bug Tracker&amp;quot; = &amp;quot;https://github.com/cheroliv/playlist-downloader/issues&amp;quot;

[project.scripts]
playlist-downloader = &amp;quot;playlist_downloader.cli:main&amp;quot;

[tool.setuptools_scm]
write_to = &amp;quot;src/playlist_downloader/_version.py&amp;quot;

[tool.setuptools.packages.find]
where = [&amp;quot;src&amp;quot;]

[tool.pytest.ini_options]
testpaths = [&amp;quot;tests&amp;quot;]
python_files = [&amp;quot;test_*.py&amp;quot;]
python_classes = [&amp;quot;Test*&amp;quot;]
python_functions = [&amp;quot;test_*&amp;quot;]
addopts = [
    &amp;quot;--cov=src/playlist_downloader&amp;quot;,
    &amp;quot;--cov-report=html&amp;quot;,
    &amp;quot;--cov-report=term-missing&amp;quot;,
    &amp;quot;--cov-fail-under=85&amp;quot;,
]

[tool.black]
line-length = 88
target-version = [&amp;#39;py38&amp;#39;]
include = &amp;#39;\.pyi?$&amp;#39;
extend-exclude = &amp;#39;&amp;#39;&amp;#39;
/(
  \.eggs
  | \.git
  | \.hg
  | \.mypy_cache
  | \.tox
  | \.venv
  | _build
  | buck-out
  | build
  | dist
)/
&amp;#39;&amp;#39;&amp;#39;

[tool.mypy]
python_version = &amp;quot;3.8&amp;quot;
warn_return_any = true
warn_unused_configs = true
disallow_untyped_defs = true
disallow_incomplete_defs = true
check_untyped_defs = true
disallow_untyped_decorators = true
no_implicit_optional = true
warn_redundant_casts = true
warn_unused_ignores = true
warn_no_return = true
warn_unreachable = true
strict_equality = true

[[tool.mypy.overrides]]
module = [
    &amp;quot;yt_dlp.*&amp;quot;,
    &amp;quot;googleapiclient.*&amp;quot;,
    &amp;quot;google_auth_oauthlib.*&amp;quot;,
]
ignore_missing_imports = true&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_alur_kerja_cicd_pengujian_dan_kualitas&amp;quot;&amp;gt;Alur kerja CI/CD - Pengujian dan Kualitas&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Alur kerja utama (&amp;lt;code&amp;gt;ci.yml&amp;lt;/code&amp;gt;) menjalankan tes pada beberapa versi Python :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-yaml hljs&amp;quot; data-lang=&amp;quot;yaml&amp;quot;&amp;gt;name: CI

on:
  push:
    branches: [ main, develop ]
  pull_request:
    branches: [ main ]

jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        python-version: [&amp;quot;3.8&amp;quot;, &amp;quot;3.9&amp;quot;, &amp;quot;3.10&amp;quot;, &amp;quot;3.11&amp;quot;]

    steps:
    - uses: actions/checkout@v4
      with:
        fetch-depth: 0

    - name: Set up Python ${{ matrix.python-version }}
      uses: actions/setup-python@v4
      with:
        python-version: ${{ matrix.python-version }}

    - name: Cache dependencies
      uses: actions/cache@v3
      with:
        path: |
          ~/.cache/pip
          ~/.cache/pre-commit
        key: ${{ runner.os }}-pip-${{ hashFiles(&amp;#39;**/requirements*.txt&amp;#39;) }}
        restore-keys: |
          ${{ runner.os }}-pip-

    - name: Install dependencies
      run: |
        python -m pip install --upgrade pip
        pip install -e &amp;quot;.[dev]&amp;quot;

    - name: Lint with flake8
      run: |
        flake8 src tests --count --select=E9,F63,F7,F82 --show-source --statistics
        flake8 src tests --count --exit-zero --max-complexity=10 --max-line-length=88 --statistics

    - name: Check code formatting with Black
      run: black --check src tests

    - name: Type checking with mypy
      run: mypy src

    - name: Run tests with pytest
      run: |
        pytest tests/ -v --cov=src/playlist_downloader \
          --cov-report=xml --cov-report=term-missing

    - name: Upload coverage to Codecov
      uses: codecov/codecov-action@v3
      if: matrix.python-version == &amp;#39;3.11&amp;#39;
      with:
        file: ./coverage.xml
        flags: unittests
        name: codecov-umbrella

  security:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4

    - name: Set up Python
      uses: actions/setup-python@v4
      with:
        python-version: &amp;quot;3.11&amp;quot;

    - name: Install dependencies
      run: |
        python -m pip install --upgrade pip
        pip install bandit[toml] safety

    - name: Run security checks with bandit
      run: bandit -r src/ -f json -o bandit-report.json

    - name: Check dependencies with safety
      run: safety check --json --output safety-report.json

    - name: Upload security reports
      uses: actions/upload-artifact@v3
      if: always()
      with:
        name: security-reports
        path: |
          bandit-report.json
          safety-report.json

  build:
    needs: [test, security]
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4
      with:
        fetch-depth: 0

    - name: Set up Python
      uses: actions/setup-python@v4
      with:
        python-version: &amp;quot;3.11&amp;quot;

    - name: Install build dependencies
      run: |
        python -m pip install --upgrade pip
        pip install build twine

    - name: Build package
      run: python -m build

    - name: Check package with twine
      run: twine check dist/*

    - name: Upload build artifacts
      uses: actions/upload-artifact@v3
      with:
        name: dist
        path: dist/&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_workflow_rilis_dan_penerapan&amp;quot;&amp;gt;Workflow Rilis dan Penerapan&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Alur kerja rilis (&amp;lt;code&amp;gt;release.yml&amp;lt;/code&amp;gt;) mengelola publikasi otomatis ke PyPI:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-yaml hljs&amp;quot; data-lang=&amp;quot;yaml&amp;quot;&amp;gt;name: Release

on:
  push:
    tags:
      - &amp;#39;v*.*.*&amp;#39;
  workflow_dispatch:
    inputs:
      environment:
        description: &amp;#39;Deployment environment&amp;#39;
        required: true
        default: &amp;#39;test&amp;#39;
        type: choice
        options:
        - test
        - production

env:
  PYTHON_VERSION: &amp;quot;3.11&amp;quot;

jobs:
  release:
    runs-on: ubuntu-latest
    environment:
      name: ${{ github.event.inputs.environment || (startsWith(github.ref, &amp;#39;refs/tags/&amp;#39;) &amp;amp;amp;&amp;amp;amp; &amp;#39;production&amp;#39; || &amp;#39;test&amp;#39;) }}

    steps:
    - uses: actions/checkout@v4
      with:
        fetch-depth: 0

    - name: Set up Python
      uses: actions/setup-python@v4
      with:
        python-version: ${{ env.PYTHON_VERSION }}

    - name: Install dependencies
      run: |
        python -m pip install --upgrade pip
        pip install build twine

    - name: Build package
      run: python -m build

    - name: Check package
      run: twine check dist/*

    - name: Publish to Test PyPI
      if: github.event.inputs.environment == &amp;#39;test&amp;#39; || (startsWith(github.ref, &amp;#39;refs/tags/&amp;#39;) &amp;amp;amp;&amp;amp;amp; contains(github.ref, &amp;#39;rc&amp;#39;))
      env:
        TWINE_USERNAME: __token__
        TWINE_PASSWORD: ${{ secrets.TEST_PYPI_API_TOKEN }}
      run: |
        twine upload --repository testpypi dist/*

    - name: Publish to PyPI
      if: github.event.inputs.environment == &amp;#39;production&amp;#39; || (startsWith(github.ref, &amp;#39;refs/tags/&amp;#39;) &amp;amp;amp;&amp;amp;amp; !contains(github.ref, &amp;#39;rc&amp;#39;))
      env:
        TWINE_USERNAME: __token__
        TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
      run: |
        twine upload dist/*

    - name: Create GitHub Release
      if: startsWith(github.ref, &amp;#39;refs/tags/&amp;#39;)
      uses: actions/create-release@v1
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      with:
        tag_name: ${{ github.ref }}
        release_name: Release ${{ github.ref }}
        draft: false
        prerelease: ${{ contains(github.ref, &amp;#39;rc&amp;#39;) }}

  post-release:
    needs: release
    runs-on: ubuntu-latest
    if: startsWith(github.ref, &amp;#39;refs/tags/&amp;#39;)

    steps:
    - uses: actions/checkout@v4

    - name: Set up Python
      uses: actions/setup-python@v4
      with:
        python-version: ${{ env.PYTHON_VERSION }}

    - name: Test installation from PyPI
      run: |
        sleep 60  # Attendre la propagation sur PyPI
        pip install playlist-downloader
        playlist-downloader --version

    - name: Update documentation
      run: |
        # Script pour mettre à jour la documentation
        echo &amp;quot;Documentation updated for version ${GITHUB_REF#refs/tags/}&amp;quot;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_diagram_arsitektur&amp;quot;&amp;gt;diagram arsitektur&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_diagram_urutan_proses_rilis&amp;quot;&amp;gt;Diagram Urutan - Proses Rilis&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
!theme plain
title Urutan rilis ke PyPI
actor Developer as dev
participant &amp;quot;GitHub&amp;quot; as gh
participant &amp;quot;GitHub Actions&amp;quot; as ga
participant &amp;quot;Test PyPI&amp;quot; as tpypi
participant &amp;quot;PyPI&amp;quot; as pypi
participant &amp;quot;Pengguna&amp;quot; as users

dev -&amp;amp;gt; gh : git push --tags v1.2.3
gh -&amp;amp;gt; ga : trigger release workflow

ga -&amp;amp;gt; ga : checkout code
ga -&amp;amp;gt; ga : setup Python environment
ga -&amp;amp;gt; ga : install dependencies
ga -&amp;amp;gt; ga : run tests
ga -&amp;amp;gt; ga : build package (wheel + sdist)
ga -&amp;amp;gt; ga : check package with twine

alt Pre-release (rc tag)
    ga -&amp;amp;gt; tpypi : upload to Test PyPI
    tpypi -&amp;amp;gt; ga : confirm upload
else Stable release
    ga -&amp;amp;gt; pypi : upload to PyPI
    pypi -&amp;amp;gt; ga : confirm upload
end

ga -&amp;amp;gt; gh : create GitHub release
ga -&amp;amp;gt; ga : test installation from PyPI

users -&amp;amp;gt; pypi : pip install playlist-downloader
pypi -&amp;amp;gt; users : download package

@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_diagram_kondisi_siklus_hidup_paket&amp;quot;&amp;gt;Diagram Kondisi - Siklus Hidup Paket&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
!theme plain
title Status Paket Python
[*] --&amp;amp;gt; Development
Development --&amp;amp;gt; Testing : commit/PR
Testing --&amp;amp;gt; Development : tests fail
Testing --&amp;amp;gt; Built : tests pass
Built --&amp;amp;gt; TestPyPI : pre-release tag
Built --&amp;amp;gt; PyPI : stable tag
TestPyPI --&amp;amp;gt; PyPI : validation OK
PyPI --&amp;amp;gt; Published
Published --&amp;amp;gt; [*]

state Development {
  [*] --&amp;amp;gt; Coding
  Coding --&amp;amp;gt; LocalTesting
  LocalTesting --&amp;amp;gt; Coding : fix issues
  LocalTesting --&amp;amp;gt; ReadyForCI : all tests pass
}

state Testing {
  [*] --&amp;amp;gt; CITests
  CITests --&amp;amp;gt; SecurityScan
  SecurityScan --&amp;amp;gt; QualityCheck
  QualityCheck --&amp;amp;gt; BuildValidation
}

@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_diagram_penyebaran_infrastruktur_cicd&amp;quot;&amp;gt;Diagram Penyebaran - Infrastruktur CI/CD&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
!theme plain
title Infrastruktur penyebaran
node &amp;quot;GitHub&amp;quot; {
  component &amp;quot;Repositori&amp;quot; as repo
  component &amp;quot;Penjalani Tindakan&amp;quot; as runner
  component &amp;quot;Toko Rahasia&amp;quot; as secrets
}

node &amp;quot;PyPI infrastruktur&amp;quot; {
  component &amp;quot;PyPI&amp;quot; as pypi
  component &amp;quot;Test PyPI&amp;quot; as testpypi
  database &amp;quot;Indeks Paket&amp;quot; as index
}

node &amp;quot;mesin pengembang&amp;quot; {
  component &amp;quot;Klien Git&amp;quot; as git
  component &amp;quot;lingkungan Python&amp;quot; as python
  component &amp;quot;IDE&amp;quot; as ide
}

node &amp;quot;Lingkungan Pengguna&amp;quot; {
  component &amp;quot;pip&amp;quot; as pip_client
  component &amp;quot;Runtime Python&amp;quot; as py_runtime
}

git --&amp;amp;gt; repo : push code/tags
repo --&amp;amp;gt; runner : trigger workflows
runner --&amp;amp;gt; secrets : read API tokens
runner --&amp;amp;gt; testpypi : upload pre-release
runner --&amp;amp;gt; pypi : upload release
pypi --&amp;amp;gt; index : store package
pip_client --&amp;amp;gt; pypi : download package
py_runtime &amp;amp;lt;-- pip_client : install package

@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_objek_dan_model_pipeline&amp;quot;&amp;gt;Objek dan Model Pipeline&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_diagram_kelas_model_model_cicd&amp;quot;&amp;gt;Diagram kelas - Model-model CI/CD&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
!theme plain
title Model Pipeline CI/CD
class PipelineConfig {
  +python_versions: List[str]
  +test_environments: List[str]
  +security_checks: bool
  +coverage_threshold: float
  +validate()
}

class BuildArtifact {
  +name: str
  +version: str
  +wheel_path: str
  +sdist_path: str
  +checksums: Dict[str, str]
  +validate_integrity()
}

class TestResult {
  +test_suite: str
  +python_version: str
  +passed: int
  +failed: int
  +coverage: float
  +duration: float
  +is_success(): bool
}

class SecurityReport {
  +bandit_issues: List[Issue]
  +safety_vulnerabilities: List[Vulnerability]
  +severity_level: str
  +is_secure(): bool
}

class DeploymentTarget {
  +name: str
  +url: str
  +api_token: str
  +environment: str
  +deploy(artifact: BuildArtifact)
}

class ReleaseManager {
  +version: str
  +changelog: str
  +artifacts: List[BuildArtifact]
  +test_results: List[TestResult]
  +security_report: SecurityReport
  +deploy_to_test()
  +deploy_to_production()
  +create_github_release()
}

PipelineConfig ||--o{ TestResult
ReleaseManager *-- BuildArtifact
ReleaseManager *-- SecurityReport
ReleaseManager o-- DeploymentTarget
DeploymentTarget ..&amp;amp;gt; BuildArtifact : uses

@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_konfigurasi_rahasia&amp;quot;&amp;gt;Konfigurasi Rahasia&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Agar pipeline berfungsi, Anda harus mengonfigurasi rahasia berikut di GitHub :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_rahasia_github_actions&amp;quot;&amp;gt;Rahasia GitHub Actions&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;# Dans Settings &amp;amp;gt; Secrets and variables &amp;amp;gt; Actions

# Token PyPI pour la production
PYPI_API_TOKEN=pypi-...

# Token Test PyPI pour les pré-releases
TEST_PYPI_API_TOKEN=pypi-...

# Token GitHub pour créer les releases
GITHUB_TOKEN=(automatiquement fourni)

# Token Codecov (optionnel)
CODECOV_TOKEN=...&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_pembuatan_token_pypi&amp;quot;&amp;gt;Pembuatan Token PyPI&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;# 1. Créer un compte sur PyPI et Test PyPI
# 2. Aller dans Account Settings &amp;amp;gt; API tokens
# 3. Créer un token avec scope &amp;quot;Entire account&amp;quot; ou spécifique au projet
# 4. Format du token : pypi-AgEIcHlwaS5vcmc...&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_skrip_pengembangan_lokal&amp;quot;&amp;gt;Skrip Pengembangan Lokal&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Untuk memudahkan pengembangan, buat skrip utilitas:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_makefile&amp;quot;&amp;gt;Makefile&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-makefile hljs&amp;quot; data-lang=&amp;quot;makefile&amp;quot;&amp;gt;.PHONY: install test lint format security build clean release-test release-prod

install:
	pip install -e &amp;quot;.[dev]&amp;quot;

test:
	pytest tests/ -v --cov=src/playlist_downloader

lint:
	flake8 src tests
	mypy src

format:
	black src tests

security:
	bandit -r src/
	safety check

build:
	python -m build
	twine check dist/*

clean:
	rm -rf build/ dist/ *.egg-info/
	find . -type d -name __pycache__ -delete
	find . -name &amp;quot;*.pyc&amp;quot; -delete

release-test: clean build
	twine upload --repository testpypi dist/*

release-prod: clean build
	twine upload dist/*

pre-commit: format lint test security
	@echo &amp;quot;✅ Prêt pour commit&amp;quot;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_skrip_versi&amp;quot;&amp;gt;Skrip Versi&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-python hljs&amp;quot; data-lang=&amp;quot;python&amp;quot;&amp;gt;#!/usr/bin/env python3
&amp;quot;&amp;quot;&amp;quot;Script pour gérer les versions du projet.&amp;quot;&amp;quot;&amp;quot;

import sys
import subprocess
from pathlib import Path

def get_current_version():
    &amp;quot;&amp;quot;&amp;quot;Récupère la version actuelle depuis git.&amp;quot;&amp;quot;&amp;quot;
    try:
        result = subprocess.run(
            [&amp;quot;git&amp;quot;, &amp;quot;describe&amp;quot;, &amp;quot;--tags&amp;quot;, &amp;quot;--abbrev=0&amp;quot;],
            capture_output=True,
            text=True,
            check=True
        )
        return result.stdout.strip()
    except subprocess.CalledProcessError:
        return &amp;quot;0.0.0&amp;quot;

def create_version_tag(version, message=None):
    &amp;quot;&amp;quot;&amp;quot;Crée un tag de version.&amp;quot;&amp;quot;&amp;quot;
    if not version.startswith(&amp;#39;v&amp;#39;):
        version = f&amp;#39;v{version}&amp;#39;

    tag_message = message or f&amp;quot;Release {version}&amp;quot;

    subprocess.run([&amp;quot;git&amp;quot;, &amp;quot;tag&amp;quot;, &amp;quot;-a&amp;quot;, version, &amp;quot;-m&amp;quot;, tag_message], check=True)
    print(f&amp;quot;✅ Tag {version} créé&amp;quot;)

    # Push le tag
    subprocess.run([&amp;quot;git&amp;quot;, &amp;quot;push&amp;quot;, &amp;quot;origin&amp;quot;, version], check=True)
    print(f&amp;quot;✅ Tag {version} poussé vers origin&amp;quot;)

if __name__ == &amp;quot;__main__&amp;quot;:
    if len(sys.argv) &amp;amp;lt; 2:
        current = get_current_version()
        print(f&amp;quot;Version actuelle: {current}&amp;quot;)
        print(&amp;quot;Usage: python version.py &amp;amp;lt;new_version&amp;amp;gt; [message]&amp;quot;)
        sys.exit(1)

    new_version = sys.argv[1]
    message = sys.argv[2] if len(sys.argv) &amp;amp;gt; 2 else None

    create_version_tag(new_version, message)&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_praktik_baik_dan_rekomendasi&amp;quot;&amp;gt;Praktik Baik dan Rekomendasi&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_versioning_semantik&amp;quot;&amp;gt;Versioning Semantik&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Gunakan versioning semantik (SemVer):&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;code&amp;gt;MAJOR.MINOR.PATCH&amp;lt;/code&amp;gt; (ex: 1.2.3)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;code&amp;gt;MAJOR&amp;lt;/code&amp;gt;: perubahan tidak kompatibel&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;code&amp;gt;MINOR&amp;lt;/code&amp;gt;: fitur baru yang kompatibel&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;code&amp;gt;PATCH&amp;lt;/code&amp;gt;: perbaikan bug yang kompatibel&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_strategi_pembranching&amp;quot;&amp;gt;Strategi Pembranching&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-text hljs&amp;quot; data-lang=&amp;quot;text&amp;quot;&amp;gt;main          ──●──●──●──●──●────●── (releases stables)
               /       /          /
develop    ──●──●──●──●──●──●──●──●── (développement)
            /     /        /
feature/xxx  ●──●──●──●──●──/ (fonctionnalités)&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_pengujian_dan_cakupan&amp;quot;&amp;gt;Pengujian dan Cakupan&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Cakupan kode minimum : 85%&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Uji unit untuk logika bisnis&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Uji integrasi untuk adapter&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Uji ujung ke ujung untuk CLI&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_keamanan&amp;quot;&amp;gt;Keamanan&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Pemindaian otomatis dependensi (Safety)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Analisis statis kode (Bandit)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Rahasia tidak pernah ada dalam kode&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Penggunaan token PyPI khusus&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_monitoring_dan_observability&amp;quot;&amp;gt;monitoring dan observability&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_metrik_pipeline&amp;quot;&amp;gt;Metrik Pipeline&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-yaml hljs&amp;quot; data-lang=&amp;quot;yaml&amp;quot;&amp;gt;# .github/workflows/metrics.yml
name: Pipeline Metrics

on:
  workflow_run:
    workflows: [&amp;quot;CI&amp;quot;, &amp;quot;Release&amp;quot;]
    types: [completed]

jobs:
  metrics:
    runs-on: ubuntu-latest
    steps:
    - name: Collect metrics
      run: |
        echo &amp;quot;Pipeline: ${{ github.event.workflow_run.name }}&amp;quot;
        echo &amp;quot;Status: ${{ github.event.workflow_run.conclusion }}&amp;quot;
        echo &amp;quot;Duration: ${{ github.event.workflow_run.updated_at - github.event.workflow_run.created_at }}&amp;quot;
        # Envoyer vers système de monitoring&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_notifikasi&amp;quot;&amp;gt;Notifikasi&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-yaml hljs&amp;quot; data-lang=&amp;quot;yaml&amp;quot;&amp;gt;# Ajout dans les workflows pour notifications
- name: Notify on failure
  if: failure()
  uses: 8398a7/action-slack@v3
  with:
    status: failure
    text: &amp;quot;❌ Pipeline failed for ${{ github.repository }}&amp;quot;
  env:
    SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_kesimpulan&amp;quot;&amp;gt;Kesimpulan&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Pipeline CI/CD lengkap untuk Python menawarkan:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Otomatisasi lengkap&amp;lt;/strong&amp;gt;dari validasi kode hingga publikasi&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Keamanan&amp;lt;/strong&amp;gt;: pindai otomatis dan pengelolaan aman rahasia&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Kualitas&amp;lt;/strong&amp;gt;tes multi-versi, linting dan cakupan kode&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;keandalan&amp;lt;/strong&amp;gt;: penerapan progresif melalui Test PyPI&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;pelacakan&amp;lt;/strong&amp;gt;: artefak, laporan dan rilis GitHub&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Adoptasi praktik ini menjamin proses pengiriman yang kuat dan profesional untuk aplikasi CLI Python Anda, memudahkan pemeliharaan dan perkembangan proyek Anda dalam jangka panjang.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_sumber_daya_tambahan&amp;quot;&amp;gt;Sumber Daya Tambahan&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://packaging.python.org/&amp;quot;&amp;gt;Panduan Pengguna Paket Python&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://docs.github.com/en/actions&amp;quot;&amp;gt;Dokumentasi GitHub Actions&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://pypi.org/help/&amp;quot;&amp;gt;PyPI Dokumentasi Bantuan&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://semver.org/&amp;quot;&amp;gt;Spesifikasi Semantic Versioning&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://keepachangelog.com/&amp;quot;&amp;gt;Pertahankan Changelog&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>Bagian 1 : Menyiapkan pipeline CI/CD sederhana untuk Python dan PyPI</title>
            <link >https://cheroliv.com/blog/2025/0085_pypi_cicd_part_1_post.html</link>
            <pubDate>Thu, 17 Jul 2025 00:00:00 +0000</pubDate>
            <guid isPermaLink="false">blog/2025/0085_pypi_cicd_part_1_post.html</guid>
            <description>&amp;lt;div id=&amp;quot;toc&amp;quot; class=&amp;quot;toc&amp;quot;&amp;gt;
&amp;lt;div id=&amp;quot;toctitle&amp;quot;&amp;gt;Sommaire&amp;lt;/div&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel1&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_pengenalan&amp;quot;&amp;gt;Pengenalan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_arsitektur_pipeline&amp;quot;&amp;gt;Arsitektur Pipeline&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_konfigurasi_pipeline_integrasi_berkelanjutan_ci&amp;quot;&amp;gt;Konfigurasi Pipeline Integrasi Berkelanjutan (CI)&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_struktur_alur_kerja_ci&amp;quot;&amp;gt;Struktur alur kerja CI&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_analisis_langkah_ci&amp;quot;&amp;gt;Analisis Langkah CI&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel3&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_1_pemicu_on&amp;quot;&amp;gt;1. Pemicu (&amp;lt;code&amp;gt;on&amp;lt;/code&amp;gt;)&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_pengenalan&amp;quot;&amp;gt;Pengenalan&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;Tujuan : Membimbing pembaca dalam menyiapkan pipeline CI/CD minimalis namun fungsional untuk aplikasi Python, dengan deployment otomatis ke PyPI.&amp;lt;/div&amp;gt;
&amp;lt;p&amp;gt;Otomatisasi proses pengembangan telah menjadi tidak terelakkan dalam proyek-proyek modern. Sebuah pipeline CI/CD yang dirancang dengan baik tidak hanya memungkinkan deteksi regresi lebih awal dalam siklus pengembangan, tetapi juga mengotomatisasi seluruh proses penyebaran.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Dalam artikel ini, kita akan menjelajahi cara menyiapkan pipeline lengkap dengan GitHub Actions untuk aplikasi Python, dari integrasi berkelanjutan (CI) hingga deploy berkelanjutan (CD) ke PyPI.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_arsitektur_pipeline&amp;quot;&amp;gt;Arsitektur Pipeline&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Pipeline kami terdiri dari dua alur kerja yang berbeda:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;olist arabic&amp;quot;&amp;gt;
&amp;lt;ol class=&amp;quot;arabic&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Alur CI&amp;lt;/strong&amp;gt;: Dieksekusi pada setiap push dan pull request&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Pipeline CD&amp;lt;/strong&amp;gt;Demicu hanya selama rilis GitHub&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
!theme plain

package &amp;quot;GitHub Repositori&amp;quot; {
  [Source Code] as source
  [GitHub Actions] as actions
}

package &amp;quot;Alur CI&amp;quot; {
  [Checkout] as checkout_ci
  [Setup Python] as python_ci
  [Install Dependencies] as deps_ci
  [Linting (Ruff)] as lint
  [Unit Tests] as tests
}

package &amp;quot;CD Pipeline&amp;quot; {
  [Checkout] as checkout_cd
  [Setup Python] as python_cd
  [Build Package] as build
  [Publish to PyPI] as pypi
}

source --&amp;amp;gt; actions : Push/PR
actions --&amp;amp;gt; checkout_ci
checkout_ci --&amp;amp;gt; python_ci
python_ci --&amp;amp;gt; deps_ci
deps_ci --&amp;amp;gt; lint
lint --&amp;amp;gt; tests

source --&amp;amp;gt; actions : Release
actions --&amp;amp;gt; checkout_cd : On Release
checkout_cd --&amp;amp;gt; python_cd
python_cd --&amp;amp;gt; build
build --&amp;amp;gt; pypi

@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_konfigurasi_pipeline_integrasi_berkelanjutan_ci&amp;quot;&amp;gt;Konfigurasi Pipeline Integrasi Berkelanjutan (CI)&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_struktur_alur_kerja_ci&amp;quot;&amp;gt;Struktur alur kerja CI&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Alur kerja CI dirancang untuk memvalidasi setiap kontribusi ke kode. Berikut konfigurasi lengkapnya :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-yaml hljs&amp;quot; data-lang=&amp;quot;yaml&amp;quot;&amp;gt;name: CI/CD Pipeline

on:
  push:
    branches:
      - main
  pull_request:
    branches:
      - main

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
    - name: Checkout code
      uses: actions/checkout@v4

    - name: Set up Python
      uses: actions/setup-python@v5
      with:
        python-version: &amp;#39;3.x&amp;#39;

    - name: Install dependencies
      run: |
        python -m pip install --upgrade pip
        pip install -r requirements.txt
        pip install ruff pytest pytest-mock

    - name: Run Linting (Ruff)
      run: ruff check .

    - name: Run Tests (Pytest)
      run: pytest&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_analisis_langkah_ci&amp;quot;&amp;gt;Analisis Langkah CI&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;sect3&amp;quot;&amp;gt;
&amp;lt;h4 id=&amp;quot;_1_pemicu_on&amp;quot;&amp;gt;1. Pemicu (&amp;lt;code&amp;gt;on&amp;lt;/code&amp;gt;)&amp;lt;/h4&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-yaml hljs&amp;quot; data-lang=&amp;quot;yaml&amp;quot;&amp;gt;----
----
on:
  push:
    branches:
      - main
  pull_request:
    branches:
      - main
----

Pipeline dipicu pada : - Setiap push ke cabang`main` - Setiap pull request menuju`main`

Pendekatan ini memastikan bahwa kode utama tetap stabil dan bahwa semua kontribusi divalidasi sebelum integrasi.

==== 2. Lingkungan Eksekusi

----

runs-on: ubuntu-latest

Ubuntu Latest menawarkan kompromi yang baik antara performa, biaya, dan kompatibilitas untuk sebagian besar proyek Python.

==== 3. Checkout Kode

```yaml
----
----
- name: Checkout code
  uses: actions/checkout@v4
----

tindakan
&amp;amp;lt;/think&amp;amp;gt;
tindakan`checkout@v4`Mengambil kode sumber dari repositori. Versi v4 memberikan peningkatan performa dan keamanan.

==== 4. Konfigurasi Python

----

- name: Set up Python uses: actions/setup-python@v5 with: python-version: &amp;#39;3.x&amp;#39;

penggunaan`&amp;#39;3.x&amp;#39;`memungkinkan penggunaan otomatis versi stabil terbaru dari Python 3, memudahkan pemeliharaan.

==== 5. Instalasi Dependensi

```yaml
----
----
- name: Install dependencies
  run: |
    python -m pip install --upgrade pip
    pip install -r requirements.txt
    pip install ruff pytest pytest-mock
----

Tahap ini : - Memperbarui pip ke versi terbaru - Instal dependensi proyek - Tambahkan alat pengembangan (linting dan ujian)

==== 6. Linting dengan Ruff

----

- name: Run Linting (Ruff) run: ruff check .

**Ruff**adalah linter Python yang sangat cepat yang ditulis dalam Rust. Dia menggabungkan fitur dari beberapa alat (Flake8, Black, isort) menjadi satu alat yang kuat.

==== 7. Pelaksanaan Tes

```yaml
----
----
- name: Run Tests (Pytest)
  run: pytest
----

Pytest menjalankan seluruh suite tes, memastikan bahwa perubahan tidak memperkenalkan regresi.

== Konfigurasi Pipeline Penerapan (CD)

=== Struktur Workflow CD

Workflow CD hanya terpicu selama rilis GitHub dan mengotomatiskan publikasi ke PyPI :

[source,yaml]
----
name: Publish to PyPI

on:
  release:
    types:
      - published

jobs:
  deploy:
    runs-on: ubuntu-latest

    steps:
    - name: Checkout code
      uses: actions/checkout@v4

    - name: Set up Python
      uses: actions/setup-python@v5
      with:
        python-version: &amp;#39;3.x&amp;#39;

    - name: Install dependencies
      run: |
        python -m pip install --upgrade pip
        pip install setuptools wheel twine

    - name: Build and publish
      env:
        TWINE_USERNAME: __token__
        TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
      run: |
        python setup.py sdist bdist_wheel
        twine upload dist/*
----

=== Analisis Tahap CD

==== 1. Pemicu Pelepasan

----

on: release: types: - published

Pipeline CD hanya dipicu saat mempublikasikan rilis GitHub. Pendekatan ini memastikan kontrol yang tepat atas penerapan.

==== 2. Instalasi Alat Pembangunan

```yaml
----
----
pip install setuptools wheel twine
----

- **setuptools**: Alat packaging Python - **roda**: Format distribusi Python modern - **benang**: Alat aman untuk mengunggah ke PyPI

==== 3. Konfigurasi Otentikasi

----

env: TWINE_USERNAME: __token__ TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}

Autentikasi menggunakan token API PyPI yang disimpan sebagai rahasia GitHub, lebih aman daripada kredensial konvensional.

==== 4. Build dan publikasi

```yaml
----
----
run: |
  python setup.py sdist bdist_wheel
  twine upload dist/*
----

- `sdist`: Buat distribusi sumber - `bdist_wheel`: Membuat sebuah wheel (distribusi biner) - `twine upload`Mempublikasikan distribusi pada PyPI

== Konfigurasi Paket Python

=== Struktur setup.py

Agar pipeline berfungsi, proyek Anda harus menyertakan sebuah file`setup.py`:

[source,python]
----
from setuptools import setup, find_packages

with open(&amp;quot;README.adoc&amp;quot;, &amp;quot;r&amp;quot;, encoding=&amp;quot;utf-8&amp;quot;) as fh:
    long_description = fh.read()

setup(
    name=&amp;quot;playlist-downloader&amp;quot;,
    version=&amp;quot;1.0.0&amp;quot;,
    author=&amp;quot;Votre Nom&amp;quot;,
    author_email=&amp;quot;votre.email@example.com&amp;quot;,
    description=&amp;quot;CLI tool for managing YouTube playlists&amp;quot;,
    long_description=long_description,
    long_description_content_type=&amp;quot;text/plain&amp;quot;,
    url=&amp;quot;https://github.com/cheroliv/playlist-downloader&amp;quot;,
    packages=find_packages(),
    classifiers=[
        &amp;quot;Development Status :: 4 - Beta&amp;quot;,
        &amp;quot;Intended Audience :: Developers&amp;quot;,
        &amp;quot;License :: OSI Approved :: MIT License&amp;quot;,
        &amp;quot;Operating System :: OS Independent&amp;quot;,
        &amp;quot;Programming Language :: Python :: 3&amp;quot;,
        &amp;quot;Programming Language :: Python :: 3.8&amp;quot;,
        &amp;quot;Programming Language :: Python :: 3.9&amp;quot;,
        &amp;quot;Programming Language :: Python :: 3.10&amp;quot;,
        &amp;quot;Programming Language :: Python :: 3.11&amp;quot;,
    ],
    python_requires=&amp;quot;&amp;amp;gt;=3.8&amp;quot;,
    install_requires=[
        &amp;quot;typer&amp;amp;gt;=0.9.0&amp;quot;,
        &amp;quot;yt-dlp&amp;amp;gt;=2023.1.6&amp;quot;,
        &amp;quot;google-api-python-client&amp;amp;gt;=2.70.0&amp;quot;,
        &amp;quot;google-auth-oauthlib&amp;amp;gt;=0.7.1&amp;quot;,
        &amp;quot;pymonad&amp;amp;gt;=2.4.0&amp;quot;,
        &amp;quot;pyyaml&amp;amp;gt;=6.0&amp;quot;,
    ],
    entry_points={
        &amp;quot;console_scripts&amp;quot;: [
            &amp;quot;playlist-downloader=cli:app&amp;quot;,
        ],
    },
)
----

=== poin-poin kunci setup.py

1. **metadata**: Nama, versi, penulis, deskripsi
2. **Ketergantungan**: Daftar packages yang diperlukan
3. **Titik Masuk**: Perintah CLI yang diekspos
4. **klasifikasi**: metadata untuk PyPI

== Keamanan dengan GitHub Secrets

=== Konfigurasi Token PyPI

1. **Membuat token API di PyPI**:

- Masuk ke PyPI Buka Pengaturan Akun &amp;amp;gt; API tokens - Buat token baru dengan izin yang sesuai

1. **Tambahkan rahasia di GitHub**:

- Pengaturan Repositori &amp;amp;gt; Rahasia dan variabel &amp;amp;gt; Tindakan - Buat rahasia baru yang dinamai`PYPI_API_TOKEN` - Tempelkan token PyPI Anda

[plantuml, secrets-flow, svg]
----
@startuml
!theme plain

actor Developer as dev
participant &amp;quot;Repositori GitHub&amp;quot; as repo
participant &amp;quot;GitHub Actions&amp;quot; as actions
participant PyPI

dev -&amp;amp;gt; repo : Configure PYPI_API_TOKEN secret
repo -&amp;amp;gt; actions : Trigger CD pipeline on release
actions -&amp;amp;gt; actions : Access secret securely
actions -&amp;amp;gt; PyPI : Authenticate with token
PyPI -&amp;amp;gt; PyPI : Validate and publish package

note over actions, PyPI
  Token never exposed in logs
  Automatic rotation possible
end note

@enduml
----

== Workflow Deployment Lengkap

=== Urutan Penyebaran

[plantuml, deployment-sequence, svg]
----
@startuml
!theme plain

actor Developer as dev
participant &amp;quot;Git lokal&amp;quot; as git
participant &amp;quot;GitHub&amp;quot; as github
participant &amp;quot;GitHub Tindakan&amp;quot; as actions
participant PyPI
participant &amp;quot;Pengguna akhir&amp;quot; as users

dev -&amp;amp;gt; git : git tag v1.0.0
dev -&amp;amp;gt; git : git push origin v1.0.0
git -&amp;amp;gt; github : Push tag
dev -&amp;amp;gt; github : Create release from tag
github -&amp;amp;gt; actions : Trigger CD pipeline
actions -&amp;amp;gt; actions : Checkout code
actions -&amp;amp;gt; actions : Setup Python environment
actions -&amp;amp;gt; actions : Install build tools
actions -&amp;amp;gt; actions : Build distributions (sdist + wheel)
actions -&amp;amp;gt; PyPI : Upload to PyPI with token
PyPI -&amp;amp;gt; PyPI : Validate and publish
users -&amp;amp;gt; PyPI : pip install playlist-downloader

note over dev, github
  Release creation can be automated
  or done manually through GitHub UI
end note

@enduml
----

=== Status Pipeline

[plantuml, pipeline-states, svg]
----
@startuml
!theme plain

[*] --&amp;amp;gt; Idle

Idle --&amp;amp;gt; CI_Running : Push/PR created
CI_Running --&amp;amp;gt; CI_Success : All checks pass
CI_Running --&amp;amp;gt; CI_Failed : Linting/Tests fail
CI_Success --&amp;amp;gt; Idle : Merge completed
CI_Failed --&amp;amp;gt; Idle : Fix and retry

Idle --&amp;amp;gt; CD_Running : Release published
CD_Running --&amp;amp;gt; CD_Success : Package published
CD_Running --&amp;amp;gt; CD_Failed : Build/Upload error
CD_Success --&amp;amp;gt; Idle : Package available on PyPI
CD_Failed --&amp;amp;gt; Idle : Fix and retry release

note on link #red : Blocks merge
note on link #green : Allows deployment

@enduml
----

== Praktik Baik dan Optimasi

=== Manajemen Versi

Gunakan tag Git semantik :

----

git tag -a v1.2.3 -m &amp;quot;Release version 1.2.3&amp;quot; git push origin v1.2.3

=== 2. Uji Matriks

Untuk menguji pada beberapa versi Python:

```yaml
----
----
strategy:
  matrix:
    python-version: [3.8, 3.9, &amp;quot;3.10&amp;quot;, &amp;quot;3.11&amp;quot;]
----

=== 3. Cache Dependensi

Percepat build dengan cache :

----

- name: Cache pip dependencies uses: actions/cache@v3 with: path: ~/.cache/pip key: ${{ runner.os }}-pip-${{ hashFiles(&amp;#39;**/requirements.txt&amp;#39;) }}

=== 4. lingkungan penerapan

Gunakan lingkungan GitHub untuk penerapan yang terkontrol :

```yaml
----
----
jobs:
  deploy:
    environment: production
    runs-on: ubuntu-latest
----

== Kasus Penggunaan dan Arsitektur

=== Diagram Kasus Penggunaan

[plantuml, use-cases, svg]
----
@startuml
!theme plain

left to right direction

actor &amp;quot;Pengembang&amp;quot; as dev
actor &amp;quot;GitHub Actions&amp;quot; as ga
actor &amp;quot;Pengguna akhir&amp;quot; as user

package &amp;quot;Sistem CI/CD&amp;quot; {
  usecase &amp;quot;Jalankan Linting&amp;quot; as lint
  usecase &amp;quot;Menjalankan tes&amp;quot; as test
  usecase &amp;quot;Membangun Paket&amp;quot; as build
  usecase &amp;quot;Terbitkan ke PyPI&amp;quot; as publish
  usecase &amp;quot;Buat Rilis&amp;quot; as release
}

dev --&amp;amp;gt; lint : Pushes code
dev --&amp;amp;gt; test : Pushes code
dev --&amp;amp;gt; release : Creates release
ga --&amp;amp;gt; build : On release trigger
ga --&amp;amp;gt; publish : After successful build
user --&amp;amp;gt; publish : Downloads package

lint .&amp;amp;gt; test : triggers
test .&amp;amp;gt; build : on success
build .&amp;amp;gt; publish : on success

@enduml
----

=== Arsitektur Penyebaran

[plantuml, deployment-architecture, svg]
----
@startuml
!theme plain

cloud &amp;quot;GitHub&amp;quot; {
  [Source Repository]
  [GitHub Actions]
  [Secrets Store]
}

cloud &amp;quot;PyPI&amp;quot; {
  [Package Registry]
  [Distribution Files]
}

node &amp;quot;Alur CI/CD&amp;quot; {
  [Linting]
  [Testing]
  [Building]
  [Publishing]
}

[Source Repository] --&amp;amp;gt; [GitHub Actions] : Triggers
[GitHub Actions] --&amp;amp;gt; [Linting]
[Linting] --&amp;amp;gt; [Testing]
[Testing] --&amp;amp;gt; [Building]
[Building] --&amp;amp;gt; [Publishing]
[Publishing] --&amp;amp;gt; [Package Registry] : Uploads
[Secrets Store] --&amp;amp;gt; [Publishing] : Provides token

note as N1
  Secure token-based
  authentication
end note

[Secrets Store] .. N1

@enduml
----

== Pengawasan dan Debugging

=== Log dan Pemantauan

GitHub Actions memberikan log terperinci untuk setiap langkah. Untuk debug:

1. **Periksa log**setiap step
2. **Aktifkan debug**dengan`ACTIONS_STEP_DEBUG`
3. **Gunakan artefak**untuk menyimpan file build

----

- name: Upload build artifacts uses: actions/upload-artifact@v3 if: failure() with: name: build-logs path: build/

=== Notifikasi

Tambahkan notifikasi Slack atau email :

```yaml
----
----
- name: Notify on failure
  if: failure()
  uses: 8398a7/action-slack@v3
  with:
    status: ${{ job.status }}
    webhook_url: ${{ secrets.SLACK_WEBHOOK }}
----

== Kesimpulan

Penerapan pipeline CI/CD yang kuat dengan GitHub Actions secara radikal mengubah pengalaman pengembangan. Dengan mengotomatisasi linting, pengujian, dan penyebaran, Anda :

- **Kurangi kesalahan**dalam produksi - **Percepat siklus**pengembangan - **Tingkatkan kepercayaan**dalam rilis Anda - **Mudahkan kolaborasi**dalam tim

Pipeline ini dapat disesuaikan dengan berbagai jenis proyek Python dengan menyesuaikan alat linting, kerangka kerja pengujian, atau destinasi penerapan.

Investasi awal dalam konfigurasi workflow-nya segera menguntungkan melalui penghematan waktu dan penurunan kesalahan manual selama penerapan.

== Sumber Daya Tambahan

- https://docs.github.com/en/actions[Dokumentasi GitHub Actions] - https://packaging.python.org/[Panduan Pengemasan Python] - https://docs.pytest.org/[Dokumentasi Pytest] - https://docs.astral.sh/ruff/[Dokumentasi Ruff] - https://twine.readthedocs.io/[Dokumentasi Twine]

✅ Pipeline fungsional tercapai! Anda kini memiliki pipeline CI/CD sederhana yang memungkinkan Anda mengotomatiskan pengujian dan menerbitkan paket Python Anda ke PyPI langsung dari GitHub Actions.

Namun, pipeline ini tetap sengaja minimalis. Ia belum mencakup beberapa aspek penting dalam konteks profesional :

Uji multi-versi Python,

Analisis keamanan otomatis,

Penyebaran bertahap melalui Test PyPI,

Pemantauan dan metrik pipeline,

Otomatisasi versioning dan integrasi praktik terbaik modern (pyproject.toml).

Di bagian berikutnya, kita akan melangkah ke tingkat yang lebih tinggi. Anda akan belajar mengubah pipeline dasar ini menjadi benar-benar rangkaian penyebaran industri, kuat dan aman, siap untuk proyek-produksi Python.
----&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>Pembangunan Bot Discord Musik: Arsitektur Fungsional dan Pengembangan Berbasis Log</title>
            <link >https://cheroliv.com/blog/2025/0084_disco_bot_discord_python_post.html</link>
            <pubDate>Wed, 16 Jul 2025 00:00:00 +0000</pubDate>
            <guid isPermaLink="false">blog/2025/0084_disco_bot_discord_python_post.html</guid>
            <description>&amp;lt;div id=&amp;quot;toc&amp;quot; class=&amp;quot;toc&amp;quot;&amp;gt;
&amp;lt;div id=&amp;quot;toctitle&amp;quot;&amp;gt;Table of Contents&amp;lt;/div&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel1&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_pendahuluan&amp;quot;&amp;gt;Pendahuluan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_konteks_dan_tantangan_saat_ini&amp;quot;&amp;gt;Konteks dan Tantangan Saat Ini&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_perkembangan_api_musik&amp;quot;&amp;gt;Perkembangan API Musik&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_pendekatan_pengembangan_berbasis_log&amp;quot;&amp;gt;Pendekatan Pengembangan Berbasis Log&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_arsitektur_konseptual&amp;quot;&amp;gt;Arsitektur Konseptual&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_teknik_stack_dan_paradigma_fungsional&amp;quot;&amp;gt;Teknik Stack dan Paradigma Fungsional&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_pilihan_teknologi&amp;quot;&amp;gt;Pilihan teknologi&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_prinsip_fungsional_yang_diterapkan&amp;quot;&amp;gt;Prinsip fungsional yang diterapkan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_metodologi_agile_dan_backlog&amp;quot;&amp;gt;Metodologi Agile dan Backlog&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_epik_utama&amp;quot;&amp;gt;Epik Utama&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel3&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_epic_1_bot_infrastruktur_discord&amp;quot;&amp;gt;Epic 1 : Bot Infrastruktur Discord&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_epic_2_integrasi_spotify&amp;quot;&amp;gt;Epic 2 : Integrasi Spotify&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_epic_3_integrasi_youtube&amp;quot;&amp;gt;Epic 3 : Integrasi YouTube&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_epic_4_fitur_musik&amp;quot;&amp;gt;Epic 4 : fitur musik&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_user_stories_terperinci&amp;quot;&amp;gt;User Stories Terperinci&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel3&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_us1_1_inisialisasi_bot&amp;quot;&amp;gt;US1.1 : Inisialisasi Bot&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_us2_1_pencarian_spotify_cerdas&amp;quot;&amp;gt;US2.1 : Pencarian Spotify Cerdas&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_us3_1_ekstraksi_youtube_yang_resilién&amp;quot;&amp;gt;US3.1 : Ekstraksi YouTube yang resilién&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_pendekatan_pengembangan_berbasis_log_2&amp;quot;&amp;gt;Pendekatan Pengembangan Berbasis Log&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_strategi_logging&amp;quot;&amp;gt;Strategi Logging&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_struktur_log&amp;quot;&amp;gt;Struktur Log&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_contoh_desain_log&amp;quot;&amp;gt;Contoh desain log&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_arsitektur_validasi_dengan_pydantic&amp;quot;&amp;gt;Arsitektur Validasi dengan Pydantic&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_model_data&amp;quot;&amp;gt;Model Data&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_manajemen_kesalahan_fungsional&amp;quot;&amp;gt;manajemen kesalahan fungsional&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_monades_dan_error_handling&amp;quot;&amp;gt;Monades dan Error Handling&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_strategi_penghindaran_pembatasan&amp;quot;&amp;gt;Strategi Penghindaran Pembatasan&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_pendekatan_multi_sumber&amp;quot;&amp;gt;Pendekatan multi-sumber&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_rencana_pengembangan_iteratif&amp;quot;&amp;gt;Rencana Pengembangan Iteratif&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_perencanaan_sprint&amp;quot;&amp;gt;Perencanaan Sprint&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_metrik_kualitas&amp;quot;&amp;gt;Metrik Kualitas&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_penyebaran_dan_pemantauan&amp;quot;&amp;gt;Penyebaran dan Pemantauan&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_arsitektur_produksi&amp;quot;&amp;gt;Arsitektur Produksi&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_pemantauan_proaktif&amp;quot;&amp;gt;Pemantauan Proaktif&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_kesimpulan_dan_perspektif&amp;quot;&amp;gt;Kesimpulan dan Perspektif&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_langkah_selanjutnya&amp;quot;&amp;gt;Langkah Selanjutnya&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_pendahuluan&amp;quot;&amp;gt;&amp;lt;a class=&amp;quot;anchor&amp;quot; href=&amp;quot;#_pendahuluan&amp;quot;&amp;gt;&amp;lt;/a&amp;gt;Pendahuluan&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Artikel ini menyajikan pendekatan metodologis yang didasarkan pada&amp;lt;strong&amp;gt;Log Driven Development&amp;lt;/strong&amp;gt;(LDD), sebuah ekstensi dari Test Driven Development, diterapkan dalam paradigma fungsional dengan Python.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Tujuan kami: menciptakan bot yang tangguh, mudah dipelihara, dan dapat dikembangkan, yang mampu menavigasi batasan API musik terkini sambil memberikan pengalaman pengguna yang lancar di Discord.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_konteks_dan_tantangan_saat_ini&amp;quot;&amp;gt;&amp;lt;a class=&amp;quot;anchor&amp;quot; href=&amp;quot;#_konteks_dan_tantangan_saat_ini&amp;quot;&amp;gt;&amp;lt;/a&amp;gt;Konteks dan Tantangan Saat Ini&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_perkembangan_api_musik&amp;quot;&amp;gt;&amp;lt;a class=&amp;quot;anchor&amp;quot; href=&amp;quot;#_perkembangan_api_musik&amp;quot;&amp;gt;&amp;lt;/a&amp;gt;Perkembangan API Musik&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Platform musik telah memperketat secara signifikan kebijakan akses mereka:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Spotify&amp;lt;/strong&amp;gt;: Pembatasan akses ke metadata, pembatasan kuota&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;YouTube&amp;lt;/strong&amp;gt;: Kebijakan anti-bot diperkuat, kompleksifikasi autentikasi&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Discord&amp;lt;/strong&amp;gt;: Persyaratan baru keamanan dan kinerja&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_pendekatan_pengembangan_berbasis_log&amp;quot;&amp;gt;&amp;lt;a class=&amp;quot;anchor&amp;quot; href=&amp;quot;#_pendekatan_pengembangan_berbasis_log&amp;quot;&amp;gt;&amp;lt;/a&amp;gt;Pendekatan Pengembangan Berbasis Log&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;LDD mengluas TDD dengan menempatkan log di tengah pengembangan :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;olist arabic&amp;quot;&amp;gt;
&amp;lt;ol class=&amp;quot;arabic&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Definisi log&amp;lt;/strong&amp;gt;sebelum implementasi&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Validasi dengan observasi&amp;lt;/strong&amp;gt;perilaku yang diharapkan&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Lacakabilitas lengkap&amp;lt;/strong&amp;gt;aliran data&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Debugging proaktif&amp;lt;/strong&amp;gt;anticipasi kesalahan&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_arsitektur_konseptual&amp;quot;&amp;gt;&amp;lt;a class=&amp;quot;anchor&amp;quot; href=&amp;quot;#_arsitektur_konseptual&amp;quot;&amp;gt;&amp;lt;/a&amp;gt;Arsitektur Konseptual&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
!theme aws-orange

package &amp;quot;Inti Bot Discord&amp;quot; {
  [Command Handler] as CH
  [Event Listener] as EL
  [Log Manager] as LM
}

package &amp;quot;Lapisan Integrasi Musik&amp;quot; {
  [Spotify Client] as SC
  [YouTube Client] as YC
  [Audio Processor] as AP
}

package &amp;quot;Inti Fungsional&amp;quot; {
  [Data Validation] as DV
  [Business Logic] as BL
  [Error Handling] as EH
}

package &amp;quot;API eksternal&amp;quot; {
  [Spotify API] as SAPI
  [YouTube API] as YAPI
  [Discord API] as DAPI
}

CH --&amp;amp;gt; BL
EL --&amp;amp;gt; BL
BL --&amp;amp;gt; DV
BL --&amp;amp;gt; EH
BL --&amp;amp;gt; LM

SC --&amp;amp;gt; SAPI
YC --&amp;amp;gt; YAPI
CH --&amp;amp;gt; DAPI

DV ..&amp;amp;gt; SC : validates
DV ..&amp;amp;gt; YC : validates
AP --&amp;amp;gt; SC
AP --&amp;amp;gt; YC

LM --&amp;amp;gt; EH : logs errors
LM --&amp;amp;gt; BL : logs operations

@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_teknik_stack_dan_paradigma_fungsional&amp;quot;&amp;gt;&amp;lt;a class=&amp;quot;anchor&amp;quot; href=&amp;quot;#_teknik_stack_dan_paradigma_fungsional&amp;quot;&amp;gt;&amp;lt;/a&amp;gt;Teknik Stack dan Paradigma Fungsional&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_pilihan_teknologi&amp;quot;&amp;gt;&amp;lt;a class=&amp;quot;anchor&amp;quot; href=&amp;quot;#_pilihan_teknologi&amp;quot;&amp;gt;&amp;lt;/a&amp;gt;Pilihan teknologi&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Stack kami berpusat pada pemrograman fungsional:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;dlist&amp;quot;&amp;gt;
&amp;lt;dl&amp;gt;
&amp;lt;dt class=&amp;quot;hdlist1&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;PyMonade&amp;lt;/strong&amp;gt;&amp;lt;/dt&amp;gt;
&amp;lt;dd&amp;gt;
&amp;lt;p&amp;gt;Manajemen efek samping dan komposisi fungsi&amp;lt;/p&amp;gt;
&amp;lt;/dd&amp;gt;
&amp;lt;dt class=&amp;quot;hdlist1&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;Pydantic&amp;lt;/strong&amp;gt;&amp;lt;/dt&amp;gt;
&amp;lt;dd&amp;gt;
&amp;lt;p&amp;gt;Validasi data type-safe dan serialisasi&amp;lt;/p&amp;gt;
&amp;lt;/dd&amp;gt;
&amp;lt;dt class=&amp;quot;hdlist1&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;Asyncio&amp;lt;/strong&amp;gt;&amp;lt;/dt&amp;gt;
&amp;lt;dd&amp;gt;
&amp;lt;p&amp;gt;Pemrograman asinkron untuk APIs&amp;lt;/p&amp;gt;
&amp;lt;/dd&amp;gt;
&amp;lt;dt class=&amp;quot;hdlist1&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;Structlog&amp;lt;/strong&amp;gt;&amp;lt;/dt&amp;gt;
&amp;lt;dd&amp;gt;
&amp;lt;p&amp;gt;Logging terstruktur untuk LDD&amp;lt;/p&amp;gt;
&amp;lt;/dd&amp;gt;
&amp;lt;/dl&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_prinsip_fungsional_yang_diterapkan&amp;quot;&amp;gt;&amp;lt;a class=&amp;quot;anchor&amp;quot; href=&amp;quot;#_prinsip_fungsional_yang_diterapkan&amp;quot;&amp;gt;&amp;lt;/a&amp;gt;Prinsip fungsional yang diterapkan&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
!theme plain

title Aliran data fungsional
participant &amp;quot;Perintah Discord&amp;quot; as DC
participant &amp;quot;Pen-validasi&amp;quot; as V
participant &amp;quot;Logika bisnis&amp;quot; as BL
participant &amp;quot;Klien API&amp;quot; as AC
participant &amp;quot;pencatat&amp;quot; as L

DC -&amp;amp;gt; V: Raw Input
activate V
V -&amp;amp;gt; V: Pydantic Validation
V -&amp;amp;gt; L: Log Validation
V --&amp;amp;gt; DC: Maybe[ValidData]
deactivate V

DC -&amp;amp;gt; BL: ValidData
activate BL
BL -&amp;amp;gt; BL: Pure Computation
BL -&amp;amp;gt; L: Log Business Logic
BL -&amp;amp;gt; AC: API Request
activate AC
AC -&amp;amp;gt; AC: IO Operation
AC -&amp;amp;gt; L: Log API Call
AC --&amp;amp;gt; BL: Maybe[Result]
deactivate AC
BL --&amp;amp;gt; DC: Either[Error, Success]
deactivate BL

DC -&amp;amp;gt; L: Log Final Result

@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_metodologi_agile_dan_backlog&amp;quot;&amp;gt;&amp;lt;a class=&amp;quot;anchor&amp;quot; href=&amp;quot;#_metodologi_agile_dan_backlog&amp;quot;&amp;gt;&amp;lt;/a&amp;gt;Metodologi Agile dan Backlog&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_epik_utama&amp;quot;&amp;gt;&amp;lt;a class=&amp;quot;anchor&amp;quot; href=&amp;quot;#_epik_utama&amp;quot;&amp;gt;&amp;lt;/a&amp;gt;Epik Utama&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Pengembangan kami terorganisasi di sekitar 4 epik utama :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect3&amp;quot;&amp;gt;
&amp;lt;h4 id=&amp;quot;_epic_1_bot_infrastruktur_discord&amp;quot;&amp;gt;&amp;lt;a class=&amp;quot;anchor&amp;quot; href=&amp;quot;#_epic_1_bot_infrastruktur_discord&amp;quot;&amp;gt;&amp;lt;/a&amp;gt;Epic 1 : Bot Infrastruktur Discord&amp;lt;/h4&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Nilai bisnis&amp;lt;/strong&amp;gt; : Dasar yang kuat dan dapat diperluas&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Kriteria penerimaan&amp;lt;/strong&amp;gt; : - Koneksi Discord stabil dengan pengelolaan koneksi ulang - Sistem perintah modular - Logging terstruktur terintegrasi - Pengelolaan kesalahan terpusat&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect3&amp;quot;&amp;gt;
&amp;lt;h4 id=&amp;quot;_epic_2_integrasi_spotify&amp;quot;&amp;gt;&amp;lt;a class=&amp;quot;anchor&amp;quot; href=&amp;quot;#_epic_2_integrasi_spotify&amp;quot;&amp;gt;&amp;lt;/a&amp;gt;Epic 2 : Integrasi Spotify&amp;lt;/h4&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Nilai Bisnis&amp;lt;/strong&amp;gt; : Akses ke metadata musik&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;kriteria penerimaan&amp;lt;/strong&amp;gt; : - Otentikasi OAuth2 yang aman - Pencarian tracks dengan cache cerdas - Manajemen kuota API - Fallback pada kesalahan jaringan&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect3&amp;quot;&amp;gt;
&amp;lt;h4 id=&amp;quot;_epic_3_integrasi_youtube&amp;quot;&amp;gt;&amp;lt;a class=&amp;quot;anchor&amp;quot; href=&amp;quot;#_epic_3_integrasi_youtube&amp;quot;&amp;gt;&amp;lt;/a&amp;gt;Epic 3 : Integrasi YouTube&amp;lt;/h4&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Nilai bisnis&amp;lt;/strong&amp;gt; : Akses ke konten audio&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Kriteria penerimaan&amp;lt;/strong&amp;gt; : - Penghindaran legal terhadap pembatasan - Ekstraksi audio yang dioptimalkan - Pengelolaan video pribadi/terhapus - Menghormati Ketentuan Layanan YouTube&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect3&amp;quot;&amp;gt;
&amp;lt;h4 id=&amp;quot;_epic_4_fitur_musik&amp;quot;&amp;gt;&amp;lt;a class=&amp;quot;anchor&amp;quot; href=&amp;quot;#_epic_4_fitur_musik&amp;quot;&amp;gt;&amp;lt;/a&amp;gt;Epic 4 : fitur musik&amp;lt;/h4&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Nilai bisnis&amp;lt;/strong&amp;gt; : Pengalaman pengguna lengkap&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Kriteria penerimaan&amp;lt;/strong&amp;gt; : - Pemutaran audio berkualitas tinggi - Antrean pemutaran cerdas - Perintah suara Discord - Sinkronisasi lintas-platform&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_user_stories_terperinci&amp;quot;&amp;gt;&amp;lt;a class=&amp;quot;anchor&amp;quot; href=&amp;quot;#_user_stories_terperinci&amp;quot;&amp;gt;&amp;lt;/a&amp;gt;User Stories Terperinci&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;sect3&amp;quot;&amp;gt;
&amp;lt;h4 id=&amp;quot;_us1_1_inisialisasi_bot&amp;quot;&amp;gt;&amp;lt;a class=&amp;quot;anchor&amp;quot; href=&amp;quot;#_us1_1_inisialisasi_bot&amp;quot;&amp;gt;&amp;lt;/a&amp;gt;US1.1 : Inisialisasi Bot&amp;lt;/h4&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Sebagai&amp;lt;/strong&amp;gt; seorang pengembang &amp;lt;strong&amp;gt;Saya inginkan&amp;lt;/strong&amp;gt; sebuah bot Discord yang terhubung dengan andal &amp;lt;strong&amp;gt;Untuk&amp;lt;/strong&amp;gt; menjamin ketersediaan layanan&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;DoD (Definisi Selesai)&amp;lt;/strong&amp;gt; : - [ ] Bot terhubung otomatis saat dimulai - [ ] Logs terstruktur mencatat setiap langkah - [ ] Penghubungan ulang otomatis saat terputus - [ ] Tes integrasi lulus&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect3&amp;quot;&amp;gt;
&amp;lt;h4 id=&amp;quot;_us2_1_pencarian_spotify_cerdas&amp;quot;&amp;gt;&amp;lt;a class=&amp;quot;anchor&amp;quot; href=&amp;quot;#_us2_1_pencarian_spotify_cerdas&amp;quot;&amp;gt;&amp;lt;/a&amp;gt;US2.1 : Pencarian Spotify Cerdas&amp;lt;/h4&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;As a Discord user *Saya ingin&amp;lt;/strong&amp;gt; mencari lagu melalui Spotify &amp;lt;strong&amp;gt;Untuk&amp;lt;/strong&amp;gt; menemukan dan berbagi musik&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;DoD&amp;lt;/strong&amp;gt; : - [ ] Pesanan`/search`fungsional - [ ] Hasil yang relevan dengan metadata - [ ] Cache lokal untuk mengoptimalkan permintaan - [ ] Penanganan anggun kesalahan API&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect3&amp;quot;&amp;gt;
&amp;lt;h4 id=&amp;quot;_us3_1_ekstraksi_youtube_yang_resilién&amp;quot;&amp;gt;&amp;lt;a class=&amp;quot;anchor&amp;quot; href=&amp;quot;#_us3_1_ekstraksi_youtube_yang_resilién&amp;quot;&amp;gt;&amp;lt;/a&amp;gt;US3.1 : Ekstraksi YouTube yang resilién&amp;lt;/h4&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Sebagai&amp;lt;/strong&amp;gt; sistem &amp;lt;strong&amp;gt;Saya ingin&amp;lt;/strong&amp;gt; mengekstrak audio YouTube secara andal Agar menjaga kelangsungan layanan&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;DoD&amp;lt;/strong&amp;gt; : - [ ] Ekstraksi tanpa pelanggaran ToS - [ ] Kualitas audio optimal - [ ] Pengelolaan pembatasan geografis - [ ] Log detail operasi&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_pendekatan_pengembangan_berbasis_log_2&amp;quot;&amp;gt;&amp;lt;a class=&amp;quot;anchor&amp;quot; href=&amp;quot;#_pendekatan_pengembangan_berbasis_log_2&amp;quot;&amp;gt;&amp;lt;/a&amp;gt;Pendekatan Pengembangan Berbasis Log&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_strategi_logging&amp;quot;&amp;gt;&amp;lt;a class=&amp;quot;anchor&amp;quot; href=&amp;quot;#_strategi_logging&amp;quot;&amp;gt;&amp;lt;/a&amp;gt;Strategi Logging&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
!theme spacelab

title Alur Pengembangan Berbasis Log
start

:Define Expected Behavior;
note right: Spécification des logs attendus

:Write Log Assertions;
note right: Tests basés sur les logs

:Implement Minimal Code;
note right: Code juste suffisant

:Run &amp;amp;amp; Observe Logs;
note right: Validation comportementale

if (Logs Match Expectations?) then (yes)
  :Refactor &amp;amp;amp; Optimize;
  note right: Amélioration continue
else (no)
  :Debug via Logs;
  note right: Analyse des écarts
  :Fix Implementation;
endif

:Integration Tests;
note right: Validation end-to-end

stop

@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_struktur_log&amp;quot;&amp;gt;&amp;lt;a class=&amp;quot;anchor&amp;quot; href=&amp;quot;#_struktur_log&amp;quot;&amp;gt;&amp;lt;/a&amp;gt;Struktur Log&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Pendekatan LDD kami menggunakan log terstruktur dengan tingkat semantik:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;dlist&amp;quot;&amp;gt;
&amp;lt;dl&amp;gt;
&amp;lt;dt class=&amp;quot;hdlist1&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;jejak&amp;lt;/strong&amp;gt;&amp;lt;/dt&amp;gt;
&amp;lt;dd&amp;gt;
&amp;lt;p&amp;gt;Aliran data terperinci&amp;lt;/p&amp;gt;
&amp;lt;/dd&amp;gt;
&amp;lt;dt class=&amp;quot;hdlist1&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;DEBUG&amp;lt;/strong&amp;gt;&amp;lt;/dt&amp;gt;
&amp;lt;dd&amp;gt;
&amp;lt;p&amp;gt;Keadaan internal fungsi&amp;lt;/p&amp;gt;
&amp;lt;/dd&amp;gt;
&amp;lt;dt class=&amp;quot;hdlist1&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;informasi&amp;lt;/strong&amp;gt;&amp;lt;/dt&amp;gt;
&amp;lt;dd&amp;gt;
&amp;lt;p&amp;gt;Operasi bisnis berhasil&amp;lt;/p&amp;gt;
&amp;lt;/dd&amp;gt;
&amp;lt;dt class=&amp;quot;hdlist1&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;memperingatkan&amp;lt;/strong&amp;gt;&amp;lt;/dt&amp;gt;
&amp;lt;dd&amp;gt;
&amp;lt;p&amp;gt;Situasi yang memburuk tetapi dikelola&amp;lt;/p&amp;gt;
&amp;lt;/dd&amp;gt;
&amp;lt;dt class=&amp;quot;hdlist1&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;kesalahan&amp;lt;/strong&amp;gt;&amp;lt;/dt&amp;gt;
&amp;lt;dd&amp;gt;
&amp;lt;p&amp;gt;Kesalahan yang memerlukan intervensi&amp;lt;/p&amp;gt;
&amp;lt;/dd&amp;gt;
&amp;lt;dt class=&amp;quot;hdlist1&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;kritis&amp;lt;/strong&amp;gt;&amp;lt;/dt&amp;gt;
&amp;lt;dd&amp;gt;
&amp;lt;p&amp;gt;kerusakan sistem&amp;lt;/p&amp;gt;
&amp;lt;/dd&amp;gt;
&amp;lt;/dl&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_contoh_desain_log&amp;quot;&amp;gt;&amp;lt;a class=&amp;quot;anchor&amp;quot; href=&amp;quot;#_contoh_desain_log&amp;quot;&amp;gt;&amp;lt;/a&amp;gt;Contoh desain log&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Sebelum mengimplementasikan fungsi pencarian Spotify, kami mendefinisikan lognya:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-none hljs&amp;quot;&amp;gt;INFO: spotify.search.start query=&amp;quot;bohemian rhapsody&amp;quot; user_id=123456
DEBUG: spotify.search.validation query_length=16 safe_chars=true
DEBUG: spotify.search.api_call endpoint=&amp;quot;/search&amp;quot; params={...}
INFO: spotify.search.success results_count=15 duration_ms=340&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_arsitektur_validasi_dengan_pydantic&amp;quot;&amp;gt;&amp;lt;a class=&amp;quot;anchor&amp;quot; href=&amp;quot;#_arsitektur_validasi_dengan_pydantic&amp;quot;&amp;gt;&amp;lt;/a&amp;gt;Arsitektur Validasi dengan Pydantic&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_model_data&amp;quot;&amp;gt;&amp;lt;a class=&amp;quot;anchor&amp;quot; href=&amp;quot;#_model_data&amp;quot;&amp;gt;&amp;lt;/a&amp;gt;Model Data&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Pendekatan fungsional kami mengutamakan validasi dini :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
!theme cerulean-outline

class SpotifyTrack {
  +id: str
  +name: str
  +artists: List[str]
  +duration_ms: int
  +external_urls: Dict[str, str]
  --
  +validate_duration() : bool
  +to_discord_embed() : Embed
}

class YouTubeVideo {
  +id: str
  +title: str
  +duration: timedelta
  +available: bool
  --
  +validate_availability() : bool
  +extract_audio_url() : Optional[str]
}

class DiscordCommand {
  +command: str
  +args: List[str]
  +user: User
  +channel: Channel
  --
  +validate_permissions() : bool
  +log_execution() : None
}

SpotifyTrack --|&amp;amp;gt; BaseModel
YouTubeVideo --|&amp;amp;gt; BaseModel
DiscordCommand --|&amp;amp;gt; BaseModel

@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_manajemen_kesalahan_fungsional&amp;quot;&amp;gt;&amp;lt;a class=&amp;quot;anchor&amp;quot; href=&amp;quot;#_manajemen_kesalahan_fungsional&amp;quot;&amp;gt;&amp;lt;/a&amp;gt;manajemen kesalahan fungsional&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_monades_dan_error_handling&amp;quot;&amp;gt;&amp;lt;a class=&amp;quot;anchor&amp;quot; href=&amp;quot;#_monades_dan_error_handling&amp;quot;&amp;gt;&amp;lt;/a&amp;gt;Monades dan Error Handling&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Penggunaan PyMonade memungkinkan penanganan kesalahan yang elegan:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
!theme toy

title Alur Penanganan Kesalahan
participant &amp;quot;Perintah&amp;quot; as C
participant &amp;quot;Maybe Monad&amp;quot; as M
participant &amp;quot;Atau Monad&amp;quot; as E
participant &amp;quot;Logger&amp;quot; as L

C -&amp;amp;gt; M: search_query
activate M

alt Valid Query
  M -&amp;amp;gt; E: Success(query)
  activate E
  E -&amp;amp;gt; E: api_call()

  alt API Success
    E -&amp;amp;gt; L: log_success()
    E --&amp;amp;gt; C: Right(result)
  else API Error
    E -&amp;amp;gt; L: log_api_error()
    E --&amp;amp;gt; C: Left(api_error)
  end
  deactivate E

else Invalid Query
  M -&amp;amp;gt; L: log_validation_error()
  M --&amp;amp;gt; C: Nothing
end

deactivate M

@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_strategi_penghindaran_pembatasan&amp;quot;&amp;gt;&amp;lt;a class=&amp;quot;anchor&amp;quot; href=&amp;quot;#_strategi_penghindaran_pembatasan&amp;quot;&amp;gt;&amp;lt;/a&amp;gt;Strategi Penghindaran Pembatasan&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_pendekatan_multi_sumber&amp;quot;&amp;gt;&amp;lt;a class=&amp;quot;anchor&amp;quot; href=&amp;quot;#_pendekatan_multi_sumber&amp;quot;&amp;gt;&amp;lt;/a&amp;gt;Pendekatan multi-sumber&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Terhadap batasan API, kami mengadopsi strategi diversifikasi:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
!theme mars

title Strategi multi-sumber
start

:User Request;

:Primary Source\n(Spotify);

if (Available?) then (yes)
  :Return Spotify Data;
  stop
else (no)
  :Log Fallback;
  :Secondary Source\n(YouTube Music);

  if (Available?) then (yes)
    :Return YouTube Data;
    stop
  else (no)
    :Tertiary Source\n(Local Cache);

    if (Available?) then (yes)
      :Return Cached Data;
      :Log Cache Hit;
      stop
    else (no)
      :Return Error;
      :Log Complete Failure;
      stop
    end
  end
end

@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_rencana_pengembangan_iteratif&amp;quot;&amp;gt;&amp;lt;a class=&amp;quot;anchor&amp;quot; href=&amp;quot;#_rencana_pengembangan_iteratif&amp;quot;&amp;gt;&amp;lt;/a&amp;gt;Rencana Pengembangan Iteratif&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_perencanaan_sprint&amp;quot;&amp;gt;&amp;lt;a class=&amp;quot;anchor&amp;quot; href=&amp;quot;#_perencanaan_sprint&amp;quot;&amp;gt;&amp;lt;/a&amp;gt;Perencanaan Sprint&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Pengembangan kami mengikuti siklus sprint 2 minggu :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;dlist&amp;quot;&amp;gt;
&amp;lt;dl&amp;gt;
&amp;lt;dt class=&amp;quot;hdlist1&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;Sprint 1-2&amp;lt;/strong&amp;gt;&amp;lt;/dt&amp;gt;
&amp;lt;dd&amp;gt;
&amp;lt;p&amp;gt;Infrastruktur dan Bot Discord Inti&amp;lt;/p&amp;gt;
&amp;lt;/dd&amp;gt;
&amp;lt;dt class=&amp;quot;hdlist1&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;Sprint 3-4&amp;lt;/strong&amp;gt;&amp;lt;/dt&amp;gt;
&amp;lt;dd&amp;gt;
&amp;lt;p&amp;gt;Integrasi Spotify dengan LDD&amp;lt;/p&amp;gt;
&amp;lt;/dd&amp;gt;
&amp;lt;dt class=&amp;quot;hdlist1&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;Sprint 5-6&amp;lt;/strong&amp;gt;&amp;lt;/dt&amp;gt;
&amp;lt;dd&amp;gt;
&amp;lt;p&amp;gt;Integrasi YouTube dan cara mengelakinya&amp;lt;/p&amp;gt;
&amp;lt;/dd&amp;gt;
&amp;lt;dt class=&amp;quot;hdlist1&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;Sprint 7-8&amp;lt;/strong&amp;gt;&amp;lt;/dt&amp;gt;
&amp;lt;dd&amp;gt;
&amp;lt;p&amp;gt;Features musik tingkat lanjut&amp;lt;/p&amp;gt;
&amp;lt;/dd&amp;gt;
&amp;lt;dt class=&amp;quot;hdlist1&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;Sprint 9-10&amp;lt;/strong&amp;gt;&amp;lt;/dt&amp;gt;
&amp;lt;dd&amp;gt;
&amp;lt;p&amp;gt;Optimisasi dan produksi&amp;lt;/p&amp;gt;
&amp;lt;/dd&amp;gt;
&amp;lt;/dl&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_metrik_kualitas&amp;quot;&amp;gt;&amp;lt;a class=&amp;quot;anchor&amp;quot; href=&amp;quot;#_metrik_kualitas&amp;quot;&amp;gt;&amp;lt;/a&amp;gt;Metrik Kualitas&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Setiap sprint dievaluasi pada:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Cakupan log&amp;lt;/strong&amp;gt;: &amp;amp;gt;90% jalur kritis&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Keandalan API&amp;lt;/strong&amp;gt;: &amp;amp;lt;1% kesalahan yang tidak ditangani&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;kinerja&amp;lt;/strong&amp;gt;: &amp;amp;lt;500ms waktu respons rata-rata&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;kemudahan pemeliharaan&amp;lt;/strong&amp;gt;: Kompleksitas siklik &amp;amp;lt;10&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_penyebaran_dan_pemantauan&amp;quot;&amp;gt;&amp;lt;a class=&amp;quot;anchor&amp;quot; href=&amp;quot;#_penyebaran_dan_pemantauan&amp;quot;&amp;gt;&amp;lt;/a&amp;gt;Penyebaran dan Pemantauan&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_arsitektur_produksi&amp;quot;&amp;gt;&amp;lt;a class=&amp;quot;anchor&amp;quot; href=&amp;quot;#_arsitektur_produksi&amp;quot;&amp;gt;&amp;lt;/a&amp;gt;Arsitektur Produksi&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
!theme vibrant

cloud &amp;quot;Server Discord&amp;quot; {
  [User Commands]
}

node &amp;quot;Lingkungan Produksi&amp;quot; {
  [Discord Bot]
  [Log Aggregator]
  [Metrics Collector]
  [Health Monitor]
}

database &amp;quot;Penyimpanan Log&amp;quot; {
  [Structured Logs]
  [Error Traces]
  [Performance Metrics]
}

cloud &amp;quot;API Eksternal&amp;quot; {
  [Spotify API]
  [YouTube API]
}

[User Commands] --&amp;amp;gt; [Discord Bot]
[Discord Bot] --&amp;amp;gt; [Log Aggregator]
[Discord Bot] --&amp;amp;gt; [Spotify API]
[Discord Bot] --&amp;amp;gt; [YouTube API]
[Log Aggregator] --&amp;amp;gt; [Structured Logs]
[Metrics Collector] --&amp;amp;gt; [Performance Metrics]
[Health Monitor] --&amp;amp;gt; [Error Traces]

@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_pemantauan_proaktif&amp;quot;&amp;gt;&amp;lt;a class=&amp;quot;anchor&amp;quot; href=&amp;quot;#_pemantauan_proaktif&amp;quot;&amp;gt;&amp;lt;/a&amp;gt;Pemantauan Proaktif&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;LDD memudahkan monitoring yang cerdas:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Peringatan berbasis pola log&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Deteksi anomali perilaku&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Metrik bisnis secara real-time&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Debugging yang didukung oleh korelasi log&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_kesimpulan_dan_perspektif&amp;quot;&amp;gt;&amp;lt;a class=&amp;quot;anchor&amp;quot; href=&amp;quot;#_kesimpulan_dan_perspektif&amp;quot;&amp;gt;&amp;lt;/a&amp;gt;Kesimpulan dan Perspektif&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Pendekatan metodologis ini menggabungkan manfaat paradigma fungsional dengan ketangguhan Log Driven Development. Hal ini memungkinkan kami untuk :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;olist arabic&amp;quot;&amp;gt;
&amp;lt;ol class=&amp;quot;arabic&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Mengantisipasi masalah&amp;lt;/strong&amp;gt;Berkat log yang dirancang sebelumnya&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Menjaga kualitas&amp;lt;/strong&amp;gt;melalui validasi terus-menerus&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Beradaptasi dengan cepat&amp;lt;/strong&amp;gt;ke perubahan API&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Memastikan kejelasan jejak&amp;lt;/strong&amp;gt;operasi lengkap&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Pengembangan iteratif dan arsitektur moduler menjamin skalabilitas menghadapi kendala yang berubah-ubah dari platform musik.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_langkah_selanjutnya&amp;quot;&amp;gt;&amp;lt;a class=&amp;quot;anchor&amp;quot; href=&amp;quot;#_langkah_selanjutnya&amp;quot;&amp;gt;&amp;lt;/a&amp;gt;Langkah Selanjutnya&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Fase 1&amp;lt;/strong&amp;gt;Implementasi core dengan PyMonade&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Tahap 2&amp;lt;/strong&amp;gt;Integrasi Spotify dengan cache cerdas&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Fase 3&amp;lt;/strong&amp;gt;: Solusi YouTube yang tahan&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Tahap 4&amp;lt;/strong&amp;gt;: Fitur canggih dan optimisasi&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Fondasi konseptual yang kuat ini akan memungkinkan kita menavigasi tantangan teknis sekaligus memberikan pengalaman pengguna yang luar biasa.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;hr&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Article ini akan diikuti oleh sebuah seri teknis yang mendetailkan implementasi setiap komponen dengan contoh kode dan pola fungsional.&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>Mendongkakan dan memantau suhu laptopnya di Ubuntu</title>
            <link >https://cheroliv.com/blog/2025/0083_cold_computer_post.html</link>
            <pubDate>Tue, 15 Jul 2025 00:00:00 +0000</pubDate>
            <guid isPermaLink="false">blog/2025/0083_cold_computer_post.html</guid>
            <description>&amp;lt;div id=&amp;quot;toc&amp;quot; class=&amp;quot;toc&amp;quot;&amp;gt;
&amp;lt;div id=&amp;quot;toctitle&amp;quot;&amp;gt;Table of Contents&amp;lt;/div&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel1&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_tujuan_dari_panduan_ini&amp;quot;&amp;gt;Tujuan dari panduan ini&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_prasyarat&amp;quot;&amp;gt;Prasyarat&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_menginstal_dan_mengkonfigurasi_alat&amp;quot;&amp;gt;Menginstal dan mengkonfigurasi alat&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_lm_sensors_untuk_mendeteksi_sensor&amp;quot;&amp;gt;lm-sensors` untuk mendeteksi sensor&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_tlp_pengelola_energi_cerdas&amp;quot;&amp;gt;tlp` : pengelola energi cerdas&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_cpufrequtils_membatasi_frekuensi_cpu&amp;quot;&amp;gt;cpufrequtils` : membatasi frekuensi CPU&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_auto_cpufreq_pengelolaan_dinamis_yang_disederhanakan&amp;quot;&amp;gt;auto-cpufreq` : pengelolaan dinamis yang disederhanakan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_thermald_perlindungan_termal_otomatis&amp;quot;&amp;gt;thermald` : perlindungan termal otomatis&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_pemantauan_waktu_nyata&amp;quot;&amp;gt;pemantauan waktu nyata&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_sensors_baris_perintah&amp;quot;&amp;gt;sensors` baris perintah&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_opsi_gui_psensor&amp;quot;&amp;gt;Opsi GUI : `psensor&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_tips_tambahan&amp;quot;&amp;gt;Tips tambahan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_membersihkan_sistem_untuk_membatasi_beban_berlebih&amp;quot;&amp;gt;Membersihkan sistem untuk membatasi beban berlebih&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_untuk_pergi_lebih_jauh&amp;quot;&amp;gt;Untuk pergi lebih jauh&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_kesimpulan&amp;quot;&amp;gt;📚 Kesimpulan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div id=&amp;quot;preamble&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Apakah Anda merasa bahwa laptop Anda terlalu panas saat berjalan di Ubuntu? Artikel ini menawarkan pendekatan&amp;lt;strong&amp;gt;perangkat lunak&amp;lt;/strong&amp;gt;lengkap untuk&amp;lt;strong&amp;gt;mendinginkan&amp;lt;/strong&amp;gt;, &amp;lt;strong&amp;gt;memantau&amp;lt;/strong&amp;gt; et &amp;lt;strong&amp;gt;mengendalikan suhu&amp;lt;/strong&amp;gt;de mesin Anda.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_tujuan_dari_panduan_ini&amp;quot;&amp;gt;Tujuan dari panduan ini&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Mengurangi suhu keseluruhan komputer&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Mengoptimalkan pengelolaan energi&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Memantau komponen kritis (CPU, GPU, dst).&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Menghindari penurunan kecepatan karena&amp;lt;strong&amp;gt;pembatasan termal&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
start
:Installer les outils;
:Analyser les capteurs;
:Appliquer des réglages;
note right: cpufreq, tlp, auto-cpufreq
:Surveiller en temps réel;
stop
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_prasyarat&amp;quot;&amp;gt;Prasyarat&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Sebuah mesin Ubuntu (20.04 atau lebih tinggi disarankan)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;koneksi internet&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Kemampuan baris perintah (tingkat pemula hingga menengah)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_menginstal_dan_mengkonfigurasi_alat&amp;quot;&amp;gt;Menginstal dan mengkonfigurasi alat&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_lm_sensors_untuk_mendeteksi_sensor&amp;quot;&amp;gt;lm-sensors` untuk mendeteksi sensor&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;sudo apt install lm-sensors
sudo sensors-detect
sensors&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;admonitionblock note&amp;quot;&amp;gt;
&amp;lt;table&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;icon&amp;quot;&amp;gt;
&amp;lt;i class=&amp;quot;fa icon-note&amp;quot; title=&amp;quot;Note&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Jawab`YES`ke semua pertanyaan tentang`sensors-detect`untuk mendeteksi modul.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_tlp_pengelola_energi_cerdas&amp;quot;&amp;gt;tlp` : pengelola energi cerdas&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;sudo apt install tlp tlp-rdw
sudo systemctl enable tlp
sudo systemctl start tlp&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
(*) --&amp;amp;gt; &amp;quot;TLP aktif?&amp;quot;
if &amp;quot;Ya&amp;quot; then
  --&amp;amp;gt; [yes] &amp;quot;Pengurangan konsumsi&amp;quot;
  --&amp;amp;gt; &amp;quot;Batas frekuensi CPU&amp;quot;
else
  --&amp;amp;gt; [no] &amp;quot;Memulai TLP&amp;quot;
  --&amp;amp;gt; &amp;quot;Aktivasi otomatis&amp;quot;
endif
--&amp;amp;gt;(*)
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_cpufrequtils_membatasi_frekuensi_cpu&amp;quot;&amp;gt;cpufrequtils` : membatasi frekuensi CPU&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;sudo apt install cpufrequtils
cpufreq-info
sudo cpufreq-set -g powersave&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;code&amp;gt;powersave&amp;lt;/code&amp;gt;: membatasi puncak frekuensi&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;code&amp;gt;performance&amp;lt;/code&amp;gt;: memprioritaskan kekuatan dengan mengorbankan suhu&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_auto_cpufreq_pengelolaan_dinamis_yang_disederhanakan&amp;quot;&amp;gt;auto-cpufreq` : pengelolaan dinamis yang disederhanakan&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;git clone https://github.com/AdnanHodzic/auto-cpufreq.git
cd auto-cpufreq
sudo ./auto-cpufreq-installer
sudo auto-cpufreq --install&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
start
:Analyse charge système;
:Réglage fréquence CPU;
:Vérification température;
:Adaptation dynamique;
stop
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_thermald_perlindungan_termal_otomatis&amp;quot;&amp;gt;thermald` : perlindungan termal otomatis&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;sudo apt install thermald
sudo systemctl enable thermald
sudo systemctl start thermald&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_pemantauan_waktu_nyata&amp;quot;&amp;gt;pemantauan waktu nyata&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_sensors_baris_perintah&amp;quot;&amp;gt;sensors` baris perintah&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;watch -n 2 sensors&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_opsi_gui_psensor&amp;quot;&amp;gt;Opsi GUI : `psensor&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;sudo apt install psensor&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Antarmuka grafis sederhana untuk CPU, GPU, kipas.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_tips_tambahan&amp;quot;&amp;gt;Tips tambahan&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Menutup tab dan aplikasi berat (contoh: peramban)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Hindari penggunaan intens pada baterai&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Memantau proses melalui`htop`:&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;sudo apt install htop
htop&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_membersihkan_sistem_untuk_membatasi_beban_berlebih&amp;quot;&amp;gt;Membersihkan sistem untuk membatasi beban berlebih&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;sudo apt autoremove
sudo apt autoclean
sudo journalctl --vacuum-time=3d&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_untuk_pergi_lebih_jauh&amp;quot;&amp;gt;Untuk pergi lebih jauh&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Anda bisa membuat layanan`systemd`disesuaikan untuk menerapkan secara otomatis beberapa pengaturan saat startup Apakah Anda ingin sebuah contoh dalam artikel berikutnya? Beritahu saya!&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_kesimpulan&amp;quot;&amp;gt;📚 Kesimpulan&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Menggabungkan alat-alat sederhana ini, Anda akan mendapatkan:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Suhu CPU yang terkontrol&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Kurang suara ventilasi&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Otonomi yang lebih baik&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Jangan lupa untuk memantau suhu Anda secara rutin untuk mencegah overheating.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;em&amp;gt;&amp;lt;em&amp;gt;_ Lebih baik mencegah daripada melelehkan CPU-nya. &amp;lt;/em&amp;gt;_&amp;lt;/em&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>Kisah dari proses debug: menguasai kepribadian Firefox dengan Bootstrap 5</title>
            <link >https://cheroliv.com/blog/2025/0082_ui_struggle_post.html</link>
            <pubDate>Mon, 14 Jul 2025 00:00:00 +0000</pubDate>
            <guid isPermaLink="false">blog/2025/0082_ui_struggle_post.html</guid>
            <description>&amp;lt;div id=&amp;quot;toc&amp;quot; class=&amp;quot;toc&amp;quot;&amp;gt;
&amp;lt;div id=&amp;quot;toctitle&amp;quot;&amp;gt;Sommaire&amp;lt;/div&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel1&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_medan_pertarungan_sebuah_mockup_tiga_peramban_rendering_berganda&amp;quot;&amp;gt;Medan pertarungan: sebuah mockup, tiga peramban, rendering berganda&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_masalah_1_kartu_melayang_dari_bagian_hero&amp;quot;&amp;gt;Masalah 1 : Kartu melayang dari bagian &amp;quot;Hero&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_masalah_2_bar_navigasi_kannibal&amp;quot;&amp;gt;Masalah 2: Bar navigasi kannibal&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_masalah_3_tipografi_anarkis&amp;quot;&amp;gt;Masalah 3 : Tipografi anarkis&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_masalah_4_bar_navigasi_yang_meluap&amp;quot;&amp;gt;Masalah 4 : Bar navigasi yang meluap&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_masalah_5_menu_hamburger_tidak_terlihat&amp;quot;&amp;gt;Masalah 5: Menu hamburger tidak terlihat&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_kesimpulan_pelajaran_dari_debugging_yang_giat&amp;quot;&amp;gt;Kesimpulan: pelajaran dari debugging yang giat&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div id=&amp;quot;preamble&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;admonitionblock note&amp;quot;&amp;gt;
&amp;lt;table&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;icon&amp;quot;&amp;gt;
&amp;lt;i class=&amp;quot;fa icon-note&amp;quot; title=&amp;quot;Note&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Waktu baca :&amp;lt;/strong&amp;gt; sekitar 7 menit &amp;lt;strong&amp;gt;Tingkat :&amp;lt;/strong&amp;gt; Menengah&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Dalam studi kasus praktis terperinci ini, kita akan membahas masalah kompatibilitas browser paling umum dan terkadang paling frustrasi yang dihadapi saat membuat mockup UI dengan Bootstrap 5.2. Dari pengelolaan Flexbox hingga halusnya rendering font, temukan perjalanan debugging kami untuk mengubah desain yang tidak stabil di Firefox menjadi pengalaman pixel-perfect di semua browser.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;hr&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_medan_pertarungan_sebuah_mockup_tiga_peramban_rendering_berganda&amp;quot;&amp;gt;Medan pertarungan: sebuah mockup, tiga peramban, rendering berganda&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Setiap pengembang front-end mengerti perasaan itu. Setelah berjam-jam bekerja, desain akhirnya sempurna. Penyelarasan rapi, tipografi elegan, animasi lancar. Kita menghela napas puas, kagumi karya kita di Chrome (atau Brave, atau Edge), lalu, sebagai langkah berhati-hati, kita membuka proyek di Firefox. Dan tiba-tiba, ini menjadi drama. Elemen yang meluap, penyelarasan yang rusak, font dengan ukuran acak-acakan&amp;amp;#8230;&amp;amp;#8203; harmoni indah itu telah hilang.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Ini tepat skenario yang kami hadapi saat mengembangkan antarmuka portofolio baru. Spesifikasi adalah sederhana: halaman beranda modern, responsif, dengan pemilih tema (terang, gelap, dan kontras tinggi), menggunakan &amp;lt;strong&amp;gt;vanilla JavaScript&amp;lt;/strong&amp;gt; dan versi terbaru &amp;lt;strong&amp;gt;Bootstrap 5.2&amp;lt;/strong&amp;gt;.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Artikel ini bukan daftar solusi ajaib. Ini adalah jurnal dari sesi debugging kita, suatu penyelaman ke &amp;quot;mengapa&amp;quot; perbedaan rendering antara mesin Blink (Chromium, Brave) dan Gecko (Firefox), serta demonstrasi bahwa solusi yang kuat dan modern selalu lebih baik daripada perbaikan sementara yang dibuat terburu-buru.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;Diagram Kasus Penggunaan: Proses debugging&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
left to right direction
actor &amp;quot;Pengembang Front-End&amp;quot; as Dev

rectangle &amp;quot;Mockup UI Bootstrap 5&amp;quot; {
  usecase &amp;quot;Memperbaiki perataan Flexbox&amp;quot; as UC1
  usecase &amp;quot;Menyesuaikan padding secara dinamis (JS)&amp;quot; as UC2
  usecase &amp;quot;Mengontrol tipografi (clamp())&amp;quot; as UC3
  usecase &amp;quot;Menyelesaikan overflow menu&amp;quot; as UC4
  usecase &amp;quot;Membuat ikon menu terlihat&amp;quot; as UC5
}

actor &amp;quot;Peramban Firefox&amp;quot; as Firefox

Dev -- (UC1)
Dev -- (UC2)
Dev -- (UC3)
Dev -- (UC4)
Dev -- (UC5)

(UC1) ..&amp;amp;gt; Firefox : &amp;amp;lt;&amp;amp;lt;extend&amp;amp;gt;&amp;amp;gt;
(UC2) ..&amp;amp;gt; Firefox : &amp;amp;lt;&amp;amp;lt;extend&amp;amp;gt;&amp;amp;gt;
(UC3) ..&amp;amp;gt; Firefox : &amp;amp;lt;&amp;amp;lt;extend&amp;amp;gt;&amp;amp;gt;
(UC4) ..&amp;amp;gt; Firefox : &amp;amp;lt;&amp;amp;lt;extend&amp;amp;gt;&amp;amp;gt;
(UC5) ..&amp;amp;gt; Firefox : &amp;amp;lt;&amp;amp;lt;extend&amp;amp;gt;&amp;amp;gt;

note right of Firefox : Révèle les problèmes de compatibilité
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;Diagram Komponen: Arsitektur Solusi Front-End&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
!theme plain

package &amp;quot;Solusi Front-End&amp;quot; {
  component &amp;quot;HTML (Cetak FTL)&amp;quot; as HTML
  component &amp;quot;script.js&amp;quot; as JS
  component &amp;quot;styles.css&amp;quot; as CSS

  interface &amp;quot;Manipulasi DOM&amp;quot; as iDOM
  interface &amp;quot;Perbaikan CSS&amp;quot; as iCSS
}

package &amp;quot;Kerangka Kerja&amp;quot; {
  component &amp;quot;Bootstrap 5.2&amp;quot; as BS
  interface &amp;quot;Variabel CSS&amp;quot; as iVars
  interface &amp;quot;Kelas Utilitas&amp;quot; as iUtils
}

HTML ..&amp;amp;gt; iDOM : nécessite
JS ..&amp;amp;gt; iDOM : réalise

HTML ..&amp;amp;gt; iCSS : nécessite
CSS ..&amp;amp;gt; iCSS : réalise

CSS ..&amp;amp;gt; iVars : nécessite
BS ..&amp;amp;gt; iVars : fournit

HTML ..&amp;amp;gt; iUtils : utilise
BS ..&amp;amp;gt; iUtils : fournit

note right of JS
  - Mesure la hauteur de la navbar
  - Applique le padding dynamiquement
end note

note right of CSS
  - Utilise clamp() pour la typographie
  - Réduit les marges du menu
  - Surcharge l&amp;#39;icône du menu via les variables
end note

@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_masalah_1_kartu_melayang_dari_bagian_hero&amp;quot;&amp;gt;Masalah 1 : Kartu melayang dari bagian &amp;quot;Hero&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Bug pertama juga adalah yang paling terlihat. Bagian selamat datang (&amp;quot;hero&amp;quot;) terdiri dari dua kolom: di kiri, judul utama; di kanan, kartu perkenalan.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Gejala :&amp;lt;/strong&amp;gt;Di Chrome dan Brave, kedua kolom tersebut berada di tengah secara vertikal sempurna. Di Firefox, kartu di sisi kanan jatuh tanpa alasan yang jelas di bawah teks di sisi kiri.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Investigasi :&amp;lt;/strong&amp;gt;Kode HTML sebenarnya terlihat sederhana dan benar, menggunakan kelas standar Bootstrap.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-html hljs&amp;quot; data-lang=&amp;quot;html&amp;quot;&amp;gt;&amp;amp;lt;section id=&amp;quot;home&amp;quot; class=&amp;quot;hero-section&amp;quot;&amp;amp;gt;
    &amp;amp;lt;div class=&amp;quot;container&amp;quot;&amp;amp;gt;
        &amp;amp;lt;!-- Cette ligne est la clé du problème --&amp;amp;gt;
        &amp;amp;lt;div class=&amp;quot;row align-items-center min-vh-100&amp;quot;&amp;amp;gt;
            &amp;amp;lt;div class=&amp;quot;col-lg-6&amp;quot;&amp;amp;gt;
                &amp;amp;lt;!-- Contenu de gauche --&amp;amp;gt;
            &amp;amp;lt;/div&amp;amp;gt;
            &amp;amp;lt;div class=&amp;quot;col-lg-6&amp;quot;&amp;amp;gt;
                &amp;amp;lt;!-- Carte de droite --&amp;amp;gt;
            &amp;amp;lt;/div&amp;amp;gt;
        &amp;amp;lt;/div&amp;amp;gt;
    &amp;amp;lt;/div&amp;amp;gt;
&amp;amp;lt;/section&amp;amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;CSS kustom kami untuk`.hero-section`menentukan`display: flex` et &amp;lt;code&amp;gt;align-items: center&amp;lt;/code&amp;gt;, dan kelas`min-vh-100`memberikan dengan baik tinggi minimum pada baris (&amp;lt;code&amp;gt;div.row&amp;lt;/code&amp;gt;). Lalu, mengapa Firefox menolak untuk menengahkan kolom?&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Penyebab mendalam :&amp;lt;/strong&amp;gt;Ini adalah contoh kasus tentang cara mesin rendering menginterpretasikan tinggi implisit. Yang`&amp;amp;lt;section&amp;amp;gt;`memiliki sebuah`min-height`, tapi tidak ada`height`eksplisit. Untuk menerapkan`align-items-center`di dalam`div.row`, Firefox perlu mengetahui tinggi referensi dari kontainer ini. Seperti orang tuanya (&amp;lt;code&amp;gt;div.container&amp;lt;/code&amp;gt; et la `&amp;amp;lt;section&amp;amp;gt;`dia sendiri) tidak memiliki tinggi&amp;lt;strong&amp;gt;ketat&amp;lt;/strong&amp;gt;, Firefox tidak tahu lagi. Mesin Blink yang lebih permissive, mampu menebak niat dan memusatkan elemen. Gecko, lebih setia terhadap spesifikasi, tidak melakukannya.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Solusi :&amp;lt;/strong&amp;gt;Buat tinggi eksplisit. Kami telah memaksakan`.container` et le `.row`mengisi 100% tinggi dari induk masing-masing dengan menggunakan kelas utilitas`h-100`dari Bootstrap.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-html hljs&amp;quot; data-lang=&amp;quot;html&amp;quot;&amp;gt;&amp;amp;lt;section id=&amp;quot;home&amp;quot; class=&amp;quot;hero-section&amp;quot;&amp;amp;gt;
    &amp;amp;lt;div class=&amp;quot;container h-100&amp;quot;&amp;amp;gt;
        &amp;amp;lt;div class=&amp;quot;row align-items-center min-vh-100 h-100&amp;quot;&amp;amp;gt;
            &amp;amp;lt;!-- ... --&amp;amp;gt;
        &amp;amp;lt;/div&amp;amp;gt;
    &amp;amp;lt;/div&amp;amp;gt;
&amp;amp;lt;/section&amp;amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;admonitionblock tip&amp;quot;&amp;gt;
&amp;lt;table&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;icon&amp;quot;&amp;gt;
&amp;lt;i class=&amp;quot;fa icon-tip&amp;quot; title=&amp;quot;Tip&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Saat seorang`align-items`Flexbox tidak berfungsi seperti yang diharapkan pada sumbu vertikal, pastikan selalu bahwa kontainer memiliki tinggi (&amp;lt;code&amp;gt;height&amp;lt;/code&amp;gt; ou &amp;lt;code&amp;gt;min-height&amp;lt;/code&amp;gt;) ditentukan dan, jika masalah berlanjut (terutama di Firefox), pastikan bahwa kontainer induk menengah mempropagasi ketinggian ini dengan baik.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_masalah_2_bar_navigasi_kannibal&amp;quot;&amp;gt;Masalah 2: Bar navigasi kannibal&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;Setelah bagian Hero diselaraskan, masalah lain muncul: bilah navigasi, dengan kelasnya`fixed-top`, menumpuk di atas judul. Un`padding-top: 80px`Ia telah diterapkan pada bagian &amp;#39;Hero&amp;#39; sebagai solusi.&amp;lt;/div&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Gejala:&amp;lt;/strong&amp;gt; Le `padding`was cukup di Chrome, tapi tidak di Firefox, di mana judulnya selalu sebagian disembunyikan.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Sebab mendalam :&amp;lt;/strong&amp;gt;Penggunaan sebuah &amp;quot;angka ajaib&amp;quot; (&amp;quot;)&amp;lt;code&amp;gt;80px&amp;lt;/code&amp;gt;) adalah praktik yang sangat buruk. Tinggi elemen seperti navigasi dapat bervariasi beberapa piksel dari satu browser ke browser lain karena perbedaan halus dalam rendering font, spasi, atau bahkan pilihan pengguna. Mengandalkan nilai tetap adalah resep untuk desain yang rapuh.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Solusi :&amp;lt;/strong&amp;gt;Solusi dinamis dan andal dalam JavaScript. Kami telah menulis skrip kecil untuk :&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Ukuran tinggi &amp;lt;strong&amp;gt;sebenarnya&amp;lt;/strong&amp;gt; dari bilah navigasi setelah halaman dirender.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Terapkan tinggi yang diukur ini seperti`padding-top`ke bagian &amp;quot;Hero&amp;quot;.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Jalankan kembali fungsi ini setiap kali jendela diubah ukurannya untuk menyesuaikan perubahan (seperti beralih ke menu hamburger).&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-javascript hljs&amp;quot; data-lang=&amp;quot;javascript&amp;quot;&amp;gt;document.addEventListener(&amp;#39;DOMContentLoaded&amp;#39;, function() {
    function adjustHeroPadding() {
        const navbar = document.querySelector(&amp;#39;.navbar.fixed-top&amp;#39;);
        const heroSection = document.querySelector(&amp;#39;.hero-section&amp;#39;);

        if (navbar &amp;amp;amp;&amp;amp;amp; heroSection) {
            const navbarHeight = navbar.offsetHeight;
            heroSection.style.paddingTop = navbarHeight + &amp;#39;px&amp;#39;;
        }
    }

    // Ajuster au chargement et au redimensionnement
    adjustHeroPadding();
    window.addEventListener(&amp;#39;resize&amp;#39;, adjustHeroPadding);
});&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;Diagram Urutan : Penyesuaian dinamis padding&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
title Solusi untuk masalah navigasi kannibal
actor Utilisateur
participant &amp;quot;Browser&amp;quot; as Browser
participant &amp;quot;script.js&amp;quot; as JS
participant &amp;quot;DOM&amp;quot; as DOM

Utilisateur -&amp;amp;gt; Browser: Charge la page
activate Browser

Browser -&amp;amp;gt; JS: Déclenche l&amp;#39;événement &amp;#39;DOMContentLoaded&amp;#39;
activate JS

JS -&amp;amp;gt; DOM: querySelector(&amp;#39;.navbar.fixed-top&amp;#39;)
activate DOM
DOM --&amp;amp;gt; JS: Retourne l&amp;#39;élément Navbar
deactivate DOM

JS -&amp;amp;gt; DOM: querySelector(&amp;#39;.hero-section&amp;#39;)
activate DOM
DOM --&amp;amp;gt; JS: Retourne l&amp;#39;élément Hero
deactivate DOM

JS -&amp;amp;gt; DOM: navbar.offsetHeight
activate DOM
DOM --&amp;amp;gt; JS: Retourne la hauteur calculée (ex: 83px)
deactivate DOM

JS -&amp;amp;gt; DOM: heroSection.style.paddingTop = &amp;quot;83px&amp;quot;
activate DOM
deactivate DOM

deactivate JS
deactivate Browser

... Plus tard ...

Utilisateur -&amp;amp;gt; Browser: Redimensionne la fenêtre
activate Browser
Browser -&amp;amp;gt; JS: Déclenche l&amp;#39;événement &amp;#39;resize&amp;#39;
activate JS
JS -&amp;amp;gt; JS: Exécute à nouveau adjustHeroPadding()
note right of JS: Le processus de mesure et\nd&amp;#39;application du padding se répète.
deactivate JS
deactivate Browser

@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Secara paralel, kita tentu saja telah menghapus aturan`padding-top: 80px;`dari file kami`styles.css`.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_masalah_3_tipografi_anarkis&amp;quot;&amp;gt;Masalah 3 : Tipografi anarkis&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Masalah berikutnya adalah yang paling merusak pada citra merek: ukuran judul utama.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Gejala:&amp;lt;/strong&amp;gt;Di Firefox, font judul &amp;quot;Développeur Formateur&amp;amp;#8230;&amp;amp;#8203;&amp;quot; sangat besar, hampir mengejutkan, sedangkan di browser lain ia harmonis.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Penyebab mendalam :&amp;lt;/strong&amp;gt;Judul menggunakan kelas`display-4`dari Bootstrap. Kelas-kelas ini menggunakan ukuran font responsif, sering kali berdasarkan satuan`rem`dan media queries. Sekali lagi, algoritma rendering Firefox, dikombinasikan dengan font &amp;#39;Inter&amp;#39;, menghasilkan perhitungan ukuran yang jauh lebih besar pada resolusi 1920x1080 kami.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Solusi :&amp;lt;/strong&amp;gt;Mengambil kembali kontrol dengan fungsi CSS`clamp()&amp;lt;code&amp;gt;. Fungsi ini merupakan revolusi untuk tipografi fluida. Dia memungkinkan untuk mendefinisikan ukuran font dengan tiga nilai: ukuran minimum, ukuran &amp;#39;yang diinginkan&amp;#39; (yang menyesuaikan dengan lebar tampilan,`vw&amp;lt;/code&amp;gt;), dan ukuran maksimal&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-css hljs&amp;quot; data-lang=&amp;quot;css&amp;quot;&amp;gt;.hero-content h1 {
    color: var(--text-primary);
    line-height: 1.2;
    /*
       Min: 2rem
       Préférée: s&amp;#39;adapte à la vue
       Max: 2.5rem (40px)
    */
    font-size: clamp(2rem, 1.2rem + 1.5vw, 2.5rem);
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;dengan`clamp()&amp;lt;code&amp;gt;, kami mampu berkata kepada peramban : \&amp;quot;Buat font tumbuh bersama layar, tetapi jangan melebihi*&amp;lt;strong&amp;gt;tidak pernah&amp;lt;/strong&amp;gt;*batas dari`2.5rem&amp;lt;/code&amp;gt;. Ini segera menyamakan tampilan di semua browser, memberi kita kendali penuh atas estetika akhir.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_masalah_4_bar_navigasi_yang_meluap&amp;quot;&amp;gt;Masalah 4 : Bar navigasi yang meluap&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;Ini adalah masalah yang telah memberi kita banyak kesulitan dan akhirnya mengungkapkan sifat mendalam dari perbedaan render mikro.&amp;lt;/div&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Gejala :&amp;lt;/strong&amp;gt;Di layar 1920x1080, tautan terakhir pada menu (&amp;quot;Blog&amp;quot;, &amp;quot;Contact&amp;quot;) tergeser keluar dari layar ke kanan, tetapi hanya pada Firefox.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Penyebab yang mendalam:&amp;lt;/strong&amp;gt;Setelah beberapa percobaan yang tidak berhasil (mengubah titik putus Bootstrap de`lg` à &amp;lt;code&amp;gt;xl`lalu`xxl&amp;lt;/code&amp;gt;), Kami telah memahami. Penyebabnya bukan logika Bootstrap, melainkan perhitungan lebar sederhana. Di Firefox, jumlah lebar semua tautan, termasuk mereka`padding` et `margin`hingga sub-pixel, adalah &amp;lt;strong&amp;gt;sedikit&amp;lt;/strong&amp;gt; lebih besar dari 1920 piksel. Di Chrome, jumlah ini juga &amp;lt;strong&amp;gt;sedikit&amp;lt;/strong&amp;gt; kurang. Kita kurang beberapa piksel.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Solusi:&amp;lt;/strong&amp;gt;Pengurangan bedah spasi. Karena tidak bisa menargetkan Firefox secara spesifik dengan &amp;#39;hacks&amp;#39; CSS usang, satu-satunya solusi yang bersih adalah menemukan gaya umum yang bekerja di mana-mana. Kami kemudian sedikit mengurangi margin dan padding horizontal setiap tautan navigasi.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-css hljs&amp;quot; data-lang=&amp;quot;css&amp;quot;&amp;gt;/* La version finale, légèrement plus compacte */
.navbar-nav .nav-link {
    font-size: 0.9rem; /* 90% de la taille de base */
    color: var(--text-primary) !important;
    font-weight: 500;
    margin: 0 0.2rem;
    padding: 0.5rem 0.5rem !important;
    border-radius: 0.375rem;
    transition: all 0.3s ease;
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Pengurangan ini, hampir tidak terlihat oleh mata telanjang pada satu elemen, telah memberi kita secara kolektif ruang yang diperlukan agar semua tautan muat di dalam kerangka pada Firefox, sekaligus mempertahankan tampilan hampir identik pada peramban lain.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_masalah_5_menu_hamburger_tidak_terlihat&amp;quot;&amp;gt;Masalah 5: Menu hamburger tidak terlihat&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;Masalah terakhir, dan bukan yang terkecil, tentang aksesibilitas seluler.&amp;lt;/div&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Gejala :&amp;lt;/strong&amp;gt;Dalam mode responsif (menu &amp;quot;hamburger&amp;quot;), ikon menu tidak terlihat pada tema &amp;quot;dark&amp;quot; dan &amp;quot;high-contrast&amp;quot;.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Penyebab mendalam:&amp;lt;/strong&amp;gt;Ikon menu Bootstrap 5 adalah`background-image`(a URL-encoded SVG). By default, the stroke color is dark, optimized for a light background. It does not automatically adapt to theme changes.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Solusi :&amp;lt;/strong&amp;gt;Menggunakan variabel CSS Bootstrap untuk menimpa ikon. Kami telah menentukan ikon SVG baru, tetapi kali ini dengan garis putih (&amp;lt;code&amp;gt;#ffffff&amp;lt;/code&amp;gt;), dan kami menerapkannya secara khusus ketika temanya`dark` ou `high-contrast`mereka aktif.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-css hljs&amp;quot; data-lang=&amp;quot;css&amp;quot;&amp;gt;/* ======================
   Fix pour l&amp;#39;icône du menu Burger (Blanc)
   ====================== */
[data-bs-theme=&amp;quot;dark&amp;quot;] .navbar-toggler-icon,
[data-bs-theme=&amp;quot;high-contrast&amp;quot;] .navbar-toggler-icon {
    --bs-navbar-toggler-icon-bg: url(&amp;quot;data:image/svg+xml,%3csvg xmlns=&amp;#39;http://www.w3.org/2000/svg&amp;#39; viewBox=&amp;#39;0 0 30 30&amp;#39;%3e%3cpath stroke=&amp;#39;%23ffffff&amp;#39; stroke-linecap=&amp;#39;round&amp;#39; stroke-miterlimit=&amp;#39;10&amp;#39; stroke-width=&amp;#39;2&amp;#39; d=&amp;#39;M4 7h22M4 15h22M4 23h22&amp;#39;/%3e%3c/svg%3e&amp;quot;);
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;admonitionblock important&amp;quot;&amp;gt;
&amp;lt;table&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;icon&amp;quot;&amp;gt;
&amp;lt;i class=&amp;quot;fa icon-important&amp;quot; title=&amp;quot;Important&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Penyesuaian komponen Bootstrap seperti ini semakin dilakukan melalui penggantian variabel CSS (&amp;lt;code&amp;gt;--bs-component-property&amp;lt;/code&amp;gt;), yang merupakan metode paling bersih dan tahan lama.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_kesimpulan_pelajaran_dari_debugging_yang_giat&amp;quot;&amp;gt;Kesimpulan: pelajaran dari debugging yang giat&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Perjalanan ini, meski kadang frustrante, kaya akan pelajaran. Setiap masalah yang diselesaikan memperkuat satu kebenaran fundamental dari pengembangan web :&amp;lt;strong&amp;gt;Tidak ada yang menggantikan pengujian lintas-browser yang ketat.&amp;lt;/strong&amp;gt;.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;Mindmap : Ringkasan debugging dan pelajaran yang telah diperoleh&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startmindmap
+ Débogage Cross-Browser : Bootstrap 5
++ **Conflit :** Chrome (Blink) vs Firefox (Gecko)
++ **Problème 1 : Alignement Hero**
+++ Cause : Hauteur implicite
+++ Solution : Classe `.h-100`
++ **Problème 2 : Navbar Cannibale**
+++ Cause : &amp;quot;Nomor Ajaib&amp;quot; (padding fixe)
+++ Solution : JS `offsetHeight`
++ **Problème 3 : Typographie Géante**
+++ Cause : Rendu des `rem`
+++ Solution : CSS `clamp()`
++ **Problème 4 : Menu qui déborde**
+++ Cause : Calcul de largeur sub-pixel
+++ Solution : Réduction des marges/paddings
++ **Problème 5 : Icône Invisible**
+++ Cause : SVG non adaptable au thème
+++ Solution : Surcharge de variable CSS
++ **Leçons Clés**
+++ Firefox force un code robuste
+++ Fuir les &amp;quot;angka ajaib&amp;quot;
+++ Maîtriser la typographie fluide
+++ Personnaliser via les variables CSS
@endmindmap&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;olist arabic&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;Berikut pelajaran yang kita pelajari :&amp;lt;/div&amp;gt;
&amp;lt;ol class=&amp;quot;arabic&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Firefox adalah sekutu :&amp;lt;/strong&amp;gt;Ketaatan lebih tinggi terhadap standar CSS memaksa kita menulis kode yang lebih kuat dan kurang ambigu.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Hindari &amp;quot;angka ajaib&amp;quot; :&amp;lt;/strong&amp;gt;Nilai tetap (seperti`padding-top: 80px`) adalah bom waktu. Selalu pilih solusi dinamis (JavaScript) atau relatif (Flexbox, Grid).&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Kuasai tipografi Anda :&amp;lt;/strong&amp;gt;Gunakan`clamp()`untuk kontrol penuh atas ukuran font responsif.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Berpikir dalam « komponen » :&amp;lt;/strong&amp;gt;Untuk menyesuaikan Bootstrap, menimpa variabel CSS-nya alih-alih membanyakkan aturan yang menimpa framework.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Jangan menargetkan satu peramban :&amp;lt;/strong&amp;gt;Solusinya hampir tidak pernah adalah membuat &amp;#39;hack&amp;#39; untuk browser tertentu, tetapi menemukan gaya umum yang berfungsi di mana-mana.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Pada akhirnya, desain prototipe sekarang kuat, dapat diprediksi, dan siap untuk diintegrasikan ke dalam sistem templating. Setiap bug yang diperbaiki bukanlah kegagalan, tetapi sebuah langkah menuju kode yang lebih baik.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>Menyelesaikan Tantangan Tes Unit Gradle dengan `gradle.properties</title>
            <link >https://cheroliv.com/blog/2025/0081_TDD_Gradle_Properties_Fix_post.html</link>
            <pubDate>Sun, 13 Jul 2025 00:00:00 +0000</pubDate>
            <guid isPermaLink="false">blog/2025/0081_TDD_Gradle_Properties_Fix_post.html</guid>
            <description>&amp;lt;div id=&amp;quot;toc&amp;quot; class=&amp;quot;toc&amp;quot;&amp;gt;
&amp;lt;div id=&amp;quot;toctitle&amp;quot;&amp;gt;Sommaire&amp;lt;/div&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel1&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_masalah_mengisolasi_uji_unit_dari_plugin_gradle&amp;quot;&amp;gt;Masalah: Mengisolasi Uji Unit dari Plugin Gradle&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_arsitektur_masalah&amp;quot;&amp;gt;Arsitektur Masalah&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_godaan_dari_gagasan_baik_yang_salah&amp;quot;&amp;gt;Godaan dari Gagasan Baik yang Salah&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_solusi_mensimulasikan_properti_dengan_extrapropertiesextension&amp;quot;&amp;gt;Solusi: Mensimulasikan properti dengan `ExtraPropertiesExtension&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_langkah_1_memahami_extrapropertiesextension&amp;quot;&amp;gt;Langkah 1 : Memahami ExtraPropertiesExtension&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_langkah_2_penyiapan_tes_persiapan&amp;quot;&amp;gt;Langkah 2: Penyiapan Tes - Persiapan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_langkah_3_injeksi_properti&amp;quot;&amp;gt;Langkah 3: Injeksi Properti&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_langkah_4_diagram_alur_solusi&amp;quot;&amp;gt;Langkah 4 : Diagram Alur Solusi&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_langkah_5_perbandingan_sebelumsesudah&amp;quot;&amp;gt;Langkah 5 : Perbandingan Sebelum/Sesudah&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_langkah_6_pengelolaan_kasus_pengujian_ganda&amp;quot;&amp;gt;Langkah 6 : Pengelolaan Kasus Pengujian Ganda&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_arsitektur_akhir_solusi&amp;quot;&amp;gt;Arsitektur Akhir Solusi&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_manfaat_pendekatan_ini&amp;quot;&amp;gt;Manfaat Pendekatan Ini&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_praktik_terbaik_dan_saran&amp;quot;&amp;gt;Praktik terbaik dan saran&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_buat_metode_utilitas&amp;quot;&amp;gt;Buat Metode Utilitas&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_validasi_properti&amp;quot;&amp;gt;Validasi properti&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_kesimpulan&amp;quot;&amp;gt;Kesimpulan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_masalah_mengisolasi_uji_unit_dari_plugin_gradle&amp;quot;&amp;gt;Masalah: Mengisolasi Uji Unit dari Plugin Gradle&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Saat menulis tes unit untuk plugin Gradle, tantangan umum adalah mengelola dependensi terhadap konfigurasi proyek, seperti properti yang ditentukan dalam file`gradle.properties`. Dalam kasus kita, plugin`jbake.ghpages`harus membaca sebuah properti`site_config_path`untuk berfungsi dengan baik. Uji unit untuk tugas`initialize`harus memeriksa perilaku plugin saat properti ini ada.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Masalah fundamentalnya adalah bahwa tes unit, menurut desainnya, harus &amp;lt;strong&amp;gt;terisolasi&amp;lt;/strong&amp;gt;. Penggunaan`ProjectBuilder`Gradle membuat sebuah instance dari`Project`dalam memori, sepenuhnya terputus dari proyek sebenarnya pada sistem file. Oleh karena itu, instance tes ini tidak membaca file secara otomatis.&amp;lt;code&amp;gt;gradle.properties`dan karena itu tidak mengetahui properti`site_config_path&amp;lt;/code&amp;gt;.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_arsitektur_masalah&amp;quot;&amp;gt;Arsitektur Masalah&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Diagram berikut menggambarkan pemisahan antara lingkungan uji dan sistem file:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
!define RECTANGLE class

package &amp;quot;Lingkungan Produksi&amp;quot; {
  RECTANGLE ProjectReal {
    + build.gradle.kts
    + gradle.properties
    + src/
  }

  RECTANGLE GradleProperties {
    site_config_path=src/jbake/settings/site.yml
  }

  RECTANGLE JbakePlugin {
    - readProperty(name: String)
    + initialize()
  }

  ProjectReal --&amp;amp;gt; GradleProperties : lit au démarrage
  JbakePlugin --&amp;amp;gt; GradleProperties : accède via project.properties
}

package &amp;quot;Lingkungan Test&amp;quot; {
  RECTANGLE ProjectTest {
    + Créé par ProjectBuilder
    + En mémoire uniquement
    + Pas de gradle.properties
  }

  RECTANGLE JbakePluginTest {
    + testInitialize()
  }

  ProjectTest -[#red]x GradleProperties : ❌ Pas d&amp;#39;accès
  JbakePluginTest --&amp;amp;gt; ProjectTest : utilise
}

note right of ProjectTest : Isolation = Pas d&amp;#39;accès\nau système de fichiers
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_godaan_dari_gagasan_baik_yang_salah&amp;quot;&amp;gt;Godaan dari Gagasan Baik yang Salah&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Salah satu pendekatan pertama mungkin adalah membuat file`gradle.properties`palsu dalam sumber daya ujian.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-text hljs&amp;quot; data-lang=&amp;quot;text&amp;quot;&amp;gt;// src/test/resources/gradle.properties
site_config_path=src/jbake/settings/site.yml&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Namun, metode ini ditakdirkan untuk gagal karena`ProjectBuilder`tidak dirancang untuk memindai sistem file dalam mencari file konfigurasi. Ujian akan tetap terisolasi dan mengabaikan file ini.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_solusi_mensimulasikan_properti_dengan_extrapropertiesextension&amp;quot;&amp;gt;Solusi: Mensimulasikan properti dengan `ExtraPropertiesExtension&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Solusi yang anggun untuk masalah ini bukanlah &amp;lt;strong&amp;gt;membaca&amp;lt;/strong&amp;gt; file, tetapi &amp;lt;strong&amp;gt;mensimulasikan&amp;lt;/strong&amp;gt; kehadiran properti langsung dalam objek`Project`uji. Gradle menyediakan mekanisme yang kuat untuk ini: &amp;lt;strong&amp;gt;properti tambahan&amp;lt;/strong&amp;gt; (Extra Properties).&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_langkah_1_memahami_extrapropertiesextension&amp;quot;&amp;gt;Langkah 1 : Memahami ExtraPropertiesExtension&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;`ExtraPropertiesExtension`adalah sebuah kontainer kunci-nilai yang terlampir pada setiap objek model Gradle. Ia memungkinkan penambahan properti secara dinamis ke sebuah proyek, sebuah tugas, atau ekstensi Gradle lain.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
!define RECTANGLE class

RECTANGLE Project {
  + name: String
  + version: String
  + extensions: ExtensionContainer
  + properties: Map&amp;amp;lt;String, ?&amp;amp;gt;
}

RECTANGLE ExtensionContainer {
  + getByType(Class&amp;amp;lt;T&amp;amp;gt;): T
  + add(String, Object): void
}

RECTANGLE ExtraPropertiesExtension {
  + set(String, Object): void
  + get(String): Object
  + has(String): boolean
  + getProperties(): Map&amp;amp;lt;String, Object&amp;amp;gt;
}

Project --&amp;amp;gt; ExtensionContainer : contient
ExtensionContainer --&amp;amp;gt; ExtraPropertiesExtension : gère
ExtraPropertiesExtension --&amp;amp;gt; Project : synchronise avec\nproject.properties

note right of ExtraPropertiesExtension : Point d&amp;#39;entrée pour\ninjecter des propriétés
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_langkah_2_penyiapan_tes_persiapan&amp;quot;&amp;gt;Langkah 2: Penyiapan Tes - Persiapan&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Mari kita mulai dengan membuat struktur dasar dari tes unit kita :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;JbakeGhPagesPluginTest.kt - Structure de base&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;class JbakeGhPagesPluginTest {

    @TempDir
    lateinit var testProjectDir: File

    @Test
    fun `check initialize and config yaml file if not existing`() {
        // Étape 1 : Créer un projet de test isolé
        val project = ProjectBuilder.builder()
            .withProjectDir(testProjectDir)
            .build()

        // Suite des étapes...
    }
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_langkah_3_injeksi_properti&amp;quot;&amp;gt;Langkah 3: Injeksi Properti&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Berikut proses detail untuk menyuntikkan properti ke dalam proyek uji :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;JbakeGhPagesPluginTest.kt - Injection complète&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;@Test
fun `check initialize and config yaml file if not existing`() {
    // Étape 1 : Créer un projet de test isolé en mémoire
    val project = ProjectBuilder.builder()
        .withProjectDir(testProjectDir)
        .build()

    // Étape 2 : Récupérer le gestionnaire de propriétés supplémentaires
    val extra = project.extensions.getByType(ExtraPropertiesExtension::class.java)

    // Étape 3 : Définir la propriété requise pour ce test
    extra.set(&amp;quot;site_config_path&amp;quot;, &amp;quot;src/jbake/settings/site.yml&amp;quot;)

    // Étape 4 : Vérifier que la propriété est bien injectée
    assertTrue(project.hasProperty(&amp;quot;site_config_path&amp;quot;))

    // Étape 5 : Appliquer le plugin qui pourra maintenant accéder à la propriété
    project.plugins.apply(&amp;quot;jbake.ghpages&amp;quot;)

    // Étape 6 : Exécuter la tâche et vérifier son comportement
    val task: Task = project.tasks.findByName(&amp;quot;initialize&amp;quot;)
        .apply(::assertNotNull)!!

    // Étape 7 : Exécuter les actions de la tâche
    task.actions.forEach { it.execute(task) }

    // Étape 8 : Assertions finales
    assertEquals(
        &amp;quot;src/jbake/settings/site.yml&amp;quot;,
        project.properties[&amp;quot;site_config_path&amp;quot;],
        &amp;quot;La propriété devrait être accessible dans le projet&amp;quot;
    )
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_langkah_4_diagram_alur_solusi&amp;quot;&amp;gt;Langkah 4 : Diagram Alur Solusi&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
start

:Créer ProjectBuilder;
:Obtenir ExtraPropertiesExtension;
:Injecter la propriété\nvia extra.set();

partition &amp;quot;verifikasi&amp;quot; {
  :Vérifier project.hasProperty();
  :Appliquer le plugin;
  :Récupérer la tâche;
}

partition &amp;quot;pelaksanaan&amp;quot; {
  :Exécuter les actions\nde la tâche;
  :Le plugin lit\nproject.properties;
}

partition &amp;quot;Pernyataan&amp;quot; {
  :Vérifier la propriété\nest accessible;
  :Valider le comportement\nattendues;
}

stop
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_langkah_5_perbandingan_sebelumsesudah&amp;quot;&amp;gt;Langkah 5 : Perbandingan Sebelum/Sesudah&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
!define RECTANGLE class

package &amp;quot;SEBELUM - Masalah&amp;quot; {
  RECTANGLE TestProject1 {
    + ProjectBuilder.build()
    + Pas de propriétés
  }

  RECTANGLE Plugin1 {
    + initialize()
    + ❌ project.properties[&amp;quot;site_config_path&amp;quot;] = null
  }

  TestProject1 --&amp;amp;gt; Plugin1 : échec

  note right of Plugin1 : Test échoue car\nla propriété n&amp;#39;existe pas
}

package &amp;quot;Setelah - Solusi&amp;quot; {
  RECTANGLE TestProject2 {
    + ProjectBuilder.build()
    + ExtraPropertiesExtension
  }

  RECTANGLE ExtraProps {
    + set(&amp;quot;site_config_path&amp;quot;, &amp;quot;...&amp;quot;)
  }

  RECTANGLE Plugin2 {
    + initialize()
    + ✅ project.properties[&amp;quot;site_config_path&amp;quot;] = &amp;quot;src/jbake/settings/site.yml&amp;quot;
  }

  TestProject2 --&amp;amp;gt; ExtraProps : configure
  ExtraProps --&amp;amp;gt; Plugin2 : propriété disponible

  note right of Plugin2 : Test réussit car\nla propriété est simulée
}
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_langkah_6_pengelolaan_kasus_pengujian_ganda&amp;quot;&amp;gt;Langkah 6 : Pengelolaan Kasus Pengujian Ganda&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Untuk menguji berbagai skenario, mari buat beberapa tes dengan konfigurasi yang beragam :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;Tests multiples avec configurations différentes&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;class JbakeGhPagesPluginTest {

    @TempDir
    lateinit var testProjectDir: File

    private fun createProjectWithProperty(propertyValue: String?): Project {
        val project = ProjectBuilder.builder()
            .withProjectDir(testProjectDir)
            .build()

        // Injection conditionnelle de la propriété
        propertyValue?.let { value -&amp;amp;gt;
            val extra = project.extensions.getByType(ExtraPropertiesExtension::class.java)
            extra.set(&amp;quot;site_config_path&amp;quot;, value)
        }

        return project
    }

    @Test
    fun `should work with valid property`() {
        val project = createProjectWithProperty(&amp;quot;src/jbake/settings/site.yml&amp;quot;)
        project.plugins.apply(&amp;quot;jbake.ghpages&amp;quot;)

        val task = project.tasks.findByName(&amp;quot;initialize&amp;quot;)!!
        task.actions.forEach { it.execute(task) }

        // Assertions pour le cas normal
        assertEquals(&amp;quot;src/jbake/settings/site.yml&amp;quot;, project.properties[&amp;quot;site_config_path&amp;quot;])
    }

    @Test
    fun `should handle missing property gracefully`() {
        val project = createProjectWithProperty(null) // Pas de propriété
        project.plugins.apply(&amp;quot;jbake.ghpages&amp;quot;)

        val task = project.tasks.findByName(&amp;quot;initialize&amp;quot;)!!

        // Le plugin devrait gérer l&amp;#39;absence de propriété
        assertDoesNotThrow {
            task.actions.forEach { it.execute(task) }
        }
    }

    @Test
    fun `should handle invalid property path`() {
        val project = createProjectWithProperty(&amp;quot;invalid/path/to/config.yml&amp;quot;)
        project.plugins.apply(&amp;quot;jbake.ghpages&amp;quot;)

        val task = project.tasks.findByName(&amp;quot;initialize&amp;quot;)!!

        // Test du comportement avec un chemin invalide
        assertThrows&amp;amp;lt;FileNotFoundException&amp;amp;gt; {
            task.actions.forEach { it.execute(task) }
        }
    }
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_arsitektur_akhir_solusi&amp;quot;&amp;gt;Arsitektur Akhir Solusi&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
!define RECTANGLE class

package &amp;quot;lingkungan uji&amp;quot; {
  RECTANGLE TestClass {
    + createProjectWithProperty()
    + testValidProperty()
    + testMissingProperty()
    + testInvalidProperty()
  }

  RECTANGLE ProjectBuilder {
    + builder()
    + withProjectDir()
    + build()
  }

  RECTANGLE TestProject {
    + extensions
    + properties
    + plugins
    + tasks
  }

  RECTANGLE ExtraPropertiesExtension {
    + set(key, value)
    + get(key)
    + has(key)
  }

  RECTANGLE JbakePlugin {
    + apply(project)
    + createInitializeTask()
    + readSiteConfigPath()
  }
}

TestClass --&amp;amp;gt; ProjectBuilder : utilise
ProjectBuilder --&amp;amp;gt; TestProject : crée
TestProject --&amp;amp;gt; ExtraPropertiesExtension : contient
TestClass --&amp;amp;gt; ExtraPropertiesExtension : configure
ExtraPropertiesExtension --&amp;amp;gt; TestProject : synchronise properties
TestProject --&amp;amp;gt; JbakePlugin : applique
JbakePlugin --&amp;amp;gt; TestProject : lit properties

note right of ExtraPropertiesExtension : Point de contrôle\npour l&amp;#39;injection
note bottom of TestProject : Environnement\ncontrôlé et isolé
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_manfaat_pendekatan_ini&amp;quot;&amp;gt;Manfaat Pendekatan Ini&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Isolasi Lengkap&amp;lt;/strong&amp;gt;Uji tidak bergantung pada file eksternal apa pun. Ia mandiri dan dapat dieksekusi dengan andal di mana-mana lingkungan (lokal, CI/CD, dst.).&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Kejelasan dan Niat&amp;lt;/strong&amp;gt;: Test secara eksplisit menyatakan prasyarat untuk eksekusinya. Siapa pun yang membaca tes akan langsung melihat bahwa plugin membutuhkan properti`site_config_path`untuk berfungsi.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;kemudahan pemeliharaan&amp;lt;/strong&amp;gt;: Jika nama properti berubah, cukup memperbaruinya di satu tempat dalam tes, tanpa perlu memanipulasi file konfigurasi tes.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;fleksibilitas&amp;lt;/strong&amp;gt;: Menjadi trivial untuk menguji berbagai skenario hanya dengan mengubah nilai properti yang di-injeksi pada setiap test (nilai valid, tidak valid, tidak ada, dsb.).&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Kinerja&amp;lt;/strong&amp;gt;Tidak ada pembacaan file, semuanya berlangsung di memori, yang membuat tes lebih cepat.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;kecocokan&amp;lt;/strong&amp;gt;Pengujian bersifat deterministik karena tidak tergantung pada keadaan sistem berkas.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_praktik_terbaik_dan_saran&amp;quot;&amp;gt;Praktik terbaik dan saran&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_buat_metode_utilitas&amp;quot;&amp;gt;Buat Metode Utilitas&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;Méthode utilitaire pour la réutilisation&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;class GradleTestUtils {
    companion object {
        fun createProjectWithProperties(
            projectDir: File,
            properties: Map&amp;amp;lt;String, String&amp;amp;gt;
        ): Project {
            val project = ProjectBuilder.builder()
                .withProjectDir(projectDir)
                .build()

            val extra = project.extensions.getByType(ExtraPropertiesExtension::class.java)
            properties.forEach { (key, value) -&amp;amp;gt;
                extra.set(key, value)
            }

            return project
        }
    }
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_validasi_properti&amp;quot;&amp;gt;Validasi properti&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;Validation robuste&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;@Test
fun `should validate property injection`() {
    val project = createProjectWithProperty(&amp;quot;test-value&amp;quot;)

    // Vérifications multiples
    assertTrue(project.hasProperty(&amp;quot;site_config_path&amp;quot;))
    assertEquals(&amp;quot;test-value&amp;quot;, project.property(&amp;quot;site_config_path&amp;quot;))
    assertNotNull(project.properties[&amp;quot;site_config_path&amp;quot;])

    // Vérification que la propriété est accessible par le plugin
    project.plugins.apply(&amp;quot;jbake.ghpages&amp;quot;)
    // ... reste du test
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_kesimpulan&amp;quot;&amp;gt;Kesimpulan&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Alih-alih berjuang agar lingkungan pengujian unit membaca file konfigurasi, praktik terbaiknya adalah mensimulasikan kondisi yang diperlukan. Penggunaan`ExtraPropertiesExtension`Untuk menentukan secara program properti Gradle adalah metode yang paling bersih dan paling kuat untuk melakukan tes unit plugin yang efektif dan andal.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Teknik ini mengubah masalah dependensi eksternal menjadi injeksi dependensi sederhana, di tengah-tengah filosofi Test-Driven Development (TDD). Ia memberikan kontrol penuh atas lingkungan ujian sekaligus menjaga isolasi yang diperlukan untuk pengujian unit berkualitas.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Diagram dan contoh yang disajikan dalam artikel ini menunjukkan bagaimana pendekatan ini dapat diimplementasikan secara progresif dan metodis, memungkinkan pembuatan rangkaian uji yang kuat dan dapat dipelihara untuk plugin Gradle Anda.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>Optimalkan pengelolaan tugas Gradle dengan Kotlin DSL dan buildSrc</title>
            <link >https://cheroliv.com/blog/2025/0079_gradle_tasks_tests_report_post.html</link>
            <pubDate>Fri, 11 Jul 2025 00:00:00 +0000</pubDate>
            <guid isPermaLink="false">blog/2025/0079_gradle_tasks_tests_report_post.html</guid>
            <description>&amp;lt;div id=&amp;quot;toc&amp;quot; class=&amp;quot;toc&amp;quot;&amp;gt;
&amp;lt;div id=&amp;quot;toctitle&amp;quot;&amp;gt;Sommaire&amp;lt;/div&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel1&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_pusatkan_konfigurasi_dengan_allprojects_dan_subprojects&amp;quot;&amp;gt;Pusatkan Konfigurasi dengan &amp;lt;code&amp;gt;allprojects&amp;lt;/code&amp;gt; dan `subprojects&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_sourceplantuml&amp;quot;&amp;gt;[source,plantuml]&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_enduml&amp;quot;&amp;gt;@enduml&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_sourcekotlin&amp;quot;&amp;gt;[source,kotlin]&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div id=&amp;quot;preamble&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Otomatisasi tugas build sangat penting untuk setiap proyek perangkat lunak, dan Gradle, dengan DSL Kotlinnya, menawarkan fleksibilitas yang luar biasa. Selama percakapan kami, kami telah menjelajahi cara memusatkan dan menggunakan kembali logika build, khususnya untuk laporan pengujian, dengan memanfaatkan&amp;lt;strong&amp;gt;allprojects&amp;lt;/strong&amp;gt;, &amp;lt;strong&amp;gt;sumber build&amp;lt;/strong&amp;gt;, dan tugas khusus dalam Kotlin.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_pusatkan_konfigurasi_dengan_allprojects_dan_subprojects&amp;quot;&amp;gt;Pusatkan Konfigurasi dengan &amp;lt;code&amp;gt;allprojects&amp;lt;/code&amp;gt; dan `subprojects&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;blok-blok`allprojects` et `subprojects`dalam Anda`build.gradle.kts`Akar merupakan dasar untuk menerapkan konfigurasi umum di seluruh proyek multi-modul Anda.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;*&amp;lt;code&amp;gt;allprojects { &amp;amp;#8230;&amp;amp;#8203; }&amp;lt;/code&amp;gt;: Terapkan konfigurasi ke&amp;lt;strong&amp;gt;proyek induk dan semua sub-proyeknya&amp;lt;/strong&amp;gt;. Ideal untuk mendefinisikan`group`, sebuah`version`, atau beberapa`repositories`umum. *&amp;lt;code&amp;gt;subprojects { &amp;amp;#8230;&amp;amp;#8203; }`Terapkan konfigurasi&amp;lt;strong&amp;gt;hanya ke sub-proyek&amp;lt;/strong&amp;gt;, mengkecualikan proyek akar. Cocok untuk menerapkan plugin khusus modul (seperti`java&amp;lt;/code&amp;gt; ou &amp;lt;code&amp;gt;kotlin-jvm&amp;lt;/code&amp;gt;) atau dependensi umum untuk perpustakaan Anda.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Berikut contoh ilustratif :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;// build.gradle.kts (projet racine)
plugins {
    base // Appliqué au projet racine
}

allprojects {
    group = &amp;quot;com.example&amp;quot;
    version = &amp;quot;1.0.0&amp;quot;

    repositories {
        mavenCentral()
    }

    tasks.withType&amp;amp;lt;org.gradle.api.tasks.testing.Test&amp;amp;gt; {
        useJUnitPlatform() // Configuration commune des tests pour tous les projets
    }
}

subprojects {
    apply(plugin = &amp;quot;java&amp;quot;)
    apply(plugin = &amp;quot;org.jetbrains.kotlin.jvm&amp;quot;)

    dependencies {
        implementation(&amp;quot;org.jetbrains.kotlin:kotlin-stdlib-jdk8&amp;quot;)
    }

}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;\==`buildSrc`pisau Swiss logika Build&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Saat logika dari tugas-tugas Anda menjadi kompleks atau perlu digunakan kembali,&amp;lt;strong&amp;gt;buildSrc&amp;lt;/strong&amp;gt;Ini adalah solusi yang diprioritaskan. Ini adalah modul Gradle khusus yang dikompilasi sebelum skrip build utama, sehingga kelasnya tersedia di classpath seluruh build Anda.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;\=== Mengapa menggunakan`buildSrc`untuk Tugas ?&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Kemampuan untuk digunakan kembali&amp;lt;/strong&amp;gt;: Sebuah tugas yang didefinisikan di`buildSrc`bisa diterapkan pada setiap proyek build.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Organisasi&amp;lt;/strong&amp;gt;: Memusatkan kode build, membuatnya lebih bersih dan dapat dipelihara.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Keamanan Tipe dan Penyelesaian Otomatis&amp;lt;/strong&amp;gt;: Kode Kotlin di`buildSrc`dikompilas, menawarkan pemeriksaan kesalahan dan autocompletion IDE Anda, meningkatkan pengalaman pengembangan.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;\=== Diagram Alur`buildSrc`(kode PlantUML)&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Untuk menghasilkan diagram ini, salin kode di bawah ini dan tempelkan ke dalam alat yang mendukung PlantUML.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_sourceplantuml&amp;quot;&amp;gt;[source,plantuml]&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;@startuml skinparam tulis tangan benar skinparam monochrome true&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;persegi panjang &amp;quot;Gradle Build Process komponen &amp;quot;buildSrc&amp;quot; sebagai BS { file &amp;quot;MyCustomTask.kt&amp;quot; as T file &amp;quot;MyConventionPlugin.kt&amp;quot; sebagai P } komponen &amp;quot;Root Project&amp;quot; sebagai RP komponen &amp;quot;Subproject A&amp;quot; sebagai SA komponen &amp;quot;Subproject B&amp;quot; sebagai SB }&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;T --\&amp;amp;gt; P : &amp;quot;didefinisikan dalam P --\&amp;amp;gt; RP : &amp;quot;diterapkan pada P --\&amp;amp;gt; SA : &amp;quot;diterapkan untuk P --\&amp;amp;gt; SB : &amp;quot;diterapkan pada&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;RP --|\&amp;amp;gt; SA : &amp;quot;contains RP --|\&amp;amp;gt; SB : &amp;quot;mengandung&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;RP -up-\&amp;amp;gt; BS : &amp;quot;tergantung pada (untuk logika build) SA -up-\&amp;amp;gt; BS : &amp;quot;tergantung pada (untuk logika pembangunan) SB -up-\&amp;amp;gt; BS : &amp;quot;tergantung pada (untuk logika build)&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;catatan di sebelah kanan T Kelas tugas kustom (ex: OpenTestReportTask) catatan akhir&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;catatan di sebelah kanan P Plugin konvensi yang mencatat tugas catatan akhir&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_enduml&amp;quot;&amp;gt;@enduml&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;\== Pembuatan Tugas Laporan Abstrak&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Untuk mengelola laporan tes, kami telah merancang pendekatan modular dengan menggunakan kelas tugas abstrak di`buildSrc`. Sesuai kebutuhan Anda, kelas ini dapat mewarisi dari`DefaultTask` ou de &amp;lt;code&amp;gt;Exec&amp;lt;/code&amp;gt;.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;\=== Tugas Abstrak`OpenTestReportTask`(mewarisi dari`DefaultTask`)&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Pendekatan ini direkomendasikan jika Anda memerlukan logika Kotlin kustom yang berinteraksi dengan sistem file atau API Gradle lainnya, lalu menjalankan perintah eksternal.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_sourcekotlin&amp;quot;&amp;gt;[source,kotlin]&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;package com.yourpackage&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;import org.gradle.api.DefaultTask impor org.gradle.api.tasks.Input import org.gradle.api.tasks.TaskAction import java.io.File&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;kelas abstrak OpenTestReportTask : DefaultTask() {&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-none hljs&amp;quot;&amp;gt;----
----
init {
    group = &amp;quot;verification&amp;quot;
    description = &amp;quot;Opens a test report in Firefox.&amp;quot;
    dependsOn(&amp;quot;check&amp;quot;) // Assure que les rapports sont générés
}

@get:Input
abstract var reportPath: String

@TaskAction
fun openReport() {
    val separator = File.separator
    val reportFile = project.layout.projectDirectory.asFile.toPath()
        .resolve(reportPath.replace(&amp;quot;/&amp;quot;, separator))
        .toAbsolutePath()
        .toFile()

    if (!reportFile.exists()) {
        logger.warn(&amp;quot;Report file does not exist: $reportFile. Ensure &amp;#39;check&amp;#39; ran.&amp;quot;)
        return
    }

    project.exec {
        commandLine(&amp;quot;firefox&amp;quot;, &amp;quot;--new-tab&amp;quot;, reportFile.absolutePath)
    }
    logger.lifecycle(&amp;quot;Opened test report: ${reportFile.absolutePath}&amp;quot;)
}
----

## }

=== Implementasi konkret

Kelas-kelas ini mewarisi tugas abstrak dan menentukan jalur spesifik laporan.

## [source,kotlin]

paket com.paketAnda

abstract class ReportUnitTestsTask : OpenTestReportTask() { inisialisasi { description = &amp;quot;Membuka laporan tes unit di Firefox. reportPath = &amp;quot;build/reports/tests/test/index.html } }

package com.yourpackage

## kelas abstrak ReportFunctionalTestsTask : OpenTestReportTask() { init { deskripsi = &amp;quot;Membuka laporan tes fungsional di Firefox.&amp;quot; jalurLaporan = &amp;quot;build/reports/tests/functionalTest/index.html&amp;quot; } }

Pencatatan Tugas

dalam`build.gradle.kts`dari proyek akar Anda :

## [source,kotlin]

tasks.register\&amp;amp;lt;com.yourpackage.ReportUnitTestsTask\&amp;amp;gt;(&amp;quot;reportTests&amp;quot;) {} tasks.register\&amp;amp;lt;com.yourpackage.ReportFunctionalTestsTask\&amp;amp;gt;(&amp;quot;reportFunctionalTests&amp;quot;) {}

\=== Diagram UML Tugas Laporan (Kode PlantUML)

Salin kode di bawah ini dan tempelkan ke dalam alat yang mendukung PlantUML.

## [source,plantuml]

@startuml skinparam tulisan tangan true skinparam monochrome true

classe abstraite DefaultTask { }

kelas abstrak Exec { \+ commandLine(args: String...) \+ exec() }

abstract class OpenTestReportTask extends DefaultTask { \+ group: String = &amp;quot;verifikasi \+ deskripsi: String \+ dependsOn(&amp;quot;check&amp;quot;) \+ abstract reportPath: String + openReport() : void }

kelas abstrak AbstractJbakeExecTask meng-extends Exec { \+ group: String = &amp;quot;verification \+ description: String \+ dependsOn(&amp;quot;check&amp;quot;) \+ abstrak jalurLaporanRelatif: String \+ exec() : void }

class ReportUnitTestsTask extends OpenTestReportTask { \+ reportPath: String = &amp;quot;build/reports/tests/test/index.html }

class ReportFunctionalTestsTask extends OpenTestReportTask { \+ reportPath: String = &amp;quot;build/reports/tests/functionalTest/index.html }

class ReportJbakeTestsTask extends AbstractJbakeExecTask { \+ reportRelativePath: String = &amp;quot;build/reports/tests/test/index.html }

class ReportJbakeFunctionalTestsTask extends AbstractJbakeExecTask { \+ reportRelativePath: String = &amp;quot;build/reports/tests/functionalTest/index.html }

OpenTestReportTask \&amp;amp;lt;-- ReportUnitTestsTask OpenTestReportTask \&amp;amp;lt;-- ReportFunctionalTestsTask

AbstractJbakeExecTask \&amp;amp;lt;-- ReportJbakeTestsTask AbstrakJbakeTugasEksekusi \&amp;amp;lt;-- LaporanJbakeTugasUjiFungsional

DefaultTask \&amp;amp;lt;|-- OpenTestReportTask Exec \&amp;amp;lt;|-- AbstractJbakeExecTask DefaultTask \&amp;amp;lt;|-- Eksekusi

## @enduml

\== Tugas Abstrak`AbstractJbakeExecTask`(mewarisi dari`Exec`)

Jika tugas Anda terutama melibatkan eksekusi perintah eksternal dengan argumen yang berubah-ubah, wariskan secara langsung dari**Eksekusi**lebih langsung.

## [source,kotlin]

package com.yourpackage

import org.gradle.api.tasks.Exec import org.gradle.api.tasks.Input import java.io.File

kelas abstrak AbstractJbakeExecTask : Exec() {

----

init { group = &amp;quot;verification&amp;quot; description = &amp;quot;Opens a Jbake project report in Firefox.&amp;quot; dependsOn(&amp;quot;check&amp;quot;) }

@get:Input abstract var reportRelativePath: String

override fun exec() { val separator = File.separator val reportFile = project.layout.projectDirectory.asFile.toPath() .resolve(reportRelativePath.replace(&amp;quot;/&amp;quot;, separator)) .toAbsolutePath() .toFile()

if (!reportFile.exists()) { logger.warn(&amp;quot;Report file does not exist: $reportFile. Ensure &amp;#39;check&amp;#39; ran.&amp;quot;) return }

commandLine(&amp;quot;firefox&amp;quot;, &amp;quot;--new-tab&amp;quot;, reportFile.absolutePath) logger.lifecycle(&amp;quot;Attempting to open report: ${reportFile.absolutePath}&amp;quot;) super.exec() // Appelle la méthode exec() de la super-classe Exec }

## }

\=== Implementasi konkret untuk`Exec`

## [source,kotlin]

package com.yourpackage

abstract class ReportJbakeTestsTask : AbstractJbakeExecTask() { inisialisasi { Membuka laporan uji unit Jbake di Firefox. reportRelativePath = &amp;quot;build/reports/tests/test/index.html } }

package com.yourpackage

## abstract class ReportJbakeFunctionalTestsTask : AbstractJbakeExecTask() { init { description = &amp;quot;Membuka laporan tes fungsional di Firefox.&amp;quot; reportRelativePath = &amp;quot;build/reports/tests/functionalTest/index.html&amp;quot; } }

\== Visibilitas Tugas`buildSrc`

Tugas dan kelas yang Anda tetapkan dalam**buildSrc**adalah :

***Terlihat dan dapat digunakan**oleh semua proyek build utama Anda (root dan sub-proyek). Itu sebabnya Anda dapat menggunakan`com.yourpackage.ReportUnitTestsTask`dalam`allprojects { ... }`. ***Tidak dapat dieksekusi**langsung sebagai tugas buildSrc (misalnya,`gradle :buildSrc:reportTests`tidak akan berfungsi jika tugas tidak tercatat secara khusus dalam`buildSrc/build.gradle.kts`).`buildSrc`adalah modul kompilasi, bukan modul aplikasi yang dapat dieksekusi untuk tugas-tugas build utama.

\=== Menjalankan laporan untuk pengujian`buildSrc`sendiri

Si `buildSrc`Ia memiliki tesnya sendiri dan menghasilkan laporan, Anda dapat merekam tugas laporan langsung di`buildSrc/build.gradle.kts`:

## [source,kotlin]

plugins { `kotlin-jvm` }

repositori { mavenCentral() }

tasks.withType\&amp;amp;lt;Test\&amp;amp;gt; { useJUnitPlatform() reports.html.outputLocation.set(layout.buildDirectory.dir(&amp;quot;reports/tests&amp;quot;)) }

import com.yourpackage.ReportJbakeTestsTask // Impor kelas tugas Anda

## tasks.register\&amp;amp;lt;ReportJbakeTestsTask\&amp;amp;gt;(&amp;quot;reportBuildSrcTests&amp;quot;) { // Jalur sudah didefinisikan di kelas, akan menunjuk ke laporan buildSrc }

Anda kemudian dapat mengeksekusi :`./gradlew :buildSrc:test`diikuti oleh`./gradlew :buildSrc:reportBuildSrcTests`.

Dengan mengadopsi praktik ini, Anda akan membangun sistem build Gradle dengan Kotlin DSL yang tidak hanya kuat, tetapi juga sangat modular, mudah dipelihara, dan mudah dipahami.&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>TDD dari Plugin Gradle Kotlin yang dapat dikonfigurasi dengan JBake dan JGit</title>
            <link >https://cheroliv.com/blog/2025/0078_jbake_gradle_plugin_syntaxe_post.html</link>
            <pubDate>Wed, 9 Jul 2025 00:00:00 +0000</pubDate>
            <guid isPermaLink="false">blog/2025/0078_jbake_gradle_plugin_syntaxe_post.html</guid>
            <description>&amp;lt;div id=&amp;quot;toc&amp;quot; class=&amp;quot;toc&amp;quot;&amp;gt;
&amp;lt;div id=&amp;quot;toctitle&amp;quot;&amp;gt;Sommaire&amp;lt;/div&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel1&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_tujuan&amp;quot;&amp;gt;Tujuan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_konfigurasi_deklaratif&amp;quot;&amp;gt;Konfigurasi deklaratif&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_arsitektur_plugin&amp;quot;&amp;gt;Arsitektur plugin&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_definisi_ekstensi_dsl&amp;quot;&amp;gt;Definisi ekstensi DSL&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_definisi_tugas_kustom&amp;quot;&amp;gt;Definisi tugas kustom&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_antarmuka_gitadapter&amp;quot;&amp;gt;Antarmuka GitAdapter&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_implementasi_konkret_dengan_jgit&amp;quot;&amp;gt;Implementasi konkret dengan JGit&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_konfigurasi_dsl_kotlin&amp;quot;&amp;gt;Konfigurasi DSL Kotlin&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_uji_unit_dengan_mockito&amp;quot;&amp;gt;Uji unit dengan Mockito&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_ketergantungan&amp;quot;&amp;gt;Ketergantungan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_uji_perilaku_git&amp;quot;&amp;gt;Uji perilaku Git&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_kesimpulan&amp;quot;&amp;gt;Kesimpulan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_langkah_selanjutnya&amp;quot;&amp;gt;Langkah selanjutnya&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div id=&amp;quot;preamble&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Pos ini menyajikan pendekatan lengkap dalam perancangan dan pengujian (TDD) dari sebuah plugin Gradle yang ditulis dalam Kotlin DSL. Plugin ini mengotomatiskan pembuatan situs statis dengan &amp;lt;strong&amp;gt;JBake&amp;lt;/strong&amp;gt; dan &amp;lt;strong&amp;gt;penerapan Git&amp;lt;/strong&amp;gt; melalui &amp;lt;strong&amp;gt;JGit&amp;lt;/strong&amp;gt;, semuanya dari file konfigurasi YAML.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_tujuan&amp;quot;&amp;gt;Tujuan&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Membuat plugin Gradle :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;dapat dikonfigurasi dalam DSL Kotlin ;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;mampu :&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;menghasilkan situs statis dari JBake ;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;menyebarkan pada repositori Git jauh melalui JGit ;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;dapat diintegrasikan dalam alur kerja CI/CD ;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;diuji sesuai dengan pendekatan TDD.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_konfigurasi_deklaratif&amp;quot;&amp;gt;Konfigurasi deklaratif&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Ini adalah contoh konfigurasi YAML untuk dimodelkan dalam DSL kami:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-yaml hljs&amp;quot; data-lang=&amp;quot;yaml&amp;quot;&amp;gt;bake:
  srcPath: &amp;quot;./site/jbake&amp;quot;
  destDirPath: &amp;quot;bake&amp;quot;
pushPage:
  from: &amp;quot;bake&amp;quot;
  to: &amp;quot;cvs&amp;quot;
  repo:
    name: &amp;quot;trainings&amp;quot;
    repository: &amp;quot;https://github.com/pages-content/trainings.git&amp;quot;
    credentials:
      username: &amp;quot;USERNAME&amp;quot;
      password: &amp;quot;SECRET_TOKEN&amp;quot;
  branch: &amp;quot;main&amp;quot;
  message: &amp;quot;cheroliv.com&amp;quot;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Konfigurasi ini direpresentasikan melalui ekstensi Gradle yang dapat dideklarasikan dalam Kotlin DSL.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_arsitektur_plugin&amp;quot;&amp;gt;Arsitektur plugin&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
package &amp;quot;plugin mesin&amp;quot; {
class WorkspaceEngineExtension
class GenerateSiteTask
interface GitAdapter
class JGitAdapter
WorkspaceEnginePlugin --&amp;amp;gt; WorkspaceEngineExtension
WorkspaceEnginePlugin --&amp;amp;gt; GenerateSiteTask
GenerateSiteTask --&amp;amp;gt; GitAdapter
JGitAdapter ..|&amp;amp;gt; GitAdapter
}
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_definisi_ekstensi_dsl&amp;quot;&amp;gt;Definisi ekstensi DSL&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;open class WorkspaceEngineExtension {
    var outputDir: String = &amp;quot;build/site&amp;quot;
    var template: String = &amp;quot;freemarker&amp;quot;
    var repoUrl: String = &amp;quot;&amp;quot;
    var branch: String = &amp;quot;main&amp;quot;
    var message: String = &amp;quot;Generated by engine&amp;quot;
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_definisi_tugas_kustom&amp;quot;&amp;gt;Definisi tugas kustom&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;abstract class GenerateSiteTask : DefaultTask() {
    lateinit var gitAdapter: GitAdapter

    @get:Input abstract val repoUrl: Property&amp;amp;lt;String&amp;amp;gt;
    @get:InputDirectory abstract val outputDir: DirectoryProperty
    @get:Input abstract val branch: Property&amp;amp;lt;String&amp;amp;gt;
    @get:Input abstract val message: Property&amp;amp;lt;String&amp;amp;gt;

    @TaskAction
    fun run() {
        val repo = gitAdapter.cloneRepository(repoUrl.get(), outputDir.get().asFile)
        gitAdapter.commitAndPush(repo, branch.get(), message.get())
    }
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_antarmuka_gitadapter&amp;quot;&amp;gt;Antarmuka GitAdapter&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;interface GitAdapter {
    fun cloneRepository(uri: String, directory: File): Repository
    fun commitAndPush(repo: Repository, branch: String, message: String)
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_implementasi_konkret_dengan_jgit&amp;quot;&amp;gt;Implementasi konkret dengan JGit&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;class JGitAdapter : GitAdapter {
    override fun cloneRepository(uri: String, directory: File): Repository {
        return Git.cloneRepository()
            .setURI(uri)
            .setDirectory(directory)
            .call()
            .repository
    }

    override fun commitAndPush(repo: Repository, branch: String, message: String) {
        Git(repo).use {
            it.add().addFilepattern(&amp;quot;.&amp;quot;).call()
            it.commit().setMessage(message).call()
            it.push().call()
        }
    }
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_konfigurasi_dsl_kotlin&amp;quot;&amp;gt;Konfigurasi DSL Kotlin&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;workspaceEngine {
    outputDir = &amp;quot;build/out&amp;quot;
    template = &amp;quot;freemarker&amp;quot;
    repoUrl = &amp;quot;https://github.com/cheroliv/trainings.git&amp;quot;
    branch = &amp;quot;main&amp;quot;
    message = &amp;quot;deploy from plugin&amp;quot;
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_uji_unit_dengan_mockito&amp;quot;&amp;gt;Uji unit dengan Mockito&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_ketergantungan&amp;quot;&amp;gt;Ketergantungan&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;testImplementation(&amp;quot;org.mockito:mockito-core:5.12.0&amp;quot;)
testImplementation(&amp;quot;org.mockito.kotlin:mockito-kotlin:5.2.1&amp;quot;)
testImplementation(&amp;quot;org.junit.jupiter:junit-jupiter:5.10.0&amp;quot;)&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_uji_perilaku_git&amp;quot;&amp;gt;Uji perilaku Git&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;class GenerateSiteTaskMockitoTest {

    @TempDir
    lateinit var tempDir: File

    @Test
    fun `task should call clone and commitAndPush`() {
        val project = ProjectBuilder.builder().build()
        val task = project.tasks.create(&amp;quot;generateSite&amp;quot;, GenerateSiteTask::class.java)

        val mockGitAdapter = mock&amp;amp;lt;GitAdapter&amp;amp;gt;()
        val mockRepo = mock&amp;amp;lt;Repository&amp;amp;gt;()

        whenever(mockGitAdapter.cloneRepository(any(), any())).thenReturn(mockRepo)

        task.gitAdapter = mockGitAdapter
        task.repoUrl.set(&amp;quot;https://github.com/cheroliv/test.git&amp;quot;)
        task.outputDir.set(project.layout.projectDirectory.dir(tempDir.name))
        task.branch.set(&amp;quot;main&amp;quot;)
        task.message.set(&amp;quot;test commit&amp;quot;)

        task.run()

        verify(mockGitAdapter).cloneRepository(eq(&amp;quot;https://github.com/cheroliv/test.git&amp;quot;), any())
        verify(mockGitAdapter).commitAndPush(eq(mockRepo), eq(&amp;quot;main&amp;quot;), eq(&amp;quot;test commit&amp;quot;))
    }
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
actor &amp;quot;Pengembang&amp;quot; as Dev
participant &amp;quot;Gradle ProjectBuilder&amp;quot; as Project
participant &amp;quot;GenerateSiteTask&amp;quot; as Task
participant &amp;quot;GitAdapter (Mock)&amp;quot; as Adapter
participant &amp;quot;JGitAdapter / Mockito&amp;quot; as Impl

Dev -&amp;amp;gt; Project : construit projet Gradle
Dev -&amp;amp;gt; Task : configure les propriétés
Task -&amp;amp;gt; Adapter : cloneRepository()
Adapter -&amp;amp;gt; Impl : simulé par Mockito
Task -&amp;amp;gt; Adapter : commitAndPush()
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_kesimpulan&amp;quot;&amp;gt;Kesimpulan&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Pemisahan tanggung jawab melalui`GitAdapter`memungkinkan penerapan lengkap TDD dalam pengembangan plugin Gradle :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;panggilan Git abstrak dan dapat diuji;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;konfigurasi DSL bersih dan jelas;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Plugin tetap agnostik terhadap implementasi sebenarnya.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Pendekatan ini menjamin keteruji-an yang tinggi dan ekstensibilitas (menuju GitHub API, GitLab, dst.).&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_langkah_selanjutnya&amp;quot;&amp;gt;Langkah selanjutnya&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Mengintegrasikan generasi JBake sebagai`BakeAdapter`&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Menambahkan tes integrasi dengan`GradleRunner`&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Mendukung pemuatan otomatis file`site.yml`melalui SnakeYAML&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>AsciiDoc: Mempelajari dan menguasai sintaksis untuk dokumentasi yang efektif</title>
            <link >https://cheroliv.com/blog/2025/0077_asciidoc_syntaxe_post.html</link>
            <pubDate>Fri, 20 Jun 2025 00:00:00 +0000</pubDate>
            <guid isPermaLink="false">blog/2025/0077_asciidoc_syntaxe_post.html</guid>
            <description>&amp;lt;div id=&amp;quot;toc&amp;quot; class=&amp;quot;toc&amp;quot;&amp;gt;
&amp;lt;div id=&amp;quot;toctitle&amp;quot;&amp;gt;Sommaire&amp;lt;/div&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel1&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_pengenalan&amp;quot;&amp;gt;Pengenalan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_apa_itu_asciidoc&amp;quot;&amp;gt;Apa itu AsciiDoc?&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_mengapa_memilih_asciidoc&amp;quot;&amp;gt;Mengapa memilih AsciiDoc?&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_diagram_kasus_penggunaan_kasus_penggunaan&amp;quot;&amp;gt;Diagram kasus penggunaan (Kasus Penggunaan)&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_diagram_peta_pikiran&amp;quot;&amp;gt;Diagram Peta Pikiran&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_diagram_dari_flow_aliran&amp;quot;&amp;gt;Diagram dari Flow (aliran)&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_struktur_dasar_file_asciidoc&amp;quot;&amp;gt;Struktur dasar file AsciiDoc&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_sintaks_dasar&amp;quot;&amp;gt;Sintaks dasar&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_judul_dan_bagian&amp;quot;&amp;gt;Judul dan bagian&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_teks_tebal_miring_dan_monospasi&amp;quot;&amp;gt;Teks tebal, miring dan monospasi&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_daftar_bullet_dan_daftar_bernomor&amp;quot;&amp;gt;Daftar bullet dan daftar bernomor&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_tautan_dan_gambar&amp;quot;&amp;gt;Tautan dan gambar&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_blok_kode&amp;quot;&amp;gt;Blok kode&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_tabel&amp;quot;&amp;gt;tabel&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_catatan_dan_peringatan&amp;quot;&amp;gt;Catatan dan peringatan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_penggunaan_atribut_dan_variabel&amp;quot;&amp;gt;Penggunaan atribut dan variabel&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_kasus_penggunaan_umum&amp;quot;&amp;gt;kasus penggunaan umum&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_praktik_terbaik&amp;quot;&amp;gt;Praktik terbaik&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_kesimpulan&amp;quot;&amp;gt;Kesimpulan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_untuk_melanjutkan_lebih_jauh&amp;quot;&amp;gt;Untuk melanjutkan lebih jauh&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_pengenalan&amp;quot;&amp;gt;Pengenalan&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;&amp;lt;em&amp;gt;Sasaran :&amp;lt;/em&amp;gt; Pengembang, penulis teknis, dan siapa pun yang ingin menulis dokumentasi teknis atau artikel yang kaya.&amp;lt;/div&amp;gt;
&amp;lt;p&amp;gt;AsciiDoc adalah bahasa markup ringan yang dirancang untuk membuat dokumen teknis terstruktur, artikel, buku, atau presentasi. Ia tercatat dengan kemudahan bacaan, kekayaan sintaksis, dan kemampuannya untuk menghasilkan berbagai format (HTML, PDF, DocBook, dst.). Dalam artikel ini, kami akan menjelajahi sintaksis penting AsciiDoc dan memberikan tips untuk memulai dengan cepat.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_apa_itu_asciidoc&amp;quot;&amp;gt;Apa itu AsciiDoc?&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;AsciiDoc adalah bahasa deskripsi dokumen, mirip dengan Markdown tetapi lebih kuat. Ia memungkinkan untuk mengstruktur dengan efektif teks, judul, daftar, tabel, blok kode, dan banyak lagi. Keserbagunaannya menjadikannya pilihan populer untuk dokumentasi proyek open source, penulisan buku, dan publikasi web.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_mengapa_memilih_asciidoc&amp;quot;&amp;gt;Mengapa memilih AsciiDoc?&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;olist arabic&amp;quot;&amp;gt;
&amp;lt;ol class=&amp;quot;arabic&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Sintaksis yang mudah dibaca dan intuitif.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Dukungan bawaan untuk struktur kompleks (tabel, catatan, peringatan, etc.).&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Generasi multi-format (HTML, PDF, ePub, DocBook…).&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Integrasi mudah dengan generator situs statis seperti JBake atau Antora.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Personalisasi lanjutan dengan atribut dan ekstensi.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_diagram_kasus_penggunaan_kasus_penggunaan&amp;quot;&amp;gt;Diagram kasus penggunaan (Kasus Penggunaan)&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Sebuah diagram kasus penggunaan memungkinkan untuk menyajikan interaksi utama antara pengguna dan sistem. Berikut contoh sederhana untuk sistem dokumentasi :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
:Utilisateur: --&amp;amp;gt; (Rédiger documentation)
:Utilisateur: --&amp;amp;gt; (Générer PDF)
:Utilisateur: --&amp;amp;gt; (Publier sur site web)
(Rédiger documentation) ..&amp;amp;gt; (Générer PDF) : inclut
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_diagram_peta_pikiran&amp;quot;&amp;gt;Diagram Peta Pikiran&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Peta pikir (peta konseptual) adalah ideal untuk menjelajahi konsep yang terkait dengan AsciiDoc dan hubungannya:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startmindmap
* AsciiDoc
** Syntaxe
*** Titres
*** Listes
*** Blocs de code
*** Tableaux
** Extensions
*** PlantUML
*** MathJax
** Export
*** HTML
*** PDF
*** EPUB
@endmindmap&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_diagram_dari_flow_aliran&amp;quot;&amp;gt;Diagram dari Flow (aliran)&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Sebuah diagram alur dapat menggambarkan proses pembuatan dokumen AsciiDoc :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
start
:Écrire fichier .adoc;
:Ajouter images et diagrammes;
if (Valider la syntaxe ?) then (oui)
:Générer HTML/PDF;
:Publier ou partager;
else (non)
:Corriger erreurs;
:back to start;
endif
stop
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_struktur_dasar_file_asciidoc&amp;quot;&amp;gt;Struktur dasar file AsciiDoc&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Sebuah file AsciiDoc biasanya dimulai dengan judul, atribut opsional, lalu konten terstruktur. Berikut contoh minimal :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-asciidoc hljs&amp;quot; data-lang=&amp;quot;asciidoc&amp;quot;&amp;gt;= Titre Principal
Auteur
2024-09-03
:toc:
:icons: font

Votre contenu commence ici...&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_sintaks_dasar&amp;quot;&amp;gt;Sintaks dasar&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_judul_dan_bagian&amp;quot;&amp;gt;Judul dan bagian&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;AsciiDoc mendukung beberapa tingkat judul :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-asciidoc hljs&amp;quot; data-lang=&amp;quot;asciidoc&amp;quot;&amp;gt;= Titre de niveau 1
== Titre de niveau 2
=== Titre de niveau 3
==== Titre de niveau 4&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_teks_tebal_miring_dan_monospasi&amp;quot;&amp;gt;Teks tebal, miring dan monospasi&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-asciidoc hljs&amp;quot; data-lang=&amp;quot;asciidoc&amp;quot;&amp;gt;*gras* _italique_ `monospace`&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_daftar_bullet_dan_daftar_bernomor&amp;quot;&amp;gt;Daftar bullet dan daftar bernomor&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-asciidoc hljs&amp;quot; data-lang=&amp;quot;asciidoc&amp;quot;&amp;gt;* Élément 1
* Élément 2

. Premier
. Deuxième
. Troisième&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_tautan_dan_gambar&amp;quot;&amp;gt;Tautan dan gambar&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-asciidoc hljs&amp;quot; data-lang=&amp;quot;asciidoc&amp;quot;&amp;gt;Lien standard : https://asciidoc.org[AsciiDoc]
Image : image::images/logo.png[AsciiDoc Logo]&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_blok_kode&amp;quot;&amp;gt;Blok kode&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-asciidoc hljs&amp;quot; data-lang=&amp;quot;asciidoc&amp;quot;&amp;gt;[source,python]&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;def hello(): print(&amp;quot;Halo AsciiDoc!&amp;quot;)&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_tabel&amp;quot;&amp;gt;tabel&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-asciidoc hljs&amp;quot; data-lang=&amp;quot;asciidoc&amp;quot;&amp;gt;|===
| Colonne 1 | Colonne 2

| Valeur A
| Valeur B

| Valeur C
| Valeur D
|===&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_catatan_dan_peringatan&amp;quot;&amp;gt;Catatan dan peringatan&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;AsciiDoc menawarkan blok informasi visual :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-asciidoc hljs&amp;quot; data-lang=&amp;quot;asciidoc&amp;quot;&amp;gt;NOTE: Ceci est une note importante.
TIP: Conseil utile pour l’utilisateur.
WARNING: Attention à ce point.&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_penggunaan_atribut_dan_variabel&amp;quot;&amp;gt;Penggunaan atribut dan variabel&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Atribut khusus memungkinkan untuk menggunakan kembali nilai atau mengkonfigurasi perilaku :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-asciidoc hljs&amp;quot; data-lang=&amp;quot;asciidoc&amp;quot;&amp;gt;:project-name: AsciiDoc Explorer

Le projet s’appelle {project-name}.&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_kasus_penggunaan_umum&amp;quot;&amp;gt;kasus penggunaan umum&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Dokumentasi proyek open source (README, panduan teknis) - Penulisan buku dan ebook - Generasi otomatis situs web statis (&amp;lt;code&amp;gt;JBake, Antora&amp;lt;/code&amp;gt;) - Presentasi teknis&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_praktik_terbaik&amp;quot;&amp;gt;Praktik terbaik&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Gunakan judul yang konsisten dan daftar isi otomatis (:toc:). - Prioritaskan peringatan untuk menarik perhatian pada poin kunci. - Susun file Anda untuk memudahkan pemeliharaan. - Manfaatkan blok kode yang diberi anotasi untuk mengilustrasikan contoh.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_kesimpulan&amp;quot;&amp;gt;Kesimpulan&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;AsciiDoc adalah alat yang kuat dan mudah diakses untuk membuat dokumentasi teknis atau artikel terstruktur. Sintaksisnya yang kaya, gabungan dengan pembuatan multi-format, menjadikannya pilihan yang tepat untuk pengembang dan penulis yang membutuhkan standar tinggi. Coba AsciiDoc di proyek Anda berikutnya dan rasakan perbedaannya!&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_untuk_melanjutkan_lebih_jauh&amp;quot;&amp;gt;Untuk melanjutkan lebih jauh&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Dokumentasi resmi : &amp;lt;a href=&amp;quot;https://asciidoc.org&amp;quot; class=&amp;quot;bare&amp;quot;&amp;gt;https://asciidoc.org&amp;lt;/a&amp;gt; - Asciidoctor : &amp;lt;a href=&amp;quot;https://asciidoctor.org&amp;quot; class=&amp;quot;bare&amp;quot;&amp;gt;https://asciidoctor.org&amp;lt;/a&amp;gt; - JBake dan AsciiDoc : &amp;lt;a href=&amp;quot;https://jbake.org/docs/2.6.4/#asciidoc_support&amp;quot; class=&amp;quot;bare&amp;quot;&amp;gt;https://jbake.org/docs/2.6.4/#asciidoc_support&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Berbagi pengalaman dan tips Anda di komentar!&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>Mengekspos layanan lokal ke Internet dari Ubuntu : Panduan lengkap tentang port forwarding</title>
            <link >https://cheroliv.com/blog/2025/0076_port_forwarding_post.html</link>
            <pubDate>Wed, 28 May 2025 00:00:00 +0000</pubDate>
            <guid isPermaLink="false">blog/2025/0076_port_forwarding_post.html</guid>
            <description>&amp;lt;div id=&amp;quot;toc&amp;quot; class=&amp;quot;toc&amp;quot;&amp;gt;
&amp;lt;div id=&amp;quot;toctitle&amp;quot;&amp;gt;Sommaire&amp;lt;/div&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel1&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_masalah_nat_dan_alamat_pribadi&amp;quot;&amp;gt;Masalah: NAT dan alamat pribadi&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_langkah_1_identifikasi_alamat_ip_publik_anda&amp;quot;&amp;gt;Langkah 1: Identifikasi alamat IP publik Anda&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_langkah_2_mengetahui_alamat_ip_lokal_dari_mesin_ubuntu_anda&amp;quot;&amp;gt;Langkah 2 : Mengetahui alamat IP lokal dari mesin Ubuntu Anda&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_langkah_3_mengatur_forwarding_port_pada_router_anda&amp;quot;&amp;gt;Langkah 3 : Mengatur forwarding port pada router Anda&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_akses_ke_antarmuka_administrasi&amp;quot;&amp;gt;Akses ke antarmuka administrasi&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_konfigurasi_pengalihan&amp;quot;&amp;gt;Konfigurasi Pengalihan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_langkah_4_konfigurasi_layanan_di_ubuntu&amp;quot;&amp;gt;Langkah 4: Konfigurasi layanan di Ubuntu&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_langkah_5_konfigurasi_firewall_ubuntu&amp;quot;&amp;gt;Langkah 5 : Konfigurasi firewall Ubuntu&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_tahap_6_pengujian_dan_validasi&amp;quot;&amp;gt;Tahap 6 : Pengujian dan validasi&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_uji_dari_jaringan_lokal&amp;quot;&amp;gt;Uji dari jaringan lokal&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_test_dari_luar&amp;quot;&amp;gt;Test dari luar&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_praktik_terbaik_dan_keamanan&amp;quot;&amp;gt;Praktik terbaik dan keamanan&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_keamanan_dasar&amp;quot;&amp;gt;keamanan dasar&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_pemantauan_koneksi&amp;quot;&amp;gt;Pemantauan koneksi&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_penggunaan_reverse_proxy&amp;quot;&amp;gt;Penggunaan reverse proxy&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_alternatif_untuk_port_forwarding&amp;quot;&amp;gt;Alternatif untuk port forwarding&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_solusi_cloud&amp;quot;&amp;gt;Solusi cloud&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_vps_dan_reverse_proxy&amp;quot;&amp;gt;VPS dan reverse proxy&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_pemecahan_masalah&amp;quot;&amp;gt;Pemecahan Masalah&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_masalah_umum&amp;quot;&amp;gt;Masalah umum&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_debug_jaringan&amp;quot;&amp;gt;Debug jaringan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_kesimpulan&amp;quot;&amp;gt;Kesimpulan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div id=&amp;quot;preamble&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;Sasaran : Menengah&amp;lt;/div&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Prasyarat yang diasumsikan&amp;lt;/strong&amp;gt; : pengetahuan dasar Ubuntu, terminal, konsep jaringan dasar&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Konsep Teknis&amp;lt;/strong&amp;gt; : NAT, forwarding port, firewall, proxy terbalik&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Perintah sistem&amp;lt;/strong&amp;gt; : penggunaan terminal, konfigurasi jaringan&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Bagian lanjutan&amp;lt;/strong&amp;gt; : pemantauan, keamanan, alternatif cloud&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_masalah_nat_dan_alamat_pribadi&amp;quot;&amp;gt;Masalah: NAT dan alamat pribadi&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;Masalah : Anda mengembangkan aplikasi web di mesin Ubuntu Anda dan ingin membuatnya dapat diakses dari luar jaringan rumah Anda? Anda berada di tempat yang tepat! Dalam artikel ini, kita akan melihat cara mengekspos layanan lokal (misalnya di port 8080) ke Internet dengan mengonfigurasi port forwarding pada router internet Anda.&amp;lt;/div&amp;gt;
&amp;lt;p&amp;gt;Secara default, box internet Anda menggunakan NAT (Network Address Translation) untuk berbagi satu alamat IP publik antara semua perangkat di jaringan rumah Anda. Mesin lokal Anda menggunakan alamat IP pribadi (192.168.x.x, 10.x.x.x, dst.) yang tidak dapat dirutekan di Internet.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Saat Anda menjalankan layanan pada mesin Ubuntu Anda (misalnya server web pada port 8080), layanan tersebut hanya dapat diakses dari jaringan lokal Anda. Untuk membuatnya dapat diakses dari Internet, kita harus mengkonfigurasi alihkan port.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_langkah_1_identifikasi_alamat_ip_publik_anda&amp;quot;&amp;gt;Langkah 1: Identifikasi alamat IP publik Anda&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Pertama-tama, mari kita dapatkan alamat IP publik koneksi internet Anda:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;# Plusieurs méthodes au choix
curl ifconfig.me
curl ipecho.net/plain
wget -qO- http://ipecho.net/plain

# Ou encore
curl -s checkip.amazonaws.com&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Alamat IP ini akan digunakan oleh pengguna internet untuk mengakses layanan Anda.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_langkah_2_mengetahui_alamat_ip_lokal_dari_mesin_ubuntu_anda&amp;quot;&amp;gt;Langkah 2 : Mengetahui alamat IP lokal dari mesin Ubuntu Anda&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Mesin Ubuntu Anda memiliki alamat IP pribadi di jaringan lokal Anda. Untuk mengetahuinya :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;# Afficher toutes les interfaces réseau
ip addr show

# Ou plus spécifiquement pour l&amp;#39;interface principale
ip route get 1.1.1.1 | awk &amp;#39;{print $7; exit}&amp;#39;

# Alternative avec hostname
hostname -I | awk &amp;#39;{print $1}&amp;#39;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Catat alamat ini (misalnya`192.168.1.100`), kita akan membutuhkannya untuk konfigurasi.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_langkah_3_mengatur_forwarding_port_pada_router_anda&amp;quot;&amp;gt;Langkah 3 : Mengatur forwarding port pada router Anda&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Konfigurasi bervariasi tergantung pada model kotak Anda, tetapi prinsipnya tetap sama:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_akses_ke_antarmuka_administrasi&amp;quot;&amp;gt;Akses ke antarmuka administrasi&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Buka browser Anda dan buka antarmuka web dari kotak Anda :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Freebox&amp;lt;/strong&amp;gt;:`192.168.1.1` ou &amp;lt;code&amp;gt;mafreebox.freebox.fr&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Livebox Orange&amp;lt;/strong&amp;gt;:`192.168.1.1`&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;SFR Box&amp;lt;/strong&amp;gt; : &amp;lt;code&amp;gt;192.168.1.1&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Bbox Bouygues&amp;lt;/strong&amp;gt;:`192.168.1.254`&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_konfigurasi_pengalihan&amp;quot;&amp;gt;Konfigurasi Pengalihan&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Cari bagian khusus untuk port forwarding (nama-nama bervariasi):&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Pengalihan port&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;NAT/PAT&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Penerusan Port&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Server game&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Buat aturan baru dengan parameter ini:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;table class=&amp;quot;tableblock frame-all grid-all stretch&amp;quot;&amp;gt;
&amp;lt;colgroup&amp;gt;
&amp;lt;col style=&amp;quot;width: 25%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 75%;&amp;quot;&amp;gt;
&amp;lt;/colgroup&amp;gt;
&amp;lt;thead&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;lapangan&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;nilai&amp;lt;/th&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/thead&amp;gt;
&amp;lt;tbody&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Nama/Deskripsi&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Layanan Web Ubuntu&amp;quot; (atau nama eksplisit lain)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Port eksternal (atau port publik)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;8080&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Alamat IP internal&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Alamat IP mesin Ubuntu Anda (contoh: 192.168.1.100)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Port internal (atau port pribadi)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;8080&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;protokol&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;TCP&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;keadaan&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;diaktifkan&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_langkah_4_konfigurasi_layanan_di_ubuntu&amp;quot;&amp;gt;Langkah 4: Konfigurasi layanan di Ubuntu&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Pastikan layanan Anda mendengarkan di semua antarmuka, bukan hanya di localhost:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;# ✅ Correct : écoute sur toutes les interfaces
python3 -m http.server 8080 --bind 0.0.0.0

# ❌ Incorrect : écoute seulement en local
python3 -m http.server 8080 --bind 127.0.0.1&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Untuk memastikan bahwa layanan Anda mendengarkan dengan benar:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;# Vérifier les ports en écoute
sudo netstat -tlnp | grep :8080
# ou avec ss (plus moderne)
sudo ss -tlnp | grep :8080&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Anda seharusnya melihat sesuatu seperti`0.0.0.0:8080`dan tidak`127.0.0.1:8080`.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_langkah_5_konfigurasi_firewall_ubuntu&amp;quot;&amp;gt;Langkah 5 : Konfigurasi firewall Ubuntu&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Ubuntu menggunakan UFW (Uncomplicated Firewall) secara default. Periksa statusnya dan izinkan port jika diperlukan :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;# Vérifier le statut du pare-feu
sudo ufw status

# Si le pare-feu est actif, autoriser le port 8080
sudo ufw allow 8080/tcp

# Ou plus spécifiquement pour un service web
sudo ufw allow &amp;#39;Apache&amp;#39;  # si vous utilisez Apache
sudo ufw allow &amp;#39;Nginx Full&amp;#39;  # si vous utilisez Nginx&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_tahap_6_pengujian_dan_validasi&amp;quot;&amp;gt;Tahap 6 : Pengujian dan validasi&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_uji_dari_jaringan_lokal&amp;quot;&amp;gt;Uji dari jaringan lokal&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Pertama, uji akses dari sebuah komputer lain di jaringan lokal Anda:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;# Remplacez par l&amp;#39;IP locale de votre machine Ubuntu
curl http://192.168.1.100:8080&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_test_dari_luar&amp;quot;&amp;gt;Test dari luar&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Sekarang, uji dari Internet menggunakan IP publik Anda :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;# Remplacez par votre IP publique
curl http://VOTRE_IP_PUBLIQUE:8080&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Anda juga dapat menggunakan alat online seperti :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://www.yougetsignal.com/tools/open-ports/&amp;quot; class=&amp;quot;bare&amp;quot;&amp;gt;https://www.yougetsignal.com/tools/open-ports/&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://canyouseeme.org/&amp;quot; class=&amp;quot;bare&amp;quot;&amp;gt;https://canyouseeme.org/&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_praktik_terbaik_dan_keamanan&amp;quot;&amp;gt;Praktik terbaik dan keamanan&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_keamanan_dasar&amp;quot;&amp;gt;keamanan dasar&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;# Configurer fail2ban pour protéger contre les attaques par force brute
sudo apt update &amp;amp;amp;&amp;amp;amp; sudo apt install fail2ban

# Limiter l&amp;#39;accès avec UFW (exemple : autoriser seulement certaines IP)
sudo ufw allow from 203.0.113.0/24 to any port 8080&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_pemantauan_koneksi&amp;quot;&amp;gt;Pemantauan koneksi&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;# Surveiller les connexions en temps réel
sudo netstat -an | grep :8080

# Logs des connexions (selon votre application)
sudo tail -f /var/log/nginx/access.log  # pour Nginx
sudo journalctl -f -u your-service      # pour un service systemd&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_penggunaan_reverse_proxy&amp;quot;&amp;gt;Penggunaan reverse proxy&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Untuk keamanan dan fleksibilitas yang lebih baik, pertimbangkan penggunaan reverse proxy seperti Nginx :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-nginx hljs&amp;quot; data-lang=&amp;quot;nginx&amp;quot;&amp;gt;server {
    listen 80;
    server_name votre-domaine.com;

    location / {
        proxy_pass http://localhost:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_alternatif_untuk_port_forwarding&amp;quot;&amp;gt;Alternatif untuk port forwarding&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_solusi_cloud&amp;quot;&amp;gt;Solusi cloud&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;ngrok&amp;lt;/strong&amp;gt;terowongan aman sementara&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Cloudflare Tunnel&amp;lt;/strong&amp;gt;solusi gratis dan aman&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;serveo.net&amp;lt;/strong&amp;gt;: tunnel SSH sederhana&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;# Exemple avec ngrok
ngrok http 8080

# Exemple avec serveo
ssh -R 80:localhost:8080 serveo.net&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_vps_dan_reverse_proxy&amp;quot;&amp;gt;VPS dan reverse proxy&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Untuk penggunaan profesional, pertimbangkan menggunakan VPS dengan reverse proxy yang mengarah ke infrastruktur lokal Anda melalui VPN.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_pemecahan_masalah&amp;quot;&amp;gt;Pemecahan Masalah&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_masalah_umum&amp;quot;&amp;gt;Masalah umum&amp;lt;/h3&amp;gt;
&amp;lt;table class=&amp;quot;tableblock frame-all grid-all stretch&amp;quot;&amp;gt;
&amp;lt;colgroup&amp;gt;
&amp;lt;col style=&amp;quot;width: 40%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 60%;&amp;quot;&amp;gt;
&amp;lt;/colgroup&amp;gt;
&amp;lt;thead&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;Masalah&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;Solusi&amp;lt;/th&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/thead&amp;gt;
&amp;lt;tbody&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Layanan tidak dapat diakses dari luar&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Periksa konfigurasi port forwarding dan bahwa layanan mendengarkan pada 0.0.0.0&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Koneksi ditolak&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Layanan tidak dimulai atau hanya mendengarkan pada 127.0.0.1&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Timeout koneksi&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Masalah firewall (box atau Ubuntu) atau port forwarding yang salah dikonfigurasi&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;IP publik berubah secara teratur&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Konfigurasi DNS dinamis (DynDNS, No-IP, etc.)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_debug_jaringan&amp;quot;&amp;gt;Debug jaringan&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;# Tester la connectivité locale
telnet localhost 8080

# Tester depuis une autre machine du réseau
telnet 192.168.1.100 8080

# Vérifier les routes réseau
ip route show

# Analyser le trafic réseau
sudo tcpdump -i any port 8080&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_kesimpulan&amp;quot;&amp;gt;Kesimpulan&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Menampilkan layanan lokal ke Internet memerlukan konfigurasi yang berhati-hati untuk port forwarding, keamanan, dan monitoring. Meskipun pendekatan ini sempurna untuk pengembangan atau proyek pribadi, pertimbangkan alternatif yang lebih kuat (VPS, CDN, layanan cloud) untuk penggunaan produksi.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Yang penting adalah selalu mengingat keamanan: gunakan kata sandi yang kuat, perbarui sistem Anda secara teratur, dan pantau akses ke layanan Anda yang terpapar.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Jangan ragu untuk berbagi pengalaman dan pertanyaan Anda di komentar!&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>Debugging Mockito: Menyelesaikan kesalahan &#39;Wanted but not invoked&#39; dalam tes plugin Gradle</title>
            <link >https://cheroliv.com/blog/2025/0099_debugging-mockito-gradle-plugin_post.html</link>
            <pubDate>Sun, 17 Nov 2024 00:00:00 +0000</pubDate>
            <guid isPermaLink="false">blog/2025/0099_debugging-mockito-gradle-plugin_post.html</guid>
            <description>&amp;lt;div id=&amp;quot;toc&amp;quot; class=&amp;quot;toc&amp;quot;&amp;gt;
&amp;lt;div id=&amp;quot;toctitle&amp;quot;&amp;gt;Table des matières&amp;lt;/div&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel1&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_pendahuluan&amp;quot;&amp;gt;Pendahuluan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_konteks_plugin_gradle_bakery&amp;quot;&amp;gt;Konteks : Plugin Gradle Bakery&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_masalah_1_memeriksa_mock_yang_salah&amp;quot;&amp;gt;Masalah #1: Memeriksa mock yang salah&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_diagnosa&amp;quot;&amp;gt;Diagnosa&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_solusi&amp;quot;&amp;gt;Solusi&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_masalah_2_unfinishedstubbingexception_berantai&amp;quot;&amp;gt;Masalah #2 : UnfinishedStubbingException berantai&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_diagnosa_2&amp;quot;&amp;gt;Diagnosa&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_solusi_2&amp;quot;&amp;gt;Solusi&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_masalah_3_file_konfigurasi_tidak_ada&amp;quot;&amp;gt;Masalah #3: File konfigurasi tidak ada&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_diagnosa_3&amp;quot;&amp;gt;Diagnosa&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_solusi_3&amp;quot;&amp;gt;solusi&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_masalah_4_afterevaluate_dan_nullpointerexception&amp;quot;&amp;gt;Masalah #4 : afterEvaluate dan NullPointerException&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_diagnosa_4&amp;quot;&amp;gt;Diagnosa&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_solusi_4&amp;quot;&amp;gt;solusi&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_solusi_lengkap&amp;quot;&amp;gt;Solusi lengkap&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_pelajaran_yang_dipelajari&amp;quot;&amp;gt;Pelajaran yang dipelajari&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_memeriksa_mock_yang_benar&amp;quot;&amp;gt;Memeriksa mock yang benar&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_2_menghindari_mock_bersarang&amp;quot;&amp;gt;2. Menghindari mock bersarang&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_3_meniru_callback_evaluasi&amp;quot;&amp;gt;3. Meniru callback evaluasi&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_4_menguji_resolusi_jalur&amp;quot;&amp;gt;4. Menguji resolusi jalur&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_arsitektur_pengujian_akhir&amp;quot;&amp;gt;Arsitektur pengujian akhir&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_kesimpulan&amp;quot;&amp;gt;Kesimpulan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_sumber_daya&amp;quot;&amp;gt;Sumber daya&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_pendahuluan&amp;quot;&amp;gt;Pendahuluan&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Selama pengembangan plugin Gradle&amp;lt;strong&amp;gt;Toko roti&amp;lt;/strong&amp;gt;Untuk blog JBake saya, saya telah menemui masalah yang tampak sederhana: sebuah tes unit yang gagal dengan kesalahan`Wanted but not invoked`. Apa yang terlihat sebagai bug trivial ternyata menjadi contoh kasus yang sempurna untuk memahami kehalusan mocking dengan Mockito dan Kotlin.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Dalam artikel ini, saya mengajak Anda melakukan perjalanan debugging metodis, di mana setiap solusi mengungkapkan masalah baru, hingga penyelesaian akhir.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_konteks_plugin_gradle_bakery&amp;quot;&amp;gt;Konteks : Plugin Gradle Bakery&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Plugin Bakery adalah sebuah wrapper di sekitar JBake yang memudahkan publikasi situs statis. Berikut adalah struktur yang disederhanakannya :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;class BakeryPlugin : Plugin&amp;amp;lt;Project&amp;amp;gt; {
    override fun apply(project: Project) {
        val extension = project.extensions.create(
            &amp;quot;bakery&amp;quot;,
            BakeryExtension::class.java
        )

        project.afterEvaluate {
            if (!project.layout.projectDirectory.asFile
                    .resolve(extension.configPath.get()).exists()) {
                println(&amp;quot;config file does not exists&amp;quot;)
            } else {
                // C&amp;#39;EST ICI QUE ÇA SE PASSE
                project.plugins.apply(JBakePlugin::class.java)

                val site = FileSystemManager.from(project, extension.configPath.get())
                // Configuration de JBake...
            }
        }
    }
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Uji yang gagal adalah sederhana:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;@Test
fun `plugin applies jbake gradle plugin`() {
    val project = createMockProject()
    val plugin = BakeryPlugin()

    plugin.apply(project)

    verify(project.plugins).apply(JBakePlugin::class.java)
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Kesalahan:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-text hljs&amp;quot; data-lang=&amp;quot;text&amp;quot;&amp;gt;Wanted but not invoked:
pluginContainer.apply(class org.jbake.gradle.JBakePlugin);
Actually, there were zero interactions with this mock.&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_masalah_1_memeriksa_mock_yang_salah&amp;quot;&amp;gt;Masalah #1: Memeriksa mock yang salah&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_diagnosa&amp;quot;&amp;gt;Diagnosa&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
actor Test
participant &amp;quot;project.plugins&amp;quot; as PP
participant &amp;quot;mockPluginContainer&amp;quot; as MPC
participant &amp;quot;BakeryPlugin&amp;quot; as BP

Test -&amp;amp;gt; Test: createMockProject()
activate Test
Test -&amp;amp;gt; MPC: create mock
Test -&amp;amp;gt; PP: doReturn mockPluginContainer
Test --&amp;amp;gt; Test: return project
deactivate Test

Test -&amp;amp;gt; BP: apply(project)
activate BP
BP -&amp;amp;gt; PP: project.plugins
PP --&amp;amp;gt; BP: returns mockPluginContainer
BP -&amp;amp;gt; MPC: apply(JBakePlugin::class.java)
deactivate BP

Test -&amp;amp;gt; PP: verify(project.plugins).apply(...)
note right
  ❌ ÉCHEC !
  Mockito vérifie &amp;quot;project.plugins&amp;quot;
  mais l&amp;#39;interaction a eu lieu sur
  &amp;quot;mockPluginContainer&amp;quot;
end note
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Masalah: Mockito tidak dapat melacak interaksi pada`project.plugins`karena itu hanyalah getter yang mengembalikan mock sebenarnya`mockPluginContainer`. Pemeriksaan harus dilakukan langsung pada instance mock.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_solusi&amp;quot;&amp;gt;Solusi&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Edit`createMockProject()`untuk mengembalikan kedua objek:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;private fun createMockProject(): Pair&amp;amp;lt;Project, PluginContainer&amp;amp;gt; {
    val mockPluginContainer = mock&amp;amp;lt;PluginContainer&amp;amp;gt;()
    val mockProject = mock&amp;amp;lt;Project&amp;amp;gt; {
        on { plugins } doReturn mockPluginContainer
    }
    return Pair(mockProject, mockPluginContainer)
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Dan sesuaikan tes :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;@Test
fun `plugin applies jbake gradle plugin`() {
    val (project, mockPluginContainer) = createMockProject()
    val plugin = BakeryPlugin()

    plugin.apply(project)

    // ✅ Vérification directe sur le bon mock
    verify(mockPluginContainer).apply(JBakePlugin::class.java)
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_masalah_2_unfinishedstubbingexception_berantai&amp;quot;&amp;gt;Masalah #2 : UnfinishedStubbingException berantai&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_diagnosa_2&amp;quot;&amp;gt;Diagnosa&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Setelah perbaikan pertama diterapkan, sebuah kesalahan baru muncul:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-text hljs&amp;quot; data-lang=&amp;quot;text&amp;quot;&amp;gt;UnfinishedStubbingException:
Unfinished stubbing detected here
Hints:
 3. you are stubbing the behaviour of another mock inside
    before &amp;#39;thenReturn&amp;#39; instruction is completed&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Kode bermasalah menggunakan sintaksis DSL Mockito-Kotlin :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;val mockProject = mock&amp;amp;lt;Project&amp;amp;gt; {
    on { extensions } doReturn mockExtensionContainer
    on { plugins } doReturn mockPluginContainer
    on { logger } doReturn mock()  // ❌ PROBLÈME ICI !
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
participant &amp;quot;Mockito&amp;quot; as M
participant &amp;quot;Pembangun Mock&amp;quot; as MB
participant &amp;quot;Mock inline&amp;quot; as IM

M -&amp;amp;gt; MB: mock&amp;amp;lt;Project&amp;amp;gt; { ... }
activate MB
MB -&amp;amp;gt; MB: on { logger } doReturn
MB -&amp;amp;gt; IM: mock()
activate IM
note right
  ❌ Création d&amp;#39;un nouveau mock
  PENDANT le stubbing en cours !

  Mockito perd le contexte
  du stubbing parent
end note
IM --&amp;amp;gt; MB: nouveau mock
deactivate IM
MB -x M: UnfinishedStubbingException
deactivate MB
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_solusi_2&amp;quot;&amp;gt;Solusi&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Membuat&amp;lt;strong&amp;gt;semua&amp;lt;/strong&amp;gt;mock di luar blok stubbing apa pun, lalu konfigurasikan dengan`whenever()` :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;private fun createMockProject(): Pair&amp;amp;lt;Project, PluginContainer&amp;amp;gt; {
    // 1️⃣ Créer TOUS les mocks d&amp;#39;abord
    val mockPluginContainer = mock&amp;amp;lt;PluginContainer&amp;amp;gt;()
    val mockExtensionContainer = mock&amp;amp;lt;ExtensionContainer&amp;amp;gt;()
    val mockLogger = mock&amp;amp;lt;org.gradle.api.logging.Logger&amp;amp;gt;()
    val mockProject = mock&amp;amp;lt;Project&amp;amp;gt;()

    // 2️⃣ Configurer les mocks séparément avec whenever()
    whenever(mockProject.plugins).thenReturn(mockPluginContainer)
    whenever(mockProject.extensions).thenReturn(mockExtensionContainer)
    whenever(mockProject.logger).thenReturn(mockLogger)

    return Pair(mockProject, mockPluginContainer)
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;admonitionblock tip&amp;quot;&amp;gt;
&amp;lt;table&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;icon&amp;quot;&amp;gt;
&amp;lt;i class=&amp;quot;fa icon-tip&amp;quot; title=&amp;quot;Tip&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Aturan emas&amp;lt;/strong&amp;gt;: Jangan pernah menelepon`mock()`di dalam blok konfigurasi mock. Selalu buat mock terlebih dahulu, lalu konfigurasikan.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_masalah_3_file_konfigurasi_tidak_ada&amp;quot;&amp;gt;Masalah #3: File konfigurasi tidak ada&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_diagnosa_3&amp;quot;&amp;gt;Diagnosa&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Meskipun dengan mocks yang benar, test terus gagal karena plugin tidak dapat menemukan file konfigurasi :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;// Dans BakeryPlugin.kt
if (!project.layout.projectDirectory.asFile
        .resolve(extension.configPath.get()).exists()) {
    println(&amp;quot;config file does not exists&amp;quot;)
    return@afterEvaluate  // ❌ Sort avant d&amp;#39;appliquer JBake !
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
participant &amp;quot;BakeryPlugin&amp;quot; as BP
participant &amp;quot;Tata Letak Proyek&amp;quot; as PL
participant &amp;quot;Ekstensi&amp;quot; as EXT
participant &amp;quot;FileSystem&amp;quot; as FS

BP -&amp;amp;gt; PL: layout.projectDirectory.asFile
PL --&amp;amp;gt; BP: /some/mock/path
BP -&amp;amp;gt; EXT: extension.configPath.get()
EXT --&amp;amp;gt; BP: &amp;quot;../../site.yml&amp;quot;
BP -&amp;amp;gt; BP: resolve(&amp;quot;../../site.yml&amp;quot;)
BP -&amp;amp;gt; FS: exists() ?
FS --&amp;amp;gt; BP: false ❌
BP -&amp;amp;gt; BP: return (skip JBake)
note right
  Le plugin ne trouve pas
  le fichier et sort
  avant d&amp;#39;appliquer JBake
end note
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_solusi_3&amp;quot;&amp;gt;solusi&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Konfigurasikan mocks agar resolusi jalur berfungsi:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;private fun createMockProject(): Pair&amp;amp;lt;Project, PluginContainer&amp;amp;gt; {
    // ... autres mocks ...

    val configFile = File(&amp;quot;../../site.yml&amp;quot;).canonicalFile
    val projectDir = configFile.parentFile

    // Configuration cohérente des chemins
    whenever(mockConfigPathProperty.get()).thenReturn(&amp;quot;site.yml&amp;quot;)
    whenever(mockProjectDirectory.asFile).thenReturn(projectDir)

    // Maintenant : projectDir.resolve(&amp;quot;site.yml&amp;quot;) existe ! ✅
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
!define SUCCESS #90EE90
!define PROBLEM #FFB6C1

rectangle &amp;quot;Konfigurasi Mock&amp;quot; {
    card &amp;quot;projectDirectory.asFile&amp;quot; SUCCESS
    card &amp;quot;returns: /real/path/to/project&amp;quot; SUCCESS

    card &amp;quot;configPath.get()&amp;quot; SUCCESS
    card &amp;quot;mengembalikan: site.yml&amp;quot; SUCCESS

    card &amp;quot;resolve()&amp;quot; SUCCESS
    card &amp;quot;/real/path/to/project/site.yml&amp;quot; SUCCESS

    card &amp;quot;ada()&amp;quot; SUCCESS
    card &amp;quot;benar ✅&amp;quot; SUCCESS
}
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_masalah_4_afterevaluate_dan_nullpointerexception&amp;quot;&amp;gt;Masalah #4 : afterEvaluate dan NullPointerException&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_diagnosa_4&amp;quot;&amp;gt;Diagnosa&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Plugin ini menerapkan JBake dalam sebuah blok`afterEvaluate`, dan mengakses`buildDirectory.dir()`:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;project.afterEvaluate {
    // ...
    project.tasks.withType(JBakeTask::class.java)
        .getByName(&amp;quot;bake&amp;quot;).apply {
            output = project.layout.buildDirectory
                .dir(site.bake.destDirPath)  // ❌ NPE ici !
                .get()
                .asFile
        }
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Mock dari`buildDirectory.dir()&amp;lt;code&amp;gt;kembali`null&amp;lt;/code&amp;gt;.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_solusi_4&amp;quot;&amp;gt;solusi&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;mengejek`afterEvaluate`agar dieksekusi segera, dan mengkonfigurasi sepenuhnya`buildDirectory`:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;private fun createMockProject(): Pair&amp;amp;lt;Project, PluginContainer&amp;amp;gt; {
    // ... autres mocks ...

    val mockBuildDirectory = mock&amp;amp;lt;DirectoryProperty&amp;amp;gt;()
    val buildDir = File(projectDir, &amp;quot;build&amp;quot;)

    // Mocker dir() pour retourner un Provider valide
    whenever(mockBuildDirectory.dir(any&amp;amp;lt;String&amp;amp;gt;())).doAnswer { invocation -&amp;amp;gt;
        val path = invocation.arguments[0] as String
        val mockDirProvider = mock&amp;amp;lt;Provider&amp;amp;lt;Directory&amp;amp;gt;&amp;amp;gt;()
        val mockDir = mock&amp;amp;lt;Directory&amp;amp;gt;()

        whenever(mockDir.asFile).thenReturn(File(buildDir, path))
        whenever(mockDirProvider.get()).thenReturn(mockDir)

        mockDirProvider
    }

    // Mocker afterEvaluate pour exécution immédiate
    whenever(mockProject.afterEvaluate(any&amp;amp;lt;Action&amp;amp;lt;Project&amp;amp;gt;&amp;amp;gt;())).doAnswer { invocation -&amp;amp;gt;
        val action = invocation.arguments[0] as Action&amp;amp;lt;Project&amp;amp;gt;
        action.execute(mockProject)  // ✅ Exécution synchrone
        null
    }

    return Pair(mockProject, mockPluginContainer)
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
participant &amp;quot;uji&amp;quot; as T
participant &amp;quot;BakeryPlugin&amp;quot; as BP
participant &amp;quot;proyek tiruan&amp;quot; as MP
participant &amp;quot;afterEvaluate Action&amp;quot; as AE

T -&amp;amp;gt; BP: apply(project)
activate BP
BP -&amp;amp;gt; MP: afterEvaluate { ... }
activate MP
note right
  Mock configuré pour
  exécuter immédiatement
end note
MP -&amp;amp;gt; AE: execute(project)
activate AE
AE -&amp;amp;gt; MP: layout.buildDirectory.dir(&amp;quot;memanggang&amp;quot;)
MP --&amp;amp;gt; AE: Provider&amp;amp;lt;Directory&amp;amp;gt; ✅
AE -&amp;amp;gt; MP: plugins.apply(JBakePlugin)
deactivate AE
MP --&amp;amp;gt; BP:
deactivate MP
BP --&amp;amp;gt; T:
deactivate BP

T -&amp;amp;gt; T: verify(mockPluginContainer).apply(...)
note right: ✅ SUCCÈS !
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_solusi_lengkap&amp;quot;&amp;gt;Solusi lengkap&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Ini fungsi`createMockProject()`akhir, yang memecahkan semua masalah :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;private fun createMockProject(): Pair&amp;amp;lt;Project, PluginContainer&amp;amp;gt; {
    // 1️⃣ CRÉER tous les mocks (pas de nested mocks !)
    val mockPluginContainer = mock&amp;amp;lt;PluginContainer&amp;amp;gt;()
    val mockExtensionContainer = mock&amp;amp;lt;ExtensionContainer&amp;amp;gt;()
    val mockLogger = mock&amp;amp;lt;org.gradle.api.logging.Logger&amp;amp;gt;()
    val mockTaskContainer = mock&amp;amp;lt;TaskContainer&amp;amp;gt;()
    val mockConfigPathProperty = mock&amp;amp;lt;Property&amp;amp;lt;String&amp;amp;gt;&amp;amp;gt;()
    val mockBakeryExtension = mock&amp;amp;lt;BakeryExtension&amp;amp;gt;()
    val mockProjectDirectory = mock&amp;amp;lt;Directory&amp;amp;gt;()
    val mockBuildDirectory = mock&amp;amp;lt;DirectoryProperty&amp;amp;gt;()
    val mockProjectLayout = mock&amp;amp;lt;ProjectLayout&amp;amp;gt;()
    val mockProject = mock&amp;amp;lt;Project&amp;amp;gt;()

    // 2️⃣ CONFIGURER la résolution des chemins
    val configFile = File(&amp;quot;../../site.yml&amp;quot;).canonicalFile
    val projectDir = configFile.parentFile
    val buildDir = File(projectDir, &amp;quot;build&amp;quot;)

    whenever(mockConfigPathProperty.get()).thenReturn(&amp;quot;site.yml&amp;quot;)
    whenever(mockConfigPathProperty.isPresent).thenReturn(true)
    whenever(mockBakeryExtension.configPath).thenReturn(mockConfigPathProperty)
    whenever(mockProjectDirectory.asFile).thenReturn(projectDir)

    // 3️⃣ CONFIGURER buildDirectory avec dir()
    whenever(mockBuildDirectory.dir(any&amp;amp;lt;String&amp;amp;gt;())).doAnswer { invocation -&amp;amp;gt;
        val path = invocation.arguments[0] as String
        val mockDirProvider = mock&amp;amp;lt;Provider&amp;amp;lt;Directory&amp;amp;gt;&amp;amp;gt;()
        val mockDir = mock&amp;amp;lt;Directory&amp;amp;gt;()
        whenever(mockDir.asFile).thenReturn(File(buildDir, path))
        whenever(mockDirProvider.get()).thenReturn(mockDir)
        mockDirProvider
    }

    // 4️⃣ ASSEMBLER le projet
    whenever(mockProjectLayout.projectDirectory).thenReturn(mockProjectDirectory)
    whenever(mockProjectLayout.buildDirectory).thenReturn(mockBuildDirectory)

    whenever(mockExtensionContainer.create(&amp;quot;bakery&amp;quot;, BakeryExtension::class.java))
        .thenReturn(mockBakeryExtension)
    whenever(mockExtensionContainer.getByType(BakeryExtension::class.java))
        .thenReturn(mockBakeryExtension)

    whenever(mockProject.extensions).thenReturn(mockExtensionContainer)
    whenever(mockProject.plugins).thenReturn(mockPluginContainer)
    whenever(mockProject.tasks).thenReturn(mockTaskContainer)
    whenever(mockProject.layout).thenReturn(mockProjectLayout)
    whenever(mockProject.logger).thenReturn(mockLogger)
    whenever(mockProject.projectDir).thenReturn(projectDir)

    // 5️⃣ CONFIGURER afterEvaluate pour exécution immédiate
    whenever(mockProject.afterEvaluate(any&amp;amp;lt;Action&amp;amp;lt;Project&amp;amp;gt;&amp;amp;gt;())).doAnswer { invocation -&amp;amp;gt;
        val action = invocation.arguments[0] as Action&amp;amp;lt;Project&amp;amp;gt;
        action.execute(mockProject)
        null
    }

    return Pair(mockProject, mockPluginContainer)
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Dan uji akhir yang lolos:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;@Test
fun `plugin applies jbake gradle plugin`() {
    val (project, mockPluginContainer) = createMockProject()
    val plugin = BakeryPlugin()

    plugin.apply(project)

    verify(mockPluginContainer).apply(JBakePlugin::class.java) // ✅ SUCCÈS !
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_pelajaran_yang_dipelajari&amp;quot;&amp;gt;Pelajaran yang dipelajari&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startmindmap
* Mocking avec Mockito
** ✅ Bonnes pratiques
*** Créer tous les mocks d&amp;#39;abord
*** Configurer avec whenever() séparément
*** Vérifier le mock exact, pas un proxy
*** Tester la résolution des chemins
** ❌ Pièges à éviter
*** mock() dans un bloc de configuration
*** Vérifier project.x au lieu de mockX
*** Ignorer les blocs afterEvaluate
*** Retourner null pour les Providers
@endmindmap&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_memeriksa_mock_yang_benar&amp;quot;&amp;gt;Memeriksa mock yang benar&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;// ❌ FAUX
verify(project.plugins).apply(JBakePlugin::class.java)

// ✅ CORRECT
val (project, mockPluginContainer) = createMockProject()
verify(mockPluginContainer).apply(JBakePlugin::class.java)&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_2_menghindari_mock_bersarang&amp;quot;&amp;gt;2. Menghindari mock bersarang&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;// ❌ FAUX - UnfinishedStubbingException
val mockProject = mock&amp;amp;lt;Project&amp;amp;gt; {
    on { logger } doReturn mock()  // Nested mock creation !
}

// ✅ CORRECT - Créer séparément
val mockLogger = mock&amp;amp;lt;org.gradle.api.logging.Logger&amp;amp;gt;()
val mockProject = mock&amp;amp;lt;Project&amp;amp;gt;()
whenever(mockProject.logger).thenReturn(mockLogger)&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_3_meniru_callback_evaluasi&amp;quot;&amp;gt;3. Meniru callback evaluasi&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;// ✅ afterEvaluate doit s&amp;#39;exécuter pour les tests
whenever(mockProject.afterEvaluate(any())).doAnswer { invocation -&amp;amp;gt;
    val action = invocation.arguments[0] as Action&amp;amp;lt;Project&amp;amp;gt;
    action.execute(mockProject)
    null
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_4_menguji_resolusi_jalur&amp;quot;&amp;gt;4. Menguji resolusi jalur&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;// Toujours vérifier que les chemins se résolvent correctement
val extension = project.extensions.getByType(BakeryExtension::class.java)
val configPath = extension.configPath.get()
val projectDir = project.layout.projectDirectory.asFile
val resolvedConfig = projectDir.resolve(configPath)

println(&amp;quot;Resolved config: ${resolvedConfig.absolutePath}&amp;quot;)
println(&amp;quot;Exists: ${resolvedConfig.exists()}&amp;quot;)&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_arsitektur_pengujian_akhir&amp;quot;&amp;gt;Arsitektur pengujian akhir&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
package &amp;quot;lapisan ujian&amp;quot; {
    [Test Class] --&amp;amp;gt; [createMockProject()]
}

package &amp;quot;Pembuatan Mock&amp;quot; {
    [createMockProject()] --&amp;amp;gt; [Create All Mocks]
    [Create All Mocks] --&amp;amp;gt; [Configure Paths]
    [Configure Paths] --&amp;amp;gt; [Configure Callbacks]
    [Configure Callbacks] --&amp;amp;gt; [Assemble Project]
}

package &amp;quot;Objek tiruan&amp;quot; {
    [Assemble Project] --&amp;amp;gt; [Project Mock]
    [Assemble Project] --&amp;amp;gt; [PluginContainer Mock]
    [Assemble Project] --&amp;amp;gt; [Extension Mocks]
    [Assemble Project] --&amp;amp;gt; [Layout Mocks]
}

package &amp;quot;Plugin yang sedang diuji&amp;quot; {
    [Test Class] --&amp;amp;gt; [BakeryPlugin]
    [BakeryPlugin] --&amp;amp;gt; [Project Mock]
    [BakeryPlugin] --&amp;amp;gt; [PluginContainer Mock]
}

package &amp;quot;verifikasi&amp;quot; {
    [Test Class] --&amp;amp;gt; [verify(mockPluginContainer)]
}
@enduml&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_kesimpulan&amp;quot;&amp;gt;Kesimpulan&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Yang seolah-olah hanya sebuah masalah tes sederhana ternyata menjadi studi kasus yang sangat baik tentang :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Kehalusan Mockito dengan Kotlin&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Pentingnya melakukan mock terhadap objek yang tepat&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Pengelolaan callback asinkron dalam pengujian&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Penyelesaian jalur dalam plugin Gradle&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Debugging metodis, dengan memahami setiap lapisan masalah, memungkinkan untuk mencapai solusi yang kuat dan dapat dipelihara.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;admonitionblock tip&amp;quot;&amp;gt;
&amp;lt;table&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;icon&amp;quot;&amp;gt;
&amp;lt;i class=&amp;quot;fa icon-tip&amp;quot; title=&amp;quot;Tip&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Tips untuk tes Anda&amp;lt;/strong&amp;gt;Jika Anda menemukan &amp;quot;Wanted but not invoked&amp;quot; dengan Mockito, selalu tanya pada diri sendiri :
1. Apakah saya sedang memeriksa mock yang benar?
2. Apakah semua mock saya dibuat di luar blok konfigurasi?
3. Apakah callback saya benar-benar dijalankan?
4. Apakah jalur saya diselesaikan dengan benar?&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_sumber_daya&amp;quot;&amp;gt;Sumber daya&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://github.com/mockito/mockito-kotlin&amp;quot;&amp;gt;Dokumentasi Mockito-Kotlin&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://docs.gradle.org/current/userguide/custom_plugins.html&amp;quot;&amp;gt;Panduan Pengembangan Plugin Gradle&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://jbake.org/&amp;quot;&amp;gt;JBake Static Site Generator&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;hr&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;em&amp;gt;Anda pernah mengalami masalah serupa dalam tes Anda? Bagikan pengalaman Anda di komentar !&amp;lt;/em&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>Kotlin Arrow : Menguasai navigasi dalam monade Either</title>
            <link >https://cheroliv.com/blog/2024/0075_kotlin_arrow-traverse_right_and_left_post.html</link>
            <pubDate>Tue, 24 Sep 2024 00:00:00 +0000</pubDate>
            <guid isPermaLink="false">blog/2024/0075_kotlin_arrow-traverse_right_and_left_post.html</guid>
            <description>&amp;lt;div id=&amp;quot;toc&amp;quot; class=&amp;quot;toc&amp;quot;&amp;gt;
&amp;lt;div id=&amp;quot;toctitle&amp;quot;&amp;gt;Sommaire&amp;lt;/div&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel1&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_pendahuluan&amp;quot;&amp;gt;Pendahuluan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_apa_itu_monade_either&amp;quot;&amp;gt;Apa itu monade Either?&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_mengapa_menggunakan_either&amp;quot;&amp;gt;Mengapa menggunakan Either?&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_konteks&amp;quot;&amp;gt;Konteks&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_berbagai_pendekatan&amp;quot;&amp;gt;Berbagai pendekatan&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_pendekatan_klasik_dengan_when&amp;quot;&amp;gt;Pendekatan klasik dengan `when&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_penggunaan_fold_untuk_pendekatan_yang_lebih_ringkas&amp;quot;&amp;gt;Penggunaan &amp;lt;code&amp;gt;fold&amp;lt;/code&amp;gt; untuk pendekatan yang lebih ringkas&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_transformasi_dengan_map_dan_mapleft&amp;quot;&amp;gt;Transformasi dengan &amp;lt;code&amp;gt;map&amp;lt;/code&amp;gt; dan `mapLeft&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_penanganan_error_dengan_getorelse&amp;quot;&amp;gt;Penanganan error dengan `getOrElse&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_aksi_samping_dengan_onleft_dan_onright&amp;quot;&amp;gt;Aksi samping dengan &amp;lt;code&amp;gt;onLeft&amp;lt;/code&amp;gt; dan `onRight&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_rantai_operasi_dengan_flatmap&amp;quot;&amp;gt;Rantai operasi dengan `flatMap&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_transformasi_dua_arah_dengan_bimap&amp;quot;&amp;gt;Transformasi dua arah dengan `bimap&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_inversi_sisi_dengan_swap&amp;quot;&amp;gt;Inversi sisi dengan `swap&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_penggunaan_dari_tap_dan_tapleft&amp;quot;&amp;gt;Penggunaan dari &amp;lt;code&amp;gt;tap&amp;lt;/code&amp;gt; dan &amp;lt;code&amp;gt;tapLeft&amp;lt;/code&amp;gt; :&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_penggunaan_recover&amp;quot;&amp;gt;Penggunaan &amp;lt;code&amp;gt;recover&amp;lt;/code&amp;gt;:&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_kasus_penggunaan_praktis&amp;quot;&amp;gt;Kasus penggunaan praktis&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_kesimpulan&amp;quot;&amp;gt;Kesimpulan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_untuk_pergi_lebih_jauh&amp;quot;&amp;gt;Untuk pergi lebih jauh&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_pendahuluan&amp;quot;&amp;gt;Pendahuluan&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;&amp;lt;em&amp;gt;Sasaran :&amp;lt;/em&amp;gt; Pengembang Kotlin tingkat menengah hingga lanjut.&amp;lt;/div&amp;gt;
&amp;lt;p&amp;gt;Di dunia pemrograman fungsional di Kotlin, perpustakaan Arrow menawarkan alat yang kuat untuk mengelola kesalahan dan kasus alternatif. Di antara alat-alat ini, monade`Either`ditandai oleh kemampuannya untuk mewakili dua keadaan yang mungkin: keberhasilan (Right) atau kegagalan (Left). Namun bagaimana cara menavigasi secara efektif antara kedua keadaan ini? Itulah yang akan kita jelajahi dalam artikel ini.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_apa_itu_monade_either&amp;quot;&amp;gt;Apa itu monade Either?&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Monad`Either`adalah sebuah struktur data yang merepresentasikan dua kemungkinan yang saling eksklusif. Di Kotlin dengan Arrow, ia sering digunakan untuk mengelola kasus keberhasilan dan kegagalan suatu operasi, menawarkan alternatif yang elegan terhadap pengecualian tradisional.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_mengapa_menggunakan_either&amp;quot;&amp;gt;Mengapa menggunakan Either?&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;olist arabic&amp;quot;&amp;gt;
&amp;lt;ol class=&amp;quot;arabic&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Penanganan eksplisit kesalahan: Memaksa developer untuk mempertimbangkan kasus kegagalan.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Komposisi fungsional: Memudahkan rantai operasi yang dapat gagal.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Keamanan tipe : Kesalahan telah diberi tipe, yang membantu mengelola mereka dengan lebih presisi.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Tanpa pengecualian: Hindari efek samping dan interupsi yang tidak terduga dalam aliran eksekusi.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_konteks&amp;quot;&amp;gt;Konteks&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Bayangkan Anda sedang mengembangkan sebuah API REST denganhttps://spring.io/projects/spring-boot/[Spring Boot,windows=read-later] et &amp;lt;a href=&amp;quot;https://kotlinlang.org/&amp;quot;&amp;gt;Kotlin&amp;lt;/a&amp;gt;, menggunakan perpustakaanhttps://arrow-kt.io/[Panah,Windows=baca-nanti]. Anda memiliki sebuah fungsi`findOneUserByEmail`yang mengembalikan`Either&amp;amp;lt;Throwable, User&amp;amp;gt;`. Bagaimana Anda dapat menangani hasil ini dengan cara yang elegan dan fungsional?&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Kami memiliki kelas User :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;@file:Suppress(
    &amp;quot;RemoveRedundantQualifierName&amp;quot;,
    &amp;quot;MemberVisibilityCanBePrivate&amp;quot;,
    &amp;quot;SqlNoDataSourceInspection&amp;quot;
)

package webapp.users

import arrow.core.Either
import arrow.core.left
import arrow.core.right
import com.fasterxml.jackson.annotation.JsonIgnore
import com.fasterxml.jackson.databind.ObjectMapper
import jakarta.validation.constraints.NotNull
import jakarta.validation.constraints.Pattern
import jakarta.validation.constraints.Size
import org.springframework.beans.factory.getBean
import org.springframework.context.ApplicationContext
import org.springframework.data.r2dbc.core.R2dbcEntityTemplate
import org.springframework.r2dbc.core.DatabaseClient
import org.springframework.r2dbc.core.awaitOne
import org.springframework.r2dbc.core.awaitRowsUpdated
import webapp.core.property.ANONYMOUS_USER
import webapp.core.property.EMPTY_STRING
import webapp.core.utils.AppUtils.cleanField
import webapp.users.EntityModel.Companion.ID_MEMBER
import webapp.users.User.UserDao.Attributes.EMAIL_ATTR
import webapp.users.User.UserDao.Attributes.ID_ATTR
import webapp.users.User.UserDao.Attributes.LANG_KEY_ATTR
import webapp.users.User.UserDao.Attributes.LOGIN_ATTR
import webapp.users.User.UserDao.Attributes.PASSWORD_ATTR
import webapp.users.User.UserDao.Attributes.VERSION_ATTR
import webapp.users.User.UserDao.Constraints.LOGIN_REGEX
import webapp.users.User.UserDao.Fields.EMAIL_FIELD
import webapp.users.User.UserDao.Fields.ID_FIELD
import webapp.users.User.UserDao.Fields.LANG_KEY_FIELD
import webapp.users.User.UserDao.Fields.LOGIN_FIELD
import webapp.users.User.UserDao.Fields.PASSWORD_FIELD
import webapp.users.User.UserDao.Fields.VERSION_FIELD
import webapp.users.User.UserDao.Relations.INSERT
import webapp.users.security.Role
import webapp.users.security.Role.RoleDao
import webapp.users.security.UserRole.UserRoleDao
import java.util.*
import jakarta.validation.constraints.Email as EmailConstraint

data class User(
    override val id: UUID? = null,

    @field:NotNull
    @field:Pattern(regexp = LOGIN_REGEX)
    @field:Size(min = 1, max = 50)
    val login: String,

    @JsonIgnore
    @field:NotNull
    @field:Size(min = 60, max = 60)
    val password: String = EMPTY_STRING,

    @field:EmailConstraint
    @field:Size(min = 5, max = 254)
    val email: String = EMPTY_STRING,

    @JsonIgnore
    val roles: MutableSet&amp;amp;lt;Role&amp;amp;gt; = mutableSetOf(Role(ANONYMOUS_USER)),

    @field:Size(min = 2, max = 10)
    val langKey: String = EMPTY_STRING,

    @JsonIgnore
    val version: Long = -1,
) : EntityModel&amp;amp;lt;UUID&amp;amp;gt;() {

    companion object {
        @JvmStatic
        fun main(args: Array&amp;amp;lt;String&amp;amp;gt;) = println(UserDao.Relations.sqlScript)
    }

    object UserDao {
        object Constraints {
            // Regex for acceptable logins
            const val LOGIN_REGEX =
                &amp;quot;^(?&amp;amp;gt;[a-zA-Z0-9!$&amp;amp;amp;*+=?^_`{|}~.-]+@[a-zA-Z0-9-]+(?:\\.[a-zA-Z0-9-]+)*)|(?&amp;amp;gt;[_.@A-Za-z0-9-]+)$&amp;quot;
            const val PASSWORD_MIN: Int = 4
            const val PASSWORD_MAX: Int = 16
            const val IMAGE_URL_DEFAULT = &amp;quot;https://placehold.it/50x50&amp;quot;
            const val PHONE_REGEX = &amp;quot;^(\\+|00)?[1-9]\\d{0,49}\$&amp;quot;
        }

        object Members {
            const val PASSWORD_MEMBER = &amp;quot;password&amp;quot;
            const val ROLES_MEMBER = &amp;quot;roles&amp;quot;
        }

        object Fields {
            const val ID_FIELD = &amp;quot;`id`&amp;quot;
            const val LOGIN_FIELD = &amp;quot;`login`&amp;quot;
            const val PASSWORD_FIELD = &amp;quot;`password`&amp;quot;
            const val EMAIL_FIELD = &amp;quot;`email`&amp;quot;
            const val LANG_KEY_FIELD = &amp;quot;`lang_key`&amp;quot;
            const val VERSION_FIELD = &amp;quot;`version`&amp;quot;
        }

        object Attributes {
            val ID_ATTR = ID_FIELD.cleanField()
            val LOGIN_ATTR = LOGIN_FIELD.cleanField()
            val PASSWORD_ATTR = PASSWORD_FIELD.cleanField()
            val EMAIL_ATTR = EMAIL_FIELD.cleanField()
            const val LANG_KEY_ATTR = &amp;quot;langKey&amp;quot;
            val VERSION_ATTR = VERSION_FIELD.cleanField()
        }

        object Relations {
            const val TABLE_NAME = &amp;quot;`user`&amp;quot;
            const val SQL_SCRIPT = &amp;quot;&amp;quot;&amp;quot;
            CREATE TABLE IF NOT EXISTS $TABLE_NAME (
                $ID_FIELD                     UUID default random_uuid() PRIMARY KEY,
                $LOGIN_FIELD                  VARCHAR,
                $PASSWORD_FIELD               VARCHAR,
                $EMAIL_FIELD                  VARCHAR,
                $LANG_KEY_FIELD               VARCHAR,
                $VERSION_FIELD                bigint
            );
            CREATE UNIQUE INDEX IF NOT EXISTS `uniq_idx_user_login`
            ON $TABLE_NAME ($LOGIN_FIELD);
            CREATE UNIQUE INDEX IF NOT EXISTS `uniq_idx_user_email`
            ON $TABLE_NAME ($EMAIL_FIELD);
&amp;quot;&amp;quot;&amp;quot;

            @Suppress(&amp;quot;SqlDialectInspection&amp;quot;)
            const val INSERT = &amp;quot;&amp;quot;&amp;quot;
            insert into $TABLE_NAME (
                $LOGIN_FIELD, $EMAIL_FIELD,
                $PASSWORD_FIELD, $LANG_KEY_FIELD,
                $VERSION_FIELD
            ) values ( :login, :email, :password, :langKey, :version)&amp;quot;&amp;quot;&amp;quot;

            @JvmStatic
            val sqlScript: String
                get() = setOf(
                    UserDao.Relations.SQL_SCRIPT,
                    RoleDao.Relations.SQL_SCRIPT,
                    UserRoleDao.Relations.SQL_SCRIPT
                ).joinToString(&amp;quot;&amp;quot;)
                    .trimMargin()
        }

        object Dao {
            val Pair&amp;amp;lt;User, ApplicationContext&amp;amp;gt;.toJson: String
                get() = second.getBean&amp;amp;lt;ObjectMapper&amp;amp;gt;().writeValueAsString(first)

            suspend fun Pair&amp;amp;lt;User, ApplicationContext&amp;amp;gt;.save(): Either&amp;amp;lt;Throwable, Long&amp;amp;gt; = try {
                second.getBean&amp;amp;lt;R2dbcEntityTemplate&amp;amp;gt;()
                    .databaseClient
                    .sql(INSERT)
                    .bind(LOGIN_ATTR, first.login)
                    .bind(EMAIL_ATTR, first.email)
                    .bind(PASSWORD_ATTR, first.password)
                    .bind(LANG_KEY_ATTR, first.langKey)
                    .bind(VERSION_ATTR, first.version)
                    .fetch()
                    .awaitRowsUpdated()
                    .right()
            } catch (e: Throwable) {
                e.left()
            }


            suspend fun ApplicationContext.findOneUserByEmail(
                email: String
            ): Either&amp;amp;lt;Throwable, User&amp;amp;gt; = try {
                getBean&amp;amp;lt;DatabaseClient&amp;amp;gt;()
                    .sql(&amp;quot;SELECT * FROM `user` WHERE LOWER(email) = LOWER(:email)&amp;quot;)
                    .bind(&amp;quot;email&amp;quot;, email)
                    .fetch()
                    .awaitOne()
                    .let { row -&amp;amp;gt;
                        User(
                            id = row[ID_ATTR] as UUID?,
                            login = row[LOGIN_ATTR] as String,
                            password = row[PASSWORD_ATTR] as String,
                            email = row[EMAIL_ATTR] as String,
                            langKey = row[LANG_KEY_ATTR] as String,
                            version = row[VERSION_ATTR] as Long
                        )
                    }.right()
            } catch (e: Throwable) {
                e.left()
            }
        }
    }

    /** Account REST API URIs */
    object UserRestApis {
        const val API_AUTHORITY = &amp;quot;/api/authorities&amp;quot;
        const val API_USERS = &amp;quot;/api/users&amp;quot;
        const val API_SIGNUP = &amp;quot;/signup&amp;quot;
        const val API_SIGNUP_PATH = &amp;quot;$API_USERS$API_SIGNUP&amp;quot;
        const val API_ACTIVATE = &amp;quot;/activate&amp;quot;
        const val API_ACTIVATE_PATH = &amp;quot;$API_USERS$API_ACTIVATE?key=&amp;quot;
        const val API_ACTIVATE_PARAM = &amp;quot;{activationKey}&amp;quot;
        const val API_ACTIVATE_KEY = &amp;quot;key&amp;quot;
        const val API_RESET_INIT = &amp;quot;/reset-password/init&amp;quot;
        const val API_RESET_FINISH = &amp;quot;/reset-password/finish&amp;quot;
        const val API_CHANGE = &amp;quot;/change-password&amp;quot;
        const val API_CHANGE_PATH = &amp;quot;$API_USERS$API_CHANGE&amp;quot;
    }
}

// Abstract entity model with Generic ID, which can be of any type
abstract class EntityModel&amp;amp;lt;T&amp;amp;gt;(
    open val id: T? = null
) {
    companion object {
        const val ID_MEMBER = &amp;quot;id&amp;quot;
    }
}

// Generic extension function that allows the ID to be applied to any EntityModel type
inline fun &amp;amp;lt;reified T : EntityModel&amp;amp;lt;ID&amp;amp;gt;, ID&amp;amp;gt; T.withId(id: ID): T {
    // Use reflection to create a copy with the passed ID
    return this::class.constructors.first { it.parameters.any { param -&amp;amp;gt; param.name == ID_MEMBER } }
        .call(id, *this::class.constructors.first().parameters.drop(1).map { param -&amp;amp;gt;
            this::class.members.first { member -&amp;amp;gt; member.name == param.name }.call(this)
        }.toTypedArray())
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_berbagai_pendekatan&amp;quot;&amp;gt;Berbagai pendekatan&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_pendekatan_klasik_dengan_when&amp;quot;&amp;gt;Pendekatan klasik dengan `when&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;val user: User by lazy { userFactory(USER) }

val result: Either&amp;amp;lt;Throwable, User&amp;amp;gt; = context.findOneUserByEmail(user.email)

when (result) {
    is Either.Left -&amp;amp;gt; {
        val error = result.value
        println(&amp;quot;Erreur : ${error.message}&amp;quot;)
    }
    is Either.Right -&amp;amp;gt; {
        val user = result.value
        println(&amp;quot;Utilisateur trouvé : ${user.login}&amp;quot;)
    }
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Metode ini, meskipun sederhana dan dapat dibaca, tidak sepenuhnya memanfaatkan kemampuan fungsional Arrow.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_penggunaan_fold_untuk_pendekatan_yang_lebih_ringkas&amp;quot;&amp;gt;Penggunaan &amp;lt;code&amp;gt;fold&amp;lt;/code&amp;gt; untuk pendekatan yang lebih ringkas&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;result.fold(
{ error -&amp;amp;gt; println(&amp;quot;Erreur : ${error.message}&amp;quot;) },
{ user -&amp;amp;gt; println(&amp;quot;Utilisateur trouvé : ${user.login}&amp;quot;) }
)&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;`fold`memungkinkan menentukan tindakan untuk kedua kasus (Left dan Right) dengan cara yang ringkas dan elegan.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_transformasi_dengan_map_dan_mapleft&amp;quot;&amp;gt;Transformasi dengan &amp;lt;code&amp;gt;map&amp;lt;/code&amp;gt; dan `mapLeft&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;val processedResult = result
    .map { user -&amp;amp;gt; &amp;quot;Utilisateur trouvé : ${user.login}&amp;quot; }
    .mapLeft { error -&amp;amp;gt; &amp;quot;Erreur : ${error.message}&amp;quot; }

println(processedResult.merge())&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Pendekatan ini memungkinkan untuk mengubah nilai-nilai yang terkandung dalam Either sambil menjaga struktur-nya, ideal untuk rantai pemrosesan yang lebih kompleks.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_penanganan_error_dengan_getorelse&amp;quot;&amp;gt;Penanganan error dengan `getOrElse&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;val user = result.getOrElse { error -&amp;amp;gt;
    println(&amp;quot;Erreur : ${error.message}&amp;quot;)
    User(login = &amp;quot;default&amp;quot;, email = &amp;quot;default@example.com&amp;quot;) // utilisateur par défaut
}
println(&amp;quot;Login : ${user.login}&amp;quot;)&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;`getOrElse`menawarkan penanganan kesalahan yang elegan dengan memungkinkan penyediaan nilai default.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_aksi_samping_dengan_onleft_dan_onright&amp;quot;&amp;gt;Aksi samping dengan &amp;lt;code&amp;gt;onLeft&amp;lt;/code&amp;gt; dan `onRight&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;result.onLeft { error -&amp;amp;gt; println(&amp;quot;Erreur : ${error.message}&amp;quot;) }
.onRight { user -&amp;amp;gt; println(&amp;quot;Utilisateur trouvé : ${user.login}&amp;quot;) }&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Metode-metode ini memungkinkan melakukan tindakan di setiap sisi tanpa mengubah Either, sempurna untuk logging atau efek samping ringan.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_rantai_operasi_dengan_flatmap&amp;quot;&amp;gt;Rantai operasi dengan `flatMap&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;fun findUser(email: String): Either&amp;amp;lt;Throwable, User&amp;amp;gt; = // ... implémentation

fun getUserPermissions(user: User): Either&amp;amp;lt;Throwable, List&amp;amp;lt;String&amp;amp;gt;&amp;amp;gt; = // ... implémentation

val userPermissions = findUser(&amp;quot;user@example.com&amp;quot;)
    .flatMap { user -&amp;amp;gt; getUserPermissions(user) }&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;code&amp;gt;flatMap`adalah berguna untuk merai operasi yang mengembalikan sendiri`Either&amp;lt;/code&amp;gt;, sehingga menghindari`Either`bersarang.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_transformasi_dua_arah_dengan_bimap&amp;quot;&amp;gt;Transformasi dua arah dengan `bimap&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;val result: Either&amp;amp;lt;Throwable, User&amp;amp;gt; = // ... obtention du résultat
val processedResult = result.bimap(
    { error -&amp;amp;gt; &amp;quot;Erreur: ${error.message}&amp;quot; },
    { user -&amp;amp;gt; &amp;quot;Utilisateur: ${user.login}&amp;quot; }
)&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;`bimap`memungkinkan mengubah baik sisi kiri maupun sisi kanan dalam satu operasi.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_inversi_sisi_dengan_swap&amp;quot;&amp;gt;Inversi sisi dengan `swap&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;val result: Either&amp;amp;lt;Throwable, User&amp;amp;gt; = // ... obtention du résultat
val swapped = result.swap()&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;code&amp;gt;swap`berguna ketika Anda ingin membalikkan sisi dari satu`Either&amp;lt;/code&amp;gt;, misalnya untuk menyesuaikan antarmuka satu fungsi dengan fungsi lain.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_penggunaan_dari_tap_dan_tapleft&amp;quot;&amp;gt;Penggunaan dari &amp;lt;code&amp;gt;tap&amp;lt;/code&amp;gt; dan &amp;lt;code&amp;gt;tapLeft&amp;lt;/code&amp;gt; :&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;result.tap { user -&amp;amp;gt; println(&amp;quot;Utilisateur trouvé : ${user.login}&amp;quot;) }
.tapLeft { error -&amp;amp;gt; println(&amp;quot;Erreur : ${error.message}&amp;quot;) }&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;serupa dengan`onLeft` et &amp;lt;code&amp;gt;onRight&amp;lt;/code&amp;gt;, tetapi metode-metode ini mengembalikan Either aslinya, yang berguna untuk menautkan operasi.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_penggunaan_recover&amp;quot;&amp;gt;Penggunaan &amp;lt;code&amp;gt;recover&amp;lt;/code&amp;gt;:&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;val recoveredUser = result.recover { error -&amp;amp;gt;
    println(&amp;quot;Erreur récupérée : ${error.message}&amp;quot;)
    User(login = &amp;quot;recovered&amp;quot;, email = &amp;quot;recovered@example.com&amp;quot;)
}
println(&amp;quot;Login : ${recoveredUser.login}&amp;quot;)&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Metode ini memungkinkan mengubah Either.Left menjadi Either.Right dengan menyediakan nilai pengganti.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_kasus_penggunaan_praktis&amp;quot;&amp;gt;Kasus penggunaan praktis&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Gunakan`fold`untuk operasi sederhana yang memerlukan pengolahan untuk setiap kasus. Pilih`map` et &amp;lt;code&amp;gt;mapLeft`untuk transformasi data tanpa mengubah struktur dari`Either&amp;lt;/code&amp;gt;. - Pilih`flatMap`saat menautkan operasi yang dapat gagal. - Gunakan`recover`untuk menyediakan nilai default jika terjadi kesalahan. - Pilih`onLeft` et &amp;lt;code&amp;gt;onRight&amp;lt;/code&amp;gt; (ou &amp;lt;code&amp;gt;tap&amp;lt;/code&amp;gt; et &amp;lt;code&amp;gt;tapLeft&amp;lt;/code&amp;gt;) untuk efek samping seperti logging - Gunakan`bimap`untuk mengubah kedua sisi menjadi satu operasi. - Terapkan`swap`ketika Anda harus menyesuaikan antarmuka sebuah fungsi dengan fungsi lain.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_kesimpulan&amp;quot;&amp;gt;Kesimpulan&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Masing-masing pendekatan ini memiliki kelebihannya tergantung pada konteks penggunaannya. Metode-metode seperti`fold`, &amp;lt;code&amp;gt;map&amp;lt;/code&amp;gt;/&amp;lt;code&amp;gt;mapLeft&amp;lt;/code&amp;gt;, et `recover`Sangat berguna ketika Anda ingin menghubungkan beberapa operasi atau mengubah data dengan cara fungsional.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Monade`Either`d&amp;amp;#8217;Arrow menawarkan fleksibilitas yang luar biasa untuk menangani kasus kesuksesan dan kesalahan dalam aplikasi Kotlin Anda. Dengan menguasai pendekatan-pendekatan ini, Anda akan dapat menulis kode yang lebih kuat, lebih mudah dibaca, dan lebih fungsional.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Dalam proyek Anda berikutnya, jangan ragu untuk menjelajah teknik-teknik ini untuk mendapatkan maksimal dari pemrograman fungsional dengan Kotlin dan Arrow!&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_untuk_pergi_lebih_jauh&amp;quot;&amp;gt;Untuk pergi lebih jauh&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Dokumentasi resmi Arrow :https://arrow-kt.io/docs/apidocs/arrow-core/arrow.core/-either/[Panah Either] - Kotlin Coroutines dengan Arrow :https://arrow-kt.io/docs/fx/[Panah Efek Koroutin]&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Jangan lupa untuk berbagi pengalaman dan teknik favorit Anda untuk bekerja dengan`Either`dalam komentar di bawah ini!&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>Panduan Praktis: pencarian file di Linux</title>
            <link >https://cheroliv.com/blog/2024/0074_seach_file_linux_post.html</link>
            <pubDate>Mon, 23 Sep 2024 00:00:00 +0000</pubDate>
            <guid isPermaLink="false">blog/2024/0074_seach_file_linux_post.html</guid>
            <description>&amp;lt;div id=&amp;quot;toc&amp;quot; class=&amp;quot;toc&amp;quot;&amp;gt;
&amp;lt;div id=&amp;quot;toctitle&amp;quot;&amp;gt;Sommaire&amp;lt;/div&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel1&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_pendahuluan&amp;quot;&amp;gt;Pendahuluan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_penggunaan_perintah_find&amp;quot;&amp;gt;Penggunaan perintah `find&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_opsi_tambahan_untuk_find&amp;quot;&amp;gt;Opsi tambahan untuk `find&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_penggunaan_perintah_locate&amp;quot;&amp;gt;Penggunaan perintah `locate&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_penggunaan_perintah_grep_untuk_mencari_teks_dalam_file&amp;quot;&amp;gt;Penggunaan perintah &amp;lt;code&amp;gt;grep&amp;lt;/code&amp;gt; untuk mencari teks dalam file&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_kesimpulan&amp;quot;&amp;gt;Kesimpulan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_pendahuluan&amp;quot;&amp;gt;Pendahuluan&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;Masalah : Mencari file dalam sistem operasi Linux melalui terminal.&amp;lt;/div&amp;gt;
&amp;lt;p&amp;gt;Dalam artikel ini, kami akan menjelajahi cara mencari file di Ubuntu menggunakan terminal Zsh. Zsh (Z shell) adalah interpreter shell yang powerful dan banyak digunakan karena fleksibilitasnya dan fitur-fitur lanjutannya.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Ubuntu, distribusi Linux populer, menawarkan beberapa alat baris perintah untuk mencari file. Kami akan menunjukkan cara menggunakan perintah seperti`find`, &amp;lt;code&amp;gt;locate&amp;lt;/code&amp;gt; et `grep`untuk mencari file dengan cepat dan efisien di terminal.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_penggunaan_perintah_find&amp;quot;&amp;gt;Penggunaan perintah `find&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Perintah`find`memungkinkan mencari file dalam direktori spesifik dan subdirektorinya. Sintaks dasarnya adalah sebagai berikut:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;find [chemin] -name [nom_du_fichier]&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Ini adalah contoh praktis di mana kami mencari sebuah file bernama`example.txt`di direktori`/home` :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;find /home -name &amp;quot;example.txt&amp;quot;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Perintah ini akan menelusuri semua subdirektori dari`/home`dan tampilkan jalur lengkap file jika ditemukan&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Manual online dari perintah`find`:https://man.archlinux.org/man/find.1.fr/[man find,windows=read-later]&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_opsi_tambahan_untuk_find&amp;quot;&amp;gt;Opsi tambahan untuk `find&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;perintah`find`sangat kuat dan memiliki banyak opsi. Berikut beberapa contoh berguna:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Cari berdasarkan jenis file&amp;lt;/strong&amp;gt;Untuk mencari hanya file, gunakan`-type f`, dan untuk mencari direktori, gunakan`-type d`. - &amp;lt;strong&amp;gt;Cari berdasarkan ukuran&amp;lt;/strong&amp;gt;: Anda juga dapat memfilter file berdasarkan ukurannya menggunakan opsi`-size`. Par exemple, pour rechercher des fichiers de plus de 100 Mo, utilisez :&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;find /home -size +100M&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_penggunaan_perintah_locate&amp;quot;&amp;gt;Penggunaan perintah `locate&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;perintah`locate`lebih cepat daripada`find`Karena ia menggunakan basis data yang sudah diindeks sebelumnya dari file yang ada di sistem. Anda harus terlebih dahulu memperbarui basis data ini dengan perintah berikut:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;sudo updatedb&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Selanjutnya, Anda dapat mencari file seperti ini:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;locate example.txt&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Saya menggunakanhttps://fr.wikipedia.org/wiki/Z_Shell[zsh]denganhttps://github.com/ohmyzsh/ohmyzsh/wiki[oh-my-zsh], ternyata perintah`locate`tidak dapat diakses dari`zsh`tapi mungkin sejak`bash`, untuk mengatasi hal ini, argumen`-c`dari perintah`bash`memungkinkan menjalankan perintah`bash`dari jenis terminal lain.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;di sini saya mencari file-file yang berakhir dengan pola`001.pdf`di direktori pengguna&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;bash -c &amp;quot;locate ~/*001.pdf&amp;quot;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Manual daring perintah`locate` : &amp;lt;a href=&amp;quot;https://man.archlinux.org/man/locate.1.fr/&amp;quot;&amp;gt;manual locate&amp;lt;/a&amp;gt;*&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_penggunaan_perintah_grep_untuk_mencari_teks_dalam_file&amp;quot;&amp;gt;Penggunaan perintah &amp;lt;code&amp;gt;grep&amp;lt;/code&amp;gt; untuk mencari teks dalam file&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Jika Anda ingin mencari teks tertentu di dalam file, Anda dapat menggunakan`grep`. Sebagai contoh, untuk mencari kemunculan kata &amp;quot;Ubuntu&amp;quot; dalam semua file`.txt`dari direktori saat ini, gunakan :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;grep &amp;quot;Ubuntu&amp;quot; *.txt&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_kesimpulan&amp;quot;&amp;gt;Kesimpulan&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Pencarian file di Ubuntu melalui terminal Zsh dapat dilakukan dengan beberapa alat yang kuat.`find`adalah fleksibel dan menawarkan banyak pilihan, sedangkan`locate`memungkinkan pencarian cepat menggunakan basis data pra-dibuat. Terakhir,`grep`memungkinkan untuk menemukan kemunculan teks di dalam file.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Perintah-perintah ini menawarkan serangkaian alat lengkap untuk membuat pencarian file Anda lebih efisien dan disesuaikan dengan kebutuhan Anda, dan setiap perintah ini memiliki manual yang dapat dilihat dengan perintah`man`&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>Catatan Linux</title>
            <link >https://cheroliv.com/blog/2021/0030_memo_linux_post.html</link>
            <pubDate>Mon, 23 Sep 2024 00:00:00 +0000</pubDate>
            <guid isPermaLink="false">blog/2021/0030_memo_linux_post.html</guid>
            <description>&amp;lt;div id=&amp;quot;toc&amp;quot; class=&amp;quot;toc&amp;quot;&amp;gt;
&amp;lt;div id=&amp;quot;toctitle&amp;quot;&amp;gt;Sommaire&amp;lt;/div&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel1&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_papan_ketik&amp;quot;&amp;gt;Papan ketik :&amp;lt;/a&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel2&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_tombol_ctrl&amp;quot;&amp;gt;&amp;lt;em&amp;gt;tombol &amp;amp;lt;Ctrl&amp;amp;gt;&amp;lt;/em&amp;gt;&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_izinotorisasi&amp;quot;&amp;gt;Izin(otorisasi)&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_pencarian_file&amp;quot;&amp;gt;Pencarian file&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_papan_ketik&amp;quot;&amp;gt;Papan ketik :&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_tombol_ctrl&amp;quot;&amp;gt;&amp;lt;em&amp;gt;tombol &amp;amp;lt;Ctrl&amp;amp;gt;&amp;lt;/em&amp;gt;&amp;lt;/h3&amp;gt;
&amp;lt;table class=&amp;quot;tableblock frame-all grid-all stretch&amp;quot;&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://korben.info/les-raccourcis-clavier-pour-bash-terminal-linux-et-macos.html&amp;quot;&amp;gt;tautan&amp;lt;/a&amp;gt;&amp;lt;br&amp;gt;
&amp;lt;a href=&amp;quot;https://blogmotion.fr/systeme/astuces-bash-linux-16175&amp;quot;&amp;gt;link lain&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_izinotorisasi&amp;quot;&amp;gt;Izin(otorisasi)&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;CHMOD digunakan untuk mengubah izin file.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;izin      perintah&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;U   G   W rwx rwx rwx     chmod 777 filename rwx rwx r-x     chmod 775 filename rwx r-x r-x     chmod 755 filename rw- rw- r--     chmod 664 filename rw- r-- r--     chmod 644 filename&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;U = Pengguna G = Group W = Dunia&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;r = Dapat dibaca w = dapat ditulis x = dapat dijalankan - = tidak ada izin&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Cara lain untuk melihat izin:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;400     dibaca oleh pemilik 040 dibaca oleh kelompok 004     dibaca oleh siapa saja (lain) 200 ditulis oleh pemilik 020     menulis oleh kelompok 002     ditulis oleh siapa saja 100     dieksekusi oleh pemilik 010     menjalankan per kelompok diejalankan oleh siapa saja&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Untuk mendapatkan kombinasi, cukup menjumlahkannya.&amp;lt;br&amp;gt;
Sebagai contoh, agar dapat dibaca, ditulis, dieksekusi oleh pemilik,&amp;lt;br&amp;gt;
dibaca, dieksekusi, oleh kelompok dan dieksekusi oleh siapa pun,&amp;lt;br&amp;gt;
Anda akan menambahkan 400+200+100+040+010+001 untuk menghasilkan 751.&amp;lt;br&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_pencarian_file&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;/blog/2024/0074_seach_file_linux_post.html&amp;quot;&amp;gt;Pencarian file&amp;lt;/a&amp;gt;&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;

&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>Mengekspos port subsistem Linux untuk host Windows</title>
            <link >https://cheroliv.com/blog/2024/0072_Exposer_un_port_linux_subsystem_post.adoc.html</link>
            <pubDate>Thu, 1 Feb 2024 00:00:00 +0000</pubDate>
            <guid isPermaLink="false">blog/2024/0072_Exposer_un_port_linux_subsystem_post.adoc.html</guid>
            <description>&amp;lt;div class=&amp;quot;olist arabic&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;&amp;lt;em&amp;gt;Waktu baca : 10 detik&amp;lt;/em&amp;gt;&amp;lt;/div&amp;gt;
&amp;lt;ol class=&amp;quot;arabic&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;masalah: Bagaimana mengekspos port 8820 dari sub-sistem Ubuntu yang hostnya Windows? Ubuntu dijalankan oleh perintah`wsl --install`.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>Menginstal Pandoc</title>
            <link >https://cheroliv.com/blog/2024/0070_installer_pandoc_post.html</link>
            <pubDate>Tue, 30 Jan 2024 00:00:00 +0000</pubDate>
            <guid isPermaLink="false">blog/2024/0070_installer_pandoc_post.html</guid>
            <description>&amp;lt;div id=&amp;quot;preamble&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;&amp;lt;em&amp;gt;Waktu membaca : 3 menit&amp;lt;/em&amp;gt; Pandoc adalah konverter dokumen universal&amp;lt;/div&amp;gt;
&amp;lt;p&amp;gt;Pandoc adalah perangkat lunak pengolah teks open source yang dapat digunakan untuk mengonversi dokumen antar berbagai format. Ada beberapa cara untuk menginstal Pandoc.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_instalasi_dari_halaman_download&amp;quot;&amp;gt;Instalasi dari halaman download&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Untuk menginstal Pandoc dari halaman unduhan, ikuti langkah berikut:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;olist arabic&amp;quot;&amp;gt;
&amp;lt;ol class=&amp;quot;arabic&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Akses halaman unduhan Pandoc :https://pandoc.org/installing.html/[&amp;lt;a href=&amp;quot;https://pandoc.org/installing.html&amp;quot; class=&amp;quot;bare&amp;quot;&amp;gt;https://pandoc.org/installing.html&amp;lt;/a&amp;gt;]&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Unduh installer paket untuk sistem operasi Anda.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Jalankan penginstal untuk menginstal Pandoc.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Instalasi dari file zip&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Untuk menginstal Pandoc dari file zip, ikuti langkah berikut :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;olist arabic&amp;quot;&amp;gt;
&amp;lt;ol class=&amp;quot;arabic&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Unduh file zip yang berisi biner Pandoc dan dokumentasi.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Ekstrak file ZIP.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Pindahkan biner ke direktori pilihan Anda.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Instalasi menggunakan Chocolatey&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Untuk menginstal Pandoc menggunakan Chocolatey, ikuti langkah-langkah berikut :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;olist arabic&amp;quot;&amp;gt;
&amp;lt;ol class=&amp;quot;arabic&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Buka prompt perintah.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Jalankan perintah berikut:&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-powershell hljs&amp;quot; data-lang=&amp;quot;powershell&amp;quot;&amp;gt;choco install pandoc&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Instalasi menggunakan winget&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Untuk menginstal Pandoc menggunakan winget, ikuti langkah berikut:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;olist arabic&amp;quot;&amp;gt;
&amp;lt;ol class=&amp;quot;arabic&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Buka prompt perintah.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Jalankan perintah berikut :&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-powershell hljs&amp;quot; data-lang=&amp;quot;powershell&amp;quot;&amp;gt;winget install --source winget --exact --id JohnMacFarlane.Pandoc&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Rekomendasi&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Secara default, Pandoc membuat PDF menggunakan LaTeX. Disarankan untuk menginstal LaTeX melalui MiKTeX. Dengan opsi`--pdf-engine`, Anda dapat menentukan program lain untuk tugas ini.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Peringatan&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Menggunakan beberapa metode instalasi dapat menyebabkan dua instalasi Pandoc yang terpisah. Disarankan untuk menguninstall Pandoc dengan benar sebelum beralih ke metode instalasi alternatif.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Instalasi pada Linux melalui WSL&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Periksa apakah versi pandoc di manajer paket Anda tidak usang Pandoc berada di repositori Debian, Ubuntu, Slackware, Arch, Fedora, NixOS, openSUSE, gentoo dan Void.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Untuk mendapatkan versi terbaru, kami menawarkan sebuah paket biner untuk arsitektur amd64 di halaman unduhan.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;File eksekusi dihubungkan secara statis dan tidak memiliki dependensi dinamik maupun dependensi pada file data eksternal. Catatan : karena tautan statis, biner pandoc dari paket ini tidak dapat menggunakan filter lua yang memerlukan modul lua eksternal yang ditulis dalam C.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Sebuah tarball dan instalator deb disediakan. Untuk menginstal deb :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;sudo dpkg -i $DEB&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Contoh pada saat penulisan:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;wget https://github.com/jgm/pandoc/releases/download/3.1.11.1/pandoc-3.1.11.1-1-amd64.deb&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;sudo dpkg -i pandoc-3.1.11.1-1-amd64.deb&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;où `$DEB`adalah jalur ke file .deb yang diunduh Ini akan menginstal file eksekusi`pandoc`dan halaman manual.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Jika Anda menggunakan distribusi berbasis RPM, Anda mungkin dapat menginstal file deb dari halaman unduhan kami dengan menggunakan alien.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;pada distribusi apa pun, Anda dapat menginstal dari tarball di`$DEST`(misalnya,&amp;lt;code&amp;gt;/usr/local/&amp;lt;/code&amp;gt; ou &amp;lt;code&amp;gt;$HOME/.local&amp;lt;/code&amp;gt;) dengan melakukan :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;tar xvzf $TGZ --strip-components 1 -C $DEST&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;où `$TGZ`adalah jalur menuju tarball zip yang diunduh. Untuk versi Pandoc sebelum 2.0, yang tidak menyediakan tarball, coba sebaliknya :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;ar p $DEB data.tar.gz | tar xvz --strip-components 2 -C $DEST&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Anda juga dapat menginstal dari sumber, menggunakan instruksi di bawah ini di bawah`Compilation à partir de la source`. Catat bahwa kebanyakan distribusi memiliki platform Haskell dalam repositori paket mereka. Sebagai contoh, pada Debian/Ubuntu, Anda dapat menginstalnya dengan&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;apt-get install haskell-platform&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Secara default, Pandoc membuat PDF menggunakan LaTeX. Kami merekomendasikan Anda menginstal TeX Live melalui manajer paket Anda. (Di Debian/Ubuntu,&amp;lt;code&amp;gt;apt-get install texlive&amp;lt;/code&amp;gt;). dengan opsi`--pdf-engine`, Anda juga dapat menentukan program lain untuk tugas ini.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>Implementasi kotlin.Triple dalam JavaScript, TypeScript, dan Python</title>
            <link >https://cheroliv.com/blog/2023/0069_triple_python_js_ts_post.html</link>
            <pubDate>Mon, 25 Dec 2023 00:00:00 +0000</pubDate>
            <guid isPermaLink="false">blog/2023/0069_triple_python_js_ts_post.html</guid>
            <description>&amp;lt;div id=&amp;quot;preamble&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;&amp;lt;em&amp;gt;Waktu baca: 2 menit&amp;lt;/em&amp;gt; Membuat implementasi lengkap dari kelas kotlin.Triple di JavaScript, TypeScript, dan Python menggunakan pemrograman fungsional.&amp;lt;/div&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Catatan :&amp;lt;/strong&amp;gt;Jenis-jenis`Maybe`, &amp;lt;code&amp;gt;Either&amp;lt;/code&amp;gt;, `Result`Tidak termasuk dalam bahasa dasar JavaScript atau TypeScript, sehingga kita harus mengimplementasikannya sendiri.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_javascript&amp;quot;&amp;gt;JavaScript&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-javascript hljs&amp;quot; data-lang=&amp;quot;javascript&amp;quot;&amp;gt;//javascript
// Maybe type
const Maybe = (value) =&amp;amp;gt; ({
  map: (fn) =&amp;amp;gt; (value !== null &amp;amp;amp;&amp;amp;amp; value !== undefined ? Maybe(fn(value)) : Maybe(null)),
  flatMap: (fn) =&amp;amp;gt; (value !== null &amp;amp;amp;&amp;amp;amp; value !== undefined ? fn(value) : Maybe(null)),
  getOrElse: (defaultValue) =&amp;amp;gt; (value !== null &amp;amp;amp;&amp;amp;amp; value !== undefined ? value : defaultValue),
});

// Either type
const Either = {
  Left: (value) =&amp;amp;gt; ({
    map: (fn) =&amp;amp;gt; Either.Left(value),
    flatMap: (fn) =&amp;amp;gt; Either.Left(value),
    getOrElse: (defaultValue) =&amp;amp;gt; defaultValue,
    isLeft: true,
    isRight: false,
  }),
  Right: (value) =&amp;amp;gt; ({
    map: (fn) =&amp;amp;gt; Either.Right(fn(value)),
    flatMap: (fn) =&amp;amp;gt; fn(value),
    getOrElse: (defaultValue) =&amp;amp;gt; value,
    isLeft: false,
    isRight: true,
  }),
};

// Triple class
class Triple {
  constructor(a, b, c) {
    this.a = a;
    this.b = b;
    this.c = c;
  }

  map(fn) {
    return new Triple(fn(this.a), fn(this.b), fn(this.c));
  }

  static of(a, b, c) {
    return new Triple(a, b, c);
  }

  // Example method using Maybe and Either
  divideBy(other) {
    return Maybe(this.b)
      .flatMap((numerator) =&amp;amp;gt;
        Maybe(other)
          .flatMap((denominator) =&amp;amp;gt;
            denominator !== 0
              ? Either.Right(numerator / denominator)
              : Either.Left(&amp;#39;Division by zero&amp;#39;)
          )
      )
      .getOrElse(Either.Left(&amp;#39;Invalid input&amp;#39;));
  }
}

// Example usage
const triple = Triple.of(1, 2, 3);
const result = triple.map((x) =&amp;amp;gt; x * 2);
console.log(result); // Triple { a: 2, b: 4, c: 6 }

const divisionResult = triple.divideBy(2);
console.log(divisionResult); // Either.Right { value: 1 }

const invalidDivisionResult = triple.divideBy(0);
console.log(invalidDivisionResult); // Either.Left { value: &amp;#39;Division by zero&amp;#39; }&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_typescript&amp;quot;&amp;gt;TypeScript&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-typescript hljs&amp;quot; data-lang=&amp;quot;typescript&amp;quot;&amp;gt;//Typescript
// Maybe type
// noinspection JSAnnotator

type Maybe&amp;amp;lt;T&amp;amp;gt; = {
  map&amp;amp;lt;U&amp;amp;gt;(fn: (value: T) =&amp;amp;gt; U): Maybe&amp;amp;lt;U&amp;amp;gt;;
  flatMap&amp;amp;lt;U&amp;amp;gt;(fn: (value: T) =&amp;amp;gt; Maybe&amp;amp;lt;U&amp;amp;gt;): Maybe&amp;amp;lt;U&amp;amp;gt;;
  getOrElse(defaultValue: T): T;
};

const just = &amp;amp;lt;T&amp;amp;gt;(value: T): Maybe&amp;amp;lt;T&amp;amp;gt; =&amp;amp;gt; ({
  map: &amp;amp;lt;U&amp;amp;gt;(fn: (value: T) =&amp;amp;gt; U) =&amp;amp;gt; just(fn(value)),
  flatMap: &amp;amp;lt;U&amp;amp;gt;(fn: (value: T) =&amp;amp;gt; Maybe&amp;amp;lt;U&amp;amp;gt;) =&amp;amp;gt; fn(value),
  getOrElse: (_: T) =&amp;amp;gt; value,
});

const nothing = &amp;amp;lt;T&amp;amp;gt;(): Maybe&amp;amp;lt;T&amp;amp;gt; =&amp;amp;gt; ({
  map: &amp;amp;lt;U&amp;amp;gt;(_: (value: T) =&amp;amp;gt; U) =&amp;amp;gt; nothing&amp;amp;lt;U&amp;amp;gt;(),
  flatMap: &amp;amp;lt;U&amp;amp;gt;(_: (value: T) =&amp;amp;gt; Maybe&amp;amp;lt;U&amp;amp;gt;) =&amp;amp;gt; nothing&amp;amp;lt;U&amp;amp;gt;(),
  getOrElse: (defaultValue: T) =&amp;amp;gt; defaultValue,
});

// Either type
type Either&amp;amp;lt;L, R&amp;amp;gt; = {
  map&amp;amp;lt;U&amp;amp;gt;(fn: (value: R) =&amp;amp;gt; U): Either&amp;amp;lt;L, U&amp;amp;gt;;
  flatMap&amp;amp;lt;U&amp;amp;gt;(fn: (value: R) =&amp;amp;gt; Either&amp;amp;lt;L, U&amp;amp;gt;): Either&amp;amp;lt;L, U&amp;amp;gt;;
  getOrElse(defaultValue: R): R;
  isLeft: boolean;
  isRight: boolean;
};

const left = &amp;amp;lt;L, R&amp;amp;gt;(value: L): Either&amp;amp;lt;L, R&amp;amp;gt; =&amp;amp;gt; ({
  map: &amp;amp;lt;U&amp;amp;gt;(_: (value: R) =&amp;amp;gt; U) =&amp;amp;gt; left&amp;amp;lt;L, U&amp;amp;gt;(value),
  flatMap: &amp;amp;lt;U&amp;amp;gt;(_: (value: R) =&amp;amp;gt; Either&amp;amp;lt;L, U&amp;amp;gt;) =&amp;amp;gt; left&amp;amp;lt;L, U&amp;amp;gt;(value),
  getOrElse: (defaultValue: R) =&amp;amp;gt; defaultValue,
  isLeft: true,
  isRight: false,
});

const right = &amp;amp;lt;L, R&amp;amp;gt;(value: R): Either&amp;amp;lt;L, R&amp;amp;gt; =&amp;amp;gt; ({
  map: &amp;amp;lt;U&amp;amp;gt;(fn: (value: R) =&amp;amp;gt; U) =&amp;amp;gt; right&amp;amp;lt;L, U&amp;amp;gt;(fn(value)),
  flatMap: &amp;amp;lt;U&amp;amp;gt;(fn: (value: R) =&amp;amp;gt; Either&amp;amp;lt;L, U&amp;amp;gt;) =&amp;amp;gt; fn(value),
  getOrElse: (_: R) =&amp;amp;gt; value,
  isLeft: false,
  isRight: true,
});

// Triple class
class Triple&amp;amp;lt;T&amp;amp;gt; {
  constructor(public a: T, public b: T, public c: T) {}

  map&amp;amp;lt;U&amp;amp;gt;(fn: (value: T) =&amp;amp;gt; U): Triple&amp;amp;lt;U&amp;amp;gt; {
    return new Triple&amp;amp;lt;U&amp;amp;gt;(fn(this.a), fn(this.b), fn(this.c));
  }

  static of&amp;amp;lt;T&amp;amp;gt;(a: T, b: T, c: T): Triple&amp;amp;lt;T&amp;amp;gt; {
    return new Triple&amp;amp;lt;T&amp;amp;gt;(a, b, c);
  }

  // Example method using Maybe and Either
  divideBy(other: T): Either&amp;amp;lt;string, number&amp;amp;gt; {
    return just(this.b)
      .flatMap((numerator) =&amp;amp;gt;
        just(other)
          .flatMap((denominator) =&amp;amp;gt;
            denominator !== 0 ? right(numerator / denominator) : left(&amp;#39;Division by zero&amp;#39;)
          )
      )
      .getOrElse(left(&amp;#39;Invalid input&amp;#39;));
  }
}

// Example usage
const triple = Triple.of(1, 2, 3);
const result = triple.map((x) =&amp;amp;gt; x * 2);
console.log(result); // Triple { a: 2, b: 4, c: 6 }

const divisionResult = triple.divideBy(2);
console.log(divisionResult); // Either.Right { value: 1 }

const invalidDivisionResult = triple.divideBy(0);
console.log(invalidDivisionResult); // Either.Left { value: &amp;#39;Division by zero&amp;#39; }&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_python_pydantic_pymonad&amp;quot;&amp;gt;Python, pydantic, pymonad&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Untuk membuat implementasi dalam Python menggunakan`pydantic`untuk validasi tipe dan`pymonad`untuk monade :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;Memansang dependensi:&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;pip install pydantic pymonad&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-python hljs&amp;quot; data-lang=&amp;quot;python&amp;quot;&amp;gt;from pymonad.either import Either, Left, Right
from pydantic import BaseModel, ValidationError, validator

# Définition de la classe Triple avec Pydantic
class Triple(BaseModel):
    a: int
    b: int
    c: int

    @validator(&amp;#39;b&amp;#39;)
    def validate_b(cls, b, values):
        if b == 0:
            raise ValueError(&amp;#39;Division by zero is not allowed&amp;#39;)
        return b

    # Méthode utilisant Either pour la gestion des erreurs
    def divide_by(self, other):
        def division(numerator, denominator):
            return Right(numerator / denominator)

        def handle_error(error):
            return Left(str(error))

        return Either(lambda: division(self.b, other)).or_else(handle_error)

# Exemple d&amp;#39;utilisation
try:
    triple = Triple(a=1, b=2, c=3)
    result = triple.divide_by(2)

    if result.is_right:
        print(f&amp;#39;Division result: {result.value}&amp;#39;)
    else:
        print(f&amp;#39;Error: {result.value}&amp;#39;)

except ValidationError as e:
    print(f&amp;#39;Validation Error: {e}&amp;#39;)
except Exception as e:
    print(f&amp;#39;An unexpected error occurred: {e}&amp;#39;)&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;Dalam contoh ini:&amp;lt;/div&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;code&amp;gt;pydantic`untuk menentukan kelas`Triple`dengan kolom bertipe. - Metode`divide_by`menggunakan monade`Either&amp;lt;/code&amp;gt; de `pymonad`untuk mengatasi kesalahan potensial yang terjadi akibat pembagian dengan nol. - Validasi kustom untuk memastikan nilai dari`b`tidak sama dengan nol. Jika sama dengan nol, sebuah pengecualian dilempar, dan`pymonad`tangkap pengecualian ini untuk mengembalikan hasil`Left`mengindikasikan kesalahan.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>Pendahuluan tentang Google Apps Script</title>
            <link >https://cheroliv.com/blog/2023/0068_introduction_google_apps_script_post.html</link>
            <pubDate>Fri, 22 Dec 2023 00:00:00 +0000</pubDate>
            <guid isPermaLink="false">blog/2023/0068_introduction_google_apps_script_post.html</guid>
            <description>&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_daftar_isi&amp;quot;&amp;gt;Daftar Isi&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;em&amp;gt;waktu bacaan : 30 menit&amp;lt;/em&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;#Pengantar&amp;quot;&amp;gt;[Pengantar]&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;#Struktur Dasar dalam Google Apps Script&amp;quot;&amp;gt;[Struktur Dasar dalam Google Apps Script]&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;#Tipe dan Variabel dalam Google Apps Script&amp;quot;&amp;gt;[Tipe dan Variabel dalam Google Apps Script]&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;#Konstanta&amp;quot;&amp;gt;Variabel, dan Huruf di Google Apps Script&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;#Operasi dan Operator di Google Apps Script&amp;quot;&amp;gt;[Operasi dan Operator di Google Apps Script]&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;#Var&amp;quot;&amp;gt;Let dan Const dalam Google Apps Script&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;#Struktur Kontrol dalam Google Apps Script&amp;quot;&amp;gt;[Struktur Kontrol dalam Google Apps Script]&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;#Perulangan di Google Apps Script&amp;quot;&amp;gt;[Perulangan di Google Apps Script]&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;#_objek_di_google_apps_script&amp;quot;&amp;gt;Objek di Google Apps Script&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;#Fungsi di Google Apps Script&amp;quot;&amp;gt;[Fungsi di Google Apps Script]&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;#Parameter dan Ruang Lingkup di Google Apps Script&amp;quot;&amp;gt;[Parameter dan Ruang Lingkup di Google Apps Script]&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;#Operator Rest dan Operator Spread di Google Apps Script&amp;quot;&amp;gt;[Operator Rest dan Operator Spread di Google Apps Script]&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Penugasan dengan dekomposisi dalam Google Apps Script&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;#Koleksi dan Pemrograman Deklaratif dalam Google Apps Script&amp;quot;&amp;gt;[Koleksi dan Pemrograman Deklaratif dalam Google Apps Script]&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_pendahuluan&amp;quot;&amp;gt;Pendahuluan&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Google Apps Script (GAS) adalah platform pengembangan skrip yang terkait dengan layanan Google yang berbeda seperti Google Sheets, Google Docs, dan Google Forms. Ia memungkinkan untuk mengotomatisasi tugas, menambahkan fitur khusus, dan mengintegrasikan layanan Google.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Dalam catatan ini, kita akan menjelajahi konsep utama Google Apps Script dengan didasarkan pada contoh konkret. Kita akan membahas konsep seperti variabel, perulangan, fungsi, dan banyak lagi, dengan menyesuaikannya pada konteks khusus GAS.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_struktur_dasar_di_google_apps_script&amp;quot;&amp;gt;Struktur dasar di Google Apps Script&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Untuk memulai, mari kita lihat struktur dasar dari skrip Google Apps.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-javascript hljs&amp;quot; data-lang=&amp;quot;javascript&amp;quot;&amp;gt;/**
 * Structure de base en Google Apps Script
 */
function exempleStructureGAS() {
    // Votre code ici
    Logger.log(&amp;quot;Hello, Google Apps Script!&amp;quot;);
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_jenis_dan_variabel_dalam_google_apps_script&amp;quot;&amp;gt;Jenis dan Variabel dalam Google Apps Script&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Google Apps Script menggunakan JavaScript sebagai bahasa pemrograman. Jenis data dan variabel berfungsi secara mirip dengan JavaScript standar.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-javascript hljs&amp;quot; data-lang=&amp;quot;javascript&amp;quot;&amp;gt;/**
 * Types et Variables en Google Apps Script
 */
function exempleTypesVariablesGAS() {
    // Votre code ici
    var nombre = 42;
    var texte = &amp;quot;Bonjour, Google Apps Script!&amp;quot;;
    Logger.log(nombre);
    Logger.log(texte);
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_operasi_dan_operator_dalam_google_apps_script&amp;quot;&amp;gt;Operasi dan Operator dalam Google Apps Script&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Operasi dan operator di Google Apps Script sama dengan JavaScript.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-javascript hljs&amp;quot; data-lang=&amp;quot;javascript&amp;quot;&amp;gt;/**
 * Opérations et Opérateurs en Google Apps Script
 */
function exempleOperationsGAS() {
    // Votre code ici
    var x = 10;
    var y = 5;
    var resultat = x + y;
    Logger.log(resultat);
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_var_let_dan_const_di_google_apps_script&amp;quot;&amp;gt;Var, Let dan Const di Google Apps Script&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Dalam GAS, konsep-konsep`var`, &amp;lt;code&amp;gt;let&amp;lt;/code&amp;gt;, et `const`digunakan untuk mendeklarasikan variabel. Namun, sangat penting untuk memahami perbedaan dan implikasi mereka.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-javascript hljs&amp;quot; data-lang=&amp;quot;javascript&amp;quot;&amp;gt;/**
 * Var, Let et Const en Google Apps Script
 */
function exempleVarLetConstGAS() {
    // Votre code ici
    // Utilisation de var
    var x = 10;
    if (true) {
        var x = 20; // La variable x est modifiée globalement
    }
    Logger.log(x);

    // Utilisation de let
    let y = 30;
    if (true) {
        let y = 40; // La variable y est limitée au bloc if
    }
    Logger.log(y);

    // Utilisation de const
    const z = 50;
    // z = 60; // Impossible de réassigner une constante
    Logger.log(z);
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_struktur_kontrol_di_google_apps_script&amp;quot;&amp;gt;Struktur Kontrol di Google Apps Script&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Struktur kontrol seperti`if`, &amp;lt;code&amp;gt;else&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;while&amp;lt;/code&amp;gt; et `for`digunakan untuk mengelola aliran eksekusi di Google Apps Script.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-javascript hljs&amp;quot; data-lang=&amp;quot;javascript&amp;quot;&amp;gt;/**
 * Structures de Contrôle en Google Apps Script
 */
function exempleStructuresControleGAS() {
    // Votre code ici
    var condition = true;

    if (condition) {
        Logger.log(&amp;quot;La condition est vraie.&amp;quot;);
    } else {
        Logger.log(&amp;quot;La condition est fausse.&amp;quot;);
    }

    var compteur = 0;
    while (compteur &amp;amp;lt; 5) {
        Logger.log(compteur);
        compteur++;
    }

    for (var i = 0; i &amp;amp;lt; 3; i++) {
        Logger.log(i);
    }
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_pengaturan_dan_scopes_dalam_google_apps_script&amp;quot;&amp;gt;Pengaturan dan Scopes dalam Google Apps Script&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Parameter dan scope berperan krusial dalam pengembangan skrip di GAS. Pahami cara mereka bekerja untuk menghindari perilaku tak terduga.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-javascript hljs&amp;quot; data-lang=&amp;quot;javascript&amp;quot;&amp;gt;/**
 * Paramètres et Scopes en Google Apps Script
 */
function exempleParametresScopesGAS(parametre) {
    // Votre code ici
    var variableGlobale = &amp;quot;Je suis global&amp;quot;;

    function afficherParametre() {
        Logger.log(parametre);
    }

    afficherParametre();
    Logger.log(variableGlobale);
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_konstanta_variabel_dan_huruf_dalam_google_apps_script&amp;quot;&amp;gt;Konstanta, Variabel, dan Huruf dalam Google Apps Script&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Memahami perbedaan antara`const`, &amp;lt;code&amp;gt;var&amp;lt;/code&amp;gt;, et `let`itu penting untuk penggunaan efektif variabel di Google Apps Script.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-javascript hljs&amp;quot; data-lang=&amp;quot;javascript&amp;quot;&amp;gt;/**
 * Constantes, Variables, et Lettres en Google Apps Script
 */
function exempleConstantesVariablesLettresGAS() {
    // Votre code ici
    const constante = &amp;quot;Je ne change pas&amp;quot;;
    Logger.log(constante);

    var variable = &amp;quot;Je peux changer&amp;quot;;
    Logger.log(variable);

    let lettre = &amp;quot;Je peux aussi changer, mais seulement dans mon bloc&amp;quot;;
    Logger.log(lettre);
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_perulangan_dalam_google_apps_script&amp;quot;&amp;gt;Perulangan dalam Google Apps Script&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Loop, seperti`for` et &amp;lt;code&amp;gt;while&amp;lt;/code&amp;gt;, adalah penting untuk mengiterasi melalui elemen dan melakukan operasi berulang.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-javascript hljs&amp;quot; data-lang=&amp;quot;javascript&amp;quot;&amp;gt;/**
 * Boucles en Google Apps Script
 */
function exempleBouclesGAS() {
    // Votre code ici
    for (var i = 0; i &amp;amp;lt; 3; i++) {
        Logger.log(i);
    }

    var condition = true;
    var compteur = 0;

    while (condition) {
        Logger.log(&amp;quot;Tour de boucle&amp;quot;);
        compteur++;
        if (compteur === 3) {
            condition = false;
        }
    }
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_objek_di_google_apps_script&amp;quot;&amp;gt;Objek di Google Apps Script&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Objek digunakan untuk mengatur data. Dalam GAS, banyak objek bawaan memudahkan interaksi dengan layanan Google.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-javascript hljs&amp;quot; data-lang=&amp;quot;javascript&amp;quot;&amp;gt;/**
 * Objets en Google Apps Script
 */
function exempleObjetsGAS() {
    // Votre code ici
    var feuille = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
    var cellule = feuille.getRange(&amp;quot;A1&amp;quot;);
    cellule.setValue(&amp;quot;Nouvelle valeur&amp;quot;);
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_konstanta_variabel_dan_huruf_dalam_google_apps_script_2&amp;quot;&amp;gt;Konstanta, Variabel, dan Huruf dalam Google Apps Script&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Memahami perbedaan antara`const`, &amp;lt;code&amp;gt;var&amp;lt;/code&amp;gt;, et `let`adalah penting untuk penggunaan efektif variabel di Google Apps Script.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-javascript hljs&amp;quot; data-lang=&amp;quot;javascript&amp;quot;&amp;gt;/**
 * Constantes, Variables, et Lettres en Google Apps Script
 */
function exempleConstantesVariablesLettresGAS() {
    // Votre code ici
    const constante = &amp;quot;Je ne change pas&amp;quot;;
    Logger.log(constante);

    var variable = &amp;quot;Je peux changer&amp;quot;;
    Logger.log(variable);

    let lettre = &amp;quot;Je peux aussi changer, mais seulement dans mon bloc&amp;quot;;
    Logger.log(lettre);
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_fungsi_dalam_google_apps_script&amp;quot;&amp;gt;Fungsi dalam Google Apps Script&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Fungsi merupakan elemen dasar dalam pemrograman. Dalam GAS, fungsi dapat dideklarasikan dan dipanggil dengan berbagai cara.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-javascript hljs&amp;quot; data-lang=&amp;quot;javascript&amp;quot;&amp;gt;/**
 * Fonctions en Google Apps Script
 */
function exempleFonctionsGAS() {
    // Votre code ici
    function additionner(a, b) {
        return a + b;
    }

    var resultat = additionner(2, 3);
    Logger.log(resultat);

    // Fonction anonyme
    var multiplier = function (x, y) {
        return x * y;
    };

    Logger.log(multiplier(4, 5));
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_operator_rest_dan_spread_di_google_apps_script&amp;quot;&amp;gt;Operator Rest dan Spread di Google Apps Script&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;operator istirahat (&amp;lt;code&amp;gt;&amp;amp;#8230;&amp;amp;#8203;&amp;lt;/code&amp;gt;) dan perambatan (&amp;lt;code&amp;gt;&amp;amp;#8230;&amp;amp;#8203;&amp;lt;/code&amp;gt;) adalah berguna untuk memanipulasi array dan objek dengan cara yang singkat.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-javascript hljs&amp;quot; data-lang=&amp;quot;javascript&amp;quot;&amp;gt;/**
 * Opérateurs de Repos et de Propagation en Google Apps Script
 */
function exempleOperateursReposPropagationGAS() {
    // Votre code ici
    // Opérateur de repos (...) pour les tableaux
    var nombres = [1, 2, 3, 4, 5];
    var [...copieNombres] = nombres;
    Logger.log(copieNombres);

    // Opérateur de propagation (...) pour les objets
    var objOriginal = { x: 1, y: 2 };
    var objClone = { ...objOriginal, z: 3 };
    Logger.log(objClone);
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_penugasan_dengan_dekomposisi_di_google_apps_script&amp;quot;&amp;gt;Penugasan dengan dekomposisi di Google Apps Script&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Penugasan dekomposisi adalah fitur yang kuat yang memungkinkan mengekstrak nilai dari objek dan array secara ringkas.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-javascript hljs&amp;quot; data-lang=&amp;quot;javascript&amp;quot;&amp;gt;/**
 * Affectation par Décomposition en Google Apps Script
 */
function exempleAffectationDecompositionGAS() {
    // Votre code ici
    var coordonnees = [3, 4];
    var [x, y] = coordonnees;
    Logger.log(x);
    Logger.log(y);

    var utilisateur = { nom: &amp;quot;John&amp;quot;, age: 30 };
    var { nom, age } = utilisateur;
    Logger.log(nom);
    Logger.log(age);
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_koleksi_dan_pemrograman_deklaratif_di_google_apps_script&amp;quot;&amp;gt;Koleksi dan Pemrograman Deklaratif di Google Apps Script&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Google Apps Script mendukung struktur data seperti array, dan konsep pemrograman fungsional dan iterasi deklaratif &amp;lt;code&amp;gt;(&amp;amp;gt;&amp;amp;lt;impératif)&amp;lt;/code&amp;gt;, seperti`map`, &amp;lt;code&amp;gt;reduce&amp;lt;/code&amp;gt; et &amp;lt;code&amp;gt;forEach&amp;lt;/code&amp;gt;.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-javascript hljs&amp;quot; data-lang=&amp;quot;javascript&amp;quot;&amp;gt;/**
 * Collections et Programmation Déclarative en Google Apps Script
 */
function exempleCollectionsFonctionnelleGAS() {
    // Votre code ici
    // Exemple de tableau (collection)
    var nombres = [1, 2, 3, 4, 5];

    // Utilisation de forEach
    nombres.forEach(function (nombre) {
        Logger.log(nombre);
    });

    // Utilisation de map
    var carresNombres = nombres.map(function (nombre) {
        return nombre * nombre;
    });
    Logger.log(carresNombres);

    // Utilisation de reduce
    var somme = nombres.reduce(function (acc, nombre) {
        return acc + nombre;
    }, 0);
    Logger.log(somme);
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_kesimpulan&amp;quot;&amp;gt;Kesimpulan&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Memo ini membahas konsep dasar Google Apps Script dengan mengilustrasikannya menggunakan contoh kode konkret. Manfaatkan pengetahuan ini untuk mengotomatiskan tugas harian Anda, menyesuaikan dokumen Anda, dan memanfaatkan sepenuhnya fitur Google Apps Script.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>Pelatihan Ekspres pada Skrip Google Form dan Otomatisasi</title>
            <link >https://cheroliv.com/blog/2023/0067_formation_express_sur_google_forms_script_post.html</link>
            <pubDate>Wed, 20 Dec 2023 00:00:00 +0000</pubDate>
            <guid isPermaLink="false">blog/2023/0067_formation_express_sur_google_forms_script_post.html</guid>
            <description>&amp;lt;div id=&amp;quot;preamble&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Durasi: 30 menit&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_tujuan&amp;quot;&amp;gt;Tujuan&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Memahami dasar-dasar Google Apps Script di Forms - Belajar mengakses editor skrip dan mengeksekusi instruksi pada pemicu&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_penundaan&amp;quot;&amp;gt;penundaan&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;olist arabic&amp;quot;&amp;gt;
&amp;lt;ol class=&amp;quot;arabic&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Pengantar Google Form Script&amp;lt;/strong&amp;gt;(5 menit)&amp;lt;/p&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Ringkasan cepat Google Form Script - Presentasi fitur utama&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Pembuatan formulir Google dan skrip terkait&amp;lt;/strong&amp;gt;(5 menit)&amp;lt;/p&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Membuat formulir sederhana dengan Google Forms - Pengantar editor skrip terkait&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Menjalankan sebuah instruksi&amp;lt;/strong&amp;gt;(10 menit)&amp;lt;/p&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Konfigurasi pemicu &amp;#39;on start&amp;#39; - Konfigurasi pemicu &amp;quot;on submit - Penulisan skrip untuk menjalankan instruksi - Tes cepat untuk memverifikasi bahwa berfungsi dengan baik dalam jurnal&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Analisis Hasil dan Praktik Terbaik&amp;lt;/strong&amp;gt;(10 menit)&amp;lt;/p&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Verifikasi hasil eksekusi instruksi - Diskusi tentang contoh skrip dan bagaimana otomatisasi dapat menghemat waktu&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_prasyarat&amp;quot;&amp;gt;Prasyarat&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Memiliki akun Google dan akses ke Google Forms dan Google Sheets&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_peralatan&amp;quot;&amp;gt;peralatan&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Komputer dengan peramban web dan koneksi internet&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_catatan&amp;quot;&amp;gt;Catatan&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Pelatihan singkat ini memberikan pengantar cepat tentang otomatisasi dengan Google Form Script. Untuk pemahaman yang lebih dalam, pelatihan yang lebih lama direkomendasikan.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_1_pengantar_google_form_script&amp;quot;&amp;gt;1 - Pengantar Google Form Script&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_slide_1_ringkasan_cepat_google_form_script&amp;quot;&amp;gt;Slide 1: Ringkasan cepat Google Form Script&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Google Form Script adalah platform skrip yang terintegrasi ke Google Forms. - Ia memungkinkan penambahan fitur kustom dan otomatisasi tugas.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_slide_2_presentasi_fitur_utama&amp;quot;&amp;gt;Slide 2: Presentasi fitur utama&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Script Editor : Editor skrip terintegrasi untuk menulis skrip Google Apps Script - Pemicu: Peristiwa seperti &amp;quot;on submit&amp;quot; yang mengaktifkan eksekusi skrip.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_slide_3_keuntungan_menggunakan_google_form_script&amp;quot;&amp;gt;Slide 3: Keuntungan Menggunakan Google Form Script&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Personalisasi : Buat formulir yang dapat dipersonalisasi dengan fitur unik. - Otomatisasi: Otomatiskan tindakan sebagai respons terhadap jawaban formulir. - Integrasi: Integrasikan skrip dengan layanan Google lain untuk pengalaman yang lebih kaya.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_catatan_untuk_presenter&amp;quot;&amp;gt;Catatan untuk presenter :&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Fitur-fitur ini menawarkan potensi besar untuk menyesuaikan dan mengotomatiskan formulir. Bagian selanjutnya dari pelatihan ini akan mengeksplorasi lebih detail tentang pembuatan skrip dan otomatisasi khusus.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://developers.google.com/apps-script/add-ons/editors/forms?hl=id&amp;quot; class=&amp;quot;bare&amp;quot;&amp;gt;https://developers.google.com/apps-script/add-ons/editors/forms?hl=id&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://developers.google.com/apps-script/reference/forms?hl=fr&amp;quot; class=&amp;quot;bare&amp;quot;&amp;gt;https://developers.google.com/apps-script/reference/forms?hl=fr&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://developers.google.com/apps-script/reference/forms/form-app?hl=id&amp;quot; class=&amp;quot;bare&amp;quot;&amp;gt;https://developers.google.com/apps-script/reference/forms/form-app?hl=id&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_pembuatan_formulir_google_dan_skrip_yang_terkait_5_min&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;Pembuatan Formulir Google dan Skrip yang terkait&amp;lt;/strong&amp;gt; (5 min)&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;

&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>Catatan Python</title>
            <link >https://cheroliv.com/blog/2023/0066_memo_python_post.html</link>
            <pubDate>Sun, 19 Nov 2023 00:00:00 +0000</pubDate>
            <guid isPermaLink="false">blog/2023/0066_memo_python_post.html</guid>
            <description>&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_instal_python_di_windows&amp;quot;&amp;gt;Instal python di windows&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://ninite.com/pythonx3/&amp;quot;&amp;gt;python di windows&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://python-docs.readthedocs.io/en/latest/starting/install3/win.html&amp;quot; class=&amp;quot;bare&amp;quot;&amp;gt;https://python-docs.readthedocs.io/en/latest/starting/install3/win.html&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://python-docs.readthedocs.io/en/latest/dev/virtualenvs.html#virtualenvironments-ref&amp;quot; class=&amp;quot;bare&amp;quot;&amp;gt;https://python-docs.readthedocs.io/en/latest/dev/virtualenvs.html#virtualenvironments-ref&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_menjalankan_python&amp;quot;&amp;gt;Menjalankan python&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;Referensi :&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;cd mon_projet

python -m venv mon_venv

source mon_venv/bin/activate

# Sur windows
mon_venv\Scripts\activate

deactivate&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_persyaratan_instalasi&amp;quot;&amp;gt;persyaratan instalasi&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;pip install -r requirements.txt&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_pendahuluan&amp;quot;&amp;gt;pendahuluan&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://python.doctor/page-apprendre-programmation-orientee-objet-poo-classes-python-cours-debutants&amp;quot;&amp;gt;python PBO&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_pola_desain_gof_dalam_python&amp;quot;&amp;gt;Pola Desain GoF dalam Python&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://github.com/whikwon/python-patterns/&amp;quot;&amp;gt;implementasi 1&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://github.com/Sean-Bradley/Design-Patterns-In-Python&amp;quot;&amp;gt;implementasi 2&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://sbcode.net/python/&amp;quot;&amp;gt;buku lengkap&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Design pattern GoF adalah sekumpulan 23 design pattern yang dijelaskan dalam buku &amp;quot;Design Patterns: Elements of Reusable Object-Oriented Software&amp;quot;.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;table class=&amp;quot;tableblock frame-all grid-all stretch&amp;quot;&amp;gt;
&amp;lt;caption class=&amp;quot;title&amp;quot;&amp;gt;Table 1. Berikut adalah daftar pola desain GoF&amp;lt;/caption&amp;gt;
&amp;lt;colgroup&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.0833%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.0833%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.0833%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.0833%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.0833%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.0833%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.0833%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.0833%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.0833%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.0833%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.0833%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.0833%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.0833%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.0833%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.0833%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.0833%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.0833%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.0833%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.0833%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.0833%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.0833%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.0833%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.0833%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.0833%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.0833%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.0833%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.0833%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.0833%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.0833%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.0833%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.0833%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.0833%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.0833%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.0833%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.0833%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.0833%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.0833%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.0833%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.0833%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.0833%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.0833%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.0833%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.0833%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.0833%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.0833%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.0833%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.0833%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 2.0849%;&amp;quot;&amp;gt;
&amp;lt;/colgroup&amp;gt;
&amp;lt;tbody&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Nama&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Deskripsi&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;Metode Pabrik&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Buat objek tanpa menentukan kelas yang tepat untuk diinstansiasi.&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;Pabrik Abstrak&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Buat keluarga objek terkait atau dependen tanpa menentukan kelas konkritnya.&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;Pembuat&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Memisahkan pembuatan objek kompleks dari representasinya sehingga proses pembuatan yang sama dapat membuat representasi yang berbeda.&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;prototipe&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Membuat objek baru dengan menyalin prototipe yang ada.&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;Singleton&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Membatasi instansiasi kelas menjadi satu objek saja.&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;adaptor&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Mengubah antarmuka suatu kelas menjadi antarmuka lain yang diharapkan oleh klien.&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;bridge&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Memisahkan abstraksi dari implementasinya sehingga keduanya dapat berubah secara independen.&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;komposit&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Buat objek dalam struktur pohon untuk merepresentasikan hierarki bagian-keseluruhan.&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;dekorator&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Melampirkan secara dinamis tanggung jawab tambahan ke sebuah objek.&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;fasad&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Memberikan satu antarmuka terpadu ke sekumpulan antarmuka dalam sebuah subsistem.&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;Flyweight&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Mengurangi biaya pembuatan dan penggunaan objek ringan.&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;Proxy&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Memberikan pengganti atau placeholder untuk objek lain untuk mengontrol akses padanya.&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;Rantai Tanggung Jawab&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Mengizinkan lebih dari satu objek untuk memproses permintaan.&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;perintah&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Mengenkapsulasi sebuah permintaan dalam sebuah objek, sehingga memungkinkan klien dikonfigurasi dengan permintaan yang berbeda, antrian atau log, dan mendukung operasi yang dapat dibatalkan.&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;penerjemah&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Berikan representasi tata bahasa suatu bahasa dan gunakan representasi tersebut untuk menafsirkan kalimat dalam bahasa tersebut.&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;Iterator&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Memberikan cara untuk melintasi secara berurutan elemen dari objek gabungan tanpa mengekspos representasi dasarnya.&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;perantara&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Memungkinkan untuk mengurangi dependensi kompleks antara objek dengan membuat mereka berkomunikasi hanya melalui objek mediator.&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;kenangan&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Memberikan kemampuan untuk mengembalikan objek ke kondisi sebelumnya (tanpa melanggar enkapsulasi).&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;Pengamat&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Menetapkan ketergantungan satu-ke-banyak antara objek sehingga ketika sebuah objek mengubah statusnya, semua dependennya diberi tahu dan diperbarui secara otomatis.&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;Negara&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Memungkinkan suatu objek untuk mengubah perilaku ketika mengubah keadaan internalnya&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;Strategi&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Menentukan sebuah keluarga algoritma, mengenkapsulasi masing-masingnya dan membuatnya dapat ditukar.&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;Metode Templat&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Menetapkan kerangka sebuah algoritma dalam sebuah metode, mendelegasikan beberapa langkah ke subkelas.&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;pengunjung&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Memungkinkan untuk mendefinisikan operasi baru yang harus dilakukan pada struktur objek tanpa mengubah kelas objek yang padanya ia beroperasi.&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_pemrograman_fungsional&amp;quot;&amp;gt;Pemrograman fungsional&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;table class=&amp;quot;tableblock frame-all grid-all stretch&amp;quot;&amp;gt;
&amp;lt;colgroup&amp;gt;
&amp;lt;col style=&amp;quot;width: 16.6666%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 27.7777%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 55.5557%;&amp;quot;&amp;gt;
&amp;lt;/colgroup&amp;gt;
&amp;lt;tbody&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;perpustakaan&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;URL&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Keterangan&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;PyMonad&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;https://github.com/jasondelaat/pymonad&amp;quot; class=&amp;quot;bare&amp;quot;&amp;gt;https://github.com/jasondelaat/pymonad&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;PyMonad adalah implementasi struktur data monadik dalam Python yang berdasarkan bahasa pemrograman seperti Haskell dan F# dengan implementasi untuk tipe monad yang paling umum digunakan dalam pemrograman fungsional.https://www.miguelfarrajota.com/2021/06/monads-in-python-with-pymonad/[artikel]&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;PyFunctional&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;https://github.com/EntilZha/PyFunctional/&amp;quot; class=&amp;quot;bare&amp;quot;&amp;gt;https://github.com/EntilZha/PyFunctional/&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;PyFunctional adalah perpustakaan Python untuk pemrograman fungsional yang menyediakan alat untuk bekerja dengan fungsi murni dan iterabel.&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Toolz&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;https://toolz.readthedocs.io/en/latest/&amp;quot; class=&amp;quot;bare&amp;quot;&amp;gt;https://toolz.readthedocs.io/en/latest/&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Toolz adalah pustaka Python untuk pemrograman fungsional yang menyediakan alat untuk bekerja dengan fungsi murni dan iterable.&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Fn.py&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;https://github.com/kachayev/fn.py&amp;quot; class=&amp;quot;bare&amp;quot;&amp;gt;https://github.com/kachayev/fn.py&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Fn.py adalah sebuah pustaka Python untuk pemrograman fungsional yang menyediakan alat untuk bekerja dengan fungsi murni dan iterable.&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;kelapa&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;https://coconut-lang.org/&amp;quot; class=&amp;quot;bare&amp;quot;&amp;gt;https://coconut-lang.org/&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Coconut menambahkan beberapa fitur ke Python untuk pemrograman fungsional, termasuk pencocokan pola.https://stackoverflow.com/questions/11909681/are-there-pattern-matching-functions-in-python-like-this[percakapan sof]&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_efek_samping&amp;quot;&amp;gt;efek samping&amp;lt;/h3&amp;gt;

&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_perulangan&amp;quot;&amp;gt;perulangan&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-python hljs&amp;quot; data-lang=&amp;quot;python&amp;quot;&amp;gt;# impérative way
result = []
for i in inputs:
    x = f(g(i))
    result.append(x)

# idiomatic way
result = map(comp(f,g), inputs)&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>Web dev</title>
            <link >https://cheroliv.com/blog/2023/0065_training_web_dev_post.html</link>
            <pubDate>Thu, 24 Aug 2023 00:00:00 +0000</pubDate>
            <guid isPermaLink="false">blog/2023/0065_training_web_dev_post.html</guid>
            <description></description>
        </item><item>
            <title>Trading card games (TCGs)</title>
            <link >https://cheroliv.com/blog/2023/0064_training_trading_card_games_post.html</link>
            <pubDate>Mon, 21 Aug 2023 00:00:00 +0000</pubDate>
            <guid isPermaLink="false">blog/2023/0064_training_trading_card_games_post.html</guid>
            <description></description>
        </item><item>
            <title>Catatan Typescript</title>
            <link >https://cheroliv.com/blog/2023/0063_memo_ts_post.html</link>
            <pubDate>Sat, 19 Aug 2023 00:00:00 +0000</pubDate>
            <guid isPermaLink="false">blog/2023/0063_memo_ts_post.html</guid>
            <description>&amp;lt;div id=&amp;quot;preamble&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;repositori github:https://github.com/cheroliv/ts-codebase[ts-codebase, window=&amp;quot;_blank]&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;toc_ts&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;underline&amp;quot;&amp;gt;Pengantar typescript&amp;lt;/span&amp;gt;&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;olist arabic&amp;quot;&amp;gt;
&amp;lt;ol class=&amp;quot;arabic&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;#ts_types&amp;quot;&amp;gt;Tipe-tipe dalam typescript&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;#fungsi&amp;quot;&amp;gt;[fungsi]&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;#data_struct&amp;quot;&amp;gt;Struktur data&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;#enums_tuples&amp;quot;&amp;gt;Enums dan tuples&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;#void_never&amp;quot;&amp;gt;Void and Never&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;#class_interface&amp;quot;&amp;gt;Kelas dan antarmuka&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;#tsconfig&amp;quot;&amp;gt;File tsconfig.json&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;#modules&amp;quot;&amp;gt;Modul&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;#janji&amp;quot;&amp;gt;[janji]&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;async-await&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;generik&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;index.html&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-html hljs&amp;quot; data-lang=&amp;quot;html&amp;quot;&amp;gt;&amp;amp;lt;!DOCTYPE html&amp;amp;gt;
&amp;amp;lt;html lang=&amp;quot;fr&amp;quot;&amp;amp;gt;

&amp;amp;lt;head&amp;amp;gt;
    &amp;amp;lt;meta charset=&amp;quot;UTF-8&amp;quot;&amp;amp;gt;
    &amp;amp;lt;meta name=&amp;quot;viewport&amp;quot; content=&amp;quot;width=device-width, initial-scale=1.0&amp;quot; /&amp;amp;gt;
    &amp;amp;lt;meta http-equiv=&amp;quot;X-UA-Compatible&amp;quot; content=&amp;quot;ie=edge&amp;quot; /&amp;amp;gt;
    &amp;amp;lt;title&amp;amp;gt;Document&amp;amp;lt;/title&amp;amp;gt;
&amp;amp;lt;/head&amp;amp;gt;

&amp;amp;lt;body&amp;amp;gt;
    Intro typescript
    &amp;amp;lt;script src=&amp;quot;01_types.js&amp;quot;&amp;amp;gt;&amp;amp;lt;/script&amp;amp;gt;
    &amp;amp;lt;script src=&amp;quot;02_data_structure.js&amp;quot;&amp;amp;gt;&amp;amp;lt;/script&amp;amp;gt;
    &amp;amp;lt;script src=&amp;quot;03_enums_tuples.js&amp;quot;&amp;amp;gt;&amp;amp;lt;/script&amp;amp;gt;
&amp;amp;lt;/body&amp;amp;gt;

&amp;amp;lt;/html&amp;amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;ts_types&amp;quot;&amp;gt;Tipe-tipe dalam typescript&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;01_types.ts&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-typescript hljs&amp;quot; data-lang=&amp;quot;typescript&amp;quot;&amp;gt;/**
 * Les types en typescript
 */

//quand le type n&amp;#39;est pas defini des le debut,
// alors il est réassignable
// c&amp;#39;est le type Any qui est attribué par défaut
let my_var;
my_var = &amp;quot;username&amp;quot;;
console.assert(my_var === &amp;quot;username&amp;quot;);
my_var = 11;
console.assert(my_var === 11);
my_var = true;
console.assert(my_var === true);

let age = 1;
console.assert(age === 1);
age = 2;
// age=&amp;quot;trois&amp;quot; c&amp;#39;est impossible
// car l&amp;#39;inference de type a attribué le type number
console.assert(age === 2);

// Déclaration explicite de type
let pi: Number = 3.14;
console.assert(typeof pi === &amp;quot;number&amp;quot;);&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;#toc_ts&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;underline&amp;quot;&amp;gt;Pengantar typescript&amp;lt;/span&amp;gt;&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;fonctions&amp;quot;&amp;gt;Fungsi&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Fungsi adalah blok kode yang dapat digunakan ulang.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Contoh skor :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-typescript hljs&amp;quot; data-lang=&amp;quot;typescript&amp;quot;&amp;gt;const add = (a: number, b: number): number =&amp;amp;gt; a + b;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;#toc_ts&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;underline&amp;quot;&amp;gt;Pengantar typescript&amp;lt;/span&amp;gt;&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;data_struct&amp;quot;&amp;gt;Struktur data&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;02_data_structure.ts&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-typescript hljs&amp;quot; data-lang=&amp;quot;typescript&amp;quot;&amp;gt;/***
 * Structure de données:
 * Avec le type any on peut changer
 * le type de la variable à tout instruction
 * Lorsque l&amp;#39;on fixe le type
 * alors il n&amp;#39;est plus possible de le changer
 * lors d&amp;#39;une assignation
 */

// Arrays
let buddies: any[] = [
    &amp;quot;cheroliv&amp;quot;,
    &amp;quot;imrandeh&amp;quot;,
    &amp;quot;issoudeh&amp;quot;,
    &amp;quot;soumi92&amp;quot;
]

console.assert(buddies[0] === &amp;quot;cheroliv&amp;quot;);
console.assert(buddies[1] === &amp;quot;imrandeh&amp;quot;);
console.assert(buddies[2] === &amp;quot;issoudeh&amp;quot;);
console.assert(buddies[3] === &amp;quot;soumi92&amp;quot;);
// Déclaré en type any, l&amp;#39;inference de type
// a bien réaligné en type string
console.assert(typeof buddies[0] === &amp;quot;string&amp;quot;);
console.assert(typeof buddies[1] === &amp;quot;string&amp;quot;);
console.assert(typeof buddies[2] === &amp;quot;string&amp;quot;);
console.assert(typeof buddies[3] === &amp;quot;string&amp;quot;);



let writers: (String | String | String | Number)[][] = [
    [&amp;quot;Chrétien&amp;quot;, &amp;quot;de Troyes&amp;quot;, &amp;quot;fr&amp;quot;, 12],
    [&amp;quot;François&amp;quot;, &amp;quot;Rabelais&amp;quot;, &amp;quot;fr&amp;quot;, 16],
    [&amp;quot;René&amp;quot;, &amp;quot;Descartes&amp;quot;, &amp;quot;fr&amp;quot;, 17],
    [&amp;quot;Jean-Jacques&amp;quot;, &amp;quot;Rousseau&amp;quot;, &amp;quot;fr&amp;quot;, 18],
    [&amp;quot;Georg&amp;quot;, &amp;quot;Hegel&amp;quot;, &amp;quot;de&amp;quot;, 19],
    [&amp;quot;Karl&amp;quot;, &amp;quot;Marx&amp;quot;, &amp;quot;de&amp;quot;, 19],
    [&amp;quot;Friedrich&amp;quot;, &amp;quot;Engels&amp;quot;, &amp;quot;de&amp;quot;, 19],
    [&amp;quot;Victor&amp;quot;, &amp;quot;Hugo&amp;quot;, &amp;quot;fr&amp;quot;, 19],
    [&amp;quot;Paul&amp;quot;, &amp;quot;Verlaine&amp;quot;, &amp;quot;fr&amp;quot;, 19],
    [&amp;quot;Arthur&amp;quot;, &amp;quot;Rimbaud&amp;quot;, &amp;quot;fr&amp;quot;, 19],
    [&amp;quot;Gérard&amp;quot;, &amp;quot;de Nerval&amp;quot;, &amp;quot;fr&amp;quot;, 19],
    [&amp;quot;Georg&amp;quot;, &amp;quot;Lukacs&amp;quot;, &amp;quot;hu&amp;quot;, 20],
    [&amp;quot;Franz&amp;quot;, &amp;quot;Kafka&amp;quot;, &amp;quot;hu&amp;quot;, 20],
    [&amp;quot;Antonio&amp;quot;, &amp;quot;Gramsci&amp;quot;, &amp;quot;it&amp;quot;, 20],
    [&amp;quot;Domenico&amp;quot;,&amp;quot;Losurdo&amp;quot;,&amp;quot;it&amp;quot;,20],
];

// console.table(writers);


// Objects
let author: {
    first_name: String,
    last_name: String,
    lang: String,
    century: Number
} = {
    // slice(-1) renvoie le dernier élèment
    first_name: writers.slice(-1)[0][0] as String,
    last_name: writers.slice(-1)[0][1] as String,
    lang: writers.slice(-1)[0][2] as String,
    century: writers.slice(-1)[0][3] as Number,
}


// assertion sur la valeur en accés par encapsulation(dot)
console.assert(author.first_name === &amp;quot;Antonio&amp;quot;)
console.assert(author.last_name === &amp;quot;Gramsci&amp;quot;)
console.assert(author.lang === &amp;quot;it&amp;quot;)
console.assert(author.century === 20);

// assertion sur le type en accès par index
console.assert(typeof author[&amp;quot;first_name&amp;quot;] === &amp;quot;string&amp;quot;)
console.assert(typeof author[&amp;quot;last_name&amp;quot;] === &amp;quot;string&amp;quot;)
console.assert(typeof author[&amp;quot;lang&amp;quot;] === &amp;quot;string&amp;quot;)
console.assert(typeof author[&amp;quot;century&amp;quot;] === &amp;quot;number&amp;quot;);&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;#toc_ts&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;underline&amp;quot;&amp;gt;Pengantar typescript&amp;lt;/span&amp;gt;&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;enums_tuples&amp;quot;&amp;gt;Enums dan tuples&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;03_enums_tuples.ts&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-typescript hljs&amp;quot; data-lang=&amp;quot;typescript&amp;quot;&amp;gt;/**
 * Enums et Tuples
 *
 * Enum: il existe les enums numérique
 * et les enums chaine de caracteres.
 *
 * Tuple: similaire aux arrays mais ne peut
 * contenir qu&amp;#39;une valeur de type spécifié.
 *
 */

//Enum numérique
// l&amp;#39;index par de debut défaut est 0
// ici on le place a 1
enum Week {
    Monday = 1,
    Tuesday = 2,
    Wednesday = 3,
    Thursday = 4,
    Friday = 5,
    Saturday = 6,
    Sunday = 7,
};

console.assert(Week.Monday == 1);
console.assert(Week.Tuesday == 2);
console.assert(Week.Wednesday == 3);
console.assert(Week.Thursday == 4);
console.assert(Week.Friday == 5);
console.assert(Week.Saturday == 6);
console.assert(Week.Sunday == 7);

console.assert(Week[1] === &amp;quot;Monday&amp;quot;);
console.assert(Week[2] === &amp;quot;Tuesday&amp;quot;);
console.assert(Week[3] === &amp;quot;Wednesday&amp;quot;);
console.assert(Week[4] === &amp;quot;Thursday&amp;quot;);
console.assert(Week[5] === &amp;quot;Friday&amp;quot;);
console.assert(Week[6] === &amp;quot;Saturday&amp;quot;);
console.assert(Week[7] === &amp;quot;Sunday&amp;quot;);

let motd_arr_fr: String[] = [
    &amp;quot;Associé à la Lune&amp;quot;,
    &amp;quot;Du dieu Tiw, associé à Mars&amp;quot;,
    &amp;quot;Du dieu germanique Odin&amp;quot;,
    &amp;quot;Du dieu germanique du tonnerre Thor&amp;quot;,
    &amp;quot;De la déesse germanique Frigga associée à Vénus&amp;quot;,
    &amp;quot;Associé à Saturne&amp;quot;,
    &amp;quot;Associé au Soleil&amp;quot;,
];

let motd_arr_en: String[] = [
    &amp;quot;associated with the Moon&amp;quot;,
    &amp;quot;from the god Tiw, associated with Mars&amp;quot;,
    &amp;quot;from Germanic god Odin&amp;quot;,
    &amp;quot;from Germanic god of thunder Thor&amp;quot;,
    &amp;quot;from Germanic goddess Frigga associated with Venus&amp;quot;,
    &amp;quot;associated with Saturn&amp;quot;,
    &amp;quot;associated with the Sun&amp;quot;,
];

// Un tuple (triple)
let monday_triple_fr: [
    Number,
    String,
    String
] = [
        Week.Monday,
        Week[Week.Monday],
        motd_arr_fr[0],
    ];


console.assert(monday_triple_fr[0] === 1);
console.assert(monday_triple_fr[1] === &amp;quot;Monday&amp;quot;);
console.assert(monday_triple_fr[2] === &amp;quot;Associé à la Lune&amp;quot;);


let monday_triple_en: [
    Number,
    String,
    String
] = [
        Week.Monday,
        Week[Week.Monday],
        motd_arr_en[0],
    ];

console.assert(monday_triple_en[0] === 1);
console.assert(monday_triple_en[1] === &amp;quot;Monday&amp;quot;);
console.assert(monday_triple_en[2] === &amp;quot;associated with the Moon&amp;quot;);


// On se fait un type pour ajouter le nom de la langue
type Meaning_of_the_day = {
    lang: String,
    meaning: (Week | String)[][],
};

//fonction d&amp;#39;affichage du type Meaning_of_the_day
const display_motd = (motd: Meaning_of_the_day) =&amp;amp;gt; {
    motd.meaning.forEach(day =&amp;amp;gt;
        console.table(`${Week[day[0] as Week]}: ${day[1]}.(${motd.lang})`)
    )
};

//fonction d&amp;#39;assertion sur le type Meaning_of_the_day
// afin de verifier la concordance du contenu avec
// motd_arr_#lang#
const test_motd = (
    motd: Meaning_of_the_day,
    motd_arr: String[]
) =&amp;amp;gt; {
    console.assert(motd.lang.length === 2)
    for (const [i, value] of motd.meaning.entries()) {
        console.assert(value[1] === motd_arr[i]);
    }
};


let motd_fr: Meaning_of_the_day = {
    lang: &amp;quot;fr&amp;quot;,
    meaning: [
        [Week.Monday, motd_arr_fr[0]],
        [Week.Tuesday, motd_arr_fr[1]],
        [Week.Wednesday, motd_arr_fr[2]],
        [Week.Thursday, motd_arr_fr[3]],
        [Week.Friday, motd_arr_fr[4]],
        [Week.Saturday, motd_arr_fr[5]],
        [Week.Sunday, motd_arr_fr[6]],
    ],
}

console.log(&amp;quot;---------&amp;quot;);
console.log(&amp;quot;display_motd(motd_fr):&amp;quot;);
display_motd(motd_fr);
test_motd(motd_fr, motd_arr_fr)


let motd_en: Meaning_of_the_day = {
    lang: &amp;quot;en&amp;quot;,
    meaning: [
        [Week.Monday, motd_arr_en[0]],
        [Week.Tuesday, motd_arr_en[1]],
        [Week.Wednesday, motd_arr_en[2]],
        [Week.Thursday, motd_arr_en[3]],
        [Week.Friday, motd_arr_en[4]],
        [Week.Saturday, motd_arr_en[5]],
        [Week.Sunday, motd_arr_en[6]],
    ],
}

console.log(&amp;quot;---------&amp;quot;);
console.log(&amp;quot;display_motd(motd_en):&amp;quot;);
display_motd(motd_en);
console.log(&amp;quot;---------&amp;quot;);
test_motd(motd_en, motd_arr_en);

// Un tuple (triple) utilisant le type
// Meaning_of_the_day pour peupler le meaning
let motd_triple: [
    Number,
    String,
    String
] = [
        Week.Monday,
        Week[Week.Monday],
        motd_fr.meaning[0][1] as String
    ];

console.assert(motd_triple[0] === 1);
console.assert(motd_triple[1] === &amp;quot;Monday&amp;quot;);
console.assert(motd_triple[2] === &amp;quot;Associé à la Lune&amp;quot;);&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;#toc_ts&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;underline&amp;quot;&amp;gt;Pengantar typescript&amp;lt;/span&amp;gt;&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;void_never&amp;quot;&amp;gt;Void and Never&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;04_vide_jamais_types.ts&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-typescript hljs&amp;quot; data-lang=&amp;quot;typescript&amp;quot;&amp;gt;// Le type void est l&amp;#39;opposé du type any
// c&amp;#39;est l&amp;#39;absence de type.
function verify_writers_length(): void {
    console.assert(writers.length &amp;amp;gt; 0);
}

verify_writers_length();

const verify_writers_length_arrow = (): void =&amp;amp;gt;
    console.assert(writers.length &amp;amp;gt; 0);

verify_writers_length_arrow();

//Type of et never
let value = 30;
console.assert(typeof value === &amp;quot;number&amp;quot;);

//never: qqchs qui n&amp;#39;arrive jamais
function foo(x: String | Number): Boolean {
    if (typeof x === &amp;quot;string&amp;quot;) {
        return true;
    } else if (typeof x === &amp;quot;number&amp;quot;) {
        return false;
    }
    return fail(&amp;quot;Error!&amp;quot;)
}

function fail(message: String): never {
    throw new Error(message as string);
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;#toc_ts&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;underline&amp;quot;&amp;gt;Pengantar typescript&amp;lt;/span&amp;gt;&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;class_interface&amp;quot;&amp;gt;Kelas dan antarmuka&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;04_void_never_types.ts&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-typescript hljs&amp;quot; data-lang=&amp;quot;typescript&amp;quot;&amp;gt;/**
 * Les classes:
 * members,
 * visibility,
 * nommage,
 * accesseurs
 * scope identifier: static,private,protected
 *
 * convention de nommage :
 * Les membres private sont préfixés avec un _ *
 */

class Member {
    username: String = &amp;quot;&amp;quot;;
    email: String = &amp;quot;&amp;quot;;
    private _password: String = &amp;quot;&amp;quot;;
    readonly signup_date: Date = new Date(Date.now());

    constructor(username: String, email: String) {
        this.username = username;
        this.email = email;
    }

    get password(): String {
        return this._password;
    }

    set password(new_password: String) {
        this._password = new_password;
    }

    /**
     * Une fonction static qui initilise un member,
     * à partir d&amp;#39;un author
     */
    static fromAuthor(author: {
        first_name: String,
        last_name: String,
        lang: String,
        century: Number
    }): Member {
        return new Member(
            `${author.first_name}.${author.last_name}`,
            `${author.first_name}.${author.last_name}@acme.com`,
        );
    }
};


let domenico = Member.fromAuthor(author);
domenico.password = &amp;quot;test&amp;quot;;

console.assert(domenico.email === &amp;quot;Domenico.Losurdo@acme.com&amp;quot;);
console.assert(domenico.password === &amp;quot;test&amp;quot;);


// Héritage
enum Forms {
    Undefined = 0,
    Polygones = 1,
    Circle = 2,
    Straight = 3,
    Segment = 4,
};

class Form {
    type: Forms = Forms.Undefined;
};

class Rectangle extends Form {
    h: Number = 0;
    w: Number = 0;
};

class Square extends Rectangle {
    side: Number = 0;
};


/**
 * Interface donne un contrat à un type.
 * Les interfaces permettent de définir la structure d&amp;#39;un objet.
 */

interface IPerson {
    username: String;
    email: String;
    readonly signup_date: Date;
}

const user1: IPerson = {
    username: Member.fromAuthor(author)[&amp;quot;username&amp;quot;],
    email: Member.fromAuthor(author)[&amp;quot;email&amp;quot;],
    signup_date: Member.fromAuthor(author)[&amp;quot;signup_date&amp;quot;],
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;#toc_ts&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;underline&amp;quot;&amp;gt;Pengantar typescript&amp;lt;/span&amp;gt;&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;tsconfig&amp;quot;&amp;gt;File tsconfig.json&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;tsconfig.json&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-json hljs&amp;quot; data-lang=&amp;quot;json&amp;quot;&amp;gt;{
  /* Visit https://aka.ms/tsconfig to read more about this file */
  &amp;quot;compileOnSave&amp;quot;: true,

  &amp;quot;compilerOptions&amp;quot;: {
    &amp;quot;target&amp;quot;: &amp;quot;es2016&amp;quot;,
    &amp;quot;module&amp;quot;: &amp;quot;CommonJS&amp;quot;,
    &amp;quot;noEmitOnError&amp;quot;: true,
    &amp;quot;strict&amp;quot;: true,
    &amp;quot;noImplicitAny&amp;quot;: true,
    &amp;quot;esModuleInterop&amp;quot;: true
  }
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;#toc_ts&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;underline&amp;quot;&amp;gt;Pengantar typescript&amp;lt;/span&amp;gt;&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;modules&amp;quot;&amp;gt;Modul&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Modul memungkinkan mengatur kode dalam beberapa file.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Contoh nilai:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-typescript hljs&amp;quot; data-lang=&amp;quot;typescript&amp;quot;&amp;gt;// Dans un fichier math.ts
export const add = (a: number, b: number): number =&amp;amp;gt; a + b;

// Dans un autre fichier
import { add } from &amp;#39;./math&amp;#39;;
let result = add(2, 3);&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;#toc_ts&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;underline&amp;quot;&amp;gt;Pengantar typescript&amp;lt;/span&amp;gt;&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;promesses&amp;quot;&amp;gt;Janji&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Janji-janji digunakan untuk melakukan operasi asinkron.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Contoh nilai :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-typescript hljs&amp;quot; data-lang=&amp;quot;typescript&amp;quot;&amp;gt;const promise = new Promise&amp;amp;lt;string&amp;amp;gt;((resolve) =&amp;amp;gt; {
    // Code asynchrone ici
    resolve(&amp;quot;Succès&amp;quot;);
});

promise.then((result) =&amp;amp;gt; {
    console.log(result);
});&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;#toc_ts&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;underline&amp;quot;&amp;gt;Pengantar typescript&amp;lt;/span&amp;gt;&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;async-await&amp;quot;&amp;gt;async/await&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Async/await menyederhanakan pengelolaan janji.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Contoh nilai :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-typescript hljs&amp;quot; data-lang=&amp;quot;typescript&amp;quot;&amp;gt;const fetchData = async (): Promise&amp;amp;lt;void&amp;amp;gt; =&amp;amp;gt; {
    const result = await someAsyncFunction();
    console.log(result);
};&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;#toc_ts&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;underline&amp;quot;&amp;gt;Pengantar typescript&amp;lt;/span&amp;gt;&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;generik&amp;quot;&amp;gt;Generik&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Generik memungkinkan pembuatan komponen yang dapat digunakan kembali dengan tipe dinamis.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Contoh nilai :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-typescript hljs&amp;quot; data-lang=&amp;quot;typescript&amp;quot;&amp;gt;const identity = &amp;amp;lt;T&amp;amp;gt;(arg: T): T =&amp;amp;gt; arg;

const output = identity&amp;amp;lt;string&amp;amp;gt;(&amp;quot;hello&amp;quot;);
console.log(output);&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>Catatan CSS</title>
            <link >https://cheroliv.com/blog/2023/0062_memo_css_post.html</link>
            <pubDate>Mon, 14 Aug 2023 00:00:00 +0000</pubDate>
            <guid isPermaLink="false">blog/2023/0062_memo_css_post.html</guid>
            <description>&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;toc&amp;quot;&amp;gt;Daftar Isi&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;olist arabic&amp;quot;&amp;gt;
&amp;lt;ol class=&amp;quot;arabic&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;#good_links&amp;quot;&amp;gt;tautan baik&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;#repo&amp;quot;&amp;gt;Repositori dan contoh kode&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;#toc_début&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;underline&amp;quot;&amp;gt;Awalan CSS&amp;lt;/span&amp;gt;&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;good_links&amp;quot;&amp;gt;tautan baik&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;#toc&amp;quot;&amp;gt;Daftar Isi&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;repo&amp;quot;&amp;gt;Repositori dan contoh kode&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;repositori GitHub:https://github.com/cheroliv/css-codebase[css-codebase, window = &amp;quot;_blank]&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;toc_début&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;underline&amp;quot;&amp;gt;Awalan CSS&amp;lt;/span&amp;gt;&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;olist arabic&amp;quot;&amp;gt;
&amp;lt;ol class=&amp;quot;arabic&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;#awal_pendahuluan&amp;quot;&amp;gt;[awal_pendahuluan]&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;#début_texts&amp;quot;&amp;gt;texts&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;#mulai_latar_belakang&amp;quot;&amp;gt;[mulai_latar_belakang]&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;#début_boxes&amp;quot;&amp;gt;Kotak (boxes)&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;awal_flexbox&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;#awal_grid&amp;quot;&amp;gt;Grid&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;#awal_posisi_abs&amp;quot;&amp;gt;[awal_posisi_abs]&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;#awal_responsif&amp;quot;&amp;gt;[awal_responsif]&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;#awal_kesimpulan&amp;quot;&amp;gt;Debug &amp;amp;amp; Kesimpulan&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;#toc&amp;quot;&amp;gt;Daftar Isi&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;début_intro&amp;quot;&amp;gt;Pengantar &amp;amp;amp; Penyiapan&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Pendahuluan berdasarkan video ini:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;videoblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;iframe src=&amp;quot;https://www.youtube.com/embed/iSWjmVcfQGg?rel=0&amp;quot; frameborder=&amp;quot;0&amp;quot; allowfullscreen&amp;gt;&amp;lt;/iframe&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;github repository:https://github.com/cheroliv/css-codebase/début_css/[awal css, window = &amp;quot;_blank]&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;memo sumber:&amp;lt;br&amp;gt;
index.html&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-html hljs&amp;quot; data-lang=&amp;quot;html&amp;quot;&amp;gt;&amp;amp;lt;!DOCTYPE html&amp;amp;gt;
&amp;amp;lt;html lang=&amp;quot;fr&amp;quot;&amp;amp;gt;
  &amp;amp;lt;head&amp;amp;gt;
    &amp;amp;lt;meta charset=&amp;quot;UTF-8&amp;quot; /&amp;amp;gt;
    &amp;amp;lt;meta http-equiv=&amp;quot;X-UA-Compatible&amp;quot; content=&amp;quot;IE=edge&amp;quot; /&amp;amp;gt;
    &amp;amp;lt;meta name=&amp;quot;viewport&amp;quot; content=&amp;quot;width=device-width, initial-scale=1.0&amp;quot; /&amp;amp;gt;
    &amp;amp;lt;title&amp;amp;gt;Cours CSS&amp;amp;lt;/title&amp;amp;gt;

    &amp;amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;style.css&amp;quot; /&amp;amp;gt;
  &amp;amp;lt;/head&amp;amp;gt;
  &amp;amp;lt;body&amp;amp;gt;
    &amp;amp;lt;header&amp;amp;gt;
      &amp;amp;lt;h1&amp;amp;gt;Les bases de CSS&amp;amp;lt;/h1&amp;amp;gt;
    &amp;amp;lt;/header&amp;amp;gt;

    &amp;amp;lt;main&amp;amp;gt;
      &amp;amp;lt;div class=&amp;quot;flexbox&amp;quot;&amp;amp;gt;
        &amp;amp;lt;h2&amp;amp;gt;Flexbox&amp;amp;lt;/h2&amp;amp;gt;
        &amp;amp;lt;ul&amp;amp;gt;
          &amp;amp;lt;li&amp;amp;gt;boite 1&amp;amp;lt;/li&amp;amp;gt;
          &amp;amp;lt;li&amp;amp;gt;boite 2&amp;amp;lt;/li&amp;amp;gt;
          &amp;amp;lt;li&amp;amp;gt;boite 3&amp;amp;lt;/li&amp;amp;gt;
        &amp;amp;lt;/ul&amp;amp;gt;
      &amp;amp;lt;/div&amp;amp;gt;

      &amp;amp;lt;div class=&amp;quot;grid&amp;quot;&amp;amp;gt;
        &amp;amp;lt;h2&amp;amp;gt;Grid&amp;amp;lt;/h2&amp;amp;gt;
        &amp;amp;lt;div class=&amp;quot;grid-container&amp;quot;&amp;amp;gt;
          &amp;amp;lt;img src=&amp;quot;./assets/img/css-logo.png&amp;quot; alt=&amp;quot;logo css&amp;quot; /&amp;amp;gt;

          &amp;amp;lt;form action=&amp;quot;&amp;quot;&amp;amp;gt;
            &amp;amp;lt;input type=&amp;quot;text&amp;quot; id=&amp;quot;firstname&amp;quot; placeholder=&amp;quot;Prénom&amp;quot; /&amp;amp;gt;
            &amp;amp;lt;input type=&amp;quot;text&amp;quot; id=&amp;quot;surname&amp;quot; placeholder=&amp;quot;Nom&amp;quot; /&amp;amp;gt;
            &amp;amp;lt;textarea
              cols=&amp;quot;30&amp;quot;
              rows=&amp;quot;10&amp;quot;
              placeholder=&amp;quot;Ici votre message&amp;quot;
            &amp;amp;gt;&amp;amp;lt;/textarea&amp;amp;gt;
            &amp;amp;lt;input type=&amp;quot;submit&amp;quot; value=&amp;quot;Valider&amp;quot; id=&amp;quot;btn-submit&amp;quot; /&amp;amp;gt;
          &amp;amp;lt;/form&amp;amp;gt;
        &amp;amp;lt;/div&amp;amp;gt;
      &amp;amp;lt;/div&amp;amp;gt;

      &amp;amp;lt;div class=&amp;quot;absolute&amp;quot;&amp;amp;gt;
        &amp;amp;lt;h2&amp;amp;gt;Absolute&amp;amp;lt;/h2&amp;amp;gt;
        &amp;amp;lt;span id=&amp;quot;circle1&amp;quot;&amp;amp;gt;&amp;amp;lt;/span&amp;amp;gt;
        &amp;amp;lt;span id=&amp;quot;circle2&amp;quot;&amp;amp;gt;&amp;amp;lt;/span&amp;amp;gt;
      &amp;amp;lt;/div&amp;amp;gt;
    &amp;amp;lt;/main&amp;amp;gt;
  &amp;amp;lt;/body&amp;amp;gt;
&amp;amp;lt;/html&amp;amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Menautkan file style.css ke halaman HTML:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-html hljs&amp;quot; data-lang=&amp;quot;html&amp;quot;&amp;gt;&amp;amp;lt;/head&amp;amp;gt;
  &amp;amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;style.css&amp;quot; /&amp;amp;gt;
&amp;amp;lt;/head&amp;amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;#toc_awal&amp;quot;&amp;gt;[toc_awal]&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;début_texts&amp;quot;&amp;gt;texts&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-css hljs&amp;quot; data-lang=&amp;quot;css&amp;quot;&amp;gt;/* import remote de font sur l&amp;#39;ensemble de la feuille*/
@import url(&amp;quot;https://fonts.googleapis.com/css2?family=Oswald:wght@500&amp;amp;amp;display=swap&amp;quot;);

/* import local de font */
@font-face {
  font-family: &amp;quot;DMSerif&amp;quot;;
  src: url(./assets/fonts/DMSerifDisplay-Regular.ttf);
}

/*
body est le parent de toutes les balises,
qu&amp;#39;il contient, donc je le déclare avant,
pour la logique de précédence de parent
*/
body {
  /* si DMSerif n&amp;#39;est pas trouvé alors il se rabat sur Verdana*/
  font-family: &amp;quot;DMSerif&amp;quot;, Verdana;
}

/* va styliser toutes les balises h1,
celles de body sont surchargés,
par la déclaration présente */
h1 {
  text-transform: uppercase;
  /* espacement entre les lettres en pixel*/
  letter-spacing: 3px;
  /* alignement du texte*/
  text-align: center;
  /* Les tailles de polices doivent être en REM  (rem = root em).
  Le REM se base pas sur l&amp;#39;élément parent pour obtenir sa taille mais sur l&amp;#39;élément racine. Ainsi 1rem prendra sa valeur de la font-size de votre document (body ou html).*/
  font-size: 2.5rem;
  /* ombrage du texte */
  text-shadow:
  3px /* offset-x */
  3px /* offset-y */
  8px /* blur-radius */
  #00000042/* color */;
  /* couleur du texte */
  color: #ab0ef4;
  /*attibuer les polices(font): liste, ordonnée par priorité, de polices à utiliser pour mettre en forme le texte de l&amp;#39;élément ciblé.*/
  font-family: &amp;quot;Oswald&amp;quot;, sans-serif;
  /* surligner le text*/
  text-decoration: underline;
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;#toc_awal&amp;quot;&amp;gt;[toc_awal]&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;début_background&amp;quot;&amp;gt;Latar belakang&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-css hljs&amp;quot; data-lang=&amp;quot;css&amp;quot;&amp;gt;@import url(&amp;quot;https://fonts.googleapis.com/css2?family=Oswald:wght@500&amp;amp;amp;display=swap&amp;quot;);

@font-face {
  font-family: &amp;quot;DMSerif&amp;quot;;
  src: url(./assets/fonts/DMSerifDisplay-Regular.ttf);
}

body {
  font-family: &amp;quot;DMSerif&amp;quot;, Verdana;
  /* image comme background*/
  background: url(./assets/img/bg.jpg) center/cover;
  /* 100VH = 100% de la taille de l&amp;#39;écran (viewport height)
  min afin de ne pas bloquer la possibilité d&amp;#39;avoir un plus grand */
  min-height: 100vh;
}

h1 {
  text-transform: uppercase;
  letter-spacing: 3px;
  text-align: center;
  font-size: 2.5rem;
  text-shadow: 3px 3px 8px #00000042;
  color: #ab0ef4;
  font-family: &amp;quot;Oswald&amp;quot;, sans-serif;
  text-decoration: underline;
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;#toc_awal&amp;quot;&amp;gt;[toc_awal]&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;début_boxes&amp;quot;&amp;gt;Kotak (boxes)&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-css hljs&amp;quot; data-lang=&amp;quot;css&amp;quot;&amp;gt;@import url(&amp;quot;https://fonts.googleapis.com/css2?family=Oswald:wght@500&amp;amp;amp;display=swap&amp;quot;);

@font-face {
  font-family: &amp;quot;DMSerif&amp;quot;;
  src: url(./assets/fonts/DMSerifDisplay-Regular.ttf);
}

/* *: létoile donne du style a tous les éléments.
Ensuite remettre un à un les parametres par defaut afin d&amp;#39;eviter les stylisation inattendu.*/
* {
  /* surcharge de base tous les elements à padding 0*/
  margin: 0;
  /* surcharge de base tous les elements à padding 0*/
  padding: 0;
}

body {
  font-family: &amp;quot;DMSerif&amp;quot;, Verdana;
  /* image comme background*/
  background: url(./assets/img/bg.jpg) center/cover;
  min-height: 100vh;
}

h1 {
  text-transform: uppercase;
  letter-spacing: 3px;
  text-align: center;
  font-size: 2.5rem;
  text-shadow: 3px 3px 8px #00000042;
  color: #ab0ef4;
  font-family: &amp;quot;Oswald&amp;quot;, sans-serif;
  text-decoration: underline;
}

main {
  background: rgba(245, 245, 245, 0.9);
  min-height: 500px;
  /* quelque soit la taille de l&amp;#39;écran,
  la boite fait toujours 90% de la taille de la page,
  les 10% restant sont une marge à droite,
  pour avoir 5% de chaque coté on ajoute une margin, tel que [0, auto]*/
  width: 90%;
  /* haut bas prend 0, et  gauche droite prend auto(center cad meme de chaque cotés)*/
  margin: 0 auto;
  /* on ajoute des bords a notre boite*/
  border: 2px solid rgb(0, 140, 255);
  /* on arrondis les coins de la boite*/
  border-radius: 20px 20px 0 0;
  /* ombrage sur la boite*/
  box-shadow: 0px 0px 20px 4px #81cfc6;
  /* marge interrieure de la boite sur tous les cotés*/
  padding: 15px;
}

h2 {
  /* afin de recuprer le comportement par defaut qui a ete surchargé par l&amp;#39;operateur étoile*/
  margin: 0;
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-html hljs&amp;quot; data-lang=&amp;quot;html&amp;quot;&amp;gt;&amp;amp;lt;!DOCTYPE html&amp;amp;gt;
&amp;amp;lt;html lang=&amp;quot;fr&amp;quot;&amp;amp;gt;
  &amp;amp;lt;head&amp;amp;gt;
    &amp;amp;lt;meta charset=&amp;quot;UTF-8&amp;quot; /&amp;amp;gt;
    &amp;amp;lt;meta http-equiv=&amp;quot;X-UA-Compatible&amp;quot; content=&amp;quot;IE=edge&amp;quot; /&amp;amp;gt;
    &amp;amp;lt;meta name=&amp;quot;viewport&amp;quot; content=&amp;quot;width=device-width, initial-scale=1.0&amp;quot; /&amp;amp;gt;
    &amp;amp;lt;title&amp;amp;gt;Cours CSS&amp;amp;lt;/title&amp;amp;gt;

    &amp;amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;style.css&amp;quot; /&amp;amp;gt;
  &amp;amp;lt;/head&amp;amp;gt;
  &amp;amp;lt;body&amp;amp;gt;
    &amp;amp;lt;header&amp;amp;gt;
      &amp;amp;lt;h1&amp;amp;gt;Les bases de CSS&amp;amp;lt;/h1&amp;amp;gt;
    &amp;amp;lt;/header&amp;amp;gt;

    &amp;amp;lt;main&amp;amp;gt;
      &amp;amp;lt;div class=&amp;quot;flexbox&amp;quot;&amp;amp;gt;
        &amp;amp;lt;h2&amp;amp;gt;Flexbox&amp;amp;lt;/h2&amp;amp;gt;
        &amp;amp;lt;ul&amp;amp;gt;
          &amp;amp;lt;li&amp;amp;gt;boite 1&amp;amp;lt;/li&amp;amp;gt;
          &amp;amp;lt;li&amp;amp;gt;boite 2&amp;amp;lt;/li&amp;amp;gt;
          &amp;amp;lt;li&amp;amp;gt;boite 3&amp;amp;lt;/li&amp;amp;gt;
        &amp;amp;lt;/ul&amp;amp;gt;
      &amp;amp;lt;/div&amp;amp;gt;
    &amp;amp;lt;/main&amp;amp;gt;
  &amp;amp;lt;/body&amp;amp;gt;
&amp;amp;lt;/html&amp;amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;#toc_début&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;underline&amp;quot;&amp;gt;Awalan CSS&amp;lt;/span&amp;gt;&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;awal_flexbox&amp;quot;&amp;gt;Flexbox&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-css hljs&amp;quot; data-lang=&amp;quot;css&amp;quot;&amp;gt;@import url(&amp;quot;https://fonts.googleapis.com/css2?family=Oswald:wght@500&amp;amp;amp;display=swap&amp;quot;);

@font-face {
  font-family: &amp;quot;DMSerif&amp;quot;;
  src: url(./assets/fonts/DMSerifDisplay-Regular.ttf);
}

body {
  font-family: &amp;quot;DMSerif&amp;quot;, Verdana;
  background: url(./assets/img/bg.jpg) center/cover;
  min-height: 100vh;
}

h1 {
  text-transform: uppercase;
  letter-spacing: 3px;
  text-align: center;
  font-size: 2.5rem;
  text-shadow: 3px 3px 8px #00000042;
  color: #ab0ef4;
  font-family: &amp;quot;Oswald&amp;quot;, sans-serif;
  text-decoration: underline;
}

main {
  background: rgba(245, 245, 245, 0.9);
  min-height: 500px;
  width: 90%;
  margin: 0 auto;
  border: 2px solid rgb(0, 140, 255);
  border-radius: 20px 20px 0 0;
  box-shadow: 0px 0px 20px 4px #81cfc6;
  padding: 15px;
}

h2 {
  margin: 0;
}

/* le point permet d&amp;#39;acceder à la classe d&amp;#39;un composant*/
.flexbox {
  border: 2px solid skyblue;
  border-radius: 10px;
  padding: 10px;
  margin-top: 20px;
  min-height: 150px;
}

/* FLEXBOX */
/* Sert à répartir équitablement des éléments sur la page */
.flexbox ul {
  padding: 0;
  /* aligne les éléments enfants de ul(les li)*/
  display: flex;
  /* reparti convenablement(équitablement les li sur la page) */
  justify-content: space-around;
}
/* ce style concerne les li de la classe,
flexbox des balises qui y font referencent*/
.flexbox li {
  /* suprime les boules des éléments de la list li*/
  list-style: none;
  height: 160px;
  width: 160px;
  margin: 10px;
  background: turquoise;
  /* Centrer un unique élément verticalement et horizontalement */
  display: flex;
  justify-content: center;
  align-items: center;
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-html hljs&amp;quot; data-lang=&amp;quot;html&amp;quot;&amp;gt;&amp;amp;lt;!DOCTYPE html&amp;amp;gt;
&amp;amp;lt;html lang=&amp;quot;fr&amp;quot;&amp;amp;gt;
  &amp;amp;lt;head&amp;amp;gt;
    &amp;amp;lt;meta charset=&amp;quot;UTF-8&amp;quot; /&amp;amp;gt;
    &amp;amp;lt;meta http-equiv=&amp;quot;X-UA-Compatible&amp;quot; content=&amp;quot;IE=edge&amp;quot; /&amp;amp;gt;
    &amp;amp;lt;meta name=&amp;quot;viewport&amp;quot; content=&amp;quot;width=device-width, initial-scale=1.0&amp;quot; /&amp;amp;gt;
    &amp;amp;lt;title&amp;amp;gt;Cours CSS&amp;amp;lt;/title&amp;amp;gt;

    &amp;amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;style.css&amp;quot; /&amp;amp;gt;
  &amp;amp;lt;/head&amp;amp;gt;
  &amp;amp;lt;body&amp;amp;gt;
    &amp;amp;lt;header&amp;amp;gt;
      &amp;amp;lt;h1&amp;amp;gt;Les bases de CSS&amp;amp;lt;/h1&amp;amp;gt;
    &amp;amp;lt;/header&amp;amp;gt;

    &amp;amp;lt;main&amp;amp;gt;
      &amp;amp;lt;div class=&amp;quot;flexbox&amp;quot;&amp;amp;gt;
        &amp;amp;lt;h2&amp;amp;gt;Flexbox&amp;amp;lt;/h2&amp;amp;gt;
        &amp;amp;lt;ul&amp;amp;gt;
          &amp;amp;lt;li&amp;amp;gt;boite 1&amp;amp;lt;/li&amp;amp;gt;
          &amp;amp;lt;li&amp;amp;gt;boite 2&amp;amp;lt;/li&amp;amp;gt;
          &amp;amp;lt;li&amp;amp;gt;boite 3&amp;amp;lt;/li&amp;amp;gt;
        &amp;amp;lt;/ul&amp;amp;gt;
      &amp;amp;lt;/div&amp;amp;gt;
    &amp;amp;lt;/main&amp;amp;gt;
  &amp;amp;lt;/body&amp;amp;gt;
&amp;amp;lt;/html&amp;amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;#awal_daftar_isi&amp;quot;&amp;gt;[awal_daftar_isi]&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;awal_grid&amp;quot;&amp;gt;Grid&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Untuk mendistribusikan elemen dengan cara yang sedikit lebih kompleks daripada dengan flexbox, dengan sistem grid di antara lainnya.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-css hljs&amp;quot; data-lang=&amp;quot;css&amp;quot;&amp;gt;@import url(&amp;quot;https://fonts.googleapis.com/css2?family=Oswald:wght@500&amp;amp;amp;display=swap&amp;quot;);

@font-face {
  font-family: &amp;quot;DMSerif&amp;quot;;
  src: url(./assets/fonts/DMSerifDisplay-Regular.ttf);
}

body {
  font-family: &amp;quot;DMSerif&amp;quot;, Verdana;
  background: url(./assets/img/bg.jpg) center/cover;
  min-height: 100vh;
}

h1 {
  text-transform: uppercase;
  letter-spacing: 3px;
  text-align: center;
  font-size: 2.5rem;
  text-shadow: 3px 3px 8px #00000042;
  color: #ab0ef4;
  font-family: &amp;quot;Oswald&amp;quot;, sans-serif;
  text-decoration: underline;
}

main {
  background: rgba(245, 245, 245, 0.9);
  min-height: 500px;
  width: 90%;
  margin: 0 auto;
  border: 2px solid rgb(0, 140, 255);
  border-radius: 20px 20px 0 0;
  box-shadow: 0px 0px 20px 4px #81cfc6;
  padding: 15px;
}

h2 {
  margin: 0;
}

.flexbox,
.grid {
  border: 2px solid skyblue;
  border-radius: 10px;
  padding: 10px;
  margin-top: 20px;
  min-height: 150px;
}

.flexbox ul {
  padding: 0;
  display: flex;
  justify-content: space-around;
}

.flexbox li {
  list-style: none;
  height: 160px;
  width: 160px;
  margin: 10px;
  background: turquoise;
  display: flex;
  justify-content: center;
  align-items: center;
}

/* GRID */
.grid-container {
  display: grid;
  /* séparatuion de la grille en deux(à ses deux enfants),
  avec à gauche 30% pour l&amp;#39;image
  et à droite 70% pour le formulaire*/
  grid-template-columns: 30% 70%;
}

/* sizing de l&amp;#39;image css-logo
contenu dans la classe grid
qui est dans le parent grid-container*/
.grid img {
  /* l&amp;#39;image remplt 80% de part de grille*/
  width: 80%;
  margin: 20px auto;
  display: block;
}

form {
  display: grid;
  /* deux colonnes de chacune 1 fraction*/
  grid-template-columns: 1fr 1fr;
  /* trois rangés de chacune 1 fraction*/
    grid-template-rows: 1fr 1fr 1fr;
    /* structuration sous forme de template
    de notre repartition des composants
    enfants de la forme*/
  grid-template-areas:
    &amp;quot;i1 i2&amp;quot;/* i1: input1, i2:input2*/
    &amp;quot;ta ta&amp;quot;/*ta: text area */
    &amp;quot;vi bt&amp;quot;/*vi:, bt: bouton */;
}

/* on donne du style sur deux éléments
en meme temps en les listant
avant les parentheses*/
input,
textarea {
  margin: 5px;
  border: 1px solid darkblue;
  padding: 10px;
  font-size: 1.1rem;
  font-family: &amp;quot;DMSerif&amp;quot;;
  border-radius: 5px;
}

/* donner du style uniquement la textearea*/
textarea {
  /* renseigner le nommage pour le grid template area*/
  grid-area: ta;
  height: 40px;
  /* empeche le cassage du style de la page
  en empechant le resizing de la textarea
  du formulaire*/
  resize: none;
}
/* pour pointer un id d&amp;#39;élément html
on utilise la notation préfixe # */
#btn-submit {
  /* renseigner le nommage*/
  grid-area: bt;
  /* change le le symbole de curseur
  souris quand il survole sur le bouton*/
  cursor: pointer;
  background: cyan;
  transition: 0.2s;
}

/* au survole(hover) de la souris
le bouton change de comportement*/
#btn-submit:hover {
  background: darkblue;
  /* couleur de text du bouton devient blanc*/
  color: white;
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-html hljs&amp;quot; data-lang=&amp;quot;html&amp;quot;&amp;gt;&amp;amp;lt;!DOCTYPE html&amp;amp;gt;
&amp;amp;lt;html lang=&amp;quot;fr&amp;quot;&amp;amp;gt;

&amp;amp;lt;head&amp;amp;gt;
    &amp;amp;lt;meta charset=&amp;quot;UTF-8&amp;quot; /&amp;amp;gt;
    &amp;amp;lt;meta http-equiv=&amp;quot;X-UA-Compatible&amp;quot; content=&amp;quot;IE=edge&amp;quot; /&amp;amp;gt;
    &amp;amp;lt;meta name=&amp;quot;viewport&amp;quot; content=&amp;quot;width=device-width, initial-scale=1.0&amp;quot; /&amp;amp;gt;
    &amp;amp;lt;title&amp;amp;gt;Cours CSS&amp;amp;lt;/title&amp;amp;gt;

    &amp;amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;style.css&amp;quot; /&amp;amp;gt;
&amp;amp;lt;/head&amp;amp;gt;

&amp;amp;lt;body&amp;amp;gt;
    &amp;amp;lt;header&amp;amp;gt;
        &amp;amp;lt;h1&amp;amp;gt;Les bases de CSS&amp;amp;lt;/h1&amp;amp;gt;
    &amp;amp;lt;/header&amp;amp;gt;

    &amp;amp;lt;main&amp;amp;gt;
        &amp;amp;lt;div class=&amp;quot;flexbox&amp;quot;&amp;amp;gt;
            &amp;amp;lt;h2&amp;amp;gt;Flexbox&amp;amp;lt;/h2&amp;amp;gt;
            &amp;amp;lt;ul&amp;amp;gt;
                &amp;amp;lt;li&amp;amp;gt;boite 1&amp;amp;lt;/li&amp;amp;gt;
                &amp;amp;lt;li&amp;amp;gt;boite 2&amp;amp;lt;/li&amp;amp;gt;
                &amp;amp;lt;li&amp;amp;gt;boite 3&amp;amp;lt;/li&amp;amp;gt;
            &amp;amp;lt;/ul&amp;amp;gt;
        &amp;amp;lt;/div&amp;amp;gt;

        &amp;amp;lt;div class=&amp;quot;grid&amp;quot;&amp;amp;gt;
            &amp;amp;lt;h2&amp;amp;gt;Grid&amp;amp;lt;/h2&amp;amp;gt;
            &amp;amp;lt;div class=&amp;quot;grid-container&amp;quot;&amp;amp;gt;
                &amp;amp;lt;img src=&amp;quot;./assets/img/css-logo.png&amp;quot; alt=&amp;quot;logo css&amp;quot; /&amp;amp;gt;

                &amp;amp;lt;form action=&amp;quot;&amp;quot;&amp;amp;gt;
                    &amp;amp;lt;input type=&amp;quot;text&amp;quot; id=&amp;quot;firstname&amp;quot; placeholder=&amp;quot;Prénom&amp;quot; /&amp;amp;gt;
                    &amp;amp;lt;input type=&amp;quot;text&amp;quot; id=&amp;quot;surname&amp;quot; placeholder=&amp;quot;Nom&amp;quot; /&amp;amp;gt;
                    &amp;amp;lt;textarea cols=&amp;quot;30&amp;quot; rows=&amp;quot;10&amp;quot; placeholder=&amp;quot;Ici votre message&amp;quot;&amp;amp;gt;&amp;amp;lt;/textarea&amp;amp;gt;
                    &amp;amp;lt;input type=&amp;quot;submit&amp;quot; value=&amp;quot;Valider&amp;quot; id=&amp;quot;btn-submit&amp;quot; /&amp;amp;gt;
                &amp;amp;lt;/form&amp;amp;gt;
            &amp;amp;lt;/div&amp;amp;gt;
        &amp;amp;lt;/div&amp;amp;gt;
    &amp;amp;lt;/main&amp;amp;gt;
&amp;amp;lt;/body&amp;amp;gt;

&amp;amp;lt;/html&amp;amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;#toc_début&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;underline&amp;quot;&amp;gt;Awalan CSS&amp;lt;/span&amp;gt;&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;début_abs_pos&amp;quot;&amp;gt;Posisi mutlak&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-css hljs&amp;quot; data-lang=&amp;quot;css&amp;quot;&amp;gt;@import url(&amp;quot;https://fonts.googleapis.com/css2?family=Oswald:wght@500&amp;amp;amp;display=swap&amp;quot;);

@font-face {
  font-family: &amp;quot;DMSerif&amp;quot;;
  src: url(./assets/fonts/DMSerifDisplay-Regular.ttf);
}

body {
  font-family: &amp;quot;DMSerif&amp;quot;, Verdana;
  background: url(./assets/img/bg.jpg) center/cover;
  min-height: 100vh;
}

h1 {
  text-transform: uppercase;
  letter-spacing: 3px;
  text-align: center;
  font-size: 2.5rem;
  text-shadow: 3px 3px 8px #00000042;
  color: #ab0ef4;
  font-family: &amp;quot;Oswald&amp;quot;, sans-serif;
  text-decoration: underline;
}

main {
  background: rgba(245, 245, 245, 0.9);
  min-height: 500px;
  width: 90%;
  margin: 0 auto;
  border: 2px solid rgb(0, 140, 255);
  border-radius: 20px 20px 0 0;
  box-shadow: 0px 0px 20px 4px #81cfc6;
  padding: 15px;
}

h2 {
  margin: 0;
}

.flexbox,
.grid,
.absolute {
  border: 2px solid skyblue;
  border-radius: 10px;
  padding: 10px;
  margin-top: 20px;
  min-height: 150px;
}

.flexbox ul {
  padding: 0;
  display: flex;
  justify-content: space-around;
}

.flexbox li {
  list-style: none;
  height: 160px;
  width: 160px;
  margin: 10px;
  background: turquoise;
  display: flex;
  justify-content: center;
  align-items: center;
}

.grid-container {
  display: grid;
  grid-template-columns: 30% 70%;
}

.grid img {
  width: 80%;
  margin: 20px auto;
  display: block;
}

form {
  display: grid;
  grid-template-columns: 1fr 1fr;
  grid-template-rows: 1fr 1fr 1fr;
  grid-template-areas: &amp;quot;i1 i2&amp;quot; &amp;quot;ta ta&amp;quot; &amp;quot;vi bt&amp;quot;;
}

input,
textarea {
  margin: 5px;
  border: 1px solid darkblue;
  padding: 10px;
  font-size: 1.1rem;
  font-family: &amp;quot;DMSerif&amp;quot;;
  border-radius: 5px;
}

textarea {
  grid-area: ta;
  height: 40px;
  resize: none;
}

#btn-submit {
  grid-area: bt;
  cursor: pointer;
  background: cyan;
  transition: 0.2s;
}

#btn-submit:hover {
  background: darkblue;
  color: white;
}

/* ABSOLUTE  */
/* Sans élément en Relative, de base, l&amp;#39;élément en absolute l&amp;#39;est par rapport au Body  */
/* Il faut mettre une position relative au parent pour contraindre l&amp;#39;élément en absolute dans ses frontières  */
.absolute {
  /* position relative par rapport à son parent,
  donc contenu dans les frontieres de son parents*/
  position: relative;
}


#circle1 {
  height: 80px;
  width: 80px;
  background: skyblue;
  position: absolute;
  border-radius: 150px;
  top: -20px;
  right: -20px;
}

#circle2 {
  height: 40px;
  width: 40px;
  border-radius: 150px;
  background: teal;
  position: absolute;
  left: 50%;
  transform:
  /*traslate de 50% de la taille du cercle,
  pour etre centré par rapport au centre du cercle,
  plutot que sur son bord gauche par défaut.*/
  translateX(-50%);
  top: 100px;
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-html hljs&amp;quot; data-lang=&amp;quot;html&amp;quot;&amp;gt;&amp;amp;lt;!DOCTYPE html&amp;amp;gt;
&amp;amp;lt;html lang=&amp;quot;fr&amp;quot;&amp;amp;gt;

&amp;amp;lt;head&amp;amp;gt;
    &amp;amp;lt;meta charset=&amp;quot;UTF-8&amp;quot; /&amp;amp;gt;
    &amp;amp;lt;meta http-equiv=&amp;quot;X-UA-Compatible&amp;quot; content=&amp;quot;IE=edge&amp;quot; /&amp;amp;gt;
    &amp;amp;lt;meta name=&amp;quot;viewport&amp;quot; content=&amp;quot;width=device-width, initial-scale=1.0&amp;quot; /&amp;amp;gt;
    &amp;amp;lt;title&amp;amp;gt;Cours CSS&amp;amp;lt;/title&amp;amp;gt;

    &amp;amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;style.css&amp;quot; /&amp;amp;gt;
&amp;amp;lt;/head&amp;amp;gt;

&amp;amp;lt;body&amp;amp;gt;
    &amp;amp;lt;header&amp;amp;gt;
        &amp;amp;lt;h1&amp;amp;gt;Les bases de CSS&amp;amp;lt;/h1&amp;amp;gt;
    &amp;amp;lt;/header&amp;amp;gt;

    &amp;amp;lt;main&amp;amp;gt;
        &amp;amp;lt;div class=&amp;quot;flexbox&amp;quot;&amp;amp;gt;
            &amp;amp;lt;h2&amp;amp;gt;Flexbox&amp;amp;lt;/h2&amp;amp;gt;
            &amp;amp;lt;ul&amp;amp;gt;
                &amp;amp;lt;li&amp;amp;gt;boite 1&amp;amp;lt;/li&amp;amp;gt;
                &amp;amp;lt;li&amp;amp;gt;boite 2&amp;amp;lt;/li&amp;amp;gt;
                &amp;amp;lt;li&amp;amp;gt;boite 3&amp;amp;lt;/li&amp;amp;gt;
            &amp;amp;lt;/ul&amp;amp;gt;
        &amp;amp;lt;/div&amp;amp;gt;

        &amp;amp;lt;div class=&amp;quot;grid&amp;quot;&amp;amp;gt;
            &amp;amp;lt;h2&amp;amp;gt;Grid&amp;amp;lt;/h2&amp;amp;gt;
            &amp;amp;lt;div class=&amp;quot;grid-container&amp;quot;&amp;amp;gt;
                &amp;amp;lt;img src=&amp;quot;./assets/img/css-logo.png&amp;quot; alt=&amp;quot;logo css&amp;quot; /&amp;amp;gt;

                &amp;amp;lt;form action=&amp;quot;&amp;quot;&amp;amp;gt;
                    &amp;amp;lt;input type=&amp;quot;text&amp;quot; id=&amp;quot;firstname&amp;quot; placeholder=&amp;quot;Prénom&amp;quot; /&amp;amp;gt;
                    &amp;amp;lt;input type=&amp;quot;text&amp;quot; id=&amp;quot;surname&amp;quot; placeholder=&amp;quot;Nom&amp;quot; /&amp;amp;gt;
                    &amp;amp;lt;textarea cols=&amp;quot;30&amp;quot; rows=&amp;quot;10&amp;quot; placeholder=&amp;quot;Ici votre message&amp;quot;&amp;amp;gt;&amp;amp;lt;/textarea&amp;amp;gt;
                    &amp;amp;lt;input type=&amp;quot;submit&amp;quot; value=&amp;quot;Valider&amp;quot; id=&amp;quot;btn-submit&amp;quot; /&amp;amp;gt;
                &amp;amp;lt;/form&amp;amp;gt;
            &amp;amp;lt;/div&amp;amp;gt;
        &amp;amp;lt;/div&amp;amp;gt;

        &amp;amp;lt;div class=&amp;quot;absolute&amp;quot;&amp;amp;gt;
            &amp;amp;lt;h2&amp;amp;gt;Absolute&amp;amp;lt;/h2&amp;amp;gt;
            &amp;amp;lt;span id=&amp;quot;circle1&amp;quot;&amp;amp;gt;&amp;amp;lt;/span&amp;amp;gt;
            &amp;amp;lt;span id=&amp;quot;circle2&amp;quot;&amp;amp;gt;&amp;amp;lt;/span&amp;amp;gt;
        &amp;amp;lt;/div&amp;amp;gt;
    &amp;amp;lt;/main&amp;amp;gt;
&amp;amp;lt;/body&amp;amp;gt;

&amp;amp;lt;/html&amp;amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;#toc_début&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;underline&amp;quot;&amp;gt;Awalan CSS&amp;lt;/span&amp;gt;&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;début_responsive&amp;quot;&amp;gt;Responsif&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-css hljs&amp;quot; data-lang=&amp;quot;css&amp;quot;&amp;gt;@import url(&amp;quot;https://fonts.googleapis.com/css2?family=Oswald:wght@500&amp;amp;amp;display=swap&amp;quot;);

@font-face {
  font-family: &amp;quot;DMSerif&amp;quot;;
  src: url(./assets/fonts/DMSerifDisplay-Regular.ttf);
}

body {
  font-family: &amp;quot;DMSerif&amp;quot;, Verdana;
  background: url(./assets/img/bg.jpg) center/cover;
  min-height: 100vh;
}

h1 {
  text-transform: uppercase;
  letter-spacing: 3px;
  text-align: center;
  /* pour etre plus responsive on peut passer la taille en vw(view port width) pour qu&amp;#39;elle s&amp;#39;adapte à la taille de l&amp;#39;ecran*/
  font-size: 2.5rem;
  text-shadow: 3px 3px 8px #00000042;
  color: #ab0ef4;
  font-family: &amp;quot;Oswald&amp;quot;, sans-serif;
  text-decoration: underline;
}

main {
  background: rgba(245, 245, 245, 0.9);
  min-height: 500px;
  width: 90%;
  margin: 0 auto;
  border: 2px solid rgb(0, 140, 255);
  border-radius: 20px 20px 0 0;
  box-shadow: 0px 0px 20px 4px #81cfc6;
  padding: 15px;
}

h2 {
  margin: 0;
}

.flexbox,
.grid,
.absolute {
  border: 2px solid skyblue;
  border-radius: 10px;
  padding: 10px;
  margin-top: 20px;
  min-height: 150px;
}

.flexbox ul {
  padding: 0;
  display: flex;
  justify-content: space-around;
}

.flexbox li {
  list-style: none;
  height: 160px;
  width: 160px;
  margin: 10px;
  background: turquoise;
  display: flex;
  justify-content: center;
  align-items: center;
}

.grid-container {
  display: grid;
  grid-template-columns: 30% 70%;
}

.grid img {
  width: 80%;
  margin: 20px auto;
  /* permet en media query de la centrer en petit écran*/
  display: block;
}

form {
  display: grid;
  grid-template-columns: 1fr 1fr;
  grid-template-rows: 1fr 1fr 1fr;
  grid-template-areas: &amp;quot;i1 i2&amp;quot; &amp;quot;ta ta&amp;quot; &amp;quot;vi bt&amp;quot;;
}

input,
textarea {
  margin: 5px;
  border: 1px solid darkblue;
  padding: 10px;
  font-size: 1.1rem;
  font-family: &amp;quot;DMSerif&amp;quot;;
  border-radius: 5px;
}

textarea {
  grid-area: ta;
  height: 40px;
  resize: none;
}

#btn-submit {
  grid-area: bt;
  cursor: pointer;
  background: cyan;
  transition: 0.2s;
}

#btn-submit:hover {
  background: darkblue;
  color: white;
}

.absolute {
  position: relative;
}


#circle1 {
  height: 80px;
  width: 80px;
  background: skyblue;
  position: absolute;
  border-radius: 150px;
  top: -20px;
  right: -20px;
}

#circle2 {
  height: 40px;
  width: 40px;
  border-radius: 150px;
  background: teal;
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  top: 100px;
}

/* RESPONSIVE:
a partir des tailles d&amp;#39;ecran permet de surcharger les reglages definis plus haut
*/
@media screen and (max-width: 900px) {
  .grid-container {
    display: block;
  }
  .grid-container img {
    width: 40%;
  }
}

@media screen and (max-width: 610px) {
  .flexbox ul {
    flex-direction: column;
    align-items: center;
  }

  form {
    grid-template-columns: 1fr;
    grid-template-rows: 1fr;
    grid-template-areas:
      &amp;quot;i1&amp;quot;
      &amp;quot;i2&amp;quot;
      &amp;quot;ta&amp;quot;
      &amp;quot;bt&amp;quot;;
  }
  input,
  textarea {
    font-size: 0.8rem;
  }
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-html hljs&amp;quot; data-lang=&amp;quot;html&amp;quot;&amp;gt;&amp;amp;lt;!DOCTYPE html&amp;amp;gt;
&amp;amp;lt;html lang=&amp;quot;fr&amp;quot;&amp;amp;gt;

&amp;amp;lt;head&amp;amp;gt;
    &amp;amp;lt;meta charset=&amp;quot;UTF-8&amp;quot; /&amp;amp;gt;
    &amp;amp;lt;meta http-equiv=&amp;quot;X-UA-Compatible&amp;quot; content=&amp;quot;IE=edge&amp;quot; /&amp;amp;gt;
    &amp;amp;lt;meta name=&amp;quot;viewport&amp;quot; content=&amp;quot;width=device-width, initial-scale=1.0&amp;quot; /&amp;amp;gt;
    &amp;amp;lt;title&amp;amp;gt;Cours CSS&amp;amp;lt;/title&amp;amp;gt;

    &amp;amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;style.css&amp;quot; /&amp;amp;gt;
&amp;amp;lt;/head&amp;amp;gt;

&amp;amp;lt;body&amp;amp;gt;
    &amp;amp;lt;header&amp;amp;gt;
        &amp;amp;lt;h1&amp;amp;gt;Les bases de CSS&amp;amp;lt;/h1&amp;amp;gt;
    &amp;amp;lt;/header&amp;amp;gt;

    &amp;amp;lt;main&amp;amp;gt;
        &amp;amp;lt;div class=&amp;quot;flexbox&amp;quot;&amp;amp;gt;
            &amp;amp;lt;h2&amp;amp;gt;Flexbox&amp;amp;lt;/h2&amp;amp;gt;
            &amp;amp;lt;ul&amp;amp;gt;
                &amp;amp;lt;li&amp;amp;gt;boite 1&amp;amp;lt;/li&amp;amp;gt;
                &amp;amp;lt;li&amp;amp;gt;boite 2&amp;amp;lt;/li&amp;amp;gt;
                &amp;amp;lt;li&amp;amp;gt;boite 3&amp;amp;lt;/li&amp;amp;gt;
            &amp;amp;lt;/ul&amp;amp;gt;
        &amp;amp;lt;/div&amp;amp;gt;

        &amp;amp;lt;div class=&amp;quot;grid&amp;quot;&amp;amp;gt;
            &amp;amp;lt;h2&amp;amp;gt;Grid&amp;amp;lt;/h2&amp;amp;gt;
            &amp;amp;lt;div class=&amp;quot;grid-container&amp;quot;&amp;amp;gt;
                &amp;amp;lt;img src=&amp;quot;./assets/img/css-logo.png&amp;quot; alt=&amp;quot;logo css&amp;quot; /&amp;amp;gt;

                &amp;amp;lt;form action=&amp;quot;&amp;quot;&amp;amp;gt;
                    &amp;amp;lt;input type=&amp;quot;text&amp;quot; id=&amp;quot;firstname&amp;quot; placeholder=&amp;quot;Prénom&amp;quot; /&amp;amp;gt;
                    &amp;amp;lt;input type=&amp;quot;text&amp;quot; id=&amp;quot;surname&amp;quot; placeholder=&amp;quot;Nom&amp;quot; /&amp;amp;gt;
                    &amp;amp;lt;textarea cols=&amp;quot;30&amp;quot; rows=&amp;quot;10&amp;quot; placeholder=&amp;quot;Ici votre message&amp;quot;&amp;amp;gt;&amp;amp;lt;/textarea&amp;amp;gt;
                    &amp;amp;lt;input type=&amp;quot;submit&amp;quot; value=&amp;quot;Valider&amp;quot; id=&amp;quot;btn-submit&amp;quot; /&amp;amp;gt;
                &amp;amp;lt;/form&amp;amp;gt;
            &amp;amp;lt;/div&amp;amp;gt;
        &amp;amp;lt;/div&amp;amp;gt;

        &amp;amp;lt;div class=&amp;quot;absolute&amp;quot;&amp;amp;gt;
            &amp;amp;lt;h2&amp;amp;gt;Absolute&amp;amp;lt;/h2&amp;amp;gt;
            &amp;amp;lt;span id=&amp;quot;circle1&amp;quot;&amp;amp;gt;&amp;amp;lt;/span&amp;amp;gt;
            &amp;amp;lt;span id=&amp;quot;circle2&amp;quot;&amp;amp;gt;&amp;amp;lt;/span&amp;amp;gt;
        &amp;amp;lt;/div&amp;amp;gt;
    &amp;amp;lt;/main&amp;amp;gt;
&amp;amp;lt;/body&amp;amp;gt;

&amp;amp;lt;/html&amp;amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;toc_awal&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;awal_kesimpulan&amp;quot;&amp;gt;Debug &amp;amp;amp; Kesimpulan&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-css hljs&amp;quot; data-lang=&amp;quot;css&amp;quot;&amp;gt;/* all elements:
supprime les comportements par défaut*/
* {
  /* les marges à zéro*/
  margin: 0;
  /* les parge internes(padding) à zéro*/
  padding: 0;
  /* tous les bords avec un cadre rouge de 2px.
  qui est ou et qui fait quoi...*/
  border: 2px solid red;
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;#daftar_isi_awal&amp;quot;&amp;gt;[daftar_isi_awal]&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>Mémo HTML</title>
            <link >https://cheroliv.com/blog/2023/0061_memo_html_post.html</link>
            <pubDate>Sun, 13 Aug 2023 00:00:00 +0000</pubDate>
            <guid isPermaLink="false">blog/2023/0061_memo_html_post.html</guid>
            <description>&amp;lt;div id=&amp;quot;toc&amp;quot; class=&amp;quot;toc&amp;quot;&amp;gt;
&amp;lt;div id=&amp;quot;toctitle&amp;quot;&amp;gt;Sommaire&amp;lt;/div&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel1&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_bon_liens&amp;quot;&amp;gt;Bon liens&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_répository_et_code_sample&amp;quot;&amp;gt;Répository et code sample&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_bon_liens&amp;quot;&amp;gt;Bon liens&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;html&amp;lt;/div&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://htmlcheatsheet.com&amp;quot; target=&amp;quot;_blank&amp;quot; rel=&amp;quot;noopener&amp;quot;&amp;gt;htmlcheatsheet.com&amp;lt;/a&amp;gt;: HTML Cheatsheet&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;font&amp;lt;/div&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://fontawesome.com&amp;quot; target=&amp;quot;_blank&amp;quot; rel=&amp;quot;noopener&amp;quot;&amp;gt;fontawesome.com&amp;lt;/a&amp;gt;: bibliothèque d&amp;amp;#8217;icones&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://www.toptal.com/designers/htmlarrows/symbols/&amp;quot; target=&amp;quot;_blank&amp;quot; rel=&amp;quot;noopener&amp;quot;&amp;gt;toptal.com/designers/htmlarrows/symbols&amp;lt;/a&amp;gt;: Icones natives en HTML&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;caractères spéciaux et échappements&amp;lt;/div&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://www.sonarsource.com/blog/encoding-differentials-why-charset-matters/&amp;quot; class=&amp;quot;bare&amp;quot;&amp;gt;https://www.sonarsource.com/blog/encoding-differentials-why-charset-matters/&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://stackoverflow.com/a/3614344/13104550&amp;quot; class=&amp;quot;bare&amp;quot;&amp;gt;https://stackoverflow.com/a/3614344/13104550&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://html.spec.whatwg.org/multipage/named-characters.html&amp;quot; class=&amp;quot;bare&amp;quot;&amp;gt;https://html.spec.whatwg.org/multipage/named-characters.html&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;#toc&amp;quot;&amp;gt;[toc]&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_répository_et_code_sample&amp;quot;&amp;gt;Répository et code sample&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;github repository: &amp;lt;a href=&amp;quot;https://github.com/cheroliv/html-codebase&amp;quot; target=&amp;quot;_blank&amp;quot; rel=&amp;quot;noopener&amp;quot;&amp;gt;html-codebase&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;memo source:&amp;lt;br&amp;gt;
index.html&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-html hljs&amp;quot; data-lang=&amp;quot;html&amp;quot;&amp;gt;&amp;amp;lt;!DOCTYPE html&amp;amp;gt;
&amp;amp;lt;html lang=&amp;quot;fr&amp;quot;&amp;amp;gt;

&amp;amp;lt;head&amp;amp;gt;
  &amp;amp;lt;meta charset=&amp;quot;UTF-8&amp;quot; /&amp;amp;gt;
  &amp;amp;lt;meta name=&amp;quot;viewport&amp;quot; content=&amp;quot;width=device-width, initial-scale=1.0&amp;quot; /&amp;amp;gt;
  &amp;amp;lt;!-- Le titre de la page  --&amp;amp;gt;
  &amp;amp;lt;title&amp;amp;gt;Cours HTML&amp;amp;lt;/title&amp;amp;gt;
  &amp;amp;lt;!-- Icone de l&amp;#39;onglet --&amp;amp;gt;
  &amp;amp;lt;link rel=&amp;quot;shortcut icon&amp;quot; href=&amp;quot;html-logo.png&amp;quot; /&amp;amp;gt;
  &amp;amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;https://use.fontawesome.com/releases/v5.8.2/css/all.css&amp;quot; /&amp;amp;gt;
&amp;amp;lt;/head&amp;amp;gt;

&amp;amp;lt;body&amp;amp;gt;
  &amp;amp;lt;header&amp;amp;gt;
    &amp;amp;lt;h1&amp;amp;gt;Texte - Titre H1&amp;amp;lt;/h1&amp;amp;gt;
    &amp;amp;lt;p&amp;amp;gt;
      &amp;amp;lt;em&amp;amp;gt;em pour mettre en italique&amp;amp;lt;/em&amp;amp;gt;,
      &amp;amp;lt;strong&amp;amp;gt;strong pour mettre en gras&amp;amp;lt;/strong&amp;amp;gt;
      &amp;amp;lt;span&amp;amp;gt;L&amp;#39;élement en span ne revient pas à la ligne&amp;amp;lt;/span&amp;amp;gt;.
    &amp;amp;lt;/p&amp;amp;gt;
    &amp;amp;lt;p&amp;amp;gt;
      Lorem ipsum dolor sit amet consectetur adipisicing elit. Exercitationem
      cumque ratione veniam officiis nulla a debitis at facilis sed
      dignissimos.
    &amp;amp;lt;/p&amp;amp;gt;
  &amp;amp;lt;/header&amp;amp;gt;

  &amp;amp;lt;section&amp;amp;gt;
    &amp;amp;lt;div&amp;amp;gt;
      &amp;amp;lt;h2&amp;amp;gt;Photo - Titre H2&amp;amp;lt;/h2&amp;amp;gt;
      &amp;amp;lt;img src=&amp;quot;./img-1.jpg&amp;quot; alt=&amp;quot;image-arbre&amp;quot; height=&amp;quot;200&amp;quot; /&amp;amp;gt;
    &amp;amp;lt;/div&amp;amp;gt;

    &amp;amp;lt;div&amp;amp;gt;
      &amp;amp;lt;h3&amp;amp;gt;Liste - Titre H3&amp;amp;lt;/h3&amp;amp;gt;
      &amp;amp;lt;ul&amp;amp;gt;
        &amp;amp;lt;li&amp;amp;gt;UL = unordered list&amp;amp;lt;/li&amp;amp;gt;
        &amp;amp;lt;li&amp;amp;gt;UL = unordered list&amp;amp;lt;/li&amp;amp;gt;
        &amp;amp;lt;li&amp;amp;gt;UL = unordered list&amp;amp;lt;/li&amp;amp;gt;
      &amp;amp;lt;/ul&amp;amp;gt;
      &amp;amp;lt;ol&amp;amp;gt;
        &amp;amp;lt;li&amp;amp;gt;OL = ordered list&amp;amp;lt;/li&amp;amp;gt;
        &amp;amp;lt;li&amp;amp;gt;OL = ordered list&amp;amp;lt;/li&amp;amp;gt;
        &amp;amp;lt;li&amp;amp;gt;OL = ordered list&amp;amp;lt;/li&amp;amp;gt;
      &amp;amp;lt;/ol&amp;amp;gt;
    &amp;amp;lt;/div&amp;amp;gt;

    &amp;amp;lt;div&amp;amp;gt;
      &amp;amp;lt;h4&amp;amp;gt;Tableaux - Titre H4&amp;amp;lt;/h4&amp;amp;gt;
      &amp;amp;lt;table border=&amp;quot;4&amp;quot; cellpadding=&amp;quot;10&amp;quot; cellspacing=&amp;quot;4&amp;quot; style=&amp;quot;text-align: center&amp;quot;&amp;amp;gt;
        &amp;amp;lt;thead&amp;amp;gt;
          &amp;amp;lt;tr&amp;amp;gt;
            &amp;amp;lt;th&amp;amp;gt;Col 1&amp;amp;lt;/th&amp;amp;gt;
            &amp;amp;lt;th&amp;amp;gt;Col 2&amp;amp;lt;/th&amp;amp;gt;
            &amp;amp;lt;th&amp;amp;gt;Col 3&amp;amp;lt;/th&amp;amp;gt;
          &amp;amp;lt;/tr&amp;amp;gt;
        &amp;amp;lt;/thead&amp;amp;gt;
        &amp;amp;lt;tbody&amp;amp;gt;
          &amp;amp;lt;tr&amp;amp;gt;
            &amp;amp;lt;td rowspan=&amp;quot;2&amp;quot;&amp;amp;gt;Row 1 Cell 1&amp;amp;lt;/td&amp;amp;gt;
            &amp;amp;lt;td&amp;amp;gt;Row 1 Cell 2&amp;amp;lt;/td&amp;amp;gt;
            &amp;amp;lt;td&amp;amp;gt;Row 1 Cell 3&amp;amp;lt;/td&amp;amp;gt;
          &amp;amp;lt;/tr&amp;amp;gt;
          &amp;amp;lt;tr&amp;amp;gt;
            &amp;amp;lt;td&amp;amp;gt;Row 2 Cell 2&amp;amp;lt;/td&amp;amp;gt;
            &amp;amp;lt;td&amp;amp;gt;Row 2 Cell 3&amp;amp;lt;/td&amp;amp;gt;
          &amp;amp;lt;/tr&amp;amp;gt;
          &amp;amp;lt;tr&amp;amp;gt;
            &amp;amp;lt;td colspan=&amp;quot;3&amp;quot;&amp;amp;gt;Row 3 Cell 1&amp;amp;lt;/td&amp;amp;gt;
          &amp;amp;lt;/tr&amp;amp;gt;
        &amp;amp;lt;/tbody&amp;amp;gt;
      &amp;amp;lt;/table&amp;amp;gt;
    &amp;amp;lt;/div&amp;amp;gt;

    &amp;amp;lt;div&amp;amp;gt;
      &amp;amp;lt;h5&amp;amp;gt;&amp;amp;lt;span&amp;amp;gt;&amp;amp;amp;#9814;&amp;amp;lt;/span&amp;amp;gt; Liens - Titre H5&amp;amp;lt;/h5&amp;amp;gt;
      &amp;amp;lt;a href=&amp;quot;https://htmlcheatsheet.com/&amp;quot; target=&amp;quot;_blank&amp;quot;&amp;amp;gt;HTML Cheatsheet&amp;amp;lt;/a&amp;amp;gt;
      &amp;amp;lt;br /&amp;amp;gt;
      &amp;amp;lt;a href=&amp;quot;https://www.toptal.com/designers/htmlarrows/symbols/&amp;quot; target=&amp;quot;_blank&amp;quot;&amp;amp;gt;Icones natifs en HTML&amp;amp;lt;/a&amp;amp;gt;
    &amp;amp;lt;/div&amp;amp;gt;

    &amp;amp;lt;div&amp;amp;gt;
      &amp;amp;lt;h6&amp;amp;gt;&amp;amp;lt;i class=&amp;quot;fas fa-video&amp;quot;&amp;amp;gt;&amp;amp;lt;/i&amp;amp;gt; Vidéo - Titre H6&amp;amp;lt;/h6&amp;amp;gt;
      &amp;amp;lt;video src=&amp;quot;video.mp4&amp;quot; height=&amp;quot;150&amp;quot; autoplay loop muted&amp;amp;gt;&amp;amp;lt;/video&amp;amp;gt;
    &amp;amp;lt;/div&amp;amp;gt;
  &amp;amp;lt;/section&amp;amp;gt;
  &amp;amp;lt;br /&amp;amp;gt;
  &amp;amp;lt;!-- Formulaires en HTML --&amp;amp;gt;
  &amp;amp;lt;section&amp;amp;gt;
    &amp;amp;lt;h2&amp;amp;gt;Formulaire&amp;amp;lt;/h2&amp;amp;gt;
    &amp;amp;lt;form action=&amp;quot;/action.php&amp;quot; method=&amp;quot;post&amp;quot;&amp;amp;gt;
      &amp;amp;lt;label for=&amp;quot;name&amp;quot;&amp;amp;gt;Nom&amp;amp;lt;/label&amp;amp;gt;
      &amp;amp;lt;input id=&amp;quot;name&amp;quot; type=&amp;quot;text&amp;quot; placeholder=&amp;quot;Entrez votre nom&amp;quot; /&amp;amp;gt;&amp;amp;lt;br /&amp;amp;gt;

      &amp;amp;lt;label for=&amp;quot;number&amp;quot;&amp;amp;gt;Entrez votre age&amp;amp;lt;/label&amp;amp;gt;
      &amp;amp;lt;input type=&amp;quot;number&amp;quot; id=&amp;quot;number-age&amp;quot; value=&amp;quot;15&amp;quot; oninput=&amp;quot;document.getElementById(&amp;#39;range-age&amp;#39;).value=this.value&amp;quot;&amp;amp;gt;
      &amp;amp;lt;input type=&amp;quot;range&amp;quot; id=&amp;quot;range-age&amp;quot; min=&amp;quot;0&amp;quot; max=&amp;quot;100&amp;quot;
        oninput=&amp;quot;document.getElementById(&amp;#39;number-age&amp;#39;).value=this.value&amp;quot;&amp;amp;gt;&amp;amp;lt;br /&amp;amp;gt;

      &amp;amp;lt;!-- Input select --&amp;amp;gt;
      &amp;amp;lt;label for=&amp;quot;gender&amp;quot;&amp;amp;gt;Genre&amp;amp;lt;/label&amp;amp;gt;
      &amp;amp;lt;select id=&amp;quot;gender&amp;quot;&amp;amp;gt;
        &amp;amp;lt;option selected=&amp;quot;selected&amp;quot; value=&amp;quot;Homme&amp;quot;&amp;amp;gt;Homme&amp;amp;lt;/option&amp;amp;gt;
        &amp;amp;lt;option value=&amp;quot;Femme&amp;quot;&amp;amp;gt;Femme&amp;amp;lt;/option&amp;amp;gt;
      &amp;amp;lt;/select&amp;amp;gt;&amp;amp;lt;br /&amp;amp;gt;

      &amp;amp;lt;!-- Input radio --&amp;amp;gt;
      &amp;amp;lt;div&amp;amp;gt;
        &amp;amp;lt;input type=&amp;quot;radio&amp;quot; name=&amp;quot;type&amp;quot; id=&amp;quot;human&amp;quot; checked /&amp;amp;gt;
        &amp;amp;lt;label for=&amp;quot;human&amp;quot;&amp;amp;gt;Humain&amp;amp;lt;/label&amp;amp;gt;

        &amp;amp;lt;input type=&amp;quot;radio&amp;quot; name=&amp;quot;type&amp;quot; id=&amp;quot;dog&amp;quot; /&amp;amp;gt;
        &amp;amp;lt;label for=&amp;quot;dog&amp;quot;&amp;amp;gt;Chien&amp;amp;lt;/label&amp;amp;gt;

        &amp;amp;lt;input type=&amp;quot;radio&amp;quot; name=&amp;quot;type&amp;quot; id=&amp;quot;cat&amp;quot; /&amp;amp;gt;
        &amp;amp;lt;label for=&amp;quot;cat&amp;quot;&amp;amp;gt;Chat&amp;amp;lt;/label&amp;amp;gt;
      &amp;amp;lt;/div&amp;amp;gt;

      &amp;amp;lt;textarea cols=&amp;quot;20&amp;quot; rows=&amp;quot;5&amp;quot; placeholder=&amp;quot;Votre message...&amp;quot;&amp;amp;gt;&amp;amp;lt;/textarea&amp;amp;gt;&amp;amp;lt;br /&amp;amp;gt;

      &amp;amp;lt;!-- Input Checkbox --&amp;amp;gt;
      &amp;amp;lt;label&amp;amp;gt;&amp;amp;lt;input type=&amp;quot;checkbox&amp;quot; /&amp;amp;gt;Accepter les CGV&amp;amp;lt;/label&amp;amp;gt; &amp;amp;lt;br /&amp;amp;gt;
      &amp;amp;lt;input type=&amp;quot;submit&amp;quot; value=&amp;quot;Submit&amp;quot; /&amp;amp;gt;
    &amp;amp;lt;/form&amp;amp;gt;
  &amp;amp;lt;/section&amp;amp;gt;
  &amp;amp;lt;br /&amp;amp;gt;
  &amp;amp;lt;!-- Mail &amp;amp;amp; envoi de fichiers --&amp;amp;gt;
  &amp;amp;lt;footer&amp;amp;gt;
    &amp;amp;lt;a href=&amp;quot;mailto:fs@gmail.com&amp;quot;&amp;amp;gt;Ecrivez-moi !&amp;amp;lt;/a&amp;amp;gt;
    &amp;amp;lt;br /&amp;amp;gt;
    &amp;amp;lt;a href=&amp;quot;notice.txt&amp;quot; download=&amp;quot;nom-du-fichier&amp;quot;&amp;amp;gt;Télécharger la notice&amp;amp;lt;/a&amp;amp;gt;
    &amp;amp;lt;details&amp;amp;gt;
      &amp;amp;lt;summary&amp;amp;gt;Plus d&amp;#39;infos&amp;amp;lt;/summary&amp;amp;gt;
      &amp;amp;lt;p&amp;amp;gt;
        Lorem ipsum dolor, sit amet consectetur adipisicing elit. Harum,
        repellendus.
      &amp;amp;lt;/p&amp;amp;gt;
    &amp;amp;lt;/details&amp;amp;gt;
  &amp;amp;lt;/footer&amp;amp;gt;
&amp;amp;lt;/body&amp;amp;gt;

&amp;amp;lt;/html&amp;amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;#toc&amp;quot;&amp;gt;[toc]&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>Mémo Javascript</title>
            <link >https://cheroliv.com/blog/2023/0060_memo_js_post.html</link>
            <pubDate>Fri, 4 Aug 2023 00:00:00 +0000</pubDate>
            <guid isPermaLink="false">blog/2023/0060_memo_js_post.html</guid>
            <description>&amp;lt;div id=&amp;quot;preamble&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;github repository: &amp;lt;a href=&amp;quot;https://github.com/cheroliv/js-codebase&amp;quot; target=&amp;quot;_blank&amp;quot; rel=&amp;quot;noopener&amp;quot;&amp;gt;js-codebase&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;toc_js&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;underline&amp;quot;&amp;gt;Intro Javascript&amp;lt;/span&amp;gt;&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;olist arabic&amp;quot;&amp;gt;
&amp;lt;ol class=&amp;quot;arabic&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;#start&amp;quot;&amp;gt;Variables, opérateurs arithmétiques et chaines de caractères&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;#arrays&amp;quot;&amp;gt;Les arrays&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;#functions&amp;quot;&amp;gt;Les fonctions&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;#param_x_scope&amp;quot;&amp;gt;Paramètres et scopes&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;#objects&amp;quot;&amp;gt;Les objects&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;#loops&amp;quot;&amp;gt;Les boucles&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;#const_var_let&amp;quot;&amp;gt;Const, var et let&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;#rest_and_spread_operators&amp;quot;&amp;gt;Rest et spread operators&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;#destructuring&amp;quot;&amp;gt;Destructuring&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;#import_export&amp;quot;&amp;gt;Import et export de modules&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;#classes&amp;quot;&amp;gt;Utilisation des classes&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;index.html&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-html hljs&amp;quot; data-lang=&amp;quot;html&amp;quot;&amp;gt;&amp;amp;lt;!DOCTYPE html&amp;amp;gt;
&amp;amp;lt;html lang=&amp;quot;fr&amp;quot;&amp;amp;gt;

&amp;amp;lt;head&amp;amp;gt;
    &amp;amp;lt;meta charset=&amp;quot;UTF-8&amp;quot;&amp;amp;gt;
    &amp;amp;lt;title&amp;amp;gt;Document&amp;amp;lt;/title&amp;amp;gt;
    &amp;amp;lt;meta name=&amp;quot;viewport&amp;quot; content=&amp;quot;width=device-width, initial-scale=1.0&amp;quot; /&amp;amp;gt;
    &amp;amp;lt;meta http-equiv=&amp;quot;X-UA-Compatible&amp;quot; content=&amp;quot;ie=edge&amp;quot; /&amp;amp;gt;
&amp;amp;lt;/head&amp;amp;gt;

&amp;amp;lt;body&amp;amp;gt;
    index
    &amp;amp;lt;script src=&amp;quot;01_start.js&amp;quot;&amp;amp;gt;&amp;amp;lt;/script&amp;amp;gt;
    &amp;amp;lt;script src=&amp;quot;02_arrays.js&amp;quot;&amp;amp;gt;&amp;amp;lt;/script&amp;amp;gt;
    &amp;amp;lt;script src=&amp;quot;03_functions.js&amp;quot;&amp;amp;gt;&amp;amp;lt;/script&amp;amp;gt;
    &amp;amp;lt;script src=&amp;quot;04_param_x_scope.js&amp;quot;&amp;amp;gt;&amp;amp;lt;/script&amp;amp;gt;
    &amp;amp;lt;script src=&amp;quot;05_objects.js&amp;quot;&amp;amp;gt;&amp;amp;lt;/script&amp;amp;gt;
    &amp;amp;lt;script src=&amp;quot;06_loops.js&amp;quot;&amp;amp;gt;&amp;amp;lt;/script&amp;amp;gt;
    &amp;amp;lt;script src=&amp;quot;07_const_var_let.js&amp;quot;&amp;amp;gt;&amp;amp;lt;/script&amp;amp;gt;
    &amp;amp;lt;script src=&amp;quot;08_rest_and_spread_operators.js&amp;quot;&amp;amp;gt;&amp;amp;lt;/script&amp;amp;gt;
    &amp;amp;lt;script src=&amp;quot;09_destructuring.js&amp;quot;&amp;amp;gt;&amp;amp;lt;/script&amp;amp;gt;
    &amp;amp;lt;script src=&amp;quot;10_module_import_export.js&amp;quot;&amp;amp;gt;&amp;amp;lt;/script&amp;amp;gt;
&amp;amp;lt;/body&amp;amp;gt;

&amp;amp;lt;/html&amp;amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;#toc_js&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;underline&amp;quot;&amp;gt;Intro Javascript&amp;lt;/span&amp;gt;&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;start&amp;quot;&amp;gt;Variables, opérateurs arithmétiques et chaines de caractères&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;01_start.js&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-javascript hljs&amp;quot; data-lang=&amp;quot;javascript&amp;quot;&amp;gt;/**
 * Variables
 */
//une variable stupide
var my_variable = &amp;quot;dummy variable&amp;quot;;
console.log(my_variable);

//assertion
console.assert(my_variable === &amp;quot;dummy variable&amp;quot;);

var username = &amp;quot;cheroliv&amp;quot;;
console.log(username);
console.assert(username === &amp;quot;cheroliv&amp;quot;);

//concaténation de string
console.assert(username === &amp;quot;cher&amp;quot; + &amp;quot;oliv&amp;quot;);

/**
 *  Opérateur arithmétique: + - * / %
 */
//integer
var x = 10;
var y = 5;
var z = 20;

console.log(x + y);
console.assert(x + y === 15);

console.log(x + y - z);
console.assert(x + y - z === -5);


console.log(x - y);
console.assert(x - y === 5);


console.log(x * y);
console.assert(x * y === 50);

console.log(x / y);
console.assert(x / y === 2);


console.log(&amp;quot;---------&amp;quot;);

x = 20;
y = 10;

console.log(x + y);
console.assert(x + y === 30);

console.log(x + y - z);
console.assert(x + y - z === 10);


console.log(x - y);
console.assert(x - y === 10);

console.log(x * y);
console.assert(x * y === 200);

console.log(x / z);
console.assert(x / z === 1);


//Littéraux de gabarits pour string avec backstick `
//Template literals (Template strings)
console.assert(`typeof z: ${typeof z}` === &amp;quot;typeof z: number&amp;quot;);

console.log(`x is type of : ${typeof x}`);
console.log(`y is type of : ${typeof y}`);
console.log(`z is type of : ${typeof z}`);

//typeof: donne le type de la variable
console.assert(typeof x === &amp;quot;number&amp;quot;);
console.assert(typeof y === &amp;quot;number&amp;quot;);
console.assert(typeof z === &amp;quot;number&amp;quot;);


console.log(&amp;quot;---------&amp;quot;);
/**
 * Incrémenation/décrémentation
 */
//incrémantation/décrémentation préfixe
console.log(++x) //incrémenté puis affiché: 21
console.log(x) //la valeur incrémenté: 21

//incrémantation/décrémentation suffixe
console.log(y++) //affiche la valeur sans incrémenation puis incrémente: 10
console.log(y) //la valeur incrémenté: 11

//décrémentation
x--;
y--;
console.assert(x === 20);
console.assert(y === 10);


/**
 * String: chaine de caractères
 */
console.log(`username: ${username}`);

//taille de la string
console.log(`username.length: ${username.length}`);
console.assert(username.length === 8);
console.assert(username.length === &amp;quot;cheroliv&amp;quot;.length);
console.log(&amp;quot;---------&amp;quot;);&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;#toc_js&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;underline&amp;quot;&amp;gt;Intro Javascript&amp;lt;/span&amp;gt;&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;arrays&amp;quot;&amp;gt;Les arrays&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;02_arrays.js&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-javascript hljs&amp;quot; data-lang=&amp;quot;javascript&amp;quot;&amp;gt;/**
 * Les arrays
 */
// auteurs [[&amp;quot;nom &amp;quot;, &amp;quot;prénom&amp;quot;, &amp;quot;pays&amp;quot;]]
var writers = [
    [&amp;quot;Karl&amp;quot;, &amp;quot;Marx&amp;quot;, &amp;quot;de&amp;quot;],
    [&amp;quot;Jean-Jacques&amp;quot;, &amp;quot;Rousseau&amp;quot;, &amp;quot;fr&amp;quot;],
    [&amp;quot;Victor&amp;quot;, &amp;quot;Hugo&amp;quot;, &amp;quot;fr&amp;quot;],
    [&amp;quot;René&amp;quot;, &amp;quot;Descartes&amp;quot;, &amp;quot;fr&amp;quot;],
    [&amp;quot;Paul&amp;quot;, &amp;quot;Verlaine&amp;quot;, &amp;quot;fr&amp;quot;],
    [&amp;quot;Antonio&amp;quot;, &amp;quot;Gramsci&amp;quot;, &amp;quot;it&amp;quot;],
    [&amp;quot;Georg&amp;quot;, &amp;quot;Lukacs&amp;quot;, &amp;quot;hu&amp;quot;],
    [&amp;quot;Franz&amp;quot;, &amp;quot;Kafka&amp;quot;, &amp;quot;hu&amp;quot;],
    [&amp;quot;Arthur&amp;quot;, &amp;quot;Rimbaud&amp;quot;, &amp;quot;fr&amp;quot;],
    [&amp;quot;Gérard&amp;quot;, &amp;quot;de Nerval&amp;quot;, &amp;quot;fr&amp;quot;],
    [&amp;quot;Chrétien&amp;quot;, &amp;quot;de Troyes&amp;quot;, &amp;quot;fr&amp;quot;],
    [&amp;quot;François&amp;quot;, &amp;quot;Rabelais&amp;quot;, &amp;quot;fr&amp;quot;],
    [&amp;quot;Georg&amp;quot;, &amp;quot;Hegel&amp;quot;, &amp;quot;de&amp;quot;],
    [&amp;quot;Friedrich&amp;quot;, &amp;quot;Engels&amp;quot;, &amp;quot;de&amp;quot;],
]

console.table(writers);

// accés aux élèments du tableau
console.log(writers[0]);
console.log(`${writers[0][0]}, ${writers[0][1]} (${writers[0][2]})`);
console.assert(&amp;quot;Karl, Marx (de)&amp;quot; === `${writers[0][0]}, ${writers[0][1]} (${writers[0][2]})`);

//sauvegarder la valeur du prénom de Marx
var karl = writers[0][0]

//éditer un élement du tableau, le prénom de Marx
writers[0][0] = &amp;quot;Karlito&amp;quot;

console.log(`${writers[0][0]}, ${writers[0][1]} (${writers[0][2]})`);
console.assert(&amp;quot;Karlito, Marx (de)&amp;quot; === `${writers[0][0]}, ${writers[0][1]} (${writers[0][2]})`);

console.assert(&amp;quot;Karl&amp;quot; === karl);

//remettre la valeur initale du prénom de Marx
writers[0][0] = karl;

console.assert(`${karl}, Marx (de)` === `${writers[0][0]}, ${writers[0][1]} (${writers[0][2]})`);

console.log(&amp;quot;---------&amp;quot;);

/**
 *  Arrays: map/forEach/pop/push/slice/sort/shift/unshift
 */

var numbers = [1, 5, 4, 3, 2];

console.log(&amp;quot;Iterate with forEach&amp;quot;);
// parcourir avec array.forEach
numbers.forEach((it) =&amp;amp;gt; console.log(it));
console.log(&amp;quot;---------&amp;quot;);

console.log(&amp;quot;Iterate with map&amp;quot;);
// parcourir avec array.map
numbers.map((it) =&amp;amp;gt; console.log(it));
console.log(&amp;quot;---------&amp;quot;);

//afficher les éléments sur une ligne
//génère la string avec le formatage des nombres
const numbersString = (numberArray) =&amp;amp;gt; {
    var consoleOutput = new String();
    numberArray.forEach(number =&amp;amp;gt; consoleOutput += `${number}, `);
    return consoleOutput = consoleOutput.substring(0, consoleOutput.length - 2);
};

//affiche la chaine entre crochets dans la console
const displayNumbers = (numbersStr) =&amp;amp;gt; {
    console.log(`[${numbersString(numbersStr)}]`);
};

console.log(&amp;quot;orginal number array&amp;quot;)
displayNumbers(numbers);
console.log(&amp;quot;---------&amp;quot;);


//push: ajoute à la fin
console.log(&amp;quot;push&amp;quot;);
numbers.push(6);
displayNumbers(numbers);
console.log(&amp;quot;---------&amp;quot;);

//pop: suprime le dernier
console.log(&amp;quot;pop&amp;quot;);
numbers.pop();
displayNumbers(numbers);
console.log(&amp;quot;---------&amp;quot;);

//unshift: ajouter le parametre au debut de l&amp;#39;array
console.log(&amp;quot;unshift&amp;quot;);
numbers.unshift(0);
displayNumbers(numbers);
console.log(&amp;quot;---------&amp;quot;);

//shift: supprime le premier element de l&amp;#39;array
console.log(&amp;quot;shift&amp;quot;);
numbers.shift();
displayNumbers(numbers);
console.log(&amp;quot;---------&amp;quot;);

//slice: renvoi l&amp;#39;array entre les positions en argument
console.log(&amp;quot;slice&amp;quot;);
var sliceNumbersResult = numbers.slice(2, 4);
displayNumbers(sliceNumbersResult);
console.log(&amp;quot;---------&amp;quot;);

//sort asc
console.log(&amp;quot;sort asc&amp;quot;);
var ascSort = numbers.sort((a, b) =&amp;amp;gt; a - b);
displayNumbers(ascSort);
console.log(&amp;quot;---------&amp;quot;);

//sort desc
console.log(&amp;quot;sort desc&amp;quot;);
var descSort = numbers.sort((a, b) =&amp;amp;gt; b - a);
displayNumbers(descSort);
console.log(&amp;quot;---------&amp;quot;);&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;#toc_js&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;underline&amp;quot;&amp;gt;Intro Javascript&amp;lt;/span&amp;gt;&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;functions&amp;quot;&amp;gt;Les fonctions&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;03_functions.js&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-javascript hljs&amp;quot; data-lang=&amp;quot;javascript&amp;quot;&amp;gt;/**
 *  Les fonctions
 */

//function style legacy
function sayHelloWorldLegacy() {
    console.log(&amp;quot;Hello world legacy!&amp;quot;);
}
sayHelloWorldLegacy();

function sayHelloLegacy(firstName, lastName, style) {
    console.log(`Hello ${firstName}, ${lastName} (${style})`);
}
sayHelloLegacy(&amp;quot;Cher&amp;quot;, &amp;quot;Oliv&amp;quot;, &amp;quot;legacy&amp;quot;);

console.log(&amp;quot;---------&amp;quot;);

//function style arrow
const sayHelloWorld = () =&amp;amp;gt; console.log(&amp;quot;Hello world!&amp;quot;);
sayHelloWorld();

const sayHello = (firstName, lastName) =&amp;amp;gt;
    console.log(`Hello ${firstName}, ${lastName}`);
sayHello(&amp;quot;Cher&amp;quot;, &amp;quot;Oliv&amp;quot;);

console.log(&amp;quot;---------&amp;quot;);

var authors = [
    [&amp;quot;Chrétien&amp;quot;, &amp;quot;de Troyes&amp;quot;, &amp;quot;fr&amp;quot;],
    [&amp;quot;François&amp;quot;, &amp;quot;Rabelais&amp;quot;, &amp;quot;fr&amp;quot;],
    [&amp;quot;René&amp;quot;, &amp;quot;Descartes&amp;quot;, &amp;quot;fr&amp;quot;],
    [&amp;quot;Jean-Jacques&amp;quot;, &amp;quot;Rousseau&amp;quot;, &amp;quot;fr&amp;quot;],
    [&amp;quot;Georg&amp;quot;, &amp;quot;Hegel&amp;quot;, &amp;quot;de&amp;quot;],
    [&amp;quot;Karl&amp;quot;, &amp;quot;Marx&amp;quot;, &amp;quot;de&amp;quot;],
    [&amp;quot;Friedrich&amp;quot;, &amp;quot;Engels&amp;quot;, &amp;quot;de&amp;quot;],
    [&amp;quot;Victor&amp;quot;, &amp;quot;Hugo&amp;quot;, &amp;quot;fr&amp;quot;],
    [&amp;quot;Paul&amp;quot;, &amp;quot;Verlaine&amp;quot;, &amp;quot;fr&amp;quot;],
    [&amp;quot;Antonio&amp;quot;, &amp;quot;Gramsci&amp;quot;, &amp;quot;it&amp;quot;],
    [&amp;quot;Georg&amp;quot;, &amp;quot;Lukacs&amp;quot;, &amp;quot;hu&amp;quot;],
    [&amp;quot;Franz&amp;quot;, &amp;quot;Kafka&amp;quot;, &amp;quot;hu&amp;quot;],
    [&amp;quot;Arthur&amp;quot;, &amp;quot;Rimbaud&amp;quot;, &amp;quot;fr&amp;quot;],
    [&amp;quot;Gérard&amp;quot;, &amp;quot;de Nerval&amp;quot;, &amp;quot;fr&amp;quot;],
]


const displayAuthors = (authorsArray) =&amp;amp;gt; authorsArray.forEach(author =&amp;amp;gt;
    console.log(`${author[0]} ${author[1]}, (${author[2]})`)
);

displayAuthors(authors);

console.log(&amp;quot;---------&amp;quot;);

/**
 * valeur par défaut des parametres d&amp;#39;une fonctions
 */
const add = (a = 0, b = 0) =&amp;amp;gt; a + b;
console.assert(add() === 0)
console.assert(add(1) === 1)
console.assert(add(1, 1) === 2)

console.log(&amp;quot;---------&amp;quot;);&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;#toc_js&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;underline&amp;quot;&amp;gt;Intro Javascript&amp;lt;/span&amp;gt;&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;param_x_scope&amp;quot;&amp;gt;Paramètres et scopes&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;04_param_x_scope.js&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-javascript hljs&amp;quot; data-lang=&amp;quot;javascript&amp;quot;&amp;gt;//global scope: visible partout, dans et hors functions
var global;

const foo = () =&amp;amp;gt; {
    //local scope: visible uniquement dans la fonction foo
    var bar = &amp;quot;bar&amp;quot;
    console.log(bar)
}

foo();
console.log(&amp;quot;---------&amp;quot;);&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;#toc_js&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;underline&amp;quot;&amp;gt;Intro Javascript&amp;lt;/span&amp;gt;&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;objects&amp;quot;&amp;gt;Les objects&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;05_objects.js&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-javascript hljs&amp;quot; data-lang=&amp;quot;javascript&amp;quot;&amp;gt;/**
 * Les objects
 */
var authors = [
    [&amp;quot;Chrétien&amp;quot;, &amp;quot;de Troyes&amp;quot;, &amp;quot;fr&amp;quot;],
    [&amp;quot;François&amp;quot;, &amp;quot;Rabelais&amp;quot;, &amp;quot;fr&amp;quot;],
    [&amp;quot;René&amp;quot;, &amp;quot;Descartes&amp;quot;, &amp;quot;fr&amp;quot;],
    [&amp;quot;Jean-Jacques&amp;quot;, &amp;quot;Rousseau&amp;quot;, &amp;quot;fr&amp;quot;],
    [&amp;quot;Georg&amp;quot;, &amp;quot;Hegel&amp;quot;, &amp;quot;de&amp;quot;],
    [&amp;quot;Karl&amp;quot;, &amp;quot;Marx&amp;quot;, &amp;quot;de&amp;quot;],
    [&amp;quot;Friedrich&amp;quot;, &amp;quot;Engels&amp;quot;, &amp;quot;de&amp;quot;],
    [&amp;quot;Victor&amp;quot;, &amp;quot;Hugo&amp;quot;, &amp;quot;fr&amp;quot;],
    [&amp;quot;Paul&amp;quot;, &amp;quot;Verlaine&amp;quot;, &amp;quot;fr&amp;quot;],
    [&amp;quot;Antonio&amp;quot;, &amp;quot;Gramsci&amp;quot;, &amp;quot;it&amp;quot;],
    [&amp;quot;Georg&amp;quot;, &amp;quot;Lukacs&amp;quot;, &amp;quot;hu&amp;quot;],
    [&amp;quot;Franz&amp;quot;, &amp;quot;Kafka&amp;quot;, &amp;quot;hu&amp;quot;],
    [&amp;quot;Arthur&amp;quot;, &amp;quot;Rimbaud&amp;quot;, &amp;quot;fr&amp;quot;],
    [&amp;quot;Gérard&amp;quot;, &amp;quot;de Nerval&amp;quot;, &amp;quot;fr&amp;quot;],
];

var hugo = {
    firstName: authors[7][0],
    lastName: authors[7][1],
    country: authors[7][2],
};

var book = {
    author: hugo,
    title: &amp;quot;Les misérables&amp;quot;
};
console.log(hugo);
console.log(book);

//accéder à un membre de l&amp;#39;objet par point
console.log(hugo.country);

//accéder à un membre de l&amp;#39;objet par crochet
console.log(book[&amp;quot;title&amp;quot;]);

console.log(book.author.lastName);

console.log(book[&amp;quot;author&amp;quot;][&amp;quot;firstName&amp;quot;]);

//ajouter une clé a un objet
hugo.gender = &amp;quot;non binary&amp;quot;;
console.assert(hugo.gender === &amp;quot;non binary&amp;quot;);


//mettre à jour une clé
hugo.gender = &amp;quot;male&amp;quot;;
console.assert(hugo.gender !== &amp;quot;non binary&amp;quot;);
console.assert(hugo[&amp;quot;gender&amp;quot;] === &amp;quot;male&amp;quot;);

//supprimer une clé
delete hugo.gender;


//verfier que l&amp;#39;objet ne contient pas la clé gender
console.assert(!Object.keys(hugo).includes(&amp;quot;gender&amp;quot;));


console.log(&amp;quot;---------&amp;quot;);&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;#toc_js&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;underline&amp;quot;&amp;gt;Intro Javascript&amp;lt;/span&amp;gt;&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;loops&amp;quot;&amp;gt;Les boucles&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;06_loops.js&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-javascript hljs&amp;quot; data-lang=&amp;quot;javascript&amp;quot;&amp;gt;/**
 * Les boucles: Itérer sur arrays et objets.
 */

console.log(&amp;quot;for loop over array&amp;quot;);

var numbers = [12, 10, 8, 6, 4, 2, 0];

for (number of numbers) {
    console.log(number);
}

console.log(&amp;quot;---------&amp;quot;);

console.log(&amp;quot;for loop over object key&amp;quot;);

var obj = { a: 1, b: 2, c: 3, d: 4 };

for (key in obj) {
    console.log(key);
}

console.log(&amp;quot;---------&amp;quot;);

console.log(&amp;quot;for loop over object value&amp;quot;);

for (value in obj) {
    console.log(obj[value]);
}

console.log(&amp;quot;---------&amp;quot;);

console.log(&amp;quot;another for loop over authors array&amp;quot;)

var authors = [
    [&amp;quot;Chrétien&amp;quot;, &amp;quot;de Troyes&amp;quot;, &amp;quot;fr&amp;quot;],
    [&amp;quot;François&amp;quot;, &amp;quot;Rabelais&amp;quot;, &amp;quot;fr&amp;quot;],
    [&amp;quot;René&amp;quot;, &amp;quot;Descartes&amp;quot;, &amp;quot;fr&amp;quot;],
    [&amp;quot;Jean-Jacques&amp;quot;, &amp;quot;Rousseau&amp;quot;, &amp;quot;fr&amp;quot;],
    [&amp;quot;Georg&amp;quot;, &amp;quot;Hegel&amp;quot;, &amp;quot;de&amp;quot;],
    [&amp;quot;Karl&amp;quot;, &amp;quot;Marx&amp;quot;, &amp;quot;de&amp;quot;],
    [&amp;quot;Friedrich&amp;quot;, &amp;quot;Engels&amp;quot;, &amp;quot;de&amp;quot;],
    [&amp;quot;Victor&amp;quot;, &amp;quot;Hugo&amp;quot;, &amp;quot;fr&amp;quot;],
    [&amp;quot;Paul&amp;quot;, &amp;quot;Verlaine&amp;quot;, &amp;quot;fr&amp;quot;],
    [&amp;quot;Antonio&amp;quot;, &amp;quot;Gramsci&amp;quot;, &amp;quot;it&amp;quot;],
    [&amp;quot;Georg&amp;quot;, &amp;quot;Lukacs&amp;quot;, &amp;quot;hu&amp;quot;],
    [&amp;quot;Franz&amp;quot;, &amp;quot;Kafka&amp;quot;, &amp;quot;hu&amp;quot;],
    [&amp;quot;Arthur&amp;quot;, &amp;quot;Rimbaud&amp;quot;, &amp;quot;fr&amp;quot;],
    [&amp;quot;Gérard&amp;quot;, &amp;quot;de Nerval&amp;quot;, &amp;quot;fr&amp;quot;],
];

for (const [i, value] of authors.entries()) {
    console.log(`${value[0]}, ${value[1]} (${value[2]})`);
}

console.log(&amp;quot;---------&amp;quot;);

console.log(&amp;quot;while loop over array&amp;quot;)

var i = 0
while (i &amp;amp;lt; authors.length) {
    console.log(`${authors[i][0]}, ${authors[i][1]} (${authors[i][2]})`);
    i++;
}

console.log(&amp;quot;---------&amp;quot;);&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;#toc_js&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;underline&amp;quot;&amp;gt;Intro Javascript&amp;lt;/span&amp;gt;&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;const_var_let&amp;quot;&amp;gt;Const, var et let&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;07_const_var_let.js&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-javascript hljs&amp;quot; data-lang=&amp;quot;javascript&amp;quot;&amp;gt;/**
 * const X var X let
 */
//var x du if n&amp;#39;est pas confiné au scope du if
const varTest = () =&amp;amp;gt; {
    var x = 1;
    console.log(x);
    console.assert(x === 1);
    if (true) {
        var x = 2;
        console.log(x);
        console.assert(x === 2);
    }
    console.log(x);
    console.assert(x !== 1);
    console.assert(x === 2);
}
varTest();

console.log(&amp;quot;---------&amp;quot;);

//var x est global à tous les fichier js
//chargés dans la page html
//var x issue du fichier 1_start.js
//ligne 46
console.log(x);
console.assert(x === 20);

console.log(&amp;quot;---------&amp;quot;);

//premier let x est confiné au scope de la fonction
//le let x du if est confiné au scope du if
const letTest = () =&amp;amp;gt; {
    let x = 1;
    console.log(x);
    console.assert(x === 1);
    if (true) {
        let x = 2;
        console.log(x);
        console.assert(x === 2);
    }
    console.log(x);
    console.assert(x === 1);
    console.assert(x !== 2);
}
letTest();

console.log(&amp;quot;---------&amp;quot;);

/**
 * const: déclare un emplacement mémoire non réattribuable
 * c&amp;#39;est modifiable pour un array,
 * mais non réinitialisable;
 * ex:
 * const arr = [25, 27, 29]
 * arr = [5, 7] //impossible
 * arr[0]=20 // possible
 * ex:
 * const n = 1;
 * n = 3; //impossible
 */
const arr = [25, 27, 29]
console.table(arr);
arr[0] = 20 // possible
console.table(arr);

arr.pop()
console.table(arr);
console.log(&amp;quot;---------&amp;quot;);&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;#toc_js&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;underline&amp;quot;&amp;gt;Intro Javascript&amp;lt;/span&amp;gt;&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;rest_and_spread_operators&amp;quot;&amp;gt;Rest et spread operators&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;08_rest_and_spread_operators.js&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-javascript hljs&amp;quot; data-lang=&amp;quot;javascript&amp;quot;&amp;gt;/**
 * Opérateur de rest:
 * const add = (...operandes) =&amp;amp;gt; a + b;
 * ou avec parametres avec valeur par défaut
 * const add = (a=0, b=0, ...operandes) =&amp;amp;gt; a + b;
 */

/**
 * spread opérateur:
 * récupère l&amp;#39;ensemble des elements d&amp;#39;une collection,
 * dans un array
 */
const user_ages = [25, 56, 12, 58, 41, 62, 26];

const max_user_age = Math.max(...user_ages);
const min_user_age = Math.min(...user_ages);

console.assert(max_user_age == 62);
console.assert(min_user_age == 12);

console.log(&amp;quot;---------&amp;quot;);&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;#toc_js&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;underline&amp;quot;&amp;gt;Intro Javascript&amp;lt;/span&amp;gt;&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;destructuring&amp;quot;&amp;gt;Destructuring&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;09_destructuring.js&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-javascript hljs&amp;quot; data-lang=&amp;quot;javascript&amp;quot;&amp;gt;/**
 * L&amp;#39;affectation par décomposition (destructuring)
 */

const user = {
    first_name: &amp;quot;Oliv&amp;quot;,
    last_name: &amp;quot;Cher&amp;quot;,
    nick_name: &amp;quot;cheroliv&amp;quot;,
    gender: &amp;quot;male&amp;quot;,
    is_adult: true
};
console.log(user);

//première façon d&amp;#39;initaliser des variables sans destructuration
let first_name_legacy = user.first_name;
let last_name_legacy = user.last_name;
let nick_name_legacy = user.nick_name;
let gender_legacy = user.gender;
let is_adult_legacy = user.is_adult;

console.assert(&amp;quot;Oliv&amp;quot; === first_name_legacy);
console.assert(&amp;quot;Cher&amp;quot; === last_name_legacy);
console.assert(&amp;quot;cheroliv&amp;quot; === nick_name_legacy);
console.assert(&amp;quot;male&amp;quot; === gender_legacy);
console.assert(true === is_adult_legacy);
console.log(&amp;quot;---------&amp;quot;);
/**
 * Initialisation par déstructuration
 */
const {
    first_name,
    last_name,
    nick_name,
    gender,
    is_adult
} = user;

console.assert(user.first_name === first_name);
console.assert(user.last_name === last_name);
console.assert(user.nick_name === nick_name);
console.assert(user.gender === gender);
console.assert(user.is_adult === is_adult);

/**
 * Intialisation par déstructuration des arrays
 */
//cible explicite
const [a1, a2] = [15, 25, 17, 81, 51, 46];
console.assert(a1 === 15);
console.assert(a2 === 25);

//cible avec position par rapport aux index par virgules
//je veux b3 à 46
const [b1, b2, , , , b3] = [15, 25, 17, 81, 51, 46];
console.assert(b1 === 15);
console.assert(b2 === 25);
console.assert(b3 === 46);

//déstructuration avec operateur de rest
// je contruit un autre avec avec les elements
// à partir du premier élèment non destructuré
const [c1, c2, ...sub_arr] = [15, 25, 17, 81, 51, 46];
console.assert(b1 === 15);
console.assert(b2 === 25);
console.assert(sub_arr.length === 4);
console.assert(sub_arr[0] === 17);
console.assert(sub_arr[1] === 81);
console.assert(sub_arr[2] === 51);
console.assert(sub_arr[3] === 46);


// Une boucle pour comparer le resultat attendu
for (const [i, value] of[15, 25, 17, 81, 51, 46].entries()) {
    //on reconstruit le tableau à chaque iteration
    console.assert([c1, c2].concat(sub_arr)[i] === value);
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;#toc_js&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;underline&amp;quot;&amp;gt;Intro Javascript&amp;lt;/span&amp;gt;&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;import_export&amp;quot;&amp;gt;Import et export de modules&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;10_module_import_export.js&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-javascript hljs&amp;quot; data-lang=&amp;quot;javascript&amp;quot;&amp;gt;/**
 * Import et export de module
 *
 * Dans le fichier html
 * au niveau de la balise d&amp;#39;import de script
 * spécifier le type module
 *
 * 1er technique: import nommé des fonctions:
 * import { sum, minus, times, div, rem } from &amp;quot;./dummy_math_functions.js&amp;quot;;
 *
 * 2eme technique: import global des fonction avec l&amp;#39;asterisque(*)
 * en préfixant l&amp;#39;import par un nommage du fichier importé:
 * import * as math from &amp;quot;./dummy_math_functions.js&amp;quot;;
 *
 * 3eme technique: import par alias avec le mot clé &amp;quot;as&amp;quot;
 * import {
 *     sum as add,
 *     minus as substract,
 *     times as multiply,
 *     div as divide,
 *     rem as modulo
 * } from &amp;quot;./dummy_math_functions.js&amp;quot;;
 */

import {
    sum,
    minus,
    times,
    div,
    rem
} from &amp;quot;./dummy_math_functions.js&amp;quot;;

console.assert(sum(1, 1) === 2);
console.assert(minus(4, 2) === 2);
console.assert(times(2, 2) === 4);
console.assert(div(4, 2) === 2);
console.assert(rem(4, 2) === 0);&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;dummy_math_functions.js&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-javascript hljs&amp;quot; data-lang=&amp;quot;javascript&amp;quot;&amp;gt;//sum
const sum = (x, y) =&amp;amp;gt; x + y;

// subtraction
const minus = (x, y) =&amp;amp;gt; x - y;

// multiplication
const times = (x, y) =&amp;amp;gt; x * y;

// division
const div = (x, y) =&amp;amp;gt; x / y;

// remainder: reste de la division euclidiene
const rem = (x, y) =&amp;amp;gt; x % y;

// log sum
const sum_log = (x, y) =&amp;amp;gt; console.log(sum(x, y));

// log subtraction
const minus_log = (x, y) =&amp;amp;gt; console.log(minus(x, y));

// log multiplication
const times_log = (x, y) =&amp;amp;gt; console.log(times(x, y));

// log division
const div_log = (x, y) =&amp;amp;gt; console.log(div(x, y));

// log remainder
const rem_log = (x, y) =&amp;amp;gt; console.log(rem(x, y));

export {
    sum,
    minus,
    times,
    div,
    rem,
    sum_log,
    minus_log,
    times_log,
    div_log,
    rem_log
};&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;#toc_js&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;underline&amp;quot;&amp;gt;Intro Javascript&amp;lt;/span&amp;gt;&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;classes&amp;quot;&amp;gt;Utilisation des classes&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;11_classes.js&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-javascript hljs&amp;quot; data-lang=&amp;quot;javascript&amp;quot;&amp;gt;/**
 * Utilisation des classes
 */
//creation d&amp;#39;un objet
class Account {
    constructor(username) {
        this.username = username;
    }
};

const acc1 = new Account(&amp;quot;cheroliv&amp;quot;);
console.table(acc1);
console.assert(acc1.username === &amp;quot;cheroliv&amp;quot;);

console.log(&amp;quot;---------&amp;quot;);

//création d&amp;#39;un objet et ajout de getter et setter(accesseurs)
class AccountInfo {
    constructor(username) {
        this._username = username;
    }

    get username() {
        return this._username;
    }

    /**
     * @param {(arg0: string) =&amp;amp;gt; void} new_username
     */
    set new_username(new_username) {
        this._username = new_username;
    }
};

const acc_info1 = new AccountInfo(&amp;quot;cheroliv&amp;quot;);
console.table(acc_info1);
//accés au membre _username avec le getter username()
console.assert(acc_info1.username === &amp;quot;cheroliv&amp;quot;);

//update la valeur username de acc_info1 avec le setter
acc_info1.new_username = &amp;quot;imrandeh&amp;quot;;
console.assert(acc_info1.username === &amp;quot;imrandeh&amp;quot;);

console.log(&amp;quot;---------&amp;quot;);&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;#toc_js&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;underline&amp;quot;&amp;gt;Intro Javascript&amp;lt;/span&amp;gt;&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>Memo Pengaturan</title>
            <link >https://cheroliv.com/blog/2022/0054_memo_cadrage_post.html</link>
            <pubDate>Sat, 31 Dec 2022 00:00:00 +0000</pubDate>
            <guid isPermaLink="false">blog/2022/0054_memo_cadrage_post.html</guid>
            <description>&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_pengaturan_bingkai&amp;quot;&amp;gt;pengaturan bingkai&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_bengkel_1_vision&amp;quot;&amp;gt;Bengkel 1: Vision&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;sect3&amp;quot;&amp;gt;
&amp;lt;h4 id=&amp;quot;_pola_qqoqcp_siapa_apa_di_mana_kapan_bagaimana_mengapa&amp;quot;&amp;gt;Pola “QQOQCP” – Siapa, Apa, Di mana, Kapan, Bagaimana, Mengapa&amp;lt;/h4&amp;gt;

&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect3&amp;quot;&amp;gt;
&amp;lt;h4 id=&amp;quot;_2_papan_visi_produk&amp;quot;&amp;gt;2. Papan Visi Produk&amp;lt;/h4&amp;gt;
&amp;lt;div class=&amp;quot;sect4&amp;quot;&amp;gt;
&amp;lt;h5 id=&amp;quot;_kelompok_target&amp;quot;&amp;gt;kelompok target&amp;lt;/h5&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Siapa pengguna yang menggunakan produk atau penawaran ? (langsung, tidak langsung)&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect4&amp;quot;&amp;gt;
&amp;lt;h5 id=&amp;quot;_kebutuhan&amp;quot;&amp;gt;Kebutuhan&amp;lt;/h5&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Apa yang menjadi 5 kebutuhan utama pengguna kunci aplikasi?&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect4&amp;quot;&amp;gt;
&amp;lt;h5 id=&amp;quot;_produk&amp;quot;&amp;gt;Produk&amp;lt;/h5&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Apa proposisi nilai saya? Apa 5 fitur utama produk yang memungkinkan untuk menjawab pada kebutuhan&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect4&amp;quot;&amp;gt;
&amp;lt;h5 id=&amp;quot;_tujuan_bisnis&amp;quot;&amp;gt;Tujuan Bisnis&amp;lt;/h5&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Apa kriteria keberhasilan ?&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect4&amp;quot;&amp;gt;
&amp;lt;h5 id=&amp;quot;_visi&amp;quot;&amp;gt;visi&amp;lt;/h5&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Ketika 4 kolom sudah lengkap, Anda harus mengisi bagian visi dari Product Vision Papan dalam satu kalimat lengkap.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_bengkel_2_personaperjalanan_pengguna&amp;quot;&amp;gt;Bengkel 2: Persona/Perjalanan Pengguna&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;Tentukan untuk setiap persona perjalanan pengguna:&amp;lt;/div&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Siapa yang akan menggunakan layanan saya? (Target)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Apa yang ingin dia lakukan ? (But)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Bagaimana dia akan melakukannya? (Perjalanan Pengguna)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_workshop_3_zoning_storyboard_sitemap&amp;quot;&amp;gt;Workshop 3: Zoning, Storyboard, Sitemap&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;sect3&amp;quot;&amp;gt;
&amp;lt;h4 id=&amp;quot;_1_zonasi&amp;quot;&amp;gt;1. Zonasi&amp;lt;/h4&amp;gt;

&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect3&amp;quot;&amp;gt;
&amp;lt;h4 id=&amp;quot;_2_papan_cerita&amp;quot;&amp;gt;2. Papan Cerita&amp;lt;/h4&amp;gt;

&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect3&amp;quot;&amp;gt;
&amp;lt;h4 id=&amp;quot;_3_peta_situs&amp;quot;&amp;gt;3. Peta Situs&amp;lt;/h4&amp;gt;

&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_workshop_4_wireframemoodboard&amp;quot;&amp;gt;Workshop 4: Wireframe/Moodboard&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;sect3&amp;quot;&amp;gt;
&amp;lt;h4 id=&amp;quot;_wireframes&amp;quot;&amp;gt;Wireframes&amp;lt;/h4&amp;gt;

&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect3&amp;quot;&amp;gt;
&amp;lt;h4 id=&amp;quot;_2_papan_suasana&amp;quot;&amp;gt;2. Papan suasana&amp;lt;/h4&amp;gt;

&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_workshop_5_pemetaan_cerita_cerita_pengguna&amp;quot;&amp;gt;Workshop 5: Pemetaan Cerita/ Cerita Pengguna&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;sect3&amp;quot;&amp;gt;
&amp;lt;h4 id=&amp;quot;_story_mapping_prioritas_us&amp;quot;&amp;gt;Story mapping (prioritas US)&amp;lt;/h4&amp;gt;

&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect3&amp;quot;&amp;gt;
&amp;lt;h4 id=&amp;quot;_sebagai_type_dutilisateur_saya_ingin_un_objectif_agar_une_raison&amp;quot;&amp;gt;Sebagai &amp;amp;lt;type d&amp;amp;#8217;utilisateur&amp;amp;gt;, saya ingin &amp;amp;lt;un objectif&amp;amp;gt; agar &amp;amp;lt;une raison&amp;amp;gt;.&amp;lt;/h4&amp;gt;

&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_lokakarya_6_daftar_tugas&amp;quot;&amp;gt;Lokakarya 6: Daftar tugas&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://www.atlassian.com/fr/agile/scrum/backlogs&amp;quot;&amp;gt;tumpukan pekerjaan&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_workshop_7_teknik_dan_arsitektur&amp;quot;&amp;gt;Workshop 7: Teknik dan Arsitektur&amp;lt;/h3&amp;gt;

&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>Catatan Kotlin</title>
            <link >https://cheroliv.com/blog/2022/0053_memo_kotlin_post.html</link>
            <pubDate>Sun, 29 May 2022 00:00:00 +0000</pubDate>
            <guid isPermaLink="false">blog/2022/0053_memo_kotlin_post.html</guid>
            <description>&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_tautan_yang_baik&amp;quot;&amp;gt;Tautan yang baik&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://play.kotlinlang.org/&amp;quot;&amp;gt;kotlinlang playground&amp;lt;/a&amp;gt;+&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://arrow-kt.io/&amp;quot;&amp;gt;arrow.kt&amp;lt;/a&amp;gt;&amp;lt;br&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://arkivanov.github.io/MVIKotlin/&amp;quot;&amp;gt;MVI:Model Tampilan Inten&amp;lt;/a&amp;gt;+&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://github.com/arkivanov/Essenty&amp;quot;&amp;gt;Essenty&amp;lt;/a&amp;gt;+&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://arkivanov.github.io/Decompose/&amp;quot;&amp;gt;mengurai&amp;lt;/a&amp;gt;+&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_berbagai_potongan_kode&amp;quot;&amp;gt;Berbagai potongan kode&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_map_reduce&amp;quot;&amp;gt;map reduce&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Mengonversi daftar bite menjadi daftar string dan meringkas daftar string&amp;lt;br&amp;gt;
dalam sebuah string yang berisi penggabungan Bagaimana mencatat permintaan yang dikirim(requestBodyContent:byte[])&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;log.info(
    requestBodyContent!!.map { it.toInt().toChar().toString() }
        .reduce { request: String, s: String -&amp;amp;gt; request + s }
)&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_enum_dan_kelas_yang_disegel&amp;quot;&amp;gt;enum dan kelas yang disegel&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;post:&amp;lt;a href=&amp;quot;0038_training_kotlin_enum_sealed_class_post&amp;quot;&amp;gt;pelatihan kotlin enum kelas tertutup&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_interface_fungsional_dan_referensi_metode&amp;quot;&amp;gt;interface fungsional dan referensi metode&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Kita dapat mengubah sebuah entitas menjadi model domain dari dto dengan metode referensi (gaya fungsional - java 8)&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;fun findAllByLoginNot(
        pageable:Pageable,
        login:String)
    :Page&amp;amp;lt;UserDto&amp;amp;gt; {
    return userDao.findAllByLoginNot(
                    pageable,
                    login).map(::fromEntity)
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;sebuahhttps://stackoverflow.com/a/22245383/837404[penjelasan yang baik]&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_menangkap_output_standar&amp;quot;&amp;gt;Menangkap output standar&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;package functional

import java.io.ByteArrayOutputStream
import java.io.PrintStream
import java.lang.System.out
import java.lang.System.setOut
import kotlin.test.AfterTest
import kotlin.test.BeforeTest
import kotlin.test.Test
import kotlin.test.assertEquals


class BasicsHOF {
    private val standardOut: PrintStream? = out
    private val outputStreamCaptor = ByteArrayOutputStream()

    @BeforeTest
    fun setUp() = setOut(PrintStream(outputStreamCaptor))

    @AfterTest
    fun tearDown() = setOut(standardOut)

    @Test
    fun `three times dope`() {

        3.times { println(&amp;quot;Hello&amp;quot;) }

        assertEquals(
            buildString {
                repeat(3) { append(&amp;quot;Hello\n&amp;quot;) }
                deleteAt(length - 1)
            }, outputStreamCaptor
                .toString()
                .trim()
        )
    }

    fun Int.times(fn: () -&amp;amp;gt; Unit) = (1..this).forEach { _ -&amp;amp;gt; fn() }
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_apa_perbedaan_di_kotlin_antara_apply_run_let_also_use_dan_with&amp;quot;&amp;gt;Apa perbedaan di Kotlin antara apply, run, let, also, use dan with?&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Perbandingan fungsi Kotlin dan Penggunaan Bentuk Lambda Reference&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_menerapkan&amp;quot;&amp;gt;menerapkan&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;Pendahuluan Fungsi Kotlin`apply`, &amp;lt;code&amp;gt;run&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;let&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;also&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;use&amp;lt;/code&amp;gt;, et `with`Menyediakan cara berbeda untuk memproses objek. Masing-masing memiliki kasus penggunaan dan perilaku masing-masing. Selain itu, bentuk lambda reference memungkinkan kode menjadi lebih mudah dibaca dan dapat digunakan kembali dengan merujuk pada fungsi lambda yang sudah ada.&amp;lt;/div&amp;gt;
&amp;lt;p&amp;gt;Fungsi`apply`digunakan untuk mengonfigurasi sebuah objek selama pembuatannya. Ia mengembalikan objek yang dipanggilnya.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Contoh dengan referensi lambda:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;val someObject = SomeClass().apply(::configureObject)&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Contoh dengan lambda :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;val someObject = SomeClass().apply {
// configuration des propriétés de someObject
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_run&amp;quot;&amp;gt;run&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Fungsi`run`digunakan untuk mengeksekusi blok kode pada sebuah objek dan mengembalikan hasil dari blok kode.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Contoh dengan referensi lambda :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;val result = someObject.run(::someFunction)&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Contoh dengan lambda :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;val result = someObject.run {
// bloc de code à exécuter sur someObject
// la dernière expression est renvoyée
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_let&amp;quot;&amp;gt;let&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Fungsi`let`digunakan untuk menjalankan blok kode pada objek dan mengembalikan hasil dari blok kode.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Contoh dengan referensi lambda :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;val result = someObject.let(::processObject)&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Contoh dengan lambda :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;val result = someObject.let {
// bloc de code à exécuter sur someObject
// la dernière expression est renvoyée
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_juga&amp;quot;&amp;gt;juga&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Fungsi`also`digunakan untuk melakukan tindakan tambahan pada sebuah objek dan mengembalikan objek yang dipanggilnya.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Contoh dengan referensi lambda :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;someObject.also(::performAdditionalAction)&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Contoh dengan lambda :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;someObject.also {
// action additionnelle sur someObject
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_use&amp;quot;&amp;gt;use&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;fungsi`use`digunakan untuk bekerja dengan sumber daya yang harus ditutup setelah penggunaan. Ia memanggil secara otomatis fungsi`close`di akhir blok.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Contoh dengan referensi lambda:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;someResource.use(::useResource)&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Contoh dengan lambda :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;someResource.use {
// travailler avec la ressource
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_dengan&amp;quot;&amp;gt;dengan&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Fungsi`with`digunakan untuk memanggil beberapa metode pada sebuah objek tanpa mengulangi namanya dan mengembalikan hasil dari ekspresi terakhir.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Contoh dengan referensi lambda :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;val result = with(someObject, ::processWithObject)&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Contoh dengan lambda :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;val result = with(someObject) {
// appeler des méthodes sur someObject
// la dernière expression est renvoyée
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Menggunakan bentuk referensi lambda atau blok`{}`, Anda dapat mengenkapsulasi logika ke dalam fungsi terpisah, sehingga meningkatkan kejelasan dan penggunaan kembali kode.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_let_function_di_kotlin&amp;quot;&amp;gt;let Function di Kotlin&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;Apakah let mengembalikan objek dengan efek samping yang diterapkan padanya atau dalam kondisi awal input tergantung pada let?&amp;lt;/div&amp;gt;
&amp;lt;p&amp;gt;Fungsi`let`Di Kotlin digunakan untuk melakukan operasi pada objek dan mengembalikan hasil yang berbeda. Namun, penting untuk dicatat bahwa fungsi`let`tidak mengubah keadaan awal objek yang dipanggil&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;table class=&amp;quot;tableblock frame-all grid-all stretch&amp;quot;&amp;gt;
&amp;lt;colgroup&amp;gt;
&amp;lt;col style=&amp;quot;width: 50%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 50%;&amp;quot;&amp;gt;
&amp;lt;/colgroup&amp;gt;
&amp;lt;tbody&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;Tanda tangan&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;code&amp;gt;inline fun &amp;amp;lt;T, R&amp;amp;gt; T.let(block: (T) &amp;amp;#8594; R): R&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;table class=&amp;quot;tableblock frame-all grid-all stretch&amp;quot;&amp;gt;
&amp;lt;colgroup&amp;gt;
&amp;lt;col style=&amp;quot;width: 50%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 50%;&amp;quot;&amp;gt;
&amp;lt;/colgroup&amp;gt;
&amp;lt;tbody&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;penggunaan&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Fungsi`let`sering digunakan untuk menerapkan transformasi pada sebuah objek dan mendapatkan hasil berdasarkan transformasi tersebut.&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;table class=&amp;quot;tableblock frame-all grid-all stretch&amp;quot;&amp;gt;
&amp;lt;colgroup&amp;gt;
&amp;lt;col style=&amp;quot;width: 50%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 50%;&amp;quot;&amp;gt;
&amp;lt;/colgroup&amp;gt;
&amp;lt;tbody&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;Hasil&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Nilai yang dikembalikan oleh fungsi`let`adalah hasil dari ekspresi lambda yang diteruskan sebagai argumen, biasanya hasil dari operasi yang dilakukan pada objek.&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;table class=&amp;quot;tableblock frame-all grid-all stretch&amp;quot;&amp;gt;
&amp;lt;colgroup&amp;gt;
&amp;lt;col style=&amp;quot;width: 50%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 50%;&amp;quot;&amp;gt;
&amp;lt;/colgroup&amp;gt;
&amp;lt;tbody&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;Efek tepi&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Meskipun fungsi`let`dia dapat menimbulkan efek samping pada objek ketika digunakan dalam ekspresi lambda, dia tidak mengubah keadaan awal objek itu sendiri.&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Sebab itu, fungsi`let`adalah cara elegan untuk melakukan operasi pada sebuah objek sekaligus mendapatkan hasil yang diturunkan, sekaligus menjaga integritas objek aslinya.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>Mémo JVM</title>
            <link >https://cheroliv.com/blog/2022/0052_memo_jvm_post.html</link>
            <pubDate>Sat, 28 May 2022 00:00:00 +0000</pubDate>
            <guid isPermaLink="false">blog/2022/0052_memo_jvm_post.html</guid>
            <description>&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;toc&amp;quot;&amp;gt;table des matières&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;olist arabic&amp;quot;&amp;gt;
&amp;lt;ol class=&amp;quot;arabic&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;#char&amp;quot;&amp;gt;chaines de caractères et caractères&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;#math&amp;quot;&amp;gt;nombres et math&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;#date&amp;quot;&amp;gt;dates et heures&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;#java8date&amp;quot;&amp;gt;dates et heures java8&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;#arrCol&amp;quot;&amp;gt;tableaux et collections&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;#sysProp&amp;quot;&amp;gt;System.Properties&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;#threads&amp;quot;&amp;gt;Threads&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;#regex&amp;quot;&amp;gt;Expressions Régulières&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;char&amp;quot;&amp;gt;chaines de caractères et caractères&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;import java.text.Collator
import java.util.*
import kotlin.test.*

class StringsTest {
    @Test
    fun `chaines de caractères et caractères`() {
        //inférence de type
        val s = &amp;quot;C&amp;#39;est&amp;quot;
        assertEquals(&amp;quot;C&amp;#39;est&amp;quot;, s)

        //concaténation
        val t: String = s + &amp;quot; le moment.&amp;quot;
        assertEquals(&amp;quot;C&amp;#39;est le moment.&amp;quot;, t)

        // StringBuilder
        val t1: String = s + buildString { append(&amp;quot; le moment.&amp;quot;) }
        assertEquals(&amp;quot;C&amp;#39;est le moment.&amp;quot;, t1)

        //interpolation
        val t2 = &amp;quot;$s le moment.&amp;quot;
        assertEquals(&amp;quot;C&amp;#39;est le moment.&amp;quot;, t2)

        //multiligne chaine de caractères
        val t3 = &amp;quot;&amp;quot;&amp;quot;$s le moment.&amp;quot;&amp;quot;&amp;quot;
        assertEquals(&amp;quot;C&amp;#39;est le moment.&amp;quot;, t3)

        //conversion de type
        val t4 = t + &amp;quot; &amp;quot; + 23.4
        assertEquals(&amp;quot;C&amp;#39;est le moment. 23.4&amp;quot;, t4)

        val t5 = &amp;#39;C&amp;#39;.toString()
        assertEquals(&amp;quot;C&amp;quot;, t5)

        //taille de la chaine de caractères
        assertEquals(16, t.length)

        //sous-chaine d&amp;#39;une chaine de caractères

        //retourne une chaine de caractères contenant,
        //les caractères aux positions x à y-1
        //sub = t.substring(x, y)

        //t = &amp;quot;C&amp;#39;est le moment.&amp;quot;
        //retourne les caractères 6 et 7
        var sub = t.substring(6, 8)
        assertEquals(&amp;quot;le&amp;quot;, sub)

        //retourne les caractères 0 à 4
        sub = t.substring(0, 5)
        assertEquals(&amp;quot;C&amp;#39;est&amp;quot;, sub)

        //la longueur d&amp;#39;une sous-chaine est toujours égale (y-x)
        val numChars = sub.length
        assertEquals(5 - 0, numChars)

        //extraction des caractères d&amp;#39;une chaine
        assertEquals(&amp;#39;e&amp;#39;, t.elementAt(2))
        assertEquals(&amp;#39;e&amp;#39;, t.get(2))
        assertEquals(&amp;#39;e&amp;#39;, t[2])

        //conversion d&amp;#39;une chaine en tableau de caractères
        val ca = t.toCharArray()
        assertEquals(t.length, ca.size)
        t.mapIndexed { index, char -&amp;amp;gt; assertEquals(char, ca[index]) }

        //place les 4 premiers caractères de t1
        //dans le tableau ca a la position 2
        (t as java.lang.String).getChars(
            /* srcBegin = */ 0,
            /* srcEnd = */ 3,
            /* dst = */ ca,
            /* dstBegin = */ 1
        )
        assertEquals(&amp;quot;CC&amp;#39;et le moment.&amp;quot;, String(ca))
        //colle tous les caractères dans un meme string
        assertEquals(&amp;quot;CC&amp;#39;et le moment.&amp;quot;, ca.concatToString())

        //to lower case
        assertEquals(&amp;quot;c&amp;#39;est le moment.&amp;quot;, t.toLowerCase())
        assertEquals(&amp;quot;c&amp;#39;est le moment.&amp;quot;, t.lowercase())

        //to upper case
        assertEquals(&amp;quot;C&amp;#39;EST LE MOMENT.&amp;quot;, t.toUpperCase())
        assertEquals(&amp;quot;C&amp;#39;EST LE MOMENT.&amp;quot;, t.uppercase())

        //comparaison de chaines de caractères
        //t = &amp;quot;C&amp;#39;est le moment.&amp;quot;
        assertFalse(t.equals(&amp;quot;hello&amp;quot;))
        assertFalse(t == &amp;quot;hello&amp;quot;)

        //ignore la casse
        assertTrue(t.equalsIgnoreCase(&amp;quot;C&amp;#39;EST LE MOMENT.&amp;quot;))
        assertTrue(t.equals(&amp;quot;C&amp;#39;EST LE MOMENT.&amp;quot;, ignoreCase = true))

        //démarre par
        assertTrue(t.startsWith(&amp;quot;C&amp;#39;est&amp;quot;))
        //se finit par
        assertTrue(t.endsWith(&amp;quot;le moment.&amp;quot;))

        //compareTo
        //retourne une valeur &amp;amp;lt; 0, car s est
        //alphabétiquement avant &amp;quot;N&amp;#39;est&amp;quot;
        val r1: Int = s.compareTo(&amp;quot;N&amp;#39;est&amp;quot;)
        assertTrue(r1 &amp;amp;lt; 0)

        //variante ignorant la casse
        val r1Prime: Int = (s as java.lang.String).compareToIgnoreCase(&amp;quot;n&amp;#39;est&amp;quot;)
        assertTrue(r1Prime &amp;amp;lt; 0)

        //retourne 0 si les chaines sont equivalente
        val r2: Int = s.compareTo(&amp;quot;C&amp;#39;est&amp;quot;)
        assertEquals(0, r2)

        //retourne une valeur &amp;amp;gt; 0 car s vient apres &amp;quot;B&amp;#39;est&amp;quot;
        val r3: Int = s.compareTo(&amp;quot;B&amp;#39;est&amp;quot;)
        assertTrue(r3 &amp;amp;gt; 0)

        //Recherche de caractères et de sous-chaines de caractères

        //recherche de caractères
        //position du premier caractères &amp;#39;t&amp;#39;
        var pos = t.indexOf(&amp;#39;t&amp;#39;)
        assertEquals(4, pos)

        //position du suivant
        pos = t.indexOf(&amp;#39;t&amp;#39;, pos + 1)
        assertEquals(14, pos)

        //retour d&amp;#39;érreur -1 si absence de suivant
        pos = t.indexOf(&amp;#39;t&amp;#39;, pos + 1)
        assertEquals(-1, pos)

        //position du dernier &amp;#39;t&amp;#39; dans la chaine: 14
        pos = t.lastIndexOf(&amp;#39;t&amp;#39;)
        assertEquals(14, pos)

        //recherche de &amp;#39;t&amp;#39; vers l&amp;#39;arrière a partir du caractères 13
        pos = t.lastIndexOf(&amp;#39;t&amp;#39;, pos - 1)
        assertEquals(4, pos)

        //recherche de sous-chaines
        //retourne 2
        pos = t.indexOf(&amp;quot;est&amp;quot;)
        assertEquals(2, pos)

        //&amp;quot;est&amp;quot; n&amp;#39;apparait qu&amp;#39;une seule fois: retourne -1
        pos = t.indexOf(&amp;quot;est&amp;quot;, pos + 1)
        assertEquals(-1, pos)

        //recherche d&amp;#39;une sous-chaine depuis l&amp;#39;arrière
        //t = &amp;quot;C&amp;#39;est le moment.&amp;quot;
        //retourne 6
        pos = t.lastIndexOf(&amp;quot;le &amp;quot;)
        assertEquals(6, pos)

        //extrait depuis la position 9,
        //renvoi toute la chaine après &amp;quot;le &amp;quot;
        val noun = t.substring(pos + 3)
        assertEquals(-1, noun.indexOf(&amp;quot;le &amp;quot;))

        //remplacement de toutes les instances d&amp;#39;un caractère
        //par un autre caractère
        //ne fonctionne que avec les caractères, pas les chaines
        val exclaim: String = t.replace(&amp;#39;.&amp;#39;, &amp;#39;!&amp;#39;)
        assertEquals(&amp;#39;!&amp;#39;, exclaim.get(exclaim.length - 1))
        assertEquals(exclaim.length - 1, exclaim.indexOf(&amp;#39;!&amp;#39;))
        assertEquals(-1, exclaim.indexOf(&amp;#39;.&amp;#39;))

        //suppression des espaces blancs
        //au début et à la fin d&amp;#39;une chaine
        val noextraspaces = t.trim()
        assertNotEquals(&amp;#39; &amp;#39;, noextraspaces.get(0))
        assertNotEquals(&amp;#39; &amp;#39;, noextraspaces.get(noextraspaces.length - 1))

        //extraction des instances uniques de chaines de caractères
        //avec intern()
        val s1 = s.intern()
        assertEquals(s, s1)
        val s2 = &amp;quot;C&amp;#39;est&amp;quot;.intern()
        assertEquals(&amp;quot;C&amp;#39;est&amp;quot;, s2)
        assertEquals(s1, s2)

        //StringBuilder pour manipuler les caractères d&amp;#39;une chaine de caractères
        //crée un tampon StringBuilder à partir d&amp;#39;une chaine de caractères
        val b = StringBuilder(&amp;quot;N&amp;#39;est&amp;quot;)

        //extrait et définit des caractères individuel du tampon StringBuilder
        //le caractères à l&amp;#39;index 0
        val c: Char = b.get(0)
        assertEquals(&amp;#39;N&amp;#39;, c)

        //modifier le premier caractère de la chaine
        b.setCharAt(0, &amp;#39;C&amp;#39;)
        assertEquals(s, b.toString())

        //ajouter des données à un StringBuilder
        b.append(&amp;#39; &amp;#39;)
        b.append(&amp;quot;le moment.&amp;quot;)
        b.append(23)

        //insère des chaines de caractères ou autre dans le StringBuilder
        b.insert(6, &amp;quot;pas &amp;quot;)
        assertEquals(&amp;quot;C&amp;#39;est pas le moment.23&amp;quot;, b.toString())

        //remplace un sous ensemble de caractères
        //avec une chaine de caractères donnée
        b.replace(2, 9, &amp;quot;est&amp;quot;)
        assertEquals(&amp;quot;C&amp;#39;est le moment.23&amp;quot;, b.toString())

        //supprime les caractères
        b.delete(15, 18)
        assertEquals(&amp;quot;C&amp;#39;est le moment&amp;quot;, b.toString())
        b.deleteCharAt(2)
        assertEquals(&amp;quot;C&amp;#39;st le moment&amp;quot;, b.toString())

        //insert à la postion 2 et décale reste à droite(sans perte de données)
        b.insert(2, &amp;#39;e&amp;#39;)

        //tronque la taille de la donnée
        b.setLength(5)
        assertEquals(&amp;quot;C&amp;#39;est&amp;quot;, b.toString())

        //inverse les caractères de la chaine
        b.reverse()
        assertEquals(&amp;quot;tse&amp;#39;C&amp;quot;, b.toString())

        //écrase le StringBuilder, pret à etre réutilisé
        b.setLength(0)
        assertEquals(&amp;quot;&amp;quot;, b.toString())

        //java.util.StringTokenizer pour fragmenter une chaine
        //de caractères en un ensemble de mots
        var st = StringTokenizer(t)
        //nb d&amp;#39;items encore présentent dans la file
        assertEquals(3, st.countTokens())

        //est ce que il y a encore des items dans la file
        assertTrue(st.hasMoreTokens())

        //récupérer le token courrant
        assertEquals(&amp;quot;C&amp;#39;est&amp;quot;, st.nextToken())
        assertEquals(&amp;quot;le&amp;quot;, st.nextToken())
        assertEquals(&amp;quot;moment.&amp;quot;, st.nextToken())
        assertFalse(st.hasMoreTokens())
        assertEquals(0, st.countTokens())

        //extraire des occurences de mots délimités
        //par des caractères autres que des expaces.
        val str = &amp;quot;a:b:c:d&amp;quot;
        st = StringTokenizer(str, &amp;quot;:&amp;quot;)
        assertEquals(4, st.countTokens())
        assertTrue(st.hasMoreTokens())
        assertEquals(&amp;quot;a&amp;quot;, st.nextToken())
        assertEquals(&amp;quot;b&amp;quot;, st.nextToken())
        assertEquals(&amp;quot;c&amp;quot;, st.nextToken())
        assertEquals(&amp;quot;d&amp;quot;, st.nextToken())
        assertFalse(st.hasMoreTokens())
        assertEquals(0, st.countTokens())


        //text=&amp;quot;C&amp;#39;est le moment.&amp;quot;
        val text = t.toCharArray()
        var p = 0

        //sauter les espaces de tete
        //pour ramener p à la position du premier caractère imprimable
        while ((p &amp;amp;lt; text.size) &amp;amp;amp;&amp;amp;amp;
            (Character.isWhitespace(text[p]))
        ) p++
        assertEquals(0, p)
        assertEquals(&amp;quot;C&amp;#39;est le moment.&amp;quot;, text.concatToString())

        //met le premier mot du texte en majuscule
        while (p &amp;amp;lt; text.size &amp;amp;amp;&amp;amp;amp; Character.isLetter(text[p])) {
            text[p] = Character.toUpperCase(text[p])
            p++
        }
        assertEquals(1, p)
        assertEquals(&amp;#39;C&amp;#39;, text[0])
        assertTrue(Character.isUpperCase(text[0]))
        assertFalse(Character.isLetter(text[1]))

        //comparer des chaines de caractères
        // avec les contrainte la locale système
        val col = Collator.getInstance()
        //le résulat est négatif car chica
        //est avant chico dans l&amp;#39;ordre alphabétique
        assertTrue(col.compare(&amp;quot;chica&amp;quot;, &amp;quot;chico&amp;quot;) &amp;amp;lt; 0)
    }
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;#toc&amp;quot;&amp;gt;table des matières&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;math&amp;quot;&amp;gt;nombres et math&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;import java.math.BigInteger
import java.security.SecureRandom
import java.text.NumberFormat
import java.util.*
import kotlin.test.*

class NumbersMathTest {
    @Test
    fun `Nombres et Math`() {
        //Constantes utiles
        Byte.MIN_VALUE
        Byte.MAX_VALUE
        Short.MIN_VALUE
        Short.MAX_VALUE
        Float.MIN_VALUE
        Float.MAX_VALUE
        Math.PI
        Math.E
        val s = &amp;quot;-42&amp;quot;
        //conversion de chaine de caractères
        //vers un nombre, si possible.
        var b: Byte = java.lang.Byte.parseByte(s)
        var sh: Short = java.lang.Short.parseShort(s)
        var i: Int = java.lang.Integer.parseInt(s)
        var l: Long = java.lang.Long.parseLong(s)
        var f: Float = java.lang.Float.parseFloat(s)
        var d: Double = java.lang.Double.parseDouble(s)

        //valeur exacte
        val f_exac = java.lang.Float.valueOf(s)
        val d_exac = java.lang.Double.valueOf(s)

        //les routines de conversions entière gérent
        //les nombres dans diverses bases.
        //1011 en binare est égal a 11 en base dix
        b = java.lang.Byte.parseByte(&amp;quot;1011&amp;quot;, 2)
        assertEquals(11, b)
        //ff en base 16(hexa) est égal à 255 en base dix.
        sh = java.lang.Short.parseShort(&amp;quot;ff&amp;quot;, 16)
        assertEquals(255, sh)

        //la méthode valueOf() peut gérer des bases arbitraires.
        i = java.lang.Integer.valueOf(&amp;quot;egg&amp;quot;, 17).toInt()
        assertEquals(4334, i)

        //la méthode decode() gére les representations octale,
        //décimal, hexadécimal, en fonction du préfixe numérique
        //de la chaine de caractères
        //un 0 de tete signifie base 8
        //un 0x de tete signifie base 16
        //les autres sont en base 10
        val sho = java.lang.Short.decode(&amp;quot;0377&amp;quot;)

        //la classe Integer peut convertir les nombres
        //en diverses chaines de caractères.
        val decimal = java.lang.Integer.toString(42)
        assertEquals(&amp;quot;42&amp;quot;, decimal)

        val decimal_ = 42.toString()
        assertEquals(&amp;quot;42&amp;quot;, decimal_)

        val binary = java.lang.Integer.toBinaryString(42)
        assertEquals(&amp;quot;101010&amp;quot;, binary)

        val octal = java.lang.Integer.toOctalString(42)
        assertEquals(&amp;quot;52&amp;quot;, octal)

        val hex = java.lang.Integer.toHexString(42)
        assertEquals(&amp;quot;2a&amp;quot;, hex)

        val base36 = java.lang.Integer.toString(42, 36)
        assertEquals(&amp;quot;16&amp;quot;, base36)

        val base36_ = 42.toString(36)
        assertEquals(&amp;quot;16&amp;quot;, base36_)

        //java.text.NumberFormat effectue la conversion
        // d&amp;#39;une maniere spécifique aux parametres locaux
        //sans parametre prend la local systeme comme reference
        val nf = NumberFormat.getNumberInstance(Locale.FRANCE)
        val formatted_number = nf.format(9876543.21)
        assertNotEquals(&amp;quot;9876543.21&amp;quot;, formatted_number)

        //parse la chaine de caractères en fonction des parametres locaux(fr)
        val n = nf.parse(&amp;quot;1234567,89&amp;quot;)
        assertEquals(1234567.89, n)

        //les valeurs monétaires sont parfois formaté
        // d&amp;#39;une maniere differente des nombres
        val money_format = NumberFormat.getCurrencyInstance(Locale.FRANCE)
        assertEquals(&amp;quot;123,40 €&amp;quot;, money_format.format(1234.56))

        //java.lang.Math
        d = Math.toRadians(27.0)
        d = Math.cos(d)
        d = Math.sqrt(d)
        d = Math.log(d)
        d = Math.exp(d)
        d = Math.pow(10.0, d)
        d = Math.atan(d)
        d = Math.toDegrees(d)
        //arrondi au dessus
        val up = Math.ceil(d)
        //arrondi au dessous
        val down = Math.floor(d)
        //arrondi au plus près
        val nearest = Math.round(d)

        //java.lang.Math.Random()
        val r = Math.random()
        assertTrue(r &amp;amp;gt;= 0.0 &amp;amp;amp;&amp;amp;amp; r &amp;amp;lt; 1.0)

        //créé un nouvel objet Random, en l&amp;#39;initialisant
        //avec l&amp;#39;heure courante
        val generator = java.util.Random(System.currentTimeMillis())

        //prochaine valeur aléatoire de taille double
        d = generator.nextDouble()
        assertTrue((d &amp;amp;gt;= 0.0) &amp;amp;amp;&amp;amp;amp; (d &amp;amp;lt; 1.0))


        //prochaine valeur aléatoire de taille float
        f = generator.nextFloat()
        assertTrue((f &amp;amp;gt;= 0.0) &amp;amp;amp;&amp;amp;amp; (f &amp;amp;lt; 1.0))


        //prochaine valeur aléatoire de taille long
        l = generator.nextLong()
        assertTrue(
            (Math.abs(l) &amp;amp;lt;= Long.MAX_VALUE) &amp;amp;amp;&amp;amp;amp;
                    (Math.abs(l) &amp;amp;gt;= 0)
        )


        //prochaine valeur aléatoire de taille int
        i = generator.nextInt()
        assertTrue(
            (Math.abs(i) &amp;amp;lt;= java.lang.Integer.MAX_VALUE) &amp;amp;amp;&amp;amp;amp;
                    (Math.abs(i) &amp;amp;gt;= 0)
        )

        val limit = 100
        //prochaine valeur aléatoire de taille int
        //la limit max du ramdom est ramené à limit
        //et la limit min est 0
        i = generator.nextInt(limit)
        assertTrue(i in 0 until limit)


        //prochaine valeur aléatoire de taille booléen
        val bool = generator.nextBoolean()
        assertNotNull(bool)


        //valeur moyenne 0.0, déviation standard 1.0
        d = generator.nextGaussian()


        //randoms bytes
        //rempli un tableau avec des valeurs byte aléatoires
        val b_arr = ByteArray(128)
        generator.nextBytes(b_arr)
        b_arr.iterator().forEachRemaining {
            assertTrue(
                it &amp;amp;lt;= Byte.MAX_VALUE &amp;amp;amp;&amp;amp;amp;
                        it &amp;amp;gt;= Byte.MIN_VALUE
            )
        }

        //java.security.SecureRandom pour les nombres aléatoires
        //utilisé en cryptographie
        val secure_generator = SecureRandom()
        //le générateur génère sa propre tete de liste sur 16 octets
        secure_generator.setSeed(secure_generator.generateSeed(16))
        val sec_b_arr = ByteArray(128)
        secure_generator.nextBytes(sec_b_arr)
        sec_b_arr.iterator().forEachRemaining {
            assertTrue(
                it &amp;amp;lt;= java.lang.Byte.MAX_VALUE &amp;amp;amp;&amp;amp;amp;
                        it &amp;amp;gt;= java.lang.Byte.MIN_VALUE
            )
        }

        //java.math.BigDecimal java.math.BigInteger
        //pour travailler sur des grandes valeurs.
        //calcule de la factorielle de 1000
        var total = BigInteger.valueOf(1)
        (2..1000).forEach {
            total = total.multiply(BigInteger.valueOf(it.toLong()))
        }
        assertTrue(total.toString().length == 2568)
    }
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;#toc&amp;quot;&amp;gt;table des matières&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;date&amp;quot;&amp;gt;dates et heures&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;import java.text.DateFormat
import java.text.SimpleDateFormat
import java.time.Instant
import java.util.*
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue

class DatesHoursTest {
    @Test
    fun `Dates et heures`() {
        //l&amp;#39;heure courante en millisecondes
        val t0 = System.currentTimeMillis()
        //une autre représentation de la meme information
        val now = java.util.Date()
        //converti un objet java.util.Date en une valeur long.
        val t1 = now.getTime()
        assertTrue(t1 &amp;amp;gt; Instant.EPOCH.toEpochMilli())
        //kotlin property access syntaxe style
        val t1_prime = now.time

        //java.text.DateFormat
        //affiche la date d&amp;#39;aujourd&amp;#39;hui en utilisant le format
        //par défaut des parametres locaux
        val defaultDateFormat = DateFormat.getDateInstance()
        //personnalisation du formatage et de la locale
        val df = DateFormat.getDateInstance(DateFormat.LONG, Locale.FRANCE)
        val localeFormattedDate = df.format(Date())

        //constantes pour les styles de pattern de formatage
        assertEquals(0, DateFormat.FULL)
        assertEquals(1, DateFormat.LONG)
        assertEquals(2, DateFormat.MEDIUM)
        assertEquals(3, DateFormat.SHORT)
        assertEquals(2, DateFormat.DEFAULT)

        //utilise pour l&amp;#39;heure un format abrégé avec
        //des parametres personnalisés
        val tf = DateFormat.getTimeInstance(
            DateFormat.SHORT,
            Locale.FRANCE
        )
        //affiche l&amp;#39;heure en utilisant le format de tf
        val shortTime = tf.format(Date())
        assertTrue(shortTime.contains(&amp;#39;:&amp;#39;))

        //affiche la date et l&amp;#39;heure en utilisant
        //un format détaillé
        val longTimeStamp = DateFormat.getDateTimeInstance(
            DateFormat.FULL,
            DateFormat.FULL,
        )
        assert(longTimeStamp.format(Date()).isNotEmpty())

        //utilisez java.text.SimpleDateFormat
        //pour définir votre propre modele de formatage
        val customFormat = SimpleDateFormat(&amp;quot;yyyy.MM.dd&amp;quot;)
        assertEquals(10, customFormat.format(Date()).length)

        //DateFormat peut également parser les date contenu dans des chaines
        val kotlinAnnounceDate = customFormat.parse(&amp;quot;2019.05.08&amp;quot;)

        //la class Date et sa représentation en millisecondes
        //n&amp;#39;autorise qu&amp;#39;une forme trés simple d&amp;#39;arithmétique
        //on ajoute 3 600 000 millisecondes à l&amp;#39;heure courante
        val anHourFromNow = now.getTime() + (60 * 60 * 1000)
        assert(anHourFromNow &amp;amp;gt; now.getTime())

        //java.util.Calendar
        //pour manipuler les dates et heures de facon plus sophistiquée
        //instanciation selon les parametres locaux
        //et le fuseau horaire local
        val calendar = Calendar.getInstance()
        //initialisation du calendrier à la date de maintenant
        calendar.setTime(now)
        //détermine le jour de l&amp;#39;année auquel correspond la date courante
        val dayOfYear = calendar.get(Calendar.DAY_OF_YEAR)
        assert(dayOfYear &amp;amp;lt; 366)
        //réinitialisation de la date courante
        calendar.set(2019, Calendar.MAY, 8)
        assertEquals(4, calendar.get(Calendar.DAY_OF_WEEK))

        //à quel jour du mois correspond le deuxieme mercredi de mai 2019
        //set(key,value)
        calendar.set(Calendar.YEAR, 2019)
        calendar.set(Calendar.MONTH, Calendar.MAY)
        calendar.set(Calendar.DAY_OF_WEEK, Calendar.WEDNESDAY)
        //defini à quel (n=2) semaine du mois est la date
        calendar.set(Calendar.DAY_OF_WEEK_IN_MONTH, 2)
        //extrait le jour du mois
        val dayOfMonth = calendar.get(Calendar.DAY_OF_MONTH)
        assertEquals(8, dayOfMonth)

        calendar.setTime(kotlinAnnounceDate)
        //ajoute 30j à la date
        calendar.add(Calendar.DATE, 30)
        val monthAfter = calendar.getTime()
        //date est elle avant ou apres?
        assertTrue(monthAfter.after(kotlinAnnounceDate))
    }
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;#toc&amp;quot;&amp;gt;table des matières&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;java8date&amp;quot;&amp;gt;dates et heures java8&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Java 8 introduit le nouveau package &amp;lt;strong&amp;gt;&amp;lt;em&amp;gt;java.time&amp;lt;/em&amp;gt;&amp;lt;/strong&amp;gt;, qui contient les classes de base qui&amp;lt;br&amp;gt;
la plupart des développeurs travaillent avec. Il contient également quatre sous-packages:&amp;lt;br&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;&amp;lt;em&amp;gt;java.time.chrono&amp;lt;/em&amp;gt;&amp;lt;/strong&amp;gt;&amp;lt;br&amp;gt;
Chronologies alternatives que les développeurs utilisant des systèmes de calendrier qui ne&amp;lt;br&amp;gt;
suivre la norme ISO va interagir avec. Un exemple serait un cal japonais&amp;lt;br&amp;gt;
système endurant.&amp;lt;br&amp;gt;
&amp;lt;strong&amp;gt;&amp;lt;em&amp;gt;java.time.format&amp;lt;/em&amp;gt;&amp;lt;/strong&amp;gt;&amp;lt;br&amp;gt;
Contient le DateTimeFormatter utilisé pour convertir les objets de date et d&amp;amp;#8217;heure&amp;lt;br&amp;gt;
dans une chaîne et également pour analyser les chaînes dans les objets de données et de temps.&amp;lt;br&amp;gt;
&amp;lt;strong&amp;gt;&amp;lt;em&amp;gt;java.time.temporal&amp;lt;/em&amp;gt;&amp;lt;/strong&amp;gt;&amp;lt;br&amp;gt;
Contient les interfaces requises par les classes de date et d&amp;amp;#8217;heure de base et également&amp;lt;br&amp;gt;
des abstractions (telles que des requêtes et des ajusteurs) pour des opérations avancées avec des dates.&amp;lt;br&amp;gt;
&amp;lt;strong&amp;gt;&amp;lt;em&amp;gt;java.time.zone&amp;lt;/em&amp;gt;&amp;lt;/strong&amp;gt;&amp;lt;br&amp;gt;
Classes utilisées pour les règles de fuseau horaire sous-jacentes;&amp;lt;br&amp;gt;
la plupart des développeurs n&amp;amp;#8217;auront pas besoin ce paquet.&amp;lt;br&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;imageblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;img src=&amp;quot;../../img/java8-date-time.png&amp;quot; alt=&amp;quot;java8 date time&amp;quot;&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;import java.time.LocalDate
import java.time.Month
import java.time.Period
import java.time.YearMonth
import java.time.temporal.*
import java.util.HashMap
import java.util.stream.Collectors
import kotlin.test.Test

class DatesHoursJava8Test {
    class BirthdayDiary {
        private val birthdays: MutableMap&amp;amp;lt;String, LocalDate&amp;amp;gt;

        init {
            birthdays = HashMap()
        }

        fun addBirthday(
            name: String, day: Int, month: Int,
            year: Int
        ): LocalDate {
            val birthday: LocalDate = LocalDate.of(year, month, day)
            birthdays[name] = birthday
            return birthday
        }

        fun getBirthdayFor(name: String): LocalDate? {
            return birthdays[name]
        }

        fun getAgeInYear(name: String, year: Int): Int {
            val period: Period = Period.between(
                birthdays[name],
                birthdays[name]!!.withYear(year)
            )
            return period.getYears()
        }

        fun getFriendsOfAgeIn(age: Int, year: Int): Set&amp;amp;lt;String&amp;amp;gt; {
            return birthdays.keys.stream()
                .filter { p: String -&amp;amp;gt; getAgeInYear(p, year) == age }
                .collect(Collectors.toSet())
        }

        fun getDaysUntilBirthday(name: String): Int {
            val period: Period = Period.between(
                LocalDate.now(),
                birthdays[name]
            )
            return period.getDays()
        }

        fun getBirthdaysIn(month: Month): Set&amp;amp;lt;String&amp;amp;gt; {
            return birthdays.entries.stream()
                .filter { (_, value): Map.Entry&amp;amp;lt;String, LocalDate&amp;amp;gt; -&amp;amp;gt; value.getMonth() === month }
                .map&amp;amp;lt;String&amp;amp;gt; { (key): Map.Entry&amp;amp;lt;String, LocalDate&amp;amp;gt; -&amp;amp;gt; key }
                .collect(Collectors.toSet())
        }

        val birthdaysInCurrentMonth: Set&amp;amp;lt;String&amp;amp;gt;
            get() = getBirthdaysIn(LocalDate.now().getMonth())
        val totalAgeInYears: Int
            get() = birthdays.keys.stream()
                .mapToInt { p: String -&amp;amp;gt;
                    getAgeInYear(
                        p,
                        LocalDate.now().getYear()
                    )
                }
                .sum()
    }

    //Les ajusteurs modifient les objets de date et d&amp;#39;heure. Supposons, par exemple, que nous voulions
    //renvoie le premier jour d&amp;#39;un trimestre qui contient un horodatage particulier :
    class FirstDayOfQuarter : TemporalAdjuster {
        override fun adjustInto(temporal: Temporal): Temporal? {
            val currentQuarter: Int = YearMonth.from(temporal)
                .get(IsoFields.QUARTER_OF_YEAR)
            return when (currentQuarter) {
                1 -&amp;amp;gt; LocalDate.from(temporal)
                    .with(TemporalAdjusters.firstDayOfYear())

                2 -&amp;amp;gt; LocalDate.from(temporal)
                    .withMonth(Month.APRIL.value)
                    .with(TemporalAdjusters.firstDayOfMonth())

                3 -&amp;amp;gt; LocalDate.from(temporal)
                    .withMonth(Month.JULY.value)
                    .with(TemporalAdjusters.firstDayOfMonth())

                4 -&amp;amp;gt; LocalDate.from(temporal)
                    .withMonth(Month.OCTOBER.value)
                    .with(TemporalAdjusters.firstDayOfMonth())

                else -&amp;amp;gt; null
            }
        }
    }

    enum class Quarter {
        FIRST, SECOND, THIRD, FOURTH
    }

    //Dans quel trimestre de l&amp;#39;année cette date se situe-t-elle ?
    class QuarterOfYearQuery : TemporalQuery&amp;amp;lt;Quarter&amp;amp;gt; {
        override fun queryFrom(temporal: TemporalAccessor): Quarter {
            val now = LocalDate.from(temporal)
            return if (now.isBefore(now.with(Month.APRIL).withDayOfMonth(1)))
                Quarter.FIRST
            else if (now.isBefore(
                    now.with(Month.JULY)
                        .withDayOfMonth(1)
                )
            ) Quarter.SECOND else if (now.isBefore(
                    now.with(Month.NOVEMBER)
                        .withDayOfMonth(1)
                )
            ) Quarter.THIRD else Quarter.FOURTH
        }
    }

    @Test
    fun `Dates et heures après java 8`() {
        val today = LocalDate.now()
        val currentMonth = today.month
        val firstMonthOfQuarter = currentMonth.firstMonthOfQuarter()

        val q = QuarterOfYearQuery()
        // Direct
        var quarter: Quarter? = q.queryFrom(LocalDate.now())
        println(quarter)
        // Indirect
        quarter = LocalDate.now().query(q)
        println(quarter)


        val now = LocalDate.now()
        val fdoq: Temporal = now.with(FirstDayOfQuarter())
        println(fdoq)
    }
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;#toc&amp;quot;&amp;gt;table des matières&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;arrCol&amp;quot;&amp;gt;tableaux et collections&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;imageblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;img src=&amp;quot;../../img/collections_classes_inheritance.png&amp;quot; alt=&amp;quot;Classes de collections et héritage&amp;quot;&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;package playground.programming

import java.util.*
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertTrue

class ArrayCollectionTest {
    @Test
    fun `Tableaux, collections`() {
        //Tableau
        //java.util.Arrays définit d&amp;#39;utiles méthodes de manipulation de tableaux,
        //y compris de tri et de recherche au sein d&amp;#39;un tableau
        val intArray = arrayOf(10, 5, 7, -3)
        //tri le tableau
        Arrays.sort(intArray)
        var pos = Arrays.binarySearch(intArray, 7)
        //la valeur 7 est trouvé a l&amp;#39;index 2
        assertEquals(2, pos)
        //12 pas trouvé retourne une valeur negative
        assert(Arrays.binarySearch(intArray, 12) &amp;amp;lt; 0)

        //les tableaux peuvent également etre triés
        //et faire l&amp;#39;objet d&amp;#39;une recherche
        val stringArray = arrayOf(&amp;quot;le&amp;quot;, &amp;quot;moment&amp;quot;, &amp;quot;c&amp;#39;est&amp;quot;)
        assertEquals(&amp;quot;c&amp;#39;est&amp;quot;, stringArray[2])
        assertEquals(&amp;quot;le&amp;quot;, stringArray[0])
        assertEquals(&amp;quot;moment&amp;quot;, stringArray[1])
        Arrays.sort(stringArray)
        assertEquals(&amp;quot;c&amp;#39;est&amp;quot;, stringArray[0])
        assertEquals(&amp;quot;le&amp;quot;, stringArray[1])
        assertEquals(&amp;quot;moment&amp;quot;, stringArray[2])

        //Arrays.equals() compare tous les éléments de deux tableaux
        //Arrays.clone() copie tous les elements du tableau dans un autre
        stringArray.forEachIndexed { i, it -&amp;amp;gt; assertEquals(it, stringArray.clone()[i]) }

        val data = ByteArray(100)
        //Arrays.fill() initialise tous les éléments des deux tableaux
        //initalise tous les éléments à -1
        Arrays.fill(data, -1)
        data.forEach { assertEquals(-1, it) }

        //attribue aux éléments 5, 6, 7, 8 et 9 la valeur -2
        Arrays.fill(data, 5, 10, -2)
        ((5 until (10 - 1))).forEach { assertEquals(-2, data[it]) }

        //récupère le type de data
        val type = data::class.java
        //est ce que data est un tableau?
        assertTrue(type.isArray())
        //est ce que data est un tableau de byte
        assertEquals(Byte::class.java, type.getComponentType())

        //Collection
        val s = java.util.HashSet&amp;amp;lt;String&amp;amp;gt;()
        s.add(&amp;quot;test&amp;quot;)
        assertTrue(s.contains(&amp;quot;test&amp;quot;))
        assertFalse(s.contains(&amp;quot;test2&amp;quot;))
        s.remove(&amp;quot;test&amp;quot;)
        assertFalse(s.contains(&amp;quot;test&amp;quot;))

        val ss = TreeSet&amp;amp;lt;String&amp;amp;gt;()
        ss.add(&amp;quot;b&amp;quot;)
        ss.add(&amp;quot;a&amp;quot;)
        ss.iterator().forEach { assertTrue(it == &amp;quot;a&amp;quot; || it == &amp;quot;b&amp;quot;) }

        //liste doublement chainée
        var dll: List&amp;amp;lt;String&amp;amp;gt; = LinkedList&amp;amp;lt;String&amp;amp;gt;()

        //plus efficace
        val l = java.util.ArrayList&amp;amp;lt;String&amp;amp;gt;()
        l.addAll(ss)
        l.addAll(1, ss)

        val obj = l.get(1)
        val obj_prime = l[1]
        assertEquals(obj, obj_prime)

        l.set(3, &amp;quot;nouvel élément&amp;quot;)
        l.add(&amp;quot;test&amp;quot;)
        l.add(0, &amp;quot;test2&amp;quot;)
        l.removeAt(1)
        l.remove(&amp;quot;a&amp;quot;)
        assertFalse(l.contains(&amp;quot;a&amp;quot;))
        l.removeAll(ss)
        assertFalse(l.containsAll(ss))
        assertFalse(l.isEmpty())
        assertTrue(l.isNotEmpty())


        val sublist = l.subList(1, 3)
        val elements = l.toArray()
        l.clear()

        val m = HashMap&amp;amp;lt;String, Integer&amp;amp;gt;()
        m.put(&amp;quot;clé&amp;quot;, Integer(42))
        m[&amp;quot;clé&amp;quot;] = Integer(42)
        val value: Integer = m.get(&amp;quot;clé&amp;quot;)!!
        assertEquals(Integer(42), value)
        m.remove(&amp;quot;clé&amp;quot;)
        assertTrue(m.isEmpty())
        val keys = m.keys
        assertTrue(keys.isEmpty())


        val set = HashSet&amp;amp;lt;String&amp;amp;gt;()
        set.add(&amp;quot;key_1&amp;quot;)
        set.add(&amp;quot;key_2&amp;quot;)
        set.add(&amp;quot;key_3&amp;quot;)
        val members = set.toArray()
        assertEquals(3, members.size)
        val list = ArrayList&amp;amp;lt;String&amp;amp;gt;()
        list.add(&amp;quot;items1&amp;quot;)
        list.add(&amp;quot;items2&amp;quot;)
        list.add(&amp;quot;items3&amp;quot;)
        val items = list.toArray()
        assertEquals(3, items.size)

        //trie et recherche d&amp;#39;éléments sur les collections
        list.add(&amp;quot;clé&amp;quot;)
        //en premier on trie
        Collections.sort(list)
        //en kotlin
        list.sort()
        //en deuxieme on cherche
        //retourne l&amp;#39;index du premier trouvé sinon -1
        pos = Collections.binarySearch(list, &amp;quot;clé&amp;quot;)
        assertEquals(0, pos)
        val list1 = mutableListOf(1, 2, 3, 4, 5)
        val list2 = mutableListOf&amp;amp;lt;Int&amp;amp;gt;(0, 0, 0, 0, 0)

        //d&amp;#39;autres méthodes intéressantes concernant Collections

        //copie list1 dans list2, 2e parametre dans 1er parametre
        Collections.copy(list2, list1)
        //comparaison de la copy avec filter
        assertTrue(list1.filterIndexed { i: Int, it: Int -&amp;amp;gt; it != list2[i] }.isEmpty())
        //comparaison de la copy avec map
        list1.mapIndexed { index: Int, it: Int -&amp;amp;gt; assertEquals(it, list2[index]) }

        //rempli avec des 0
        Collections.fill(list2, 0)
        assertTrue(list2.none { it != 0 })

        //le maximum
        assertEquals(5, Collections.max(list1))

        //le minimum
        assertEquals(1, Collections.min(list1))

        //renverse
        Collections.reverse(list)
        listOf(&amp;quot;items3&amp;quot;, &amp;quot;items2&amp;quot;, &amp;quot;items1&amp;quot;, &amp;quot;clé&amp;quot;).mapIndexed { i: Int, it: String -&amp;amp;gt; assertEquals(it, list[i]) }

        //mélange la list
        Collections.shuffle(list)

        //retourne un ensemble immuable possédant un seul élément 0
        Collections.singleton(0)
        //renvoi un emballage immuable autour d&amp;#39;une liste
        Collections.unmodifiableList(list)
        //renvoi un emballage synchronisé autour d&amp;#39;une map, ensemble clé valeur
        Collections.synchronizedMap(m)

        //java.util.Properties un est objet key value
    }
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;#toc&amp;quot;&amp;gt;table des matières&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;sysProp&amp;quot;&amp;gt;System.Properties&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://docs.oracle.com/javase/tutorial/essential/environment/sysprop.html&amp;quot;&amp;gt;System.Properties doc officielle&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;voici un tableau de quelques propriétés intéressantes&amp;lt;br&amp;gt;
Ces propriétés sont intéressantes pour avoir des informations&amp;lt;br&amp;gt;
sur le système hôte de la JVM.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;table class=&amp;quot;tableblock frame-all grid-all stretch&amp;quot;&amp;gt;
&amp;lt;colgroup&amp;gt;
&amp;lt;col style=&amp;quot;width: 3.3333%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 3.3333%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 3.3333%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 3.3333%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 3.3333%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 3.3333%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 3.3333%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 3.3333%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 3.3333%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 3.3333%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 3.3333%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 3.3333%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 3.3333%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 3.3333%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 3.3333%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 3.3333%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 3.3333%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 3.3333%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 3.3333%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 3.3333%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 3.3333%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 3.3333%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 3.3333%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 3.3333%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 3.3333%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 3.3333%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 3.3333%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 3.3333%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 3.3333%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 3.3343%;&amp;quot;&amp;gt;
&amp;lt;/colgroup&amp;gt;
&amp;lt;tbody&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;Key&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;Meaning&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;file.separator&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Character that separates components of a file path. This is &amp;quot;/&amp;quot; on UNIX and &amp;quot;\&amp;quot; on Windows.&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;java.class.path&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Path used to find directories and JAR archives containing class files. Elements of the class path are separated by a platform-specific character specified in the path.separator property.&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;java.home&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Installation directory for Java Runtime Environment (JRE)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;java.vendor&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;JRE vendor name&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;java.vendor.url&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;JRE vendor URL&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;java.version&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;JRE version number&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;line.separator&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Sequence used by operating system to separate lines in text files&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;os.arch&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Operating system architecture&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;os.name&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Operating system name&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;os.version&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Operating system version&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;path.separator&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Path separator character used in java.class.path&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;user.dir&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;User working directory&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;user.home&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;User home directory&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;user.name&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;User account name&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;#toc&amp;quot;&amp;gt;table des matières&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;threads&amp;quot;&amp;gt;Threads&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;import java.text.DateFormat
import java.util.*
import kotlin.test.Test

class ThreadsTest {

    @Test
    fun `threads test`() {
        //java.lang.Thread représente le thread fondamentale de l&amp;#39;API java
        //il existe deux manière de définir un thread
        //1) étendre la classe Thread ou une lambda en kotlin
        //2) implémenter l&amp;#39;interface Runnable,
        //      puis passer une instance de cet objet Runnable au constructeur de Thread.

        val list1: List&amp;amp;lt;Int&amp;amp;gt; = List(
            size = 45,
            init = { (1..31).random() }
        )
        println(&amp;quot;list1$list1&amp;quot;)
        //facon 1
        val t = Thread {
            Collections.sort(list1)
            println(&amp;quot;list1 sorted$list1&amp;quot;)
        }
        t.start()

        val list2 = List(
            size = 45,
            init = { (1..31).random() }
        )
        println(&amp;quot;list2$list2&amp;quot;)
        //facon 2
        val sorter = BackgroundSorter(list2)
        sorter.start()

        //priorité des threads
        //tant qu&amp;#39;un thread de niveau de priorité supérieure n&amp;#39;est pas fini
        //alors celui de niveau inférieur ne peut s&amp;#39;exécuter

        //on définit un thread avec une priorité inférieur à la normale
        t.setPriority(Thread.NORM_PRIORITY - 1)

        //ici on définit un thread avec une priorité inférieur à la priorité
        //du thread courant
        t.setPriority(Thread.currentThread().getPriority() - 1)

        //Thread.yield() fait une pause pour laisser les autres threads de meme priorité s&amp;#39;exécuter
    }

    class BackgroundSorter(private val l: List&amp;amp;lt;Int&amp;amp;gt;) : Thread() {
        override fun run() {
            Collections.sort(l)
            println(&amp;quot;list2 sorted$l&amp;quot;)
        }
    }

    //pour l&amp;#39;arrêt du thread, plutôt qu&amp;#39;utiliser la fonction Thread.stop()
    // qui laisse la memoire dans un état non controlé.
    //utiliser la méthode tel que l&amp;#39;exemple pleaseStop()
    class DummyClock(
        private val df: DateFormat = DateFormat.getTimeInstance(DateFormat.MEDIUM),
        private var keepRunning: Boolean = true
    ) : Thread() {
        init {
            isDaemon = true
            start()
        }

        override fun run() {
            while (keepRunning) {
                println(df.format(Date()))
                try {
                    sleep(1000)
                } catch (e: InterruptedException) {
                    println(e.message)
                }
            }
        }

        fun pleaseStop() {
            keepRunning = false
        }
    }

    //java.util.Timer
    //java.util.TimerTask
    //ces classes permettent l&amp;#39;exécution de taches répétitives
    @Test
    fun `Timer et TimerTask test`() {
        DummyClock()
    }
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;#toc&amp;quot;&amp;gt;table des matières&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;regex&amp;quot;&amp;gt;Expressions Régulières&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;import java.util.*
import java.util.regex.Matcher
import java.util.regex.Pattern
import java.util.stream.Collectors
import kotlin.test.Test
import kotlin.test.assertEquals

class RegularExpressionsTest {
    @Test
    fun `expressions régulières`() {
        val p: Pattern = Pattern.compile(&amp;quot;honou?r&amp;quot;)
        val caesarUK = &amp;quot;For Brutus is an honourable man&amp;quot;
        val mUK: Matcher = p.matcher(caesarUK)
        assertEquals(true, mUK.find(), &amp;quot;Should matches UK spelling&amp;quot;)
        val caesarUS = &amp;quot;For Brutus is an honorable man&amp;quot;
        val mUS: Matcher = p.matcher(caesarUS)
        assertEquals(true, mUS.find(), &amp;quot;Should matches US spelling&amp;quot;)
    }

    @Test
    fun `expressions régulières plus complexes`() {
        //Notez que nous devons utiliser \\ car nous avons besoin d&amp;#39;un littéral \
        //et Java utilise un seul \ comme caractère d&amp;#39;échappement
        var pStr = &amp;quot;\\d&amp;quot; // Un chiffre numérique

        var text = &amp;quot;Apollo 13&amp;quot;
        var p = Pattern.compile(pStr)
        var m = p.matcher(text)
        print(pStr + &amp;quot; matches &amp;quot; + text + &amp;quot;? &amp;quot; + m.find())
        println(&amp;quot; ; match: &amp;quot; + m.group())
        pStr = &amp;quot;[a..zA..Z]&amp;quot; //N&amp;#39;importe quelle lettre

        p = Pattern.compile(pStr)
        m = p.matcher(text)
        print(pStr + &amp;quot; matches &amp;quot; + text + &amp;quot;? &amp;quot; + m.find())
        println(&amp;quot; ; match: &amp;quot; + m.group())

        //N&amp;#39;importe quel nombre de lettres, qui doivent toutes être comprises entre &amp;#39;a&amp;#39; et &amp;#39;j&amp;#39;
        //mais peut-être en majuscule ou en minuscule.
        pStr = &amp;quot;([a..jA..J]*)&amp;quot;
        p = Pattern.compile(pStr)
        m = p.matcher(text)
        print(pStr + &amp;quot; matches &amp;quot; + text + &amp;quot;? &amp;quot; + m.find())
        println(&amp;quot; ; match: &amp;quot; + m.group())
        text = &amp;quot;abacab&amp;quot;
        //&amp;#39;a&amp;#39; suivi de quatre caractères quelconques, suivi de &amp;#39;b&amp;#39;
        pStr = &amp;quot;a....b&amp;quot;
        p = Pattern.compile(pStr)
        m = p.matcher(text)
        print(pStr + &amp;quot; matches &amp;quot; + text + &amp;quot;? &amp;quot; + m.find())
        println(&amp;quot; ; match: &amp;quot; + m.group())
    }

    @Test
    fun `Quelles chaînes correspondent à la regex ?`() {
        val pStr = &amp;quot;\\d&amp;quot; // Un chiffre numérique
        val p = Pattern.compile(pStr)
        val ls: List&amp;amp;lt;String&amp;amp;gt; = Arrays.asList(&amp;quot;Cat&amp;quot;, &amp;quot;Dog&amp;quot;, &amp;quot;Ice-9&amp;quot;, &amp;quot;99 Luftballoons&amp;quot;)
        val containDigits: List&amp;amp;lt;String&amp;amp;gt; = ls.stream()
            .filter(p.asPredicate())
            .collect(Collectors.toList())
        assert(containDigits.contains(&amp;quot;Ice-9&amp;quot;))
        assert(containDigits.contains(&amp;quot;99 Luftballoons&amp;quot;))
        assertEquals(2, containDigits.size)
    }
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_tableau_regex_metacharacters&amp;quot;&amp;gt;Tableau regex metacharacters&amp;lt;/h3&amp;gt;
&amp;lt;table class=&amp;quot;tableblock frame-all grid-all stretch&amp;quot;&amp;gt;
&amp;lt;colgroup&amp;gt;
&amp;lt;col style=&amp;quot;width: 1.7543%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 1.7543%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 1.7543%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 1.7543%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 1.7543%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 1.7543%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 1.7543%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 1.7543%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 1.7543%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 1.7543%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 1.7543%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 1.7543%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 1.7543%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 1.7543%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 1.7543%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 1.7543%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 1.7543%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 1.7543%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 1.7543%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 1.7543%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 1.7543%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 1.7543%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 1.7543%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 1.7543%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 1.7543%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 1.7543%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 1.7543%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 1.7543%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 1.7543%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 1.7543%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 1.7543%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 1.7543%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 1.7543%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 1.7543%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 1.7543%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 1.7543%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 1.7543%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 1.7543%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 1.7543%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 1.7543%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 1.7543%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 1.7543%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 1.7543%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 1.7543%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 1.7543%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 1.7543%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 1.7543%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 1.7543%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 1.7543%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 1.7543%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 1.7543%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 1.7543%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 1.7543%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 1.7543%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 1.7543%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 1.7543%;&amp;quot;&amp;gt;
&amp;lt;col style=&amp;quot;width: 1.7592%;&amp;quot;&amp;gt;
&amp;lt;/colgroup&amp;gt;
&amp;lt;tbody&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Metacharacter&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;fonctionnalité&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Notes&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;?&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Caractère facultatif—zéro ou une instance&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;*&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Zéro ou plus du caractère précédent&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;+&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Un ou plusieurs des caractères précédents&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;{M,N}&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Entre M et N instances du caractère précédent&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;\d&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Un chiffre&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;\D&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Un caractère non numérique&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;\w&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Un caractère de mot&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Chiffres, lettres et _&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;\W&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Un caractère sans mot&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;\s&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Un caractère d&amp;amp;#8217;espacement&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;\S&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Un caractère non blanc&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;\n&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Caractère de saut de ligne&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;\t&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Caractère de tabulation&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;.&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Un caractère n&amp;amp;#8217;importe lequel&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;N&amp;amp;#8217;inclut pas la nouvelle ligne en Java&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;[ ]&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Tout caractère contenu entre crochets&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Appelé une classe de caractères&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;[^ ]&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Tout caractère non contenu entre crochets&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Appelé une classe de caractères inversée&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;( )&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Construire un groupe d&amp;amp;#8217;éléments de motif&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Appelé un groupe (ou groupe de capture)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;|&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Définir des possibilités alternatives&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Implémente le OU logique&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;^&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Début de chaîne $ Fin de chaîne&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;compléter &amp;lt;a href=&amp;quot;https://www.codeurjava.com/2015/05/les-expressions-regulieres-avec-regex.html&amp;quot;&amp;gt;avec&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>Catatan Gradle</title>
            <link >https://cheroliv.com/blog/2022/0047_memo_gradle_post.html</link>
            <pubDate>Mon, 23 May 2022 00:00:00 +0000</pubDate>
            <guid isPermaLink="false">blog/2022/0047_memo_gradle_post.html</guid>
            <description>&amp;lt;div id=&amp;quot;toc&amp;quot; class=&amp;quot;toc&amp;quot;&amp;gt;
&amp;lt;div id=&amp;quot;toctitle&amp;quot;&amp;gt;Sommaire&amp;lt;/div&amp;gt;
&amp;lt;ul class=&amp;quot;sectlevel1&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_tampilkan_bantuan&amp;quot;&amp;gt;Tampilkan bantuan&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_menetapkan_properti_sistem_jvm_d_system_prop&amp;quot;&amp;gt;Menetapkan properti sistem JVM: `-D, --system-prop&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_menetapkan_properti_proyek_untuk_skrip_build_pmypropmyvalue&amp;quot;&amp;gt;Menetapkan properti proyek untuk skrip build : `-Pmyprop=myvalue&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_menentukan_direktori_awal_gradle_nilai_defaultnya_adalah_direktori_saat_ini&amp;quot;&amp;gt;Menentukan direktori awal Gradle. Nilai defaultnya adalah direktori saat ini.&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_mengoverride_properti_param_component_melalui_baris_perintah&amp;quot;&amp;gt;Mengoverride properti &amp;#39;param_component&amp;#39; melalui baris perintah:&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_menambahkan_folder_ke_source_set_dalam_proyek_biasa_dengan_kotlin_dsl&amp;quot;&amp;gt;Menambahkan folder ke source-set dalam proyek biasa dengan kotlin dsl&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_menambahkan_folder_ke_source_set_dalam_proyek_android_dengan_groovy_dsl&amp;quot;&amp;gt;Menambahkan folder ke source-set dalam proyek Android dengan Groovy DSL&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_menghindari_membangun_dan_menguji_sub_modul&amp;quot;&amp;gt;Menghindari membangun dan menguji sub-modul&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_buat_arsip_sebuah_direktori&amp;quot;&amp;gt;Buat arsip sebuah direktori&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_menampilkan_dependensi_build&amp;quot;&amp;gt;Menampilkan dependensi build&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_contoh_penggunaan_reduce_penggabungan_satu_daftar_dalam_sebuah_string&amp;quot;&amp;gt;Contoh penggunaan reduce : penggabungan satu daftar dalam sebuah string&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_mendapatkan_deskripsi_tugas_tertentu&amp;quot;&amp;gt;Mendapatkan deskripsi tugas tertentu&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_untuk_menjalankan_tugas_dari_root_workingdir_maka_awali_tugas_dengan&amp;quot;&amp;gt;Untuk menjalankan tugas dari root workingDir, maka awali tugas dengan `:&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_untuk_memulai_ulang_tugas_yang_gagal_gunakan_opsi_rerun_tasks&amp;quot;&amp;gt;Untuk memulai ulang tugas yang gagal, gunakan opsi `--rerun-tasks&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_menjalankan_tugas_dalam_proyek_tertentu_dari_direktori_lain&amp;quot;&amp;gt;Menjalankan tugas dalam proyek tertentu dari direktori lain&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_tampilkan_bantuan&amp;quot;&amp;gt;Tampilkan bantuan&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;./gradlew --help&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_menetapkan_properti_sistem_jvm_d_system_prop&amp;quot;&amp;gt;Menetapkan properti sistem JVM: `-D, --system-prop&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;./gradlew -Dmyprop=myvalue
#ou
./gradlew --system-prop myvalue&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_menetapkan_properti_proyek_untuk_skrip_build_pmypropmyvalue&amp;quot;&amp;gt;Menetapkan properti proyek untuk skrip build : `-Pmyprop=myvalue&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;./gradlew -Pmyprop=myvalue
#ou
./gradlew --project-prop myvalue&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_menentukan_direktori_awal_gradle_nilai_defaultnya_adalah_direktori_saat_ini&amp;quot;&amp;gt;Menentukan direktori awal Gradle. Nilai defaultnya adalah direktori saat ini.&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;./gradlew -p ~/src/next_startup
#ou
./gradlew --project-dir ~/src/next_startup&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_mengoverride_properti_param_component_melalui_baris_perintah&amp;quot;&amp;gt;Mengoverride properti &amp;#39;param_component&amp;#39; melalui baris perintah:&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;./gradlew -Pparam_component=CUSTOM_VALUE&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_menambahkan_folder_ke_source_set_dalam_proyek_biasa_dengan_kotlin_dsl&amp;quot;&amp;gt;Menambahkan folder ke source-set dalam proyek biasa dengan kotlin dsl&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;sourceSets {
    getByName(&amp;quot;test&amp;quot;){
        java.srcDir(&amp;quot;src/scripts/groovy&amp;quot;)
    }
    getByName(&amp;quot;test&amp;quot;){
        java.srcDir(&amp;quot;src/scripts/kscript&amp;quot;)
    }
    getByName(&amp;quot;test&amp;quot;){
        java.srcDir(&amp;quot;src/test/javascript&amp;quot;)
    }
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_menambahkan_folder_ke_source_set_dalam_proyek_android_dengan_groovy_dsl&amp;quot;&amp;gt;Menambahkan folder ke source-set dalam proyek Android dengan Groovy DSL&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;android {
    sourceSets {
        main.java.srcDirs += &amp;quot;src/main/../../../../ceelo/domain/src/main/java/&amp;quot;
        test.java.srcDirs += &amp;quot;src/test/../../../../ceelo/domain/src/test/java/&amp;quot;
    }
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_menghindari_membangun_dan_menguji_sub_modul&amp;quot;&amp;gt;Menghindari membangun dan menguji sub-modul&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;gradle membangun -x :excluded-module:memeriksa -x :excluded-module:merakit -x :excluded-module:membangun&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;Memindahkan file&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;tasks.register(&amp;quot;moveWebappNode&amp;quot;) {
    doLast {
        ant.withGroovyBuilder {
            &amp;quot;move&amp;quot;(
                &amp;quot;webapp/node_modules&amp;quot; to &amp;quot;$rootDir/webapp-src/node_modules&amp;quot;,
            )
        }
    }
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_buat_arsip_sebuah_direktori&amp;quot;&amp;gt;Buat arsip sebuah direktori&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;tasks.register&amp;amp;lt;Tar&amp;amp;gt;(&amp;quot;tarWebapp&amp;quot;) {
    dependsOn(&amp;quot;moveWebappNpm&amp;quot;)
    group = WEBAPP
    description = &amp;quot;tar webapp&amp;quot;
    doLast {
        setOf(
            &amp;quot;build&amp;quot;,
            &amp;quot;target&amp;quot;,
            &amp;quot;node_modules&amp;quot;
        ).forEach { dir -&amp;amp;gt; exclude { it.name .dir } }
        archiveFileName.set(&amp;quot;webapp.tar&amp;quot;)
        destinationDirectory.set(File(&amp;quot;${rootDir.absolutePath}$sep$WEBAPP_SRC&amp;quot;))
    }
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_menampilkan_dependensi_build&amp;quot;&amp;gt;Menampilkan dependensi build&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;tasks.register(&amp;quot;printDependencies&amp;quot;) {
    description = &amp;quot;print project dependencies&amp;quot;
    group = WEBAPP
    doLast {
        println(&amp;quot;${project.name} dependencies&amp;quot;)
        mutableMapOf&amp;amp;lt;String, Map&amp;amp;lt;String, String&amp;amp;gt;&amp;amp;gt;(
            &amp;quot;buildDependencies&amp;quot; to buildDependencies,
            &amp;quot;domainDeps&amp;quot; to domainDeps,
            &amp;quot;domainTestDeps&amp;quot; to domainTestDeps,
        ).apply { putAll(appModules) }
            .forEach { module -&amp;amp;gt;
                if (module.value.isNotEmpty()) {
                    println(&amp;quot;${module.key}:&amp;quot;)
                    module.value.forEach { println(dependency(it)) }
                    println()
                }
            }
    }
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_contoh_penggunaan_reduce_penggabungan_satu_daftar_dalam_sebuah_string&amp;quot;&amp;gt;Contoh penggunaan reduce : penggabungan satu daftar dalam sebuah string&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;tasks.register(&amp;quot;printWebappSrc&amp;quot;) {
    description = &amp;quot;print webapp sources&amp;quot;
    group = WEBAPP
    doLast {
        webAppSrc
            .reduce { acc, s -&amp;amp;gt; &amp;quot;$acc\n\t$s&amp;quot; }
            .run { println(&amp;quot;$WEBAPP_SRC: $this\n&amp;quot;) }
    }
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_mendapatkan_deskripsi_tugas_tertentu&amp;quot;&amp;gt;Mendapatkan deskripsi tugas tertentu&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;./gradlew -q help --task foo&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_untuk_menjalankan_tugas_dari_root_workingdir_maka_awali_tugas_dengan&amp;quot;&amp;gt;Untuk menjalankan tugas dari root workingDir, maka awali tugas dengan `:&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;./gradlew :foo&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_untuk_memulai_ulang_tugas_yang_gagal_gunakan_opsi_rerun_tasks&amp;quot;&amp;gt;Untuk memulai ulang tugas yang gagal, gunakan opsi `--rerun-tasks&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;./gradlew check --rerun-tasks&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_menjalankan_tugas_dalam_proyek_tertentu_dari_direktori_lain&amp;quot;&amp;gt;Menjalankan tugas dalam proyek tertentu dari direktori lain&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;pilihan`-p` (ou &amp;lt;code&amp;gt;--project-dir&amp;lt;/code&amp;gt;) memungkinkan menentukan jalur akses ke sebuah proyek Gradle dan mengeksekusi sebuah tugas di sana, bahkan jika direktori saat ini berbeda.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Contoh, untuk meluncurkan tugas`build`proyek yang terletak di`~/projets/mon-autre-projet`sambil berada di direktori lain:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;./gradlew -p ~/projets/mon-autre-projet build&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Hal ini sangat berguna untuk membuat skrip aksi pada beberapa proyek dari satu lokasi pusat.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;lihat juga :&amp;lt;/div&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://docs.gradle.org/current/userguide/command_line_interface.html#sec:command_line_warnings&amp;quot;&amp;gt;referensi&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://docs.gradle.org/current/userguide/command_line_interface.html#sec:command_line_options&amp;quot;&amp;gt;referensi&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://stackoverflow.com/a/36178581/837404&amp;quot;&amp;gt;diskusi&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>Pelatihan - kotlin: enum, kelas tersegel</title>
            <link >https://cheroliv.com/blog/2022/0038_training_kotlin_enum_sealed_class_post.html</link>
            <pubDate>Tue, 17 May 2022 00:00:00 +0000</pubDate>
            <guid isPermaLink="false">blog/2022/0038_training_kotlin_enum_sealed_class_post.html</guid>
            <description>&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;kelas sealed bukan merupakan enumerasi(kata kunci:enum).&amp;lt;br&amp;gt;
Dari Java, Anda mungkin tergoda untuk membebani enumerasi kontribusi fitur Anda: // Kode Java&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-java hljs&amp;quot; data-lang=&amp;quot;java&amp;quot;&amp;gt;enum PizzaOrderStatus {
    ORDER_RECEIVED,
    PIZZA_BEING_MADE,
    OUT_FOR_DELIVERY,
    COMPLETED;
    public PizzaOrderStatus nextStatus() {
        switch (this) {
        case ORDER_RECEIVED: return PIZZA_BEING_MADE;
        case PIZZA_BEING_MADE: return OUT_FOR_DELIVERY;
        case OUT_FOR_DELIVERY: return COMPLETED;
        case COMPLETED:return COMPLETED;
        }
    }
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Alih-alih, Anda dapat menggunakan kelas sealed :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;sealed class PizzaOrderStatus(protected val orderId: Int) {
    abstract fun nextStatus() : PizzaOrderStatus
    class OrderReceived(orderId: Int) : PizzaOrderStatus(orderId) {
        override fun nextStatus(): PizzaOrderStatus {
            return PizzaBeingMade(orderId)
        }
    }
    class PizzaBeingMade(orderId: Int) : PizzaOrderStatus(orderId) {
        override fun nextStatus(): PizzaOrderStatus {
            return OutForDelivery(orderId)
        }
    }
    class OutForDelivery(orderId: Int) : PizzaOrderStatus(orderId) {
        override fun nextStatus(): PizzaOrderStatus {
            return Completed(orderId)
        }
    }
    class Completed(orderId: Int) : PizzaOrderStatus(orderId) {
        override fun nextStatus(): PizzaOrderStatus {
            return this
        }
    }
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Keuntungan dari pendekatan ini adalah bahwa sekarang kami dapat mengirim data dengan status:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;var status: PizzaOrderStatus = OrderReceived(123)
while (status !is Completed) {
    status = when (status) {
        is OrderReceived -&amp;amp;gt; status.nextStatus()
        is PizzaBeingMade -&amp;amp;gt; status.nextStatus()
        is OutForDelivery -&amp;amp;gt; status.nextStatus()
        is Completed -&amp;amp;gt; status
    }
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Umumnya, sealed classes bagus jika Anda ingin memiliki data terkait dengan satu Negara.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>Pelatihan - Kotlin</title>
            <link >https://cheroliv.com/blog/2022/0036_training_kotlin_post.html</link>
            <pubDate>Thu, 12 May 2022 00:00:00 +0000</pubDate>
            <guid isPermaLink="false">blog/2022/0036_training_kotlin_post.html</guid>
            <description>&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_peta_pikir&amp;quot;&amp;gt;Peta pikir&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;span class=&amp;quot;image&amp;quot;&amp;gt;&amp;lt;img src=&amp;quot;../../diagram/training_kotlin.png&amp;quot; alt=&amp;quot;training_kotlin&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_pemrograman_fungsional&amp;quot;&amp;gt;Pemrograman fungsional&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Latihan diambil dari buku:&amp;lt;br&amp;gt;
Pemrograman fungsional dalam Kotlin oleh tutorial&amp;lt;br&amp;gt;
Ditulis oleh Massimo Carli&amp;lt;br&amp;gt;
repo: &amp;lt;a href=&amp;quot;https://github.com/kodecocodes/fpk-materials&amp;quot; class=&amp;quot;bare&amp;quot;&amp;gt;https://github.com/kodecocodes/fpk-materials&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_sintaksis&amp;quot;&amp;gt;Sintaksis&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;sect3&amp;quot;&amp;gt;
&amp;lt;h4 id=&amp;quot;_firstprogramtest&amp;quot;&amp;gt;FirstProgramTest&amp;lt;/h4&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;konsep: tampilan layar, fungsi&amp;lt;br&amp;gt;
FirstProgramTest:https://github.com/cheroliv/cheroliv.com/blob/master/codes/src/test/kotlin/programming/FirstProgramTest.kt[sumber]+&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect3&amp;quot;&amp;gt;
&amp;lt;h4 id=&amp;quot;_concatfunctiontest&amp;quot;&amp;gt;ConcatFunctionTest&amp;lt;/h4&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;konsep: memori, variabel, nilai, objek, ekstensi fungsi&amp;lt;br&amp;gt;
ExampleUnitTest: &amp;lt;a href=&amp;quot;https://github.com/cheroliv/cheroliv.com/blob/master/codes/src/test/kotlin/programming/ConcatFunctionTest.kt&amp;quot;&amp;gt;sumber&amp;lt;/a&amp;gt;+&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect3&amp;quot;&amp;gt;
&amp;lt;h4 id=&amp;quot;_birthdaymessagetestoutput&amp;quot;&amp;gt;BirthdayMessageTestOutput&amp;lt;/h4&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;konsep: himpunan, loop&amp;lt;br&amp;gt;
&amp;lt;a href=&amp;quot;https://developer.android.com/codelabs/basic-android-kotlin-training-first-kotlin-program?continue=https%3A%2F%2Fdeveloper.android.com%2Fcourses%2Fpathways%2Fandroid-basics-kotlin-one%23codelab-https%3A%2F%2Fdeveloper.android.com%2Fcodelabs%2Fbasic-android-kotlin-training-first-kotlin-program&amp;quot;&amp;gt;Pengantar kotlin&amp;lt;/a&amp;gt;&amp;lt;br&amp;gt;
BirthdayMessageTestOutput:https://github.com/cheroliv/cheroliv.com/blob/master/codes/src/test/kotlin/programming/BirthdayMessageTestOutput.kt[sumber]+&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_kursus_kotlin&amp;quot;&amp;gt;Kursus kotlin&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;videoblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;iframe src=&amp;quot;https://www.youtube.com/embed/YRjY3jRrQYY?rel=0&amp;quot; frameborder=&amp;quot;0&amp;quot; allowfullscreen&amp;gt;&amp;lt;/iframe&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_tutorial_koleksi_di_java&amp;quot;&amp;gt;Tutorial koleksi di Java&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://docs.oracle.com/javase/tutorial/collections/index.html&amp;quot; target=&amp;quot;_blank&amp;quot; rel=&amp;quot;noopener&amp;quot;&amp;gt;Koleksi&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Jantung antarmuka koleksi&amp;lt;br&amp;gt;
&amp;lt;span class=&amp;quot;image&amp;quot;&amp;gt;&amp;lt;img src=&amp;quot;../../img/0036_training_kotlin_post/colls-coreInterfaces.gif&amp;quot; alt=&amp;quot;Koleksi&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;br&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_dasar_fungsional&amp;quot;&amp;gt;dasar fungsional&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;sect3&amp;quot;&amp;gt;
&amp;lt;h4 id=&amp;quot;_pendekatan_deklaratif_x_imperatif&amp;quot;&amp;gt;pendekatan deklaratif X imperatif&amp;lt;/h4&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;package functional

import kotlin.test.Test
import kotlin.test.assertEquals

class DeclarativeTests {
    val input = listOf(
        &amp;quot;123&amp;quot;, &amp;quot;abc&amp;quot;, &amp;quot;1ds&amp;quot;, &amp;quot;987&amp;quot;, &amp;quot;abdf&amp;quot;, &amp;quot;1d3&amp;quot;, &amp;quot;de1&amp;quot;, &amp;quot;88&amp;quot;, &amp;quot;101&amp;quot;
    )

    fun imperativeSum(list: List&amp;amp;lt;String&amp;amp;gt;): Int {
        var sum = 0
        for (item in list) {
            try {
                sum += item.toInt()
            } catch (_: NumberFormatException) {
            }
        }
        return sum
    }

    @Test
    fun `test imperative approach`() {
        imperativeSum(input).run {
            println(&amp;quot;Sum $this&amp;quot;)
            assertEquals(1299, this)
        }
    }

    fun isValidNumber(s: String) = try {
        s.toInt()
        true
    } catch (_: NumberFormatException) {
        false
    }

    fun declarativeSum(list: List&amp;amp;lt;String&amp;amp;gt;) = list
        .filter(::isValidNumber)
        .map(String::toInt)
        .sum()

    @Test
    fun `test declarative approach`() {
        assertEquals(1299, declarativeSum(input).apply {
            println(&amp;quot;Sum $this&amp;quot;)
        })
    }
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect3&amp;quot;&amp;gt;
&amp;lt;h4 id=&amp;quot;_latihan_1_1&amp;quot;&amp;gt;Latihan 1.1&amp;lt;/h4&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Implementasikan fungsi sumInRange, yang menjumlahkan nilai-nilai di dalam&amp;lt;br&amp;gt;
sebuah List&amp;amp;lt;String&amp;amp;gt; dalam rentang yang diberikan. tanda tangan adalah :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;fun sumInRange(input: List&amp;amp;lt;String&amp;amp;gt;, range: IntRange): Int&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;@Test
fun `Exercise 1_1`() {
    assertEquals(4, sumInRange(
        listOf(&amp;quot;1&amp;quot;, &amp;quot;10&amp;quot;, &amp;quot;a&amp;quot;, &amp;quot;7&amp;quot;, &amp;quot;ad2&amp;quot;, &amp;quot;3&amp;quot;),
            1..5
        ).apply { println(&amp;quot;sumInRange 1..5: $this&amp;quot;) }
    )
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Cobalah dan periksa jawaban Anda denganhttps://github.com/cheroliv/cheroliv.com/blob/master/codes/src/test/kotlin/functional/DeclarativeTests.kt[solusi, windows=&amp;quot;_blank].&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect3&amp;quot;&amp;gt;
&amp;lt;h4 id=&amp;quot;_fungsi_orde_tinggi&amp;quot;&amp;gt;fungsi orde tinggi&amp;lt;/h4&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;package functional

import java.io.ByteArrayOutputStream
import java.io.PrintStream
import java.lang.System.*
import java.lang.Thread.sleep
import kotlin.math.sign
import kotlin.test.Test
import kotlin.test.assertEquals

class BasicsHOFTests {
    val ONE_SECOND = 1000L

    @Test
    fun `high order function`() {
        //capture de la sortie standard
        val standardOut: PrintStream? = out
        val outputStreamCaptor = ByteArrayOutputStream()
        setOut(PrintStream(outputStreamCaptor))

        3.times { println(&amp;quot;Hello&amp;quot;) }
        assertEquals(
            buildString {
                repeat(3) { append(&amp;quot;Hello\n&amp;quot;) }
                deleteAt(length - 1)
            }, outputStreamCaptor
                .toString()
                .trim()
        )

        //libération de la sortie standard
        setOut(standardOut)
    }

    fun Int.times1(fn: () -&amp;amp;gt; Unit) {
        for (i in 1..this) {
            fn()
        }
    }

    fun Int.times2(fn: () -&amp;amp;gt; Unit) {
        for (i in 1..this) fn()
    }

    fun Int.times3(fn: () -&amp;amp;gt; Unit) =
        (1..this).forEach { fn() }


    fun Int.times4(fn: () -&amp;amp;gt; Unit) =
        repeat((1..this).count()) { fn() }

    fun Int.times(fn: () -&amp;amp;gt; Unit) =
        (1..this).forEach { _ -&amp;amp;gt; fn() }
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect3&amp;quot;&amp;gt;
&amp;lt;h4 id=&amp;quot;_latihan_1_2&amp;quot;&amp;gt;Latihan 1.2&amp;lt;/h4&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Menerapkan chrono, yang menerima fungsi dengan tipe`() &amp;amp;#8594;`&amp;lt;br&amp;gt;
Unit pada masukan dan mengembalikan waktu yang dihabiskan untuk mengeksekusinya. Tanda tandanya:&amp;lt;br&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;fun chrono(fn : () -&amp;amp;gt; Unité) : Long&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;@Test
fun `Exercise 1_2`() {
    val waitOneSec = { sleep(ONE_SECOND) }
    chrono(waitOneSec).apply {
        println(&amp;quot;chrono: $this&amp;quot;)
        assertEquals(1, sign)
    }
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Coba itu dan periksa jawaban Anda denganhttps://github.com/cheroliv/cheroliv.com/blob/master/codes/src/test/kotlin/functional/BasicsHOFTests.kt[solusi, windows=&amp;quot;_blank].&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect3&amp;quot;&amp;gt;
&amp;lt;h4 id=&amp;quot;_komposisi&amp;quot;&amp;gt;komposisi&amp;lt;/h4&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;package functional

import kotlin.test.Test
import kotlin.test.assertEquals


fun double(x: Int): Int = 2 * x
fun square(x: Int): Int = x * x
fun squareAndDouble1(x: Int) = double(square(x))

infix fun &amp;amp;lt;A, B, C&amp;amp;gt; ((A) -&amp;amp;gt; B).compose(g: (B) -&amp;amp;gt; C)
        : (A) -&amp;amp;gt; C = { a -&amp;amp;gt; g(this(a)) }

class CompositionTests {
    @Test
    fun composition_impure() {
        assertEquals(200, double(square(10)))
        assertEquals(200, squareAndDouble1(10))
    }

    @Test
    fun composition_pure() {
        val squareAndDouble = ::square compose ::double
        assertEquals(200, squareAndDouble(10))
    }

}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect3&amp;quot;&amp;gt;
&amp;lt;h4 id=&amp;quot;_fungsi_murni_dan_kemampuan_diuji&amp;quot;&amp;gt;Fungsi murni dan kemampuan diuji&amp;lt;/h4&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;package functional

import java.io.ByteArrayOutputStream
import java.io.PrintStream
import java.lang.System.out
import java.lang.System.setOut
import kotlin.test.Test
import kotlin.test.assertEquals


var count = 0

//impure car une variable global subit un effet de bord
fun impure(value: Int): Int {
    count++
    return value + count
}

//impure car utilisation de la sortie standard qui fait muter le system
fun addOneAndLog(x: Int): Int {
    val result = x + 1
    println(&amp;quot;New Value is $result&amp;quot;)
    return result
}

//pure
fun addOne(x: Int) = (x + 1).run {
    Pair(this, &amp;quot;New Value is $this&amp;quot;)
}

class PureTests {
    @Test
    fun `impure fonction`() {
        assertEquals(3, impure(2))

        val standardOut = out
        val outputStreamCaptor = ByteArrayOutputStream()
        setOut(PrintStream(outputStreamCaptor))

        addOneAndLog(3)

        assertEquals(
            &amp;quot;New Value is 4&amp;quot;,
            outputStreamCaptor
                .toString()
                .trim()
        )
        setOut(standardOut)
    }

    @Test
    fun `pure fonction`() {
        addOne(3).run {
            assertEquals(4, first)
            assertEquals(&amp;quot;New Value is 4&amp;quot;, second)
        }
    }
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect3&amp;quot;&amp;gt;
&amp;lt;h4 id=&amp;quot;_penanganan_pengecualian&amp;quot;&amp;gt;Penanganan pengecualian&amp;lt;/h4&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;package functional

import org.junit.jupiter.api.assertThrows
import kotlin.Result.Companion.failure
import kotlin.Result.Companion.success
import kotlin.test.Test
import kotlin.test.assertEquals

//NumberFormatException est un effet de bord qui rend la fonction impure
fun strToInt(str: String) = str.toInt()

//pure
fun strToIntOrNull(str: String) = try {
    str.toInt()
} catch (nfe: NumberFormatException) {
    null
}

//pure avec gestion de l&amp;#39;exception plus élégante
fun strToIntResult(str: String): Result&amp;amp;lt;Int&amp;amp;gt; =
    try {
        success(str.toInt())
    } catch (nfe: NumberFormatException) {
        failure(nfe)
    }

class ExceptionHandlingTests {
    @Test
    fun impure() {
        assertThrows&amp;amp;lt;NumberFormatException&amp;amp;gt; { strToInt(&amp;quot;foo&amp;quot;) }
        assertEquals(1, strToInt(&amp;quot;1&amp;quot;))
    }

    @Test
    fun pure() {
        assertEquals(null, strToIntOrNull(&amp;quot;foo&amp;quot;))
        assertEquals(1, strToIntOrNull(&amp;quot;1&amp;quot;))
    }

    @Test
    fun `pure avec result`() {
        assertEquals(1, strToIntResult(&amp;quot;1&amp;quot;).getOrNull())
        assertEquals(
            &amp;quot;For input string: \&amp;quot;foo\&amp;quot;&amp;quot;,
            strToIntResult(&amp;quot;foo&amp;quot;)
                .exceptionOrNull()
                ?.message
        )
    }
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_poin_poin_kunci&amp;quot;&amp;gt;poin-poin kunci&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Sementara pemrograman berorientasi objek berarti memprogram dengan objek,&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;pemrograman fungsional berarti memprogram dengan fungsi. Anda memecah masalah menjadi beberapa sub-masalah, yang Anda modelkan dengan fungsi-fungsi.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Fungsi orde tinggi menerima fungsi lain sebagai input atau mengembalikan fungsi lain&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;berfungsi sebagai nilai pengembalian. Teori kategori adalah teori komposisi, dan Anda menggunakannya untuk memahami bagaimana menyusun fungsi-fungsimu dalam sebuah program kerja Nilai output dari fungsi murni hanya tergantung pada parameter inputnya, dan ia tidak memiliki efek samping&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Sebuah efek samping adalah sesuatu yang dilakukan oleh sebuah fungsi ke dunia luar. Ini&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;mungkin menjadi log di output standar atau modifikasi nilai variabel global Pemrograman fungsional berfungsi untuk fungsi murni, tetapi juga menyediakan alat untuk mengubah fungsi tidak murni menjadi fungsi murni. Anda dapat membuat fungsi tidak murni menjadi murni dengan memindahkan efek untuk membuatnya bagian dari nilai pengembalian&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Pemrograman fungsional adalah tentang komposisi.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Penanganan kesalahan adalah contoh khas efek samping, dan Kotlin memberi Anda alat. untuk mengelola mereka secara fungsional.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_mendalam_java8_ekspresi_lambda_dan_antarmuka_fungsional&amp;quot;&amp;gt;Mendalam java8: ekspresi lambda dan antarmuka fungsional&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;videoblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;iframe src=&amp;quot;https://www.youtube.com/embed/20waNRw6wMA?rel=0&amp;amp;amp;list=PLzzeuFUy_Cng0wZhqbnkvWAW0d2fdVfyQ&amp;quot; frameborder=&amp;quot;0&amp;quot; allowfullscreen&amp;gt;&amp;lt;/iframe&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_teori_kategori&amp;quot;&amp;gt;Teori Kategori&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Teori matematika kategori:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;videoblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;iframe src=&amp;quot;https://www.youtube.com/embed/LVHoROSF3KA?rel=0&amp;quot; frameborder=&amp;quot;0&amp;quot; allowfullscreen&amp;gt;&amp;lt;/iframe&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_dasar_dasar_fungsi&amp;quot;&amp;gt;Dasar-dasar fungsi&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;sect3&amp;quot;&amp;gt;
&amp;lt;h4 id=&amp;quot;_latihan_2_1&amp;quot;&amp;gt;Latihan 2.1&amp;lt;/h4&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Bisakah Anda menulis contoh fungsi yang memetakan nilai yang berbeda&amp;lt;br&amp;gt;
yang memiliki domain dengan nilai-nilai yang tidak berbeda dalam rentang, seperti f(b) dan f(c) dalam gambar di bawah ini?&amp;lt;br&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Cobalah, lalu periksa proyek tantangan untuk solusi yang menunjukkan bagaimana kamu melakukannya.&amp;lt;br&amp;gt;
Anda akan menemukan saran dan penjelasan dengan mengikuti tautan ke &amp;lt;a href=&amp;quot;https://github.com/cheroliv/cheroliv.com/blob/master/codes/src/test/kotlin/functional/BasicsHOFTests.kt&amp;quot;&amp;gt;solusi&amp;lt;/a&amp;gt;.&amp;lt;br&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect3&amp;quot;&amp;gt;
&amp;lt;h4 id=&amp;quot;_latihan_2_2&amp;quot;&amp;gt;Latihan 2.2&amp;lt;/h4&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Bisakah kamu menulis fungsi invers dari dua kali ?&amp;lt;br&amp;gt;
Apa domain dan jangkauan untuk fungsi invers?&amp;lt;br&amp;gt;
Lihat proyek tantangan dan Lampiran B untuk solusi.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;fun chrono(fn : () -&amp;amp;gt; Unité) : Long&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;@Test
fun `Exercise 1_2`() {
    val waitOneSec = { sleep(ONE_SECOND) }
    chrono(waitOneSec).apply {
        println(&amp;quot;chrono: $this&amp;quot;)
        assertEquals(1, sign)
    }
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Coba dan periksa jawaban Anda denganhttps://github.com/cheroliv/cheroliv.com/blob/master/codes/src/test/kotlin/functional/BasicsHOFTests.kt[solution, windows=&amp;quot;_blank].&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>memo design system</title>
            <link >https://cheroliv.com/blog/2022/0031_memo_design_system_post.html</link>
            <pubDate>Sun, 9 Jan 2022 00:00:00 +0000</pubDate>
            <guid isPermaLink="false">blog/2022/0031_memo_design_system_post.html</guid>
            <description>&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_alat&amp;quot;&amp;gt;Alat&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;dlist&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;Ilustrasi&amp;lt;/strong&amp;gt;&amp;lt;/div&amp;gt;
&amp;lt;dl&amp;gt;
&amp;lt;dt class=&amp;quot;hdlist1&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;https://undraw.co/&amp;quot;&amp;gt;undraw.co&amp;lt;/a&amp;gt;&amp;lt;/dt&amp;gt;
&amp;lt;dd&amp;gt;
&amp;lt;p&amp;gt;Ilustrasi open source untuk semua ide yang dapat Anda bayangkan dan ciptakan.&amp;lt;/p&amp;gt;
&amp;lt;/dd&amp;gt;
&amp;lt;dt class=&amp;quot;hdlist1&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;https://absurd.design/&amp;quot;&amp;gt;absurd.design&amp;lt;/a&amp;gt;&amp;lt;/dt&amp;gt;
&amp;lt;dd&amp;gt;
&amp;lt;p&amp;gt;Ilustrasi absurd yang bermakna.&amp;lt;/p&amp;gt;
&amp;lt;/dd&amp;gt;
&amp;lt;dt class=&amp;quot;hdlist1&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;https://drawkit.com/&amp;quot;&amp;gt;drawkit.com&amp;lt;/a&amp;gt;&amp;lt;/dt&amp;gt;
&amp;lt;dd&amp;gt;
&amp;lt;p&amp;gt;Ilustrasi gratis yang indah. Pembaruan mingguan.&amp;lt;/p&amp;gt;
&amp;lt;/dd&amp;gt;
&amp;lt;dt class=&amp;quot;hdlist1&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;https://icons8.com/&amp;quot;&amp;gt;icons8.com&amp;lt;/a&amp;gt;&amp;lt;/dt&amp;gt;
&amp;lt;dd&amp;gt;
&amp;lt;p&amp;gt;Ikon, ilustrasi, foto, musik, dan alat desain.&amp;lt;/p&amp;gt;
&amp;lt;/dd&amp;gt;
&amp;lt;dt class=&amp;quot;hdlist1&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;https://blush.design/&amp;quot;&amp;gt;blush.design&amp;lt;/a&amp;gt;&amp;lt;/dt&amp;gt;
&amp;lt;dd&amp;gt;
&amp;lt;p&amp;gt;Ikon, ilustrasi, foto, musik, dan alat desain.&amp;lt;/p&amp;gt;
&amp;lt;/dd&amp;gt;
&amp;lt;dt class=&amp;quot;hdlist1&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;https://www.humaaans.com/&amp;quot;&amp;gt;humaaans.com&amp;lt;/a&amp;gt;&amp;lt;/dt&amp;gt;
&amp;lt;dd&amp;gt;
&amp;lt;p&amp;gt;Campur dan pilih ilustrasi orang dengan pustaka desain&amp;lt;/p&amp;gt;
&amp;lt;/dd&amp;gt;
&amp;lt;/dl&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;dlist&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;Warna&amp;lt;/strong&amp;gt;&amp;lt;/div&amp;gt;
&amp;lt;dl&amp;gt;
&amp;lt;dt class=&amp;quot;hdlist1&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;https://coolors.co/&amp;quot;&amp;gt;coolors.co&amp;lt;/a&amp;gt;&amp;lt;/dt&amp;gt;
&amp;lt;dd&amp;gt;
&amp;lt;p&amp;gt;Pembuat skema warna ultra-cepat!&amp;lt;/p&amp;gt;
&amp;lt;/dd&amp;gt;
&amp;lt;/dl&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;dlist&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;Tren&amp;lt;/strong&amp;gt;&amp;lt;/div&amp;gt;
&amp;lt;dl&amp;gt;
&amp;lt;dt class=&amp;quot;hdlist1&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;https://fr.pinterest.com&amp;quot;&amp;gt;pinterest&amp;lt;/a&amp;gt;&amp;lt;/dt&amp;gt;
&amp;lt;dd&amp;gt;
&amp;lt;p&amp;gt;Penemuan visual ide: gambar, video, koleksi tematik.&amp;lt;/p&amp;gt;
&amp;lt;/dd&amp;gt;
&amp;lt;dt class=&amp;quot;hdlist1&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;https://dribbble.com&amp;quot;&amp;gt;menggiring&amp;lt;/a&amp;gt;&amp;lt;/dt&amp;gt;
&amp;lt;dd&amp;gt;
&amp;lt;p&amp;gt;Platform untuk desainer berbagi kreasi mereka dan berinteraksi.&amp;lt;/p&amp;gt;
&amp;lt;/dd&amp;gt;
&amp;lt;/dl&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;dlist&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;UX/UI dan Design Systems&amp;lt;/strong&amp;gt;&amp;lt;/div&amp;gt;
&amp;lt;dl&amp;gt;
&amp;lt;dt class=&amp;quot;hdlist1&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;https://material.io/&amp;quot;&amp;gt;Desain Material Google&amp;lt;/a&amp;gt;&amp;lt;/dt&amp;gt;
&amp;lt;dd&amp;gt;
&amp;lt;p&amp;gt;Sistem desain Google untuk antarmuka pengguna yang terintegrasi dan adaptif.&amp;lt;/p&amp;gt;
&amp;lt;/dd&amp;gt;
&amp;lt;dt class=&amp;quot;hdlist1&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;https://developer.apple.com/design/human-interface-guidelines/&amp;quot;&amp;gt;Panduan Antarmuka Manusia Apple&amp;lt;/a&amp;gt;&amp;lt;/dt&amp;gt;
&amp;lt;dd&amp;gt;
&amp;lt;p&amp;gt;Rekomendasi Apple untuk merancang aplikasi iOS dan macOS yang intuitif.&amp;lt;/p&amp;gt;
&amp;lt;/dd&amp;gt;
&amp;lt;dt class=&amp;quot;hdlist1&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;https://www.uxpin.com/&amp;quot;&amp;gt;UXPin&amp;lt;/a&amp;gt;&amp;lt;/dt&amp;gt;
&amp;lt;dd&amp;gt;
&amp;lt;p&amp;gt;Platform desain dan kolaborasi untuk membuat prototipe dan sistem desain.&amp;lt;/p&amp;gt;
&amp;lt;/dd&amp;gt;
&amp;lt;dt class=&amp;quot;hdlist1&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;https://www.frontify.com/&amp;quot;&amp;gt;Frontify&amp;lt;/a&amp;gt;&amp;lt;/dt&amp;gt;
&amp;lt;dd&amp;gt;
&amp;lt;p&amp;gt;Platform manajemen merek dan sistem desain untuk menjamin konsistensi.&amp;lt;/p&amp;gt;
&amp;lt;/dd&amp;gt;
&amp;lt;dt class=&amp;quot;hdlist1&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;https://designsystemsrepo.com/design-systems/&amp;quot;&amp;gt;Repositori Sistem Desain&amp;lt;/a&amp;gt;&amp;lt;/dt&amp;gt;
&amp;lt;dd&amp;gt;
&amp;lt;p&amp;gt;Koleksi sumber dan contoh sistem desain untuk inspirasi.&amp;lt;/p&amp;gt;
&amp;lt;/dd&amp;gt;
&amp;lt;/dl&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;dlist&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;Pengembangan Frontend&amp;lt;/strong&amp;gt;&amp;lt;/div&amp;gt;
&amp;lt;dl&amp;gt;
&amp;lt;dt class=&amp;quot;hdlist1&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;https://htmlcheatsheet.com&amp;quot; target=&amp;quot;&amp;quot;_blank&amp;quot;&amp;gt;htmlcheatsheet.com&amp;lt;/a&amp;gt;&amp;lt;/dt&amp;gt;
&amp;lt;dd&amp;gt;
&amp;lt;p&amp;gt;Cheat Sheet HTML&amp;lt;/p&amp;gt;
&amp;lt;/dd&amp;gt;
&amp;lt;dt class=&amp;quot;hdlist1&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;https://fontawesome.com&amp;quot; target=&amp;quot;&amp;quot;_blank&amp;quot;&amp;gt;fontawesome.com&amp;lt;/a&amp;gt;&amp;lt;/dt&amp;gt;
&amp;lt;dd&amp;gt;
&amp;lt;p&amp;gt;koleksi ikon&amp;lt;/p&amp;gt;
&amp;lt;/dd&amp;gt;
&amp;lt;dt class=&amp;quot;hdlist1&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;https://www.toptal.com/designers/htmlarrows/symbols/&amp;quot;&amp;gt;toptal.com/designers/htmlarrows/symbols&amp;lt;/a&amp;gt;&amp;lt;/dt&amp;gt;
&amp;lt;dd&amp;gt;
&amp;lt;p&amp;gt;Ikon bawaan dalam HTML&amp;lt;/p&amp;gt;
&amp;lt;/dd&amp;gt;
&amp;lt;dt class=&amp;quot;hdlist1&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;https://css-tricks.com/&amp;quot;&amp;gt;CSS-Tricks&amp;lt;/a&amp;gt;&amp;lt;/dt&amp;gt;
&amp;lt;dd&amp;gt;
&amp;lt;p&amp;gt;Wajib untuk CSS, dengan artikel, tutorial, dan tips frontend modern.&amp;lt;/p&amp;gt;
&amp;lt;/dd&amp;gt;
&amp;lt;dt class=&amp;quot;hdlist1&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;https://www.frontendmentor.io/&amp;quot;&amp;gt;Mentor Frontend&amp;lt;/a&amp;gt;&amp;lt;/dt&amp;gt;
&amp;lt;dd&amp;gt;
&amp;lt;p&amp;gt;Tantangan kode frontend yang didasarkan pada proyek nyata untuk meningkatkan keterampilan Anda.&amp;lt;/p&amp;gt;
&amp;lt;/dd&amp;gt;
&amp;lt;dt class=&amp;quot;hdlist1&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;https://dev.to/&amp;quot;&amp;gt;Dev.to&amp;lt;/a&amp;gt;&amp;lt;/dt&amp;gt;
&amp;lt;dd&amp;gt;
&amp;lt;p&amp;gt;Komunitas pengembang untuk berbagi artikel, tutorial, dan proyek.&amp;lt;/p&amp;gt;
&amp;lt;/dd&amp;gt;
&amp;lt;dt class=&amp;quot;hdlist1&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;https://codepen.io/&amp;quot;&amp;gt;CodePen&amp;lt;/a&amp;gt;&amp;lt;/dt&amp;gt;
&amp;lt;dd&amp;gt;
&amp;lt;p&amp;gt;Editor kode daring dan komunitas untuk berbagi cuplikan kode dan demo.&amp;lt;/p&amp;gt;
&amp;lt;/dd&amp;gt;
&amp;lt;/dl&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;dlist&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;Pengembangan Frontend yang didukung AI&amp;lt;/strong&amp;gt;&amp;lt;/div&amp;gt;
&amp;lt;dl&amp;gt;
&amp;lt;dt class=&amp;quot;hdlist1&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;https://v0.dev/&amp;quot;&amp;gt;v0.dev&amp;lt;/a&amp;gt;&amp;lt;/dt&amp;gt;
&amp;lt;dd&amp;gt;
&amp;lt;p&amp;gt;v0 mengonversi deskripsi dalam bahasa natural menjadi kode dan antarmuka pengguna.&amp;lt;/p&amp;gt;
&amp;lt;/dd&amp;gt;
&amp;lt;dt class=&amp;quot;hdlist1&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;https://uizard.io/&amp;quot;&amp;gt;Uizard&amp;lt;/a&amp;gt;&amp;lt;/dt&amp;gt;
&amp;lt;dd&amp;gt;
&amp;lt;p&amp;gt;Uizard berfokus pada transformasi prototipe (gambar, sketsa) menjadi kode HTML, CSS, dan React.&amp;lt;/p&amp;gt;
&amp;lt;/dd&amp;gt;
&amp;lt;dt class=&amp;quot;hdlist1&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;https://www.usegalileo.ai/explore&amp;quot;&amp;gt;usegalileo.ai&amp;lt;/a&amp;gt;&amp;lt;/dt&amp;gt;
&amp;lt;dd&amp;gt;
&amp;lt;p&amp;gt;Galileo AI memungkinkan Anda menggambarkan antarmuka yang Anda inginkan dengan bahasa alami, dan AI menghasilkan desain Figma yang dapat diedit.&amp;lt;/p&amp;gt;
&amp;lt;/dd&amp;gt;
&amp;lt;dt class=&amp;quot;hdlist1&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;https://play.teleporthq.io/&amp;quot;&amp;gt;TeleportHQ&amp;lt;/a&amp;gt;&amp;lt;/dt&amp;gt;
&amp;lt;dd&amp;gt;
&amp;lt;p&amp;gt;TeleportHQ adalah platform pengembangan front-end low-code yang menggunakan AI untuk membantu Anda membangun dan menyebarkan situs web secara visual.&amp;lt;/p&amp;gt;
&amp;lt;/dd&amp;gt;
&amp;lt;/dl&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>Kubernetes, Microk8s, Docker: langkah awal</title>
            <link >https://cheroliv.com/blog/2020/0029_kubernetes_microk8s_docker_premiers_pas_post.html</link>
            <pubDate>Tue, 3 Nov 2020 00:00:00 +0000</pubDate>
            <guid isPermaLink="false">blog/2020/0029_kubernetes_microk8s_docker_premiers_pas_post.html</guid>
            <description>&amp;lt;div id=&amp;quot;preamble&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://microk8s.io/docs/&amp;quot;&amp;gt;dokumen resmi microk8s&amp;lt;/a&amp;gt;+&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect3&amp;quot;&amp;gt;
&amp;lt;h4 id=&amp;quot;_pemasangan_paket_microk8s&amp;quot;&amp;gt;Pemasangan paket microk8s:&amp;lt;/h4&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>Gradle: Exécuter un processus externe depuis une tâche gradle</title>
            <link >https://cheroliv.com/blog/2020/0028_gradle_executer_un_processus_externe_depuis_une_tache_gradle_post.html</link>
            <pubDate>Mon, 28 Sep 2020 00:00:00 +0000</pubDate>
            <guid isPermaLink="false">blog/2020/0028_gradle_executer_un_processus_externe_depuis_une_tache_gradle_post.html</guid>
            <description>&amp;lt;div class=&amp;quot;sect3&amp;quot;&amp;gt;
&amp;lt;h4 id=&amp;quot;_gradle_kotlin_dsl&amp;quot;&amp;gt;Gradle-Kotlin-DSL:&amp;lt;/h4&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-kotlin hljs&amp;quot; data-lang=&amp;quot;kotlin&amp;quot;&amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>Kotlin: NotImplementedException, NotImplementedError</title>
            <link >https://cheroliv.com/blog/2020/0027_kotlin_not_implemented_exception_not_implemented_error_post.html</link>
            <pubDate>Sun, 27 Sep 2020 00:00:00 +0000</pubDate>
            <guid isPermaLink="false">blog/2020/0027_kotlin_not_implemented_exception_not_implemented_error_post.html</guid>
            <description>&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;NotImplementedException NotImplementedError, utilité pour les tests, ça permet de savoir, où on en est, et la cause du fail.&amp;lt;br&amp;gt;
Pas encore implémenté, permet de lever la bonne exception.&amp;lt;br&amp;gt;
Il y a :&amp;lt;br&amp;gt;
&amp;lt;a href=&amp;quot;http://commons.apache.org/proper/commons-lang/javadocs/api-release/org/apache/commons/lang3/NotImplementedException.html&amp;quot;&amp;gt;org.apache.commons.lang3.NotImplementedException&amp;lt;/a&amp;gt;&amp;lt;br&amp;gt;
&amp;lt;a href=&amp;quot;https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-not-implemented-error/&amp;quot;&amp;gt;kotlin.NotImplementedError&amp;lt;/a&amp;gt;&amp;lt;br&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>Untar</title>
            <link >https://cheroliv.com/blog/2020/0026_untar_post.html</link>
            <pubDate>Sat, 26 Sep 2020 00:00:00 +0000</pubDate>
            <guid isPermaLink="false">blog/2020/0026_untar_post.html</guid>
            <description>&amp;lt;div class=&amp;quot;sect3&amp;quot;&amp;gt;
&amp;lt;h4 id=&amp;quot;_liens_interserver_net&amp;quot;&amp;gt;Liens: &amp;lt;a href=&amp;quot;https://www.interserver.net/tips/kb/extract-tar-gz-files-using-linux-command-line/&amp;quot;&amp;gt;interserver.net&amp;lt;/a&amp;gt;&amp;lt;/h4&amp;gt;

&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>Gradle: Ulangi tes meskipun tes berada dalam keadaan UP-TO-DATE</title>
            <link >https://cheroliv.com/blog/2020/0025_gradle_rejouer_les_tests_meme_lorsque_les_tests_sont_UP-TO-DATE_post.html</link>
            <pubDate>Fri, 25 Sep 2020 00:00:00 +0000</pubDate>
            <guid isPermaLink="false">blog/2020/0025_gradle_rejouer_les_tests_meme_lorsque_les_tests_sont_UP-TO-DATE_post.html</guid>
            <description></description>
        </item><item>
            <title>Docker, n’installe pas l’application mais tire la plutôt</title>
            <link >https://cheroliv.com/blog/2020/0024_docker_n_installe_pas_l_application_mais_tire_la_plutot_post.html</link>
            <pubDate>Thu, 24 Sep 2020 00:00:00 +0000</pubDate>
            <guid isPermaLink="false">blog/2020/0024_docker_n_installe_pas_l_application_mais_tire_la_plutot_post.html</guid>
            <description>&amp;lt;div class=&amp;quot;sect3&amp;quot;&amp;gt;
&amp;lt;h4 id=&amp;quot;_source_hackernoon_com&amp;quot;&amp;gt;source: &amp;lt;a href=&amp;quot;https://hackernoon.com/dont-install-postgres-docker-pull-postgres-bee20e200198&amp;quot;&amp;gt;hackernoon.com&amp;lt;/a&amp;gt;&amp;lt;/h4&amp;gt;

&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>Gherkin en francais :</title>
            <link >https://cheroliv.com/blog/2020/0023_gherkin_en_francais_post.html</link>
            <pubDate>Wed, 23 Sep 2020 00:00:00 +0000</pubDate>
            <guid isPermaLink="false">blog/2020/0023_gherkin_en_francais_post.html</guid>
            <description></description>
        </item><item>
            <title>Gradle cli: surcharger un paramètre</title>
            <link >https://cheroliv.com/blog/2020/0022_gradle_cli_surcharger_un_parametre_post.html</link>
            <pubDate>Tue, 22 Sep 2020 00:00:00 +0000</pubDate>
            <guid isPermaLink="false">blog/2020/0022_gradle_cli_surcharger_un_parametre_post.html</guid>
            <description>&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-none hljs&amp;quot;&amp;gt;----
----
$ ./gradlew -Pparam_component=CUSTOM
----
----&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>Catatan git</title>
            <link >https://cheroliv.com/blog/2020/0021_memo_git_post.html</link>
            <pubDate>Mon, 21 Sep 2020 00:00:00 +0000</pubDate>
            <guid isPermaLink="false">blog/2020/0021_memo_git_post.html</guid>
            <description>&amp;lt;div class=&amp;quot;sect3&amp;quot;&amp;gt;
&amp;lt;h4 id=&amp;quot;_sumber&amp;quot;&amp;gt;Sumber:&amp;lt;/h4&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;*https://xenovation.com/blog/source-control-management/git/how-to-change-remote-git-repository[xenovation] *https://intellipaat.com/community/3102/git-show-remote-url-how-can-i-determine-the-url-that-a-local-git-repository-was-originally-cloned-from[intellipaat]&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect3&amp;quot;&amp;gt;
&amp;lt;h4 id=&amp;quot;_mengubah_repo_remote_di_git&amp;quot;&amp;gt;mengubah repo remote di git&amp;lt;/h4&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;menampilkan URL repo remote saat ini:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>Mengunduh dari URL</title>
            <link >https://cheroliv.com/blog/2020/0021_aspirer_depuis_une_URL_post.html</link>
            <pubDate>Sun, 20 Sep 2020 00:00:00 +0000</pubDate>
            <guid isPermaLink="false">blog/2020/0021_aspirer_depuis_une_URL_post.html</guid>
            <description>&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-none hljs&amp;quot;&amp;gt;----
----
$ wget -r -k -np http://site.com/docs/manual/
----

Untuk menentukan folder target, tambahkan argumen -P lalu berikan jalur.
----&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>Créer un disk bootable</title>
            <link >https://cheroliv.com/blog/2020/0020_creer_un_disk_bootable_post.html</link>
            <pubDate>Sat, 19 Sep 2020 00:00:00 +0000</pubDate>
            <guid isPermaLink="false">blog/2020/0020_creer_un_disk_bootable_post.html</guid>
            <description>&amp;lt;div class=&amp;quot;sect3&amp;quot;&amp;gt;
&amp;lt;h4 id=&amp;quot;_liens_etcher&amp;quot;&amp;gt;Liens: &amp;lt;a href=&amp;quot;https://www.balena.io/etcher/&amp;quot;&amp;gt;etcher&amp;lt;/a&amp;gt;&amp;lt;/h4&amp;gt;

&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>Membongkar via CLI di folder target</title>
            <link >https://cheroliv.com/blog/2020/0019_dezipper_en_CLI_dans_un_dossier_cible_post.html</link>
            <pubDate>Fri, 18 Sep 2020 00:00:00 +0000</pubDate>
            <guid isPermaLink="false">blog/2020/0019_dezipper_en_CLI_dans_un_dossier_cible_post.html</guid>
            <description>&amp;lt;div class=&amp;quot;sect3&amp;quot;&amp;gt;
&amp;lt;h4 id=&amp;quot;_tautan_askubuntu&amp;quot;&amp;gt;Tautan: &amp;lt;a href=&amp;quot;https://askubuntu.com/questions/86849/how-to-unzip-a-zip-file-from-the-terminal&amp;quot;&amp;gt;askubuntu&amp;lt;/a&amp;gt;&amp;lt;/h4&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Jika Anda ingin mengekstrak ke folder tujuan tertentu, Anda dapat menggunakan : &amp;lt;code&amp;gt;` $ unzip fichier.zip -d direktori_tujuan &amp;lt;/code&amp;gt;` Jika direktori sumber dan direktori tujuan sama, Anda cukup melakukan: &amp;lt;code&amp;gt;`&amp;lt;/code&amp;gt; $ unzip file.zip (empty)&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>Afficher la somme des tailles de fichier dans la liste des répertoires</title>
            <link >https://cheroliv.com/blog/2020/0018_Afficher_la_somme_des_tailles_de_fichier_dans_la_liste_des_r%C3%A9pertoires_post.html</link>
            <pubDate>Thu, 17 Sep 2020 00:00:00 +0000</pubDate>
            <guid isPermaLink="false">blog/2020/0018_Afficher_la_somme_des_tailles_de_fichier_dans_la_liste_des_r%C3%A9pertoires_post.html</guid>
            <description>&amp;lt;div class=&amp;quot;sect3&amp;quot;&amp;gt;
&amp;lt;h4 id=&amp;quot;_liens_stackexchange&amp;quot;&amp;gt;Liens: &amp;lt;a href=&amp;quot;https://unix.stackexchange.com/questions/72661/show-sum-of-file-sizes-in-directory-listing&amp;quot;&amp;gt;stackexchange&amp;lt;/a&amp;gt;&amp;lt;/h4&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-none hljs&amp;quot;&amp;gt;----
----
$ du -ach --exclude &amp;quot;./.*&amp;quot; /home/troll/
----
----&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>Détruire une branch sur github</title>
            <link >https://cheroliv.com/blog/2020/0017_detruire_une_branch_sur_github_post.html</link>
            <pubDate>Wed, 16 Sep 2020 00:00:00 +0000</pubDate>
            <guid isPermaLink="false">blog/2020/0017_detruire_une_branch_sur_github_post.html</guid>
            <description>&amp;lt;div class=&amp;quot;sect3&amp;quot;&amp;gt;
&amp;lt;h4 id=&amp;quot;_détruire_une_branch_sur_github&amp;quot;&amp;gt;Détruire une branch sur github&amp;lt;/h4&amp;gt;

&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect3&amp;quot;&amp;gt;
&amp;lt;h4 id=&amp;quot;_lien_gist&amp;quot;&amp;gt;Lien: &amp;lt;a href=&amp;quot;https://gist.github.com/cheroliv/921a13ed23ae6d4d66af8d43dd536987&amp;quot;&amp;gt;gist&amp;lt;/a&amp;gt;&amp;lt;/h4&amp;gt;

&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>Asciidoc/Markdown Mémo</title>
            <link >https://cheroliv.com/blog/2020/0016_asciidoc_markdown_memo_post.html</link>
            <pubDate>Tue, 15 Sep 2020 00:00:00 +0000</pubDate>
            <guid isPermaLink="false">blog/2020/0016_asciidoc_markdown_memo_post.html</guid>
            <description>&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;Pourquoi asciidoc ou markdown ? Ici, je m’oriente vers asciidoc, car j’ai déjà quelques tickets écris en asciidoc.&amp;lt;br&amp;gt;&amp;lt;/div&amp;gt;
&amp;lt;p&amp;gt;Asciidoc est un langage de balisage, qui permet de produire du contenu, en apportant une mise en page qui garde le texte lisible pour un humain.&amp;lt;br&amp;gt;
C’est une façon de standardiser les traitements de textes pour des contenus simples sans logiciel supplémentaire.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;ulist&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;Liens :&amp;lt;/div&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://blog.oxiane.com/2018/06/13/asciidoc-documentation-as-code/&amp;quot; target=&amp;quot;_blank&amp;quot; rel=&amp;quot;noopener&amp;quot;&amp;gt;@jmdoudoux sur blog.oxiane.com&amp;lt;/a&amp;gt; [asciidoc]&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://powerman.name/doc/asciidoc&amp;quot; target=&amp;quot;_blank&amp;quot; rel=&amp;quot;noopener&amp;quot;&amp;gt;powerman.name&amp;lt;/a&amp;gt; [asciidoc]&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>Debian: Menginstal file .deb melalui baris perintah</title>
            <link >https://cheroliv.com/blog/2019/0013_how-to-install-a-deb-file_post.html</link>
            <pubDate>Mon, 19 Aug 2019 00:00:00 +0000</pubDate>
            <guid isPermaLink="false">blog/2019/0013_how-to-install-a-deb-file_post.html</guid>
            <description>&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Untuk menginstal paket Debian dari file .deb melalui command line.&amp;lt;br&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;$ sudo dpkg -i /chemin/du/fichier/deb/&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;lihat di sini untukhttps://unix.stackexchange.com/questions/159094/how-to-install-a-deb-file-by-dpkg-i-or-by-apt[referensi]+&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>Gradle: dossier resources visible dans l&#39;IDE</title>
            <link >https://cheroliv.com/blog/2019/0011_gradle_configuration_ide_jbake_content_post.html</link>
            <pubDate>Fri, 16 Aug 2019 00:00:00 +0000</pubDate>
            <guid isPermaLink="false">blog/2019/0011_gradle_configuration_ide_jbake_content_post.html</guid>
            <description>&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Dans un projet applicatif piloté par le gestionnaire de build Gradle&amp;lt;br&amp;gt;
il est plus agréable d’avoir la visibilité sur tous les dossiers&amp;lt;br&amp;gt;
participant au développement de l’application,&amp;lt;br&amp;gt;
comme des dossiers de ressources qui ne sont pas&amp;lt;br&amp;gt;
dans les chemins par convention.&amp;lt;br&amp;gt;
Par exemple je veux que le dossier src/jbake,&amp;lt;br&amp;gt;
soit visible en tant ‘resource folder’,&amp;lt;br&amp;gt;
il est possible de le faire en rentrant dans la configuration du projet,&amp;lt;br&amp;gt;
dans les wizards de l’IDE. Cependant cette option n’est pas sauvegardée au rechargement suivant&amp;lt;br&amp;gt;
du projet Gradle dans l’IDE. Pour apporter cette configuration de façon permanente&amp;lt;br&amp;gt;
la solution est de surcharger la configuration des chemins de sources&amp;lt;br&amp;gt;
dans le fichier de configuration build.gradle tel que aux lignes 18, 19, 20 et 21 :&amp;lt;br&amp;gt;
build.gradle&amp;lt;br&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-groovy hljs&amp;quot; data-lang=&amp;quot;groovy&amp;quot;&amp;gt;plugins {
    id &amp;quot;java&amp;quot;
    id &amp;quot;groovy&amp;quot;
    id &amp;quot;org.jbake.site&amp;quot; version &amp;quot;5.0.0&amp;quot;
    id &amp;quot;org.ajoberstar.git-publish&amp;quot; version &amp;quot;2.1.1&amp;quot;
}
repositories {
    mavenCentral()
    jcenter()
}
group project_group
version project_version
sourceSets {
    main {
        java { srcDirs = [] }
        groovy { srcDirs =[&amp;quot;src/main/java&amp;quot;, &amp;quot;src/main/groovy&amp;quot;] }
        resources {
            srcDirs =[
                    &amp;quot;src/main/resources&amp;quot;,
                    &amp;quot;src/jbake&amp;quot;//in order to get site src content in the IDE
            ]
        }
    }
    test {
        java { srcDirs = [] }
        groovy { srcDirs =[&amp;quot;src/test/java&amp;quot;, &amp;quot;src/test/groovy&amp;quot;] }
    }
}
test {
    useJUnitPlatform()
}
configurations {
    ivy
}
dependencies {
    ivy &amp;quot;org.apache.ivy:ivy:$ivy_version&amp;quot;
    implementation &amp;quot;org.codehaus.groovy:groovy-all:$groovy_version&amp;quot;
    implementation &amp;quot;org.asciidoctor:asciidoctor-java-integration:$asciidoctor_java_integration_version&amp;quot;
    implementation &amp;quot;org.freemarker:freemarker:$freemarker_version&amp;quot;
    testImplementation &amp;quot;org.junit.jupiter:junit-jupiter-api:$junit5_version&amp;quot;
    testRuntimeOnly &amp;quot;org.junit.jupiter:junit-jupiter-engine:$junit5_version&amp;quot;
}
gitPublish {
    repoUri = github_pages_blog_repository
    branch = git_branch
    contents {
        from(file(jbake_content_from)) {
            into jbake_content_to
        }
    }
}
tasks.withType(GroovyCompile) {
    groovyClasspath += configurations.ivy
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>skrip Groovy, grab dan konfigurasi Ivy</title>
            <link >https://cheroliv.com/blog/2019/0010_gradle_groovy_script_grab_ivy_configuration_post.html</link>
            <pubDate>Wed, 14 Aug 2019 00:00:00 +0000</pubDate>
            <guid isPermaLink="false">blog/2019/0010_gradle_groovy_script_grab_ivy_configuration_post.html</guid>
            <description>&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Subjek tiket adalah konfigurasi skrip build Gradle.&amp;lt;br&amp;gt;
Saya ingin dapat menggunakan skrip Groovy tanpa referensi&amp;lt;br&amp;gt;
ke proyek Gradle tempat saya melampirkan.&amp;lt;br&amp;gt;
Saya ingin dapat menjalankan skrip Groovy ini di luar Gradle&amp;lt;br&amp;gt;
tanpa anotasi ivy untuk pengambilan dependensi eksternal&amp;lt;br&amp;gt;
tidak menimbulkan masalah performa atau kompilasi&amp;lt;br&amp;gt;
dari skrip build Gradle.&amp;lt;br&amp;gt;
Untuk mengilustrasikan konfigurasi saya mengandalkan dua skrip:&amp;lt;br&amp;gt;
skrip build Gradle dan skrip Groovy.&amp;lt;br&amp;gt;
Skrip Groovy berisi di awal pada baris 2,&amp;lt;br&amp;gt;
sebuah instruksi untuk menarik ketergantungan eksternal&amp;lt;br&amp;gt;
dengan menggunakan pengelola dependensi ivy.&amp;lt;br&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-Groovy hljs&amp;quot; data-lang=&amp;quot;Groovy&amp;quot;&amp;gt;#!/usr/bin/env groovy
@Grab(&amp;quot;commons-io:commons-io:2.6&amp;quot;)

import org.apache.commons.io.FileUtils

static String getSeparator() {
    System.getProperty(&amp;quot;file.separator&amp;quot;)
}

String from = &amp;quot;${System.getProperty(&amp;quot;user.home&amp;quot;)}${separator}.config${separator}transmission${separator}torrents&amp;quot;
String to = &amp;quot;${System.getProperty(&amp;quot;user.home&amp;quot;)}${separator}Documents${separator}torrents_completed&amp;quot;

File fromDlDir = new File(&amp;quot;${System.getProperty(&amp;quot;user.home&amp;quot;)}${separator}Téléchargements&amp;quot;)
File fromDir = new File(from)
File toDir = new File(to)

Collection&amp;amp;lt;File&amp;amp;gt; torrentFiles = FileUtils.listFiles(
        fromDir,
        [&amp;quot;torrent&amp;quot;] as String[],
        false)
torrentFiles.addAll(FileUtils.listFiles(
        fromDlDir,
        [&amp;quot;torrent&amp;quot;, &amp;quot;torrent.added&amp;quot;] as String[],
        false))


torrentFiles.empty ?: torrentFiles.each { it -&amp;amp;gt;
    FileUtils.copyFileToDirectory(it, toDir)
}
println torrentFiles.size()&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Dalam file build.gradle, sekarang mari kita lihat bagaimana mengharmonkan&amp;lt;br&amp;gt;
kedua manajer dependensi, ivy dan Gradle. Perjanjian antara keduanya dilakukan pada baris:&amp;lt;br&amp;gt;
26, 30, 37, 38 et 39&amp;lt;br&amp;gt;
Jadi saya dapat menjalankan skrip Groovy saya secara manual,&amp;lt;br&amp;gt;
dan kompilasi Gradle tidak terganggu oleh @Grab.&amp;lt;br&amp;gt;
Ketergantungan skrip Groovy tidak perlu disediakan dalam Gradle.&amp;lt;br&amp;gt;
Sebaliknya saya tidak dapat menjalankan skrip Groovy dari Gradle.&amp;lt;br&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-Groovy hljs&amp;quot; data-lang=&amp;quot;Groovy&amp;quot;&amp;gt;plugins {
    id &amp;quot;java&amp;quot;
    id &amp;quot;groovy&amp;quot;
}

repositories {
    mavenCentral()
    jcenter()
}

sourceSets {
    main {
        java { srcDirs = [] }
        groovy { srcDirs =[&amp;quot;src/main/java&amp;quot;, &amp;quot;src/main/groovy&amp;quot;] }
        resources {
            srcDirs =[&amp;quot;src/main/resources&amp;quot;]
        }
    }
    test {
        java { srcDirs = [] }
        groovy { srcDirs =[&amp;quot;src/test/java&amp;quot;, &amp;quot;src/test/groovy&amp;quot;] }
    }
}

configurations {
    ivy
}

dependencies {
    ivy &amp;quot;org.apache.ivy:ivy:$ivy_version&amp;quot;
    implementation &amp;quot;org.codehaus.groovy:groovy-all:$groovy_version&amp;quot;
}



tasks.withType(GroovyCompile) {
    groovyClasspath += configurations.ivy
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;referensi tiket padahttps://stackoverflow.com/a/18174033/837404[stackoverflow]&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>Groovy: sedikit aljabar, FPB dan KPK</title>
            <link >https://cheroliv.com/blog/2019/0009_groovy_algebre_pgcd_ppmc_post.html</link>
            <pubDate>Tue, 13 Aug 2019 00:00:00 +0000</pubDate>
            <guid isPermaLink="false">blog/2019/0009_groovy_algebre_pgcd_ppmc_post.html</guid>
            <description>&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_ppmc&amp;quot;&amp;gt;PPMC&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://fr.wikipedia.org/wiki/Plus_petit_commun_multiple&amp;quot;&amp;gt;wiki PPMC atau Kelipatan Persekutuan Terkecil&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;AlgebraUtils.groovy&amp;lt;br&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-groovy hljs&amp;quot; data-lang=&amp;quot;groovy&amp;quot;&amp;gt;package com.cheroliv.misc

import groovy.transform.CompileStatic

@CompileStatic
class AlgebraUtils {

    /**
     * Great common divisor
     * Plus grand commun diviseur(pgcd)
     * Great Common Divisor
     * @param a
     * @param b
     * @return
     */
    static Integer gcd(Integer a, Integer b) {
        !b ? a : gcd(b, a % b)
    }


    /**
     * Least common multiple
     * Plus petit commun multiplicateur(ppmc)
     * @param a
     * @param b
     * @return
     */
    static BigInteger lcm(Integer a, Integer b) {
        (a * b / gcd(a, b)) as BigInteger
    }

    static void main(String... args) {
        Integer a = 96
        Integer b = 28
        println &amp;quot;gcd($a, $b) = ${gcd(a, b)}&amp;quot;
        a = 790
        b = 990
        println &amp;quot;lcm($a, $b) = ${lcm(a, b)}&amp;quot;
    }
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;hasil:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;gcd(96, 28) = 4 lcm(790, 990) = 78210&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>Bash : Mencari file berdasarkan ekstensi output ke file dengan find</title>
            <link >https://cheroliv.com/blog/2019/0008_bash_find_trouver_fichiers_par_extension_post.html</link>
            <pubDate>Mon, 12 Aug 2019 00:00:00 +0000</pubDate>
            <guid isPermaLink="false">blog/2019/0008_bash_find_trouver_fichiers_par_extension_post.html</guid>
            <description>&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Saya mencari file berdasarkan kriteria nama: daftar file yang memiliki ekstensi .h dan .cpp&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;$ find . -name &amp;#39;*.h&amp;#39; -o -name &amp;#39;*.cpp&amp;#39; + +&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;daftar file yang memiliki ekstensi .h dan .cpp&amp;lt;br&amp;gt;
Hasilnya disimpan dalam file list_of_txt_files.list.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;$ find . -name &amp;#39;*.h&amp;#39; -o -name &amp;#39;*.cpp&amp;#39; -print &amp;amp;gt; list_of_txt_files.list&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Saya mencari daftar file dengan ekstensi .chm&amp;lt;br&amp;gt;
dan saya menghancurkan daftar ini.&amp;lt;br&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;$ find . -name &amp;quot;*.chm&amp;quot; -type f -delete&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>Gérer un projet digital avec une méthodologie en cascade</title>
            <link >https://cheroliv.com/blog/2019/0015_projet_avec_methodologie_en_cascade_post.html</link>
            <pubDate>Thu, 1 Aug 2019 00:00:00 +0000</pubDate>
            <guid isPermaLink="false">blog/2019/0015_projet_avec_methodologie_en_cascade_post.html</guid>
            <description>&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_analyser_et_receuillir_les_besoins&amp;quot;&amp;gt;Analyser et receuillir les besoins&amp;lt;/h3&amp;gt;

&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_definir_la_gestion_de_projet&amp;quot;&amp;gt;Definir la gestion de projet&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;imageblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;img src=&amp;quot;../../diagram/definir_projet.png&amp;quot; alt=&amp;quot;definir la gestion de projet&amp;quot;&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;imageblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;img src=&amp;quot;../../img/triangle_qcd.png&amp;quot; alt=&amp;quot;triangle_qcd&amp;quot;&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_contextualiser_le_projet&amp;quot;&amp;gt;Contextualiser le projet&amp;lt;/h3&amp;gt;

&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_analyser_et_recueillir_des_besoins&amp;quot;&amp;gt;Analyser et recueillir des besoins&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;sect3&amp;quot;&amp;gt;
&amp;lt;h4 id=&amp;quot;_formuler_les_objectifs_et_livrables&amp;quot;&amp;gt;Formuler les objectifs et livrables&amp;lt;/h4&amp;gt;

&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_décrypter_une_grille_de_lecture&amp;quot;&amp;gt;Décrypter une grille de lecture&amp;lt;/h3&amp;gt;

&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_cadrez_le_projet_avec_votre_équipe&amp;quot;&amp;gt;Cadrez le projet avec votre équipe&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;imageblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;img src=&amp;quot;../../img/conductor.png&amp;quot; alt=&amp;quot;chef d&amp;amp;#8217;orchestre&amp;quot;&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_consulter_les_experts_pour_trouver_la_solution&amp;quot;&amp;gt;Consulter les experts pour trouver la solution&amp;lt;/h3&amp;gt;

&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_choisir_une_méthodologie_de_gestion_de_projet&amp;quot;&amp;gt;Choisir une méthodologie de gestion de projet&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;imageblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;img src=&amp;quot;../../img/cascading_planning.png&amp;quot; alt=&amp;quot;planning en cascade&amp;quot;&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;imageblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;img src=&amp;quot;../../img/Cycle_V_details.jpeg&amp;quot; alt=&amp;quot;cycle en V&amp;quot;&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>Mencantumkan basis data dalam instance PostgreSQL</title>
            <link >https://cheroliv.com/blog/2019/0014_list_all_databases_in_postgresql_post.html</link>
            <pubDate>Tue, 30 Jul 2019 00:00:00 +0000</pubDate>
            <guid isPermaLink="false">blog/2019/0014_list_all_databases_in_postgresql_post.html</guid>
            <description>&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Saya tidak ingin menginstalhttps://www.pgadmin.org/[pgAdmin] ou &amp;lt;a href=&amp;quot;http://phppgadmin.sourceforge.net/&amp;quot;&amp;gt;phppgadmin&amp;lt;/a&amp;gt; sementara semua ada di baris perintah.&amp;lt;br&amp;gt;
Untuk melakukannya, kita harus meluncurkan terminal dan masuk sebagai user postgres.&amp;lt;br&amp;gt;
pengguna postgres adalah satu-satunya yang secara default memiliki hak untuk menjalankan aplikasi psql yang merupakan klien baris perintah (&amp;lt;a href=&amp;quot;https://fr.wikipedia.org/wiki/Interface_en_ligne_de_commande&amp;quot;&amp;gt;Antarmuka Baris Perintah&amp;lt;/a&amp;gt;) postgresql,&amp;lt;br&amp;gt;
Agar bisa berbicara dengan basis data melalui baris perintah dari terminal.&amp;lt;br&amp;gt;
Untuk terhubung, jalankan terminal dan ketik ini:&amp;lt;br&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;$ sudo -i -u postgres&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Untuk terhubung dan langsung masuk ke prompt perintah postgresql, ketik ini:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;$ sudo -i -u postgres psql&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Untuk mencantumkan basis data dari sebuah instance postgresql, berikut perintah :&amp;lt;br&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-SQL hljs&amp;quot; data-lang=&amp;quot;SQL&amp;quot;&amp;gt;postgres=# \l&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;hasil:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Daftar basis data Nama    | Pemilik | Enkodifikasi | Kolasi | Jenis karakter |    Hak akses&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;fiber     | tech         | UTF8     | fr_FR.UTF-8     | fr_FR.UTF-8  | =Tc/tech&amp;lt;br&amp;gt;
|              |          |                 |              | tech=CTc/tech fiberdev  | tech         | UTF8     | fr_FR.UTF-8     | fr_FR.UTF-8  | =Tc/tech&amp;lt;br&amp;gt;
|              |          |                 |              | tech=CTc/tech fiberweb  | tech         | UTF8     | fr_FR.UTF-8     | fr_FR.UTF-8  | =Tc/tech&amp;lt;br&amp;gt;
|              |          |                 |              | tech=CTc/tech postgres  | postgres     | UTF8     | fr_FR.UTF-8     | fr_FR.UTF-8  | template0 | postgres     | UTF8     | fr_FR.UTF-8     | fr_FR.UTF-8  | =c/postgres&amp;lt;br&amp;gt;
|              |          |                 |              | postgres=CTc/postgres template1 | postgres     | UTF8     | fr_FR.UTF-8     | fr_FR.UTF-8  | =c/postgres&amp;lt;br&amp;gt;
|              |          |                 |              | postgres=CTc/postgres (6 baris)&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;ketik q untuk keluar dari tampilan hasil&amp;lt;br&amp;gt;
&amp;lt;br&amp;gt;
untuk menampilkan skema dari sebuah instance postgresl.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-sql hljs&amp;quot; data-lang=&amp;quot;sql&amp;quot;&amp;gt;postgres=# SELECT schema_name FROM information_schema.schemata;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;hasil&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;umum information_schema pg_catalog pg_toast_temp_1 pg_temp_1 pg_toast (6 baris)&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;ou&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-sql hljs&amp;quot; data-lang=&amp;quot;sql&amp;quot;&amp;gt;postgres=# SELECT nspname FROM pg_catalog.pg_namespace;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;hasil&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;pg_toast pg_temp_1 pg_toast_temp_1 pg_catalog information_schema publik (6 baris)&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>Groovy: Renomer des fichiers recursivement</title>
            <link >https://cheroliv.com/blog/2019/0007_groovy_rename_recursively_files_post.html</link>
            <pubDate>Mon, 29 Jul 2019 00:00:00 +0000</pubDate>
            <guid isPermaLink="false">blog/2019/0007_groovy_rename_recursively_files_post.html</guid>
            <description>&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Mon lecteur de vidéo sur ma TV a sa façon de passer&amp;lt;br&amp;gt;
d&amp;amp;#8217;un fichier à l&amp;amp;#8217;autre en lecture continue&amp;lt;br&amp;gt;
et son mode de lecture est de passer de 1&amp;amp;#8230;&amp;amp;#8203;..mp4 à 10&amp;amp;#8230;&amp;amp;#8203;.mp4,&amp;lt;br&amp;gt;
et non pas de 1&amp;amp;#8230;&amp;amp;#8203;mp4 à 2&amp;amp;#8230;&amp;amp;#8203;.mp4.&amp;lt;br&amp;gt;
Le problème est qu&amp;amp;#8217;il y a près de 900 fichiers et dossiers.&amp;lt;br&amp;gt;
Bingo un super cas d&amp;amp;#8217;utilisation de Groovy pour manipuler&amp;lt;br&amp;gt;
des fichiers, renommage, recherche de motif dans le nom du fichier&amp;lt;br&amp;gt;
ou dossier.&amp;lt;br&amp;gt;
Ici le but est de changer les noms commençant par&amp;lt;br&amp;gt;
un chiffre et dont le second caractère du nom&amp;lt;br&amp;gt;
n&amp;amp;#8217;est pas un chiffre, j&amp;amp;#8217;ajouterai à ce nom un 0 au début&amp;lt;br&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;renameFilesToStartWithTwoDigit.groovy&amp;lt;br&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-groovy hljs&amp;quot; data-lang=&amp;quot;groovy&amp;quot;&amp;gt;#!/usr/bin/env groovy
/**
 * get a crossplateform separator
 * @return
 */
static String getSeparator() {
    System.getProperty(&amp;quot;file.separator&amp;quot;)
}
/**
 * rename file
 * @param file
 * @param newName
 * @return the renamed file
 */
static File rename(File file, String newName) {
    assert file.renameTo(file
            .parentFile.path +
            getSeparator() +
            newName)
    new File(file
            .parentFile.path +
            getSeparator() +
            newName)
}

String rootFolderPath = System.getProperty(&amp;quot;user.home&amp;quot;) +
        &amp;quot;/Bureau/ng-courses/Udemy - Angular 7 &amp;quot; +
        &amp;quot;(formerly Angular 2) - The Complete Guide&amp;quot;

File rootFolder = new File(rootFolderPath)
assert rootFolder.exists()
assert rootFolder.directory
assert rootFolder.canRead()

List&amp;amp;lt;File&amp;amp;gt; dirList = new ArrayList&amp;amp;lt;&amp;amp;gt;()

//Adding rootFolder in dirList
// just in case there is some files in it
dirList.add(rootFolder)

//looping to get list of directories
//recursively inside rootFolder
rootFolder.traverse { File file -&amp;amp;gt;
    !file.directory ?: dirList.add(file)
}

//looping through dirList
//to rename directories filling criterias
for (int i = dirList.size() - 1; i &amp;amp;gt;= 0; i--) {
    Character[] chars = dirList.get(i).name.chars
    !(chars[0].digit &amp;amp;amp;&amp;amp;amp; !chars[1].digit) ?:
            dirList.set(i, rename(dirList.get(i),
                    &amp;quot;0${dirList.get(i).name}&amp;quot;))
    if (dirList.get(i).directory) {
        //looping through directory
        //to rename file filling criterias
        dirList.get(i).listFiles().each { File f -&amp;amp;gt;
            Character[] cs = f.name.chars
            !(cs[0].digit &amp;amp;amp;&amp;amp;amp; !cs[1].digit) ?:
                    rename(f, &amp;quot;0${f.name}&amp;quot;)
        }
    }
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>Groovy: Recherche de fichiers avec critère, copie de la selection</title>
            <link >https://cheroliv.com/blog/2019/0006_groovy_find_files_copy_to_post.html</link>
            <pubDate>Fri, 19 Jul 2019 00:00:00 +0000</pubDate>
            <guid isPermaLink="false">blog/2019/0006_groovy_find_files_copy_to_post.html</guid>
            <description>&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Le but de ce script est de rechercher des fichiers,&amp;lt;br&amp;gt;
à partir d&amp;amp;#8217;un répertoire parent; récupérer la sélection&amp;lt;br&amp;gt;
dans une liste. Enfin copier cette liste de fichiers&amp;lt;br&amp;gt;
dans un répertoire destination cela en Groovy.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;findCopyTo.groovy&amp;lt;br&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-groovy hljs&amp;quot; data-lang=&amp;quot;groovy&amp;quot;&amp;gt;#!/usr/bin/env groovy
@Grab(&amp;quot;commons-io:commons-io:2.6&amp;quot;)

import groovy.io.FileType
import org.apache.commons.io.FileUtils

/**
 * un separateur OS independant
 * @return
 */
static String getSep() {
    System.getProperty(&amp;quot;file.separator&amp;quot;)
}
/**
 * la liste des fichiers dont le titre contient un item
 * de la liste des motifs
 *
 * @param path
 * @param motifs
 * @return la liste des chemins complet de fichiers
 */
static List&amp;amp;lt;String&amp;amp;gt; findFilesContainingMotifs(
        String path,
        List&amp;amp;lt;String&amp;amp;gt; motifs) {
    List&amp;amp;lt;String&amp;amp;gt; filePathResultList = new ArrayList&amp;amp;lt;&amp;amp;gt;()
    new File(path)
            .eachFileRecurse(FileType.FILES) {
                if (it.name.toLowerCase().contains(&amp;#39;cucumber&amp;#39;)) {
                    filePathResultList.add it.path
                }
            }
    filePathResultList
}

/**
 * copy vers la liste des fichiers
 * @param files
 * @param to
 */
static void copyFilesTo(List&amp;amp;lt;String&amp;amp;gt; files, String to) {
    File toDirDest = new File(to)
    files.each { String filepath -&amp;amp;gt;
        File file = new File(filepath)
        //si le dernier caractere de to n&amp;#39;est pas un separateur
        // alors ajoute le a la chaine
        (to.substring(to.length() - 1) == sep) ?: to.concat(sep)
        if (to + file.name != filepath) {
            FileUtils.copyFileToDirectory(file, toDirDest)
            file.delete()
        }
    }
}
/**
 * la list des arguments
 */
String userName = System.getProperty(&amp;quot;user.name&amp;quot;)
List&amp;amp;lt;String&amp;amp;gt; motifs = [&amp;#39;cucumber&amp;#39;]
String path = &amp;quot;/media/$userName/320/Books/&amp;quot;
String to = &amp;quot;/media/$userName/320/Books/bdd/&amp;quot;
/**
 * lappel aux methodes
 */
copyFilesTo(findFilesContainingMotifs(path, motifs), to)

//affiche moi les fichiers déplacés
findFilesContainingMotifs(path, motifs).each {
    println it
}&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>Groovy: Manipulation de fichiers, recherche et suppression</title>
            <link >https://cheroliv.com/blog/2019/0005_groovy_manipulation_files_post.html</link>
            <pubDate>Thu, 18 Jul 2019 00:00:00 +0000</pubDate>
            <guid isPermaLink="false">blog/2019/0005_groovy_manipulation_files_post.html</guid>
            <description>&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Ici je vais m’intéresser au chargement d&amp;amp;#8217;une propriété dans un build projet &amp;lt;a href=&amp;quot;https://docs.gradle.org/current/userguide/userguide.html&amp;quot;&amp;gt;gradle&amp;lt;/a&amp;gt;&amp;lt;br&amp;gt;
sans que celle ci soit disponible dans le code.&amp;lt;br&amp;gt;
La propriété sera défini dans le fichier gradle.properties du dossier ~/.gradle/gradle.properties&amp;lt;br&amp;gt;
~ est un raccourcie de la variable symbolique HOME_PATH représentant la racine utilisateur de session: le &amp;lt;a href=&amp;quot;https://fr.wikipedia.org/wiki/R%C3%A9pertoire_personnel&amp;quot;&amp;gt;répertoire personnel&amp;lt;/a&amp;gt;.&amp;lt;br&amp;gt;
De la même façon dans un build projet &amp;lt;a href=&amp;quot;https://maven.apache.org/guides/index.html&amp;quot;&amp;gt;maven&amp;lt;/a&amp;gt;, avec ~/.m2/settings.xml Le but de ce script c&amp;amp;#8217;est de supprimer les fichiers qui correspondraient aux motifs énoncés&amp;lt;br&amp;gt;
dans la variable strCriteria. Pour cela je vais utiliser les &amp;lt;a href=&amp;quot;https://fr.wikipedia.org/wiki/Fermeture_(informatique)&amp;quot;&amp;gt;closures&amp;lt;/a&amp;gt;: each, findAll,  collect&amp;lt;br&amp;gt;
ainsi que la classe StringTokenizer qui va découper en morceau ma chaîne de critères&amp;lt;br&amp;gt;
pour en récupérer une liste de strings. Si un des fichiers de mon dossier à nettoyer contient un des motif dans son nom&amp;lt;br&amp;gt;
alors il sera un candidat à la destruction en étant ajouté à la liste résultat.&amp;lt;br&amp;gt;
Et à la dernière ligne un petit exemple d&amp;amp;#8217;&amp;lt;a href=&amp;quot;https://en.wikipedia.org/wiki/Elvis_operator&amp;quot;&amp;gt;Elvis operator&amp;lt;/a&amp;gt;&amp;lt;br&amp;gt;
filemanip.groovy&amp;lt;br&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-groovy hljs&amp;quot; data-lang=&amp;quot;groovy&amp;quot;&amp;gt;#!/usr/bin/env groovy
//Mes criteres de selection des fichiers à detruire
String strCriteria = &amp;quot;Dummies Dummie ajax Objective-C Windows blackberry &amp;quot; +
        &amp;quot;wordpress joomla ios c# f# rails ruby flex asp flash Dreamweaver adobe &amp;quot; +
        &amp;quot;CoffeeScript c++ .chm .epub .rar .zip .tar drupal drupals .net php javascript &amp;quot; +
        &amp;quot;.js iphone ipad js _js facebook gwt vaadin linq python py zope window &amp;quot; +
        &amp;quot;3D scala gaming active_directory tomcat jboss glassfish microsoft 2d &amp;quot; +
        &amp;quot;lego blogger blog cocoa dojo dom dotnetnuke eclipse .gz unity sharepoint &amp;quot; +
        &amp;quot;.tgz iwork filemaker .doc .txt&amp;quot;


//repertoire personnel
String userDir = System.getProperty(&amp;quot;user.home&amp;quot;)
//un separateur de chemin crossplatform
String sep = System.getProperty(&amp;quot;file.separator&amp;quot;)
//la variable symbolique
String sym_books_vrac_path = &amp;quot;books_vrac_path&amp;quot;

//le chemin de mon fichier gradle.properties hors projet
String propertiesFilePath = userDir
        .concat(&amp;quot;${sep}.gradle${sep}gradle.properties&amp;quot;)


File propertiesFile = new File(propertiesFilePath)


//je m&amp;#39;aassure que le fichier existe et que ce n&amp;#39;est pas un dossier
assert propertiesFile.exists() &amp;amp;amp;&amp;amp;amp; !propertiesFile.directory

Properties gradleProperties = new Properties()

//on boucle avec un inpustream sur le fichier
//gradle.properties pour peupler ma variable gradleProperties
propertiesFile.withInputStream { InputStream it -&amp;amp;gt;
    gradleProperties.load(it)
}

//le chemin du dossier books_vrac est la valeur de notre clé books_vrac_path
String pathVracDir = gradleProperties.get(sym_books_vrac_path)

//j&amp;#39;ai récuperé tous ces livres en tapant cette commande dans le terminal
//wget -m -np -c -R &amp;quot;index.html*&amp;quot; &amp;quot;http://the-eye.eu/public/Books/IT%20Various/&amp;quot;

//on ouvre le dossier dont le chemin est dans la variable pathVracDir
// on s&amp;#39;assure qu&amp;#39;il existe et que c&amp;#39;est un dossier
File vracDir = new File(pathVracDir)
assert vracDir.exists() &amp;amp;amp;&amp;amp;amp; vracDir.directory


//separer les élèments de ma liste de critère
StringTokenizer tokenizer = new StringTokenizer(strCriteria)

//la liste de critere
List&amp;amp;lt;String&amp;amp;gt; criteriaList = new ArrayList&amp;amp;lt;&amp;amp;gt;()

//j&amp;#39;itère sur les tokens et je construit ma liste de token,
// mot clef critère de recherche
tokenizer.each { String it -&amp;amp;gt;
    criteriaList.add(it.toLowerCase())
}

//je collect tous les noms de fichier dans le dossier
List&amp;amp;lt;String&amp;amp;gt; fileNameList = vracDir
        .listFiles()
        .collect { File it -&amp;amp;gt;
            it.directory ?: it.name
        }

// je collect les noms de fichier qui contiendraient un des criteres
List&amp;amp;lt;String&amp;amp;gt; resultList = fileNameList.findAll { String name -&amp;amp;gt;
    criteriaList.findAll { String crit -&amp;amp;gt;
        name.toLowerCase().contains(crit)
    }
}

// je parcours ma liste de résultat pour les effacer
resultList.each { String fileName -&amp;amp;gt;
    assert new File(vracDir.path + sep + fileName).delete()
}

println fileNameList.size()
println resultList.size()
resultList.empty ?: println(vracDir.path + sep + resultList.first())&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>Groovy: Remplacer une séquence de caractères dans une String et charger une Properties depuis une file</title>
            <link >https://cheroliv.com/blog/2019/0004_groovy_replace_string_load_properties_post.html</link>
            <pubDate>Wed, 17 Jul 2019 00:00:00 +0000</pubDate>
            <guid isPermaLink="false">blog/2019/0004_groovy_replace_string_load_properties_post.html</guid>
            <description>&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Voici un bout de code fonctionnel en groovy,&amp;lt;br&amp;gt;
pour changer toutes les occurrences d&amp;amp;#8217;une séquence de caractères dans une String&amp;lt;br&amp;gt;
et charger un fichier .properties dans un objet properties.&amp;lt;br&amp;gt;
Exécuter une commande dans une String.&amp;lt;br&amp;gt;
replaceTextinFile.groovy&amp;lt;br&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-groovy hljs&amp;quot; data-lang=&amp;quot;groovy&amp;quot;&amp;gt;#!/usr/bin/env groovy
println System.getProperty(&amp;quot;user.home&amp;quot;)

static String getSeparator_() {
    System.getProperty(&amp;quot;file.separator&amp;quot;)
}

String separator = separator_

String path = System.getProperty(&amp;quot;user.home&amp;quot;)
        .concat(&amp;quot;${separator}.gradle&amp;quot; +
                separator +
                &amp;quot;gradle.properties&amp;quot;)

File propertiesFile = new File(path)

assert propertiesFile.exists() &amp;amp;amp;&amp;amp;amp; !propertiesFile.directory

Properties gradleProperties = new Properties()

propertiesFile.withInputStream { InputStream it -&amp;amp;gt;
    gradleProperties.load(it)
}

String user_name = gradleProperties.getProperty(&amp;quot;user_name&amp;quot;)

String text
String resultText
text = new File(&amp;quot;${System.getProperty(&amp;quot;user.home&amp;quot;)}&amp;quot; +
        separator +
        &amp;quot;src&amp;quot; +
        separator +
        &amp;quot;cheroliv.github.io&amp;quot; +
        separator +
        &amp;quot;src&amp;quot; +
        separator +
        &amp;quot;main&amp;quot; +
        separator +
        &amp;quot;resources&amp;quot; +
        separator +
        &amp;quot;eco_space.txt&amp;quot;).text

String to = separator +
        &amp;quot;media&amp;quot; +
        separator +
        &amp;quot;${user_name}&amp;quot; +
        separator +
        &amp;quot;320&amp;quot; +
        separator +
        &amp;quot;videoCours&amp;quot; +
        separator
String from = &amp;quot;${System.getProperty(&amp;quot;user.home&amp;quot;)}&amp;quot; +
        separator +
        &amp;quot;p2p&amp;quot; +
        separator

resultText = text.replace(to, from)

println resultText

//execute command line in a string
println &amp;quot;rsync -avr ${resultText} ${to}&amp;quot;.execute().text&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>Groovy: Caractères ASCII</title>
            <link >https://cheroliv.com/blog/2019/0003_groovy_char_ascci_post.html</link>
            <pubDate>Wed, 10 Jul 2019 00:00:00 +0000</pubDate>
            <guid isPermaLink="false">blog/2019/0003_groovy_char_ascci_post.html</guid>
            <description>&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Voici un bout de code fonctionnel en Groovy, qui génère un fichier texte,&amp;lt;br&amp;gt;
avec les 256 premiers caractères lisibles du tableau ASCII&amp;lt;br&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-groovy hljs&amp;quot; data-lang=&amp;quot;groovy&amp;quot;&amp;gt;#!/usr/bin/env groovy
import java.nio.charset.StandardCharsets

List&amp;amp;lt;Character&amp;amp;gt; chars = new ArrayList&amp;amp;lt;&amp;amp;gt;()
int j = 0
Character jumpLine = &amp;#39;\n&amp;#39;

256.times { Integer idx -&amp;amp;gt;
    if (Character.isAlphabetic(idx) || Character.isDigit(idx)) {
        if (j % 10 == 0 &amp;amp;amp;&amp;amp;amp; j != 0) {
            chars.add(jumpLine)
            chars.add(idx as char)
        } else chars.add(idx as char)
        j++
    }
}

String seperator = System.getProperty(&amp;quot;file.separator&amp;quot;)

String path = &amp;quot;${System.getProperty(&amp;quot;user.home&amp;quot;)}${seperator}ascii.txt&amp;quot;
File speCharFile = new File(path)

if (speCharFile.exists() &amp;amp;amp;&amp;amp;amp; !speCharFile.directory) {
    speCharFile.text = &amp;quot;&amp;quot;
} else {
    speCharFile.createNewFile()
}

String text = new String()

chars.each { Character it -&amp;amp;gt;
    text = it == jumpLine ? &amp;quot;$text$it&amp;quot; : &amp;quot;$text $it &amp;quot;
}

speCharFile.setText(text, StandardCharsets.UTF_8.toString())&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;j&amp;amp;#8217;ai nommé le fichier spe_char.groovy,&amp;lt;br&amp;gt;
depuis le dossier ou est le fichier&amp;lt;br&amp;gt;
ouvrir un terminal et copier coller pour exécuter le script&amp;lt;br&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre class=&amp;quot;highlightjs highlight&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;language-bash hljs&amp;quot; data-lang=&amp;quot;bash&amp;quot;&amp;gt;$ groovy spe_char.groovy&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;résultat:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;0  1  2  3  4  5  6  7  8  9 A  B  C  D  E  F  G  H  I  J K  L  M  N  O  P  Q  R  S  T U  V  W  X  Y  Z  a  b  c  d e  f  g  h  i  j  k  l  m  n o  p  q  r  s  t  u  v  w  x y  z  ª  µ  º  À  Á  Â  Ã  Ä Å  Æ  Ç  È  É  Ê  Ë  Ì  Í  Î Ï  Ð  Ñ  Ò  Ó  Ô  Õ  Ö  Ø  Ù Ú  Û  Ü  Ý  Þ  ß  à  á  â  ã ä  å  æ  ç  è  é  ê  ë  ì  í î  ï  ð  ñ  ò  ó  ô  õ  ö  ø ù  ú  û  ü  ý  þ  ÿ&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item>

    </channel>
</rss>
