<?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 부트스트랩 템플릿</description>
        <language>en-gb</language>
        <pubDate>Thu, 8 Oct 2026 11:38:17 +0000</pubDate>
        <lastBuildDate>Thu, 8 Oct 2026 11:38:17 +0000</lastBuildDate>

        <item>
            <title>HyperFrames-Gradle : AsciiDoc을 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;#_비디오_파이프라인의_구멍&amp;quot;&amp;gt;비디오 파이프라인의 구멍&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_hyperframes_엔진&amp;quot;&amp;gt;HyperFrames : 엔진&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_플러그인_아키텍처&amp;quot;&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;#_네_단계_파이프라인&amp;quot;&amp;gt;네 단계 파이프라인&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_asciidoc_dsl&amp;quot;&amp;gt;AsciiDoc DSL&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;#_두_개의_비디오_플러그인_두_가지_용도&amp;quot;&amp;gt;두 개의 비디오 플러그인, 두 가지 용도&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_로드맵의_6개_에픽&amp;quot;&amp;gt;로드맵의 6개 에픽&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_왜_동작하나요&amp;quot;&amp;gt;왜 동작하나요?&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_결론_잃어버린_고리&amp;quot;&amp;gt;결론: 잃어버린 고리&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_참고문헌&amp;quot;&amp;gt;참고문헌&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;읽는 시간 : 8 minutes&amp;lt;/div&amp;gt;
&amp;lt;p&amp;gt;두 주 전에, 내가 시작했다`capsule-gradle`덱을 변환하는 플러그인 reveal.js의 WebM 비디오 캡슐에 TTS가 적용된 것입니다. 오늘, 새로운 플러그인을 추가합니다 : `hyperframes-gradle`それが capsule-gradle가 하지 않는 무언가를 수행합니다 : AsciiDoc을 &amp;lt;strong&amp;gt;직접&amp;lt;/strong&amp;gt; MP4 영상으로 변환하기, 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;이 플러그인이 존재하는 이유와 기존 시스템과의 연동 방식은 다음과 같습니다, 그리고 HeyGen의 HyperFrames 엔진이 콘텐츠 제작 파이프라인에 제공하는 것.&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;_비디오_파이프라인의_구멍&amp;quot;&amp;gt;비디오 파이프라인의 구멍&amp;lt;/h2&amp;gt;
&amp;lt;div 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;/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;복습 캡슐&amp;lt;/strong&amp;gt; (capsule-gradle) — WebM으로 캡처된 reveal.js 덱과&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;narration TTS. 모듈을 복습하기에 완벽합니다.&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;애니메이션 슬라이드&amp;lt;/strong&amp;gt; (slider-gradle) — 인터랙티브 reveal.js HTML 데크를 위한&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;/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;/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;사용 사례&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;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;수정 캡슐&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;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;티저 영상&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;A무것도&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;&amp;lt;strong&amp;gt;아무것도&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;문서-비디오&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;없음&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;첫 번째 use case는 중요합니다 : 어떻게 자동으로 생성할 수 있나요 GSAP 애니메이션이 포함된, 프로젝트 또는 모듈을 소개하는 60초 비디오, TTS 내레이션, 배경 음악 — 모두 동일한 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;Capsule-gradle은 응답할 수 없습니다: &amp;lt;strong&amp;gt;이미&amp;lt;/strong&amp;gt; reveal.js 덱이 필요합니다. slider-gradle는 이 데크를 잘 생성하지만, 비디오 렌더링용으로 설계되지 않았습니다. 직접적인 파이프라인이 필요했습니다: AsciiDoc → 비디오&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_엔진&amp;quot;&amp;gt;HyperFrames : 엔진&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;발견했다https://github.com/heygen-com/hyperframes[하이퍼프레임] il y a 며칠. HeyGen이 만든 Apache 2.0 라이선스의 오픈소스 프레임워크, AI 비디오의 유니콘. GitHub에서 별 22,700개. 피치는 문장 :&amp;lt;/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;HTML 작성. 비디오 렌더링. 에이전트를 위한 설계.&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;원칙 : HTML을 작성합니다.`data-*`속성, 참조합니다 GSAP 애니메이션 (또는 CSS, Lottie, Three.js… 등)이며, 엔진이 이를 모두 MP4로 렌더링 Puppeteer (헤드리스 크롬) + FFmpeg를 통해. 결정론적 결과: 동일한 입력, 같은 프레임, 같은 비디오.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;무엇이 HyperFrames를 Remotion과 다르게 만드는가? React 없이, 없이 번들러, JSX 없이. AI 에이전트가 네이티브로 작성할 수 있는 순수한 HTML. 이건 &amp;lt;strong&amp;gt;정확히&amp;lt;/strong&amp;gt; 내 파이프라인에 맞춰져 있습니다: planner-gradle와 deepseek와 함께 SPG/SPD 플랜에서 이 HTML을 생성할 수 있으며, Gradle 플러그인이 있습니다. 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;_플러그인_아키텍처&amp;quot;&amp;gt;플러그인 아키텍처&amp;lt;/h2&amp;gt;
&amp;lt;div 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는 모든 플러그인과 동일한 패턴을 따릅니다: &amp;lt;a href=&amp;quot;0124_architecture_plugin_gradle_racine_consommateur_independant_post.html&amp;quot;&amp;gt;패턴 « 플러그인 indépendant + 소비자 루트 »&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;_네_단계_파이프라인&amp;quot;&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&amp;gt;@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultFontSize 11

title 파이프라인 hyperframes-gradle
rectangle &amp;quot;AsciiDoc
소스&amp;quot; as adoc #LightYellow
rectangle &amp;quot;AsciidoctorJ&amp;quot; as asciidoctorj #LightBlue
rectangle &amp;quot;HTML HyperFrames
(data-* 속성)&amp;quot; as html #LightGreen
rectangle &amp;quot;HyperFrames CLI
(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;*AsciidoctorJ*는 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;&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; HTML HyperFrames과 함께 생성한다&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-*`해당하는 속성&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;*RenderHyperframesTask*는 HyperFrames CLI를 통해 호출합니다`ProcessBuilder`(empty)&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는 &amp;amp;#8230;&amp;amp;#8203;에 배포됩니다`output/`와 함께`metadata.json`오케스트레이션을 위해.&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;_asciidoc_dsl&amp;quot;&amp;gt;AsciiDoc DSL&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;플러그인의 진정한 추가 가치는 AsciiDoc DSL입니다. 사용자 그는 절대 HTML을 쓰지 않는다. 그는 기존의 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;= 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;docinfo 속성 (&amp;lt;code&amp;gt;:hyperframes-width:&amp;lt;/code&amp;gt;) 치수를 설정합니다. 커스텀 블록은 구성 요소와 트랙을 정의합니다. 블록 `[hyperframes-animation]`원시 GSAP 코드가 포함되어 있습니다 — 에이전트 IA 자연어 설명에서 생성할 수 있다.&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;유일한 기술적 마찰 지점: HyperFrames는 Node.js/TypeScript 기반입니다, 그리고 내 플러그인은 Kotlin/JVM입니다. 솔루션은`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;JVM 코드 ↔ Node.js 간 결합이 없습니다. 계약은 명령줄로 지정됩니다. 그리고 디스크의 파일들. 이것은 동일한 패턴이`plantuml-gradle` (PlantUML CLI를 호출하는) 또는`slider-gradle`(AsciidoctorJ를 호출하는). 해 아래에는 새로울 것이 없다 — 최신 도구에 적용된 것뿐이다.&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;_두_개의_비디오_플러그인_두_가지_용도&amp;quot;&amp;gt;두 개의 비디오 플러그인, 두 가지 용도&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;캡슐-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)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;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 → HTML 하이퍼프레임&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;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 → 영상&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;Playwright 자바 → 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;애니메이션&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;전환 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;GSAP/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;스택&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;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&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;/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 ──→ 덱 reveal.js ──→ capsule-gradle ──→ WebM (복습 캡슐)&amp;lt;/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 (독립형 비디오)&amp;lt;/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가 슬라이드를 생성합니다. capsule-gradle은 리뷰 캡슐을 만듭니다. hyperframes-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;_로드맵의_6개_에픽&amp;quot;&amp;gt;로드맵의 6개 에픽&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;플러그인은 6개의 EPIC로 구성되어 있습니다 :&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;서사시적인&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;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;부트스트랩 거버넌스 + 프레이밍 아키텍처&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;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;Gradle 스텁 플러그인 + AsciidoctorJ 통합&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;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;브리지 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 관례 (블록, docinfo, 템플릿)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;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;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;즉시 사용 가능한 템플릿 (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 + 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;세션 000(bootstrap)는 이미 완료되었습니다. 세션 001은 HF-1을 공격할 것입니다: Gradle 플러그인 스텁과 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;_왜_동작하나요&amp;quot;&amp;gt;왜 동작하나요?&amp;lt;/h2&amp;gt;
&amp;lt;div 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;/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는 &amp;#39;에이전트를 위해 제작되었습니다&amp;#39;&amp;lt;/strong&amp;gt;. 내 파이프라인은 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;(planner-gradle + deepseek)가 콘텐츠를 생성합니다. HyperFrames는 받아들입니다. 원시 HTML — LLMs이 가장 잘 다루는 형식입니다. React이 없으며, 없음 JSX가 없으며, 에이전트에게 학습 곡선이 없습니다.&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;AsciiDoc DSL은 자연스럽다&amp;lt;/strong&amp;gt;. 내 생태계 전체가 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 — 모두 소비하는`.adoc`. 사용자 정의 블록 추가`[hyperframes-composition]`확장입니다 논리적이다, 단절이 아니다&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;Node.js 브릿지는 검증된 패턴입니다.&amp;lt;/strong&amp;gt; JVM 외부의 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;저는 이미 PlantUML, Graphviz, Piper에 대해 이것을 하고 있습니다. HyperFrames 추가 아키텍처에 아무것도 바꾸지 않는다 — 그것은 단지 외부 도구 하나 더일 뿐이다, 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;패턴의 진짜 힘은 npm 의존성이 없다는 점입니다 Gradle 빌드. 플러그인은 Node.js 코드를 가져오지 않습니다. 이를 실행 쉘 명령. HyperFrames가 진화하거나 깨지면, 플러그인은 결합되지 않습니다. — 명령어를 업데이트하기만 하면 됩니다.&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;_결론_잃어버린_고리&amp;quot;&amp;gt;결론: 잃어버린 고리&amp;lt;/h2&amp;gt;
&amp;lt;div 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와 함께, 내 비디오 파이프라인이 완성되었습니다. 저는 :&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;인터랙티브 슬라이드&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;수정 캡슐&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;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;세 가지 형식. 하나의 소스 파일: 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;다음 목표는 HF-1 : 플러그인을 컴파일하고 생성하는 것입니다. 주석이 달린 AsciiDoc 문서에서 첫 번째 HTML HyperFrames를 생성합니다. 세션 001은 이미 프레임이 잡혔습니다.&amp;lt;/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가 존재해. 거버넌스가 마련되어 있어. 백로그 작성됨. 아키텍처가 문서화되었습니다. 플러그인이 탄생했습니다.&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;_참고문헌&amp;quot;&amp;gt;참고문헌&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;0124_architecture_plugin_gradle_racine_consommateur_independant_post.html&amp;quot;&amp;gt;Article 0124 — 독립 플러그인 아키텍처 + 소비자 루트&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/heygen-com/hyperframes&amp;quot;&amp;gt;HyperFrames — 깃허브&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 — 문서&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;— 상세한 아키텍처&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>나의 LLM 소비 감사 : SQLite, OpenCode, Ollama Cloud, 그리고 왜 나의 프로 구독이 세계 시장을 폭발시키는가?</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;#_스택_이해하기_누가_무엇을_하는가&amp;quot;&amp;gt;스택 이해하기: 누가 무엇을 하는가?&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_서론_왜_감사가_필요한가&amp;quot;&amp;gt;서론: 왜 감사가 필요한가?&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_opencode_데이터베이스_금광&amp;quot;&amp;gt;OpenCode 데이터베이스: 금광&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;#_스키마&amp;quot;&amp;gt;스키마&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_추출_코드&amp;quot;&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;#_결과_73일_소비&amp;quot;&amp;gt;결과: 73일 소비&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;#_전체_보기&amp;quot;&amp;gt;전체 보기&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_모델_및_공급업체별_환기&amp;quot;&amp;gt;모델 및 공급업체별 환기&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_세션_내_모델_세션_테이블이_보지_못하는_것&amp;quot;&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;#_요금_비교_서구_공급업체&amp;quot;&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;#_anthropic_클로드&amp;quot;&amp;gt;Anthropic — 클로드&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;#_구글_제미니&amp;quot;&amp;gt;구글 — 제미니&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_허깅페이스_추론_제공자&amp;quot;&amp;gt;허깅페이스 추론 제공자&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;#_요금_비교_중국_공급업체&amp;quot;&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;#_deepseek_api_직접_platform_deepseek_com&amp;quot;&amp;gt;DeepSeek API 직접 (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;#_요약_테이블_모든_제공자&amp;quot;&amp;gt;요약 테이블 — 모든 제공자&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_숨은_비용_프롬프트_엔지니어링_및_반복&amp;quot;&amp;gt;숨은 비용 : 프롬프트 엔지니어링 및 반복&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_ollama_cloud_pro_구독_누적_docker_방법&amp;quot;&amp;gt;Ollama Cloud Pro 구독 누적 : Docker 방법&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_감사가_측정하지_않는_것&amp;quot;&amp;gt;감사가 측정하지 않는 것&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_결론_숫자의_판결&amp;quot;&amp;gt;결론 : 숫자의 판결&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_참조&amp;quot;&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;#_요금_출처&amp;quot;&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;/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;읽기 시간: 22 minutes&amp;lt;/div&amp;gt;
&amp;lt;p&amp;gt;사람들은 종종 내 AI 사용량이 얼마나 비용이 드는지 자주 묻는다. 저는 &amp;#39;한 달에 20유로&amp;amp;#8217;라고 답하고, 사람들은 놀란 눈으로 저를 바라본다. 그렇다면 오늘, 저는 여러분께 검은 상자를 열어 보이겠다. 이 글은 나의 73일간의 바이브 코딩에 대한 종합 감사이다: OpenCode의 SQLite 데이터베이스에서 데이터를 추출하는 방법론, 테이블을 쿼리하는 SQL 코드, 그리고 시장의 모든 공급업체 — 서양과 중국을 포함한 — 에 대한 포괄적인 비교. 결과가 너무나 비대칭적이어서 나는 계산을 세 번 확인해야 했다.&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;/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;_스택_이해하기_누가_무엇을_하는가&amp;quot;&amp;gt;스택 이해하기: 누가 무엇을 하는가?&amp;lt;/h2&amp;gt;
&amp;lt;div 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;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;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 내 스택 LLM의 세 명의 배우
left to right direction

actor &amp;quot;저 (개발자)&amp;quot; as Dev

package &amp;quot;오픈코드
(github.com/anomalyco/opencode)&amp;quot; as OC {
  rectangle &amp;quot;vibe coding의 CLI
오픈 소스, 무료&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 (로컬 설치)&amp;quot; as OL {
  rectangle &amp;quot;추론 클라이언트
내 머신에 설치됨&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 클라우드
(ollama.com)&amp;quot; as OCloud {
  rectangle &amp;quot;클라우드 추론 서비스
프로 구독: 20€/월&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;프롬프트를 입력합니다&amp;quot;
CLI --&amp;amp;gt; Client : &amp;quot;이 모델을 불러&amp;quot;
Client --&amp;amp;gt; Cloud : &amp;quot;SSH 키 + 요청
(모델 :cloud)&amp;quot;
Client --&amp;amp;gt; Client : &amp;quot;로컬 실행
(클라우드가 없는 모델)&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는 모델 제공자가 아닙니다.&amp;lt;/strong&amp;gt;이것은 오픈 소스 CLI입니다 (&amp;lt;a href=&amp;quot;https://github.com/anomalyco/opencode&amp;quot;&amp;gt;github.com/anomalyco/opencode&amp;lt;/a&amp;gt;) AI 에이전트를 오케스트레이션합니다. 구독을 판매하지 않으며, 추론을 제공하지 않습니다. 이것은의 동등물이다`git`버전 관리를 위한: 도구이지 서비스가 아닙니다. &amp;lt;strong&amp;gt;Ollama Cloud는 추론 제공업체입니다.&amp;lt;/strong&amp;gt;내 로컬 Ollama 설치는 &amp;lt;strong&amp;gt;client&amp;lt;/strong&amp;gt; 역할을 합니다 — 고유한 SSH 키(Device Key)를 통해 Ollama 서버에 인증하고, 태그가 지정된 모델의 실행을 그들에게 위임합니다.&amp;lt;code&amp;gt;:cloud&amp;lt;/code&amp;gt;. 프로 구독(20유로/월)은 상위 할당량을 해제하고 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;_서론_왜_감사가_필요한가&amp;quot;&amp;gt;서론: 왜 감사가 필요한가?&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;두 달 전, 저는 로컬 Ollama 설치에서 모델을 무료로 사용하고 있었습니다. 그리고 저는 월 20유로에 첫 번째 Ollama Cloud Pro 구독을 시작했습니다. 이로 인해 클라우드 모델(태그가 있는`:cloud`) 제공업체를 통해`ollama-cloud`. 두 번째 구독이 한 달 후에 이어졌습니다, 별도의 Docker 인스턴스에서 자신의 계정과 자신의 SSH 키를 가진 — &amp;lt;a href=&amp;quot;0120_cumuler_deux_abonnements_ollama_pro_docker_post.html&amp;quot;&amp;gt;구독 누적에 대한 내 글&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;strong&amp;gt;이 20유로가 월마다 타당한가요 ?&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;소비&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;그리고 OpenCode — CLI — 는 이를 모두 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;_opencode_데이터베이스_금광&amp;quot;&amp;gt;OpenCode 데이터베이스: 금광&amp;lt;/h2&amp;gt;
&amp;lt;div 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는 세션 기록 전반를 SQLite 파일 안에 위치해 있는`~/.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;_스키마&amp;quot;&amp;gt;스키마&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;베이스에는 약 20개의 테이블이 있지만, 그중 세 개는 우리 감사에 매우 중요합니다:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;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 간소화된 스키마 — OpenCode SQLite
entity &amp;quot;세션&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;메시지&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;프로젝트&amp;quot; as P {
  * id : TEXT
  --
  * worktree : TEXT
  * name : TEXT
}

S ||--o{ M : &amp;quot;세션 아이디&amp;quot;
S }o--|| P : &amp;quot;프로젝트_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;테이블`session`전역 감사에 적합합니다 : 각 세션은 집계된 토큰 수, 사용된 모델, 그리고 비용을 가지고 있습니다. 필드`model`원시 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;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;필드`cost`OpenCode 데이터베이스에서는 *외부 API 요금*만 반영됩니다. 즉, API 키(Google Gemini, OpenAI 등)를 통해 사용량 기준으로 지불된 비용만 표시됩니다. Ollama을 통한 호출 — 로컬이든 Ollama Cloud Pro를 통해든 — 은 표시됩니다.`cost=0`비용이 정액제(구독)이기 때문에, 토큰당이 아닙니다. 이러한 호출의 실제 비용은 월간 구독료입니다.&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;_추출_코드&amp;quot;&amp;gt;추출 코드&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;설치할 필요가 없습니다. Node.js 22+는 실험용 SQLite 모듈을 포함합니다 (&amp;lt;code&amp;gt;node:sqlite&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;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;플래그`--experimental-sqlite`Node.js 22.x에서 필요합니다. Node.js 24+에서는 모듈이 안정적이며 더 이상 플래그가 필요하지 않습니다.&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;_결과_73일_소비&amp;quot;&amp;gt;결과: 73일 소비&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;_전체_보기&amp;quot;&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;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;값&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;맥락&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;세션 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 세션/일 평균&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 Input&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억&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;그중 94.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;&amp;lt;strong&amp;gt;토큰 출력&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;/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 Reasoning&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;무시할 만한 — 거의 생각 안 함&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;외부 API 비용&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;구독 외 비용 (Gemini + 무료 등급)&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;이 \$0.22 수치는 중요한데, 이는 Ollama Cloud Pro 구독 외에 내가 추가로 지불한 모든 것을 나타냅니다. 즉 거의 없습니다. 구독이 필수 사항을 다 커버합니다.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;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 월간 진화 — 입력 토큰
rectangle &amp;quot;2026년 3월
9 세션
37,7M 토큰&amp;quot; as MARS #FFE082
rectangle &amp;quot;2026년 4월
486 세션
1.33억 토큰&amp;quot; as AVR #81C784
rectangle &amp;quot;2026년 5월
527 세션
12.5억 토큰&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;3월과 4월 사이 증가량은 놀라웠습니다: 한 달 만에 35배 증가했습니다. 이는 &amp;#39;테스트&amp;#39; 사용에서 &amp;#39;운영&amp;#39; 사용으로의 전환점 — 즉, Gradle 플러그인을 위해 OpenCode 에이전트를 지속적으로 실행하기 시작했을 때입니다.&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;_모델_및_공급업체별_환기&amp;quot;&amp;gt;모델 및 공급업체별 환기&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;필드`model`테이블의`session`JSON인`json_extract()`깨끗하게 환기할 수 있습니다 :&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;모델 × 공급자&amp;lt;/th&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;입력&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;Output&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;비용&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;(No text provided to translate)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;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 @ 구글&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;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;세 가지 다른 공급자, 정확히 같은 DeepSeek V4 Pro 모델 :
* &amp;lt;code&amp;gt;ollama-cloud&amp;lt;/code&amp;gt;→ 통해서 &amp;lt;strong&amp;gt;유료 Pro 구독&amp;lt;/strong&amp;gt; (Ollama가 관리하는 클라우드)
* &amp;lt;code&amp;gt;ollama-b&amp;lt;/code&amp;gt;→ &amp;lt;strong&amp;gt;두 번째 Pro 구독&amp;lt;/strong&amp;gt;, 별도의 Docker 컨테이너에 격리됨
* &amp;lt;code&amp;gt;ollama&amp;lt;/code&amp;gt;→ 현지 로캘 인스턴스, &amp;lt;strong&amp;gt;gratuite&amp;lt;/strong&amp;gt; (내 기계에서 직접 실행됨)
세 가지의 합은 936M 입력, 8,4M 출력 — 이것이 DeepSeek V4 Pro의 총 소비량이며, 모든 제공업체를 합친 것입니다. 오직`ollama-cloud`(220M 입력)에는 구독료가 발생합니다. 남은 716M은 무료입니다 (로컬 또는 구독 누적).&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;_세션_내_모델_세션_테이블이_보지_못하는_것&amp;quot;&amp;gt;세션 내 모델 : 세션 테이블이 보지 못하는 것&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;테이블`session`주요 모델 *principal*만 세션을 캡처합니다. 하지만 동일한 세션 동안 OpenCode 에이전트는 여러 모델 간에 전환할 수 있습니다. 테이블`message`보여준다 :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;이제 Qwen, GLM, Kimi 그리고 다른 모델들을 더 사용하지 않습니다. 그것들은 4월에 꽤 많은 것을 테스트했기 때문에 기록에 나타납니다. 5월 중순부터 저는 DeepSeek V4 Pro에 잠겨 있습니다 — 이것이 긴 컨텍스트에서 에이전트 거버넌스와 함께 부하를 견딜 수 있는 유일한 모델입니다 (cf. &amp;lt;a href=&amp;quot;0112_comparaison_kimi_glm_deepseek_long_contexte_plugin_gradle_opencode_post.html&amp;quot;&amp;gt;나의 Kimi/GLM/DeepSeek 비교&amp;lt;/a&amp;gt;). 히스토리에서의 deepseek 메시지 32%는 오해를 일으킵니다: 오늘은 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;_요금_비교_서구_공급업체&amp;quot;&amp;gt;요금 비교 — 서구 공급업체&amp;lt;/h2&amp;gt;
&amp;lt;div 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;/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;220M 토큰 입력 + 1,9M 토큰 출력&amp;lt;/strong&amp;gt;— &amp;lt;strong&amp;gt;uniquement&amp;lt;/strong&amp;gt; 통과된 볼륨`ollama-cloud`(내가 유료 구독과 일치하는 유일한 것). 나머지 716M은 무료입니다 (Ollama 로컬 및 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;공급업체&amp;lt;/th&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;$/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;비용/월&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;× 내 구독&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;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;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_클로드&amp;quot;&amp;gt;Anthropic — 클로드&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;모델&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;월 비용&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;× 내 구독&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;소네트 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;Haiku 4.5, Claude의 가장 &amp;#39;경제적인&amp;#39; 모델조차 내 구독료의 11배에 해당한다. 게다가 내 코드베이스를 전혀 알지 못하며, 내 스타일로 생성하지도 못하고, Gradle Kotlin DSL API에 대해 환각을 일으킨다.&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;모델&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;월 비용&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;× 내 구독&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×&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 미니&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;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;_구글_제미니&amp;quot;&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;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;모델&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;비용/월&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;× 내 구독&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;제미니 3.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;\$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;제미나이 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;\$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 69€는 순수 가격 기준으로 유일한 근접 경쟁자 — 하지만 이는 가볍고 밀도가 높은 모델이지, DeepSeek V4 Pro 같은 1.6T MoE 모델이 아닙니다. 생성된 코드의 품질과는 전혀 관련이 없습니다.&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;_허깅페이스_추론_제공자&amp;quot;&amp;gt;허깅페이스 추론 제공자&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;HuggingFace 자체적으로 추론을 수행하지 않으며 — 제3자 &amp;lt;strong&amp;gt;providers&amp;lt;/strong&amp;gt; (Novita, Together, DeepInfra, Cerebras, SambaNova, Fireworks, Groq…)를 통합된 API를 통해 집계합니다. 가장 저렴하거나 가장 빠른 provider를 선택합니다. 다음은 2026년 5월에 확인된 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;제공자 HF&amp;lt;/th&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;$/M&amp;lt;/th&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;× 내 구독&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 (가장 저렴한)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;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;딥인프라&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;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×&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;함께&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;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;파이어워크스&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;/tbody&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;HuggingFace에서 가장 저렴한 제공업체(Novita)조차도 내 구독료의 19배를 청구합니다. 그리고 Ollama Cloud와 달리, HuggingFace는 PRO HF 구독이 없을 경우(월 \$9) 플랫폼 이용료도 추가로 청구합니다.&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에는 제가 사용하는 모델이 &amp;lt;strong&amp;gt;아예&amp;lt;/strong&amp;gt; 하나도 없습니다 — 그들의 카탈로그는 GPT-OSS, Llama, Qwen 32B로 제한됩니다. 비교할 수 없습니다.&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;_요금_비교_중국_공급업체&amp;quot;&amp;gt;요금 비교 — 중국 공급업체&amp;lt;/h2&amp;gt;
&amp;lt;div 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이 중국 모델이라면, 그 네이티브 API는 더 저렴해야 하지 않나요?&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;_deepseek_api_직접_platform_deepseek_com&amp;quot;&amp;gt;DeepSeek API 직접 (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;기간&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;비용/월&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;프로모션 -75% (5월 31일까지)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;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&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;정상 요금 (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;\$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;DeepSeek의 공격적인 프로모션(-75% 연장)에도 불구하고 직접 API 비용이 듭니다.&amp;lt;strong&amp;gt;5×&amp;lt;/strong&amp;gt;더 비싸다. 정상 요금으로는&amp;lt;strong&amp;gt;20배&amp;lt;/strong&amp;gt;. 그것이 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;_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는 자신의 모델만 제안합니다. 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;중국 모델을 모두 집계하는 유일한 공급자 — 하지만 마진을 붙여 재판매를 통해. 가격은 자바스크립트를 통해 동적으로 로드되어 스크래핑이 불가능하게 된다.&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;_요약_테이블_모든_제공자&amp;quot;&amp;gt;요약 테이블 — 모든 제공자&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;공급자&amp;lt;/th&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;$/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;월 비용&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;× 나의 구독&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;제미니 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;제미니 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;\$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 (프로모)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;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 미니&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;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;\$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;딥시크 V4 프로&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;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 (일반)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;딥시크 V4 프로&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;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;함께 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;클로드 소네트 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 예상 월간 비용 — 220M 입력 + 1.9M 출력
rectangle &amp;quot;Ollama Cloud Pro
~20€&amp;quot; as OC #4CAF50
rectangle &amp;quot;제미니 플래시
69달러&amp;quot; as GF #8BC34A
rectangle &amp;quot;DeepSeek 프로모션
$98&amp;quot; as DSP #FFC107
rectangle &amp;quot;GPT Mini
\$174&amp;quot; as GM #FF9800
rectangle &amp;quot;클로드 하이쿠
\$230&amp;quot; as CH #FF5722
rectangle &amp;quot;HF/Novita
$374&amp;quot; as HF #F44336
rectangle &amp;quot;DeepSeek 일반
\$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
$1 148&amp;quot; as CO #B71C1C
rectangle &amp;quot;GPT 5.5\n\$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;_숨은_비용_프롬프트_엔지니어링_및_반복&amp;quot;&amp;gt;숨은 비용 : 프롬프트 엔지니어링 및 반복&amp;lt;/h2&amp;gt;
&amp;lt;div 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;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;OpenCode + Ollama Cloud Pro + 내 거버넌스 에이전트(EAGER/LAZY/Hot/Warm/Cold, &amp;lt;a href=&amp;quot;0108_gouvernance_agent_opencode_eager_lazy_post.html&amp;quot;&amp;gt;거버넌스에 대한 문서&amp;lt;/a&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;0 반복적인 프롬프트 엔지니어링&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;90%+ first-shot correct&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;연속 8회 세션에서 딥시크 환각 제로&amp;lt;/strong&amp;gt;(문서화된 &amp;lt;a href=&amp;quot;0112_comparaison_kimi_glm_deepseek_long_contexte_plugin_gradle_opencode_post.html&amp;quot;&amp;gt;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;Claude 혹은 GPT 미세 조정 없이 거버넌스 없이 :&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번의 반복&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;체계적인 프롬프트 엔지니어링&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;컨텍스트가 30K 토큰을 초과하면 Kotlin/Gradle API에 대한 환각이 발생합니다.&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;월간 « 효과적 » 비용 — 토큰 × 반복 횟수 × 수정 시간 — 은 내 솔루션의 비용의 27배에서 54배이며, 이는 &amp;lt;a href=&amp;quot;0122_ratio_efficacite_27x_45x_flotte_experts_ia_vs_claude_gpt_post.html&amp;quot;&amp;gt;l&amp;amp;#8217;article sur le ratio d&amp;amp;#8217;efficacité&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;_ 토큰당 가격 문제에 그치는 것이 아니다. 가격이 57배인 Claude Opus이면서 3배 더 많은 반복이 필요한 경우, 실질적인 비용은 약 170배 더 높다. 솔직히 말해, 인턴에게 맥킨지 파트너 급여를 지불하는 것과 같다. &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;_ollama_cloud_pro_구독_누적_docker_방법&amp;quot;&amp;gt;Ollama Cloud Pro 구독 누적 : 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;한 개의 구독만으로는 충분하지 않을 때, 이를 여러 개 결합합니다. 원칙은 각 구독을 자체 Ollama 설치 및 자체 SSH 인증 키로 분리하는 것입니다. 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;전체 방법은 &amp;lt;a href=&amp;quot;0120_cumuler_deux_abonnements_ollama_pro_docker_post.html&amp;quot;&amp;gt;도커 누적 문서&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&amp;gt;@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultFontSize 12

title 아키텍처 — 한 머신에서 두 개의 Ollama Cloud Pro 구독
left to right direction

node &amp;quot;내 물리적 기계&amp;quot; {
  frame &amp;quot;Ollama 인스턴스 A (네이티브)
포트 :11434&amp;quot; as Native {
    database &amp;quot;정체성 A
/usr/share/ollama/.ollama/id_ed25519&amp;quot;
  }
  frame &amp;quot;Ollama 인스턴스 B (Docker)
포트 :11435&amp;quot; as Docker {
    database &amp;quot;Identité B\n~/ollama-b-data/id_ed25519&amp;quot;
  }
}

cloud &amp;quot;올라마 클라우드&amp;quot; {
  actor &amp;quot;계정 프로 A\n(이메일 1)&amp;quot; as A
  actor &amp;quot;프로 B 계정
(이메일 2)&amp;quot; as B
}

Native --&amp;amp;gt; A : &amp;quot;디바이스 키 A&amp;quot;
Docker --&amp;amp;gt; B : &amp;quot;디바이스 키 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;패턴은 간단합니다 :&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;볼륨 만들기`~/ollama-b-data`&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Docker 컨테이너 실행`ollama/ollama:0.20.2`에 매핑된`11435:11434`&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;컨테이너의 SSH 키를 가져오기 → ollama.com에 저장하기&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;하다`ollama signin`두 번째 이메일과 함께&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;두 개의 공급자와 OpenCode 구성하기 (`ollama`에서 :11434,`ollama-b`에 :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;두 개의 계좌, 하나의 파란색 카드, 갈등이 없습니다.&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;_감사가_측정하지_않는_것&amp;quot;&amp;gt;감사가 측정하지 않는 것&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;사각지대&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;영향&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;생산된 코드 품질&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;26억 개의 토큰은 코드가 컴파일되는지 여부를 말하지 않는다. 내 DeepSeek V4 Pro 8회 세션의 first-shot 성공률은 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;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는 무료이지만, 그 가치(에이전트, 샌드박스, 기록, 프롬프트 캐싱)는 엄청납니다. 구독 가격에 포함되지 않았습니다 — 별도의 도구입니다.&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;기회 비용&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시간을 저급 모델의 환각을 디버깅하는 데 쓰면 월 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;&amp;lt;strong&amp;gt;마음의 평화&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;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;인용된 가격은 2026년 5월의 가격입니다. 6개월 후에는 모든 것이 하락할 것입니다. 하지만 구조적인 힘의 균형 — 고정 구독 대비 종량제 — 은 대량 소비자에게 여전히 구독이 유리할 것이다.&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;_결론_숫자의_판결&amp;quot;&amp;gt;결론 : 숫자의 판결&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;73일 후, 1,022회 OpenCode 세션, 그리고 26억 토큰 :&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;미터법&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;판결&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;외부 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;$0.22&amp;lt;/strong&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;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유로/월&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;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/월&amp;lt;/strong&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;&amp;lt;strong&amp;gt;$1,157/월&amp;lt;/strong&amp;gt;— 58배 더 비쌈&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;&amp;lt;strong&amp;gt;\$374/mois&amp;lt;/strong&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 직접&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;— 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;가장 저렴한 등가물 (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/개월&amp;lt;/strong&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;&amp;lt;strong&amp;gt;시장에서 최고의 제안&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 월 20유로&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;&amp;lt;em&amp;gt;__&amp;lt;/em&amp;gt; 월 20유로에 Ollama Cloud를 통해 DeepSeek V4 Pro(1.6T 파라미터, MoE, CSA+HCA, 1M 컨텍스트)에 접속하면 세계 시장에서 가장 좋은 가성비를 누릴 수 있습니다. 중국 공급업체와 직접 거래해도 5배에서 20배 더 비쌉니다. 저비용 제공업체 집계를 통해 서비스하는 HuggingFace조차도 19배 더 비쌉니다. 서구 시장(Claude, GPT)은 50-60배 더 비싸며, 이는 마치 다른 행성과 같습니다.&amp;lt;/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;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;그렇다면, 월 20유로는 내 개발자 생활에서의 최고의 기술적 투자다. 그리고 이제 당신은それを 입증할 수 있는 수치가 있습니다.&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;_참조&amp;quot;&amp;gt;참조&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;Article 0108 — 거버넌스 에이전트 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;문서 0112 — DeepSeek V4 Pro 대 Kimi K2.6 대 GLM 5.1&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;문서 0119 — DGX Spark vs Cloud vs 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;0120_cumuler_deux_abonnements_ollama_pro_docker_post.html&amp;quot;&amp;gt;문서 0120 — Ollama Cloud Pro와 Docker용 두 구독 결합&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;0121_plugin_gradle_piloter_deux_instances_ollama_pro_post.html&amp;quot;&amp;gt;문서 0121 — Ollama 두 인스턴스를 제어하는 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;0122_ratio_efficacite_27x_45x_flotte_experts_ia_vs_claude_gpt_post.html&amp;quot;&amp;gt;문서 0122 — 효율 비율 27배에서 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;_요금_출처&amp;quot;&amp;gt;요금 출처&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 — Pro 요금 \$20/개월&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 — 공식 요금 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 — 가격 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 — 제공업체별 요금&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 — Claude 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://openai.com/api/pricing/&amp;quot;&amp;gt;OpenAI — 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 — 추론 요금&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 — 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 — GPU 요금 및 모델&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 — 사용 가능한 모델&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 — GitHub 오픈 소스 CLI&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>아키텍처 « 독립 플러그인 + 소비자 루트 » : 왜 내 Gradle 빌드가 중복되는가</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;#_문제_내_시간을_잡아먹은_세_가지_아키텍처&amp;quot;&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;#_option_1_분리된_플러그인_dogfooding_안_함&amp;quot;&amp;gt;Option 1 : 분리된 플러그인 (Dogfooding 안 함)&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_옵션_2_클래식_모노레포_includeplugin&amp;quot;&amp;gt;옵션 2 : 클래식 모노레포 (&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;#_옵션_3_컴포지트_빌드_includebuild&amp;quot;&amp;gt;옵션 3 : 컴포지트 빌드 (&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;#_해결책_독립된_두_개의_빌드_소비자_루트&amp;quot;&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;#_워크플로우&amp;quot;&amp;gt;워크플로우&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_구체적인_워크플로&amp;quot;&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;#_왜_이_아키텍처가_나를_사로잡았는가&amp;quot;&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;#_1_네이티브_도그푸딩_즉각적인_피드백&amp;quot;&amp;gt;1. 네이티브 도그푸딩, 즉각적인 피드백&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_2_제로_콘피그_클로닝&amp;quot;&amp;gt;2. 제로-콘피그 클로닝&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_3_격리된_빌드_독립된_ci&amp;quot;&amp;gt;3. 격리된 빌드, 독립된 CI&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;#_서브_폴더의_해부학_name_plugin&amp;quot;&amp;gt;서브 폴더의 해부학 `{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;#_버전_카탈로그_두_개의_별도_파일&amp;quot;&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;#_우리가_하지_않는_것&amp;quot;&amp;gt;우리가 하지 않는 것&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_비교_세_가지_아키텍처가_마주하는_도그푸딩&amp;quot;&amp;gt;비교 : 세 가지 아키텍처가 마주하는 도그푸딩&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_dag_계약_루트_빌드는_하위_폴더에서_절대_가져오지_않는다&amp;quot;&amp;gt;DAG 계약: 루트 빌드는 하위 폴더에서 절대 가져오지 않는다.&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_결론_시간을_절약해_주는_겉보기상의_중복&amp;quot;&amp;gt;결론 : 시간을 절약해 주는 겉보기상의 중복&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_참고문헌&amp;quot;&amp;gt;참고문헌&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;읽기 시간 : 11 minutes&amp;lt;/div&amp;gt;
&amp;lt;p&amp;gt;Gradle 플러그인 저장소를 복제합니다. 터미널을 엽니다. 무엇을 입력하시나요? 그리고?&amp;lt;code&amp;gt;./gradlew tasks&amp;lt;/code&amp;gt;, 물론이죠. 하지만 어느 폴더인가요? 루트 폴더인가요? 서브모듈 폴더인가요? 먼저 README를 읽어야 builder 방법을 알 수 있나요? 주저한다면 그저 한 초일 뿐인데, 프로젝트 아키텍처가 깨졌습니다.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;다음은 제가 이 문제를 완전히 해결한 방법 — 그리고 왜 이 패턴은 오늘날 내 모든 Gradle 플러그인의 서명인`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;_문제_내_시간을_잡아먹은_세_가지_아키텍처&amp;quot;&amp;gt;문제: 내 시간을 잡아먹은 세 가지 아키텍처&amp;lt;/h2&amp;gt;
&amp;lt;div 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 플러그인을 구성하는 전통적인 방법들마다 치명적인 결함이 있었다.&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;_option_1_분리된_플러그인_dogfooding_안_함&amp;quot;&amp;gt;Option 1 : 분리된 플러그인 (Dogfooding 안 함)&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;프로젝트에는 플러그인만 포함되어 있습니다. 소비 예시가 없습니다. 없음 프로젝트가 그것을 수행한다. 테스트하려면 외부 프로젝트를 만들어야 합니다, y 를 통해 플러그인을 참조하다`mavenLocal`또는 복합 빌드, 그리고 단지 거기서 그것이 작동하는지 확인하다&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;소비 예시가 없는 Gradle 플러그인은 소비 예시가 없는 라이브러리다 통합 테스트. 마지막 수정이 무엇인지 결코 알 수 없습니다. 사용자 경험을 깨뜨렸습니다.&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_클래식_모노레포_includeplugin&amp;quot;&amp;gt;옵션 2 : 클래식 모노레포 (&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`생성합니다`settings.gradle.kts`와 함께`include(&amp;quot;plugin&amp;quot;)`. 루트와 서브모듈은 같은 데몬과 동일한 구성을 공유합니다, 같은 카탈로그들. 편리하지만 결합된.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;문제점:&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;해야 한다&amp;lt;/strong&amp;gt;서브 모듈과 동일한 Gradle 버전을 가지고 있다.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;`libs.versions.toml`공유됨—공통 버전 카탈로그, 의존성&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;/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;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;CI는 두 모듈을 모두 빌드해야 하며, 플러그인만 변경된 경우에도 마찬가지입니다.&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;_옵션_3_컴포지트_빌드_includebuild&amp;quot;&amp;gt;옵션 3 : 컴포지트 빌드 (&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;우리는 두 개를 별도의 Gradle 빌드로 분리하고 이를 통해 연결합니다. &amp;lt;code&amp;gt;includeBuild(&amp;quot;mon-plugin&amp;quot;)`안에`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;이미 더 나아졌습니다. 플러그인 빌드가 격리되었습니다. 하지만 소비자 외부 빌드를 명시적으로 참조해야 한다 — 출력의 `./gradlew tasks`루트에서 의 올바른 구성에 따라 달라집니다 복합체. 복제는 zero-config이 아니다: 알아야 할 점은 플러그인은 별도의 폴더에 위치하고, 루트가 이를 참조합니다 등.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;나는 더 나은 것을 찾고 있었어. 훨씬 더 나은 것을.&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;_해결책_독립된_두_개의_빌드_소비자_루트&amp;quot;&amp;gt;해결책: 독립된 두 개의 빌드, 소비자 루트&amp;lt;/h2&amp;gt;
&amp;lt;div 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;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;키 :`{name}-plugin/`는 &amp;lt;strong&amp;gt;완전하고 독립적인&amp;lt;/strong&amp;gt; Gradle 프로젝트입니다. 그는 자체 wrapper, 자체 settings, 자체 카탈로그 de를 가지고 있습니다. 버전. 그것은 클론되고, 빌드되고, 테스트되고, 게시되며, 루트 없이 존재를 알고 있어야 합니다.&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;#39;&amp;lt;strong&amp;gt;une&amp;lt;/strong&amp;gt;&amp;#39; 일만 한다: 플러그인을 적용한다.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;경우에 따라 세 줄`bakery-gradle`. 그만입니다. 제로`include()&amp;lt;code&amp;gt;, 영`includeBuild()&amp;lt;/code&amp;gt;, 제로 하위 프로젝트. 어떤 플러그인을 적용하는 것처럼 일반적인 Gradle 빌드 어떤 소비자가それを 할까요?&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;_워크플로우&amp;quot;&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&amp;gt;@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultFontSize 12

left to right direction

package &amp;quot;루트 (소비자)&amp;quot; #CCFFCC {
    usecase &amp;quot;저장소를 복제&amp;quot; as Clone
    usecase &amp;quot;./gradlew 작업&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/ (독립 빌드)&amp;quot; #CCE5FF {
    usecase &amp;quot;./gradlew 빌드&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;플러그인에 의존함\n로컬에서 게시됨&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;_구체적인_워크플로&amp;quot;&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-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;_왜_이_아키텍처가_나를_사로잡았는가&amp;quot;&amp;gt;왜 이 아키텍처가 나를 사로잡았는가?&amp;lt;/h2&amp;gt;
&amp;lt;div 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;/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_네이티브_도그푸딩_즉각적인_피드백&amp;quot;&amp;gt;1. 네이티브 도그푸딩, 즉각적인 피드백&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Gradle 플러그인을 테스트하는 가장 좋은 방법은 사용하는 것이다. 모의 단위 테스트가 아닙니다. 아닌`GradleRunner`프로젝트와 함께 테스트. &amp;lt;strong&amp;gt;실제&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-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;플러그인이 깨진 경우, 루트 빌드가 이를 알려줍니다. 갈 필요가 없습니다. 외부 테스트 프로젝트를 찾는. 도그푸딩은 &amp;lt;strong&amp;gt;첫 번째&amp;lt;/strong&amp;gt; 새로운 기여자가 시작하는 작업. 이것이 최종적인 smoke test입니다.&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;규칙은 간단합니다 : 루트가 컴파일되고`./gradlew tasks` 플러그인 작업을 표시하고, 플러그인이 정상적으로 작동합니다. 놀라운 점은 없습니다. 생산 중&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_제로_콘피그_클로닝&amp;quot;&amp;gt;2. 제로-콘피그 클로닝&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`그리고 새로 온 사람은 모든 것을 본다 아무것도 설정하지 않고 걷다.`build.gradle.kts`뿌리는 플러그인의 살아있는 사용 설명서. Le`site.yml`옆에 예상된 구성을 보여줍니다.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;대안과 비교해 보세요 : 세 개의 문단으로 구성된 README가 설명하십시오: 플러그인을 어떻게 빌드하고 프로젝트를 어떻게 빌드하는지 테스트의. 새로운 기여자가 README를 대각선으로 읽는다, 잘못하고, 이슈를 열어 — 그런데 정보가 &amp;lt;strong&amp;gt;실행 가능&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;가장 견고한 문서는 읽는 문서가 아니다. 이것은 우리가&amp;lt;strong&amp;gt;실행하다&amp;lt;/strong&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&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_3_격리된_빌드_독립된_ci&amp;quot;&amp;gt;3. 격리된 빌드, 독립된 CI&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;플러그인에는 자체 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;플러그인에서 루트를 건드리지 않고 Gradle 업그레이드&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;플러그인에 종속성을 추가하지만 루트로 유출되지 않도록&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&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;/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;플러그인을 빌드/테스트하는 CI를 하나 가지고, 또 다른 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;root — 독립적으로&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;_서브_폴더의_해부학_name_plugin&amp;quot;&amp;gt;서브 폴더의 해부학 `{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;플러그인 폴더에 무엇이 있는지 좀 더 자세히 살펴봅시다:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;거기에 다 있습니다. 루트와 하위 폴더 사이에 분산이 없습니다. 플러그인에서 작업하는 개발자한테 절대 필요하지 않다 떠나다`codebase-plugin/`. 플러그인을 사용하는 개발자 뿌리만 바라보고 — 그리고`build.gradle.kts`3줄의 그는 그에게 필요한 모든 것을 말해준다.&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;_버전_카탈로그_두_개의_별도_파일&amp;quot;&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;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;(뿌리)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;의존성 개수&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;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 (플러그인 + README 선택 사항)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;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;역할&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;누가 읽나&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;/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;/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;의존성 충돌을 디버깅하는 데 한 시간을 이미 보냈다면 당신의 플러그인과 테스트 프로젝트 사이에서, 그 가치를 이해합니다. 이 분리. 독립적인 카탈로그는 이 문제를 제거합니다. 구성으로.&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;_우리가_하지_않는_것&amp;quot;&amp;gt;우리가 하지 않는 것&amp;lt;/h2&amp;gt;
&amp;lt;div 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;/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;/strong&amp;gt; 당신은 해야 합니다.&amp;lt;code&amp;gt;publishToMavenLocal&amp;lt;/code&amp;gt; ou 루트가 이를 소비하기 전에 레포지터리에 배포하라.&amp;lt;/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;/strong&amp;gt; 제약입니다. 루트 플러그인을 외부 클라이언트처럼 소비합니다 — Maven을 통해. 정확히 서드파티 프로젝트가 하는 것처럼. 플러그인을 게시할 수 없는 경우, 루트가 즉시 알려줍니다.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;_비교_세_가지_아키텍처가_마주하는_도그푸딩&amp;quot;&amp;gt;비교 : 세 가지 아키텍처가 마주하는 도그푸딩&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;독립형 플러그인&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;모노레포`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;복합`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;루트 + 독립형 플러그인.&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`플러그인의 작업을 제공한다&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;(blank)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;루트와 독립적인 플러그인 빌드&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;카탈로그 버전 분리&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;없다`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;네이티브 도그푸드 구성 없이&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;독립형 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;(Empty output)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;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;(No content to translate; output 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;오른쪽 열은 모든 체크박스를 체크한다. 그러니깐 내가 더 뒤로 돌아가겠습니다.&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;_dag_계약_루트_빌드는_하위_폴더에서_절대_가져오지_않는다&amp;quot;&amp;gt;DAG 계약: 루트 빌드는 하위 폴더에서 절대 가져오지 않는다.&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;이 아키텍처는 DAG N0→N3에 자연스럽게 통합됩니다. 내 워크스페이스에서. 패턴은 : *플러그인 N2는 허브입니다 그 자체의 종속성에서 N3 루트는 단말이다. 허브*를 적용하는&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;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 DAG 계약 — 소비자 루트 vs 독립 플러그인
package &amp;quot;N3 — 뿌리 (저장소)&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;버전 플러그인&amp;quot;
ROOT -down-&amp;amp;gt; PLUGIN : &amp;quot;publishToMavenLocal&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`6줄이다. 없다`src/&amp;lt;/code&amp;gt;, 없음`buildSrc/&amp;lt;code&amp;gt;, 없어`gradle/rag-bench.gradle.kts&amp;lt;/code&amp;gt;. 그냥 &amp;lt;code&amp;gt;plugins { alias(libs.plugins.codebase) }`그리고 저장소들. 모든 복잡함이 존재한다`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;_결론_시간을_절약해_주는_겉보기상의_중복&amp;quot;&amp;gt;결론 : 시간을 절약해 주는 겉보기상의 중복&amp;lt;/h2&amp;gt;
&amp;lt;div 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;gradlew&amp;lt;/code&amp;gt;, 두`settings.gradle.kts`, 두`libs.versions.toml`— 이건 중복입니다!&amp;lt;/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;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;중복 »은 같은 정보를 두 곳에 복제하는 것이다. 여기서, 두 개의 &amp;lt;strong&amp;gt;별개의&amp;lt;/strong&amp;gt; 파일은 두 가지 &amp;lt;strong&amp;gt;별개의&amp;lt;/strong&amp;gt; 용도로 사용됩니다 : 플러그인 카탈로그 (30+ builder용 의존성)와 카탈로그 루트에서 (소비를 위해 2-3개의 의존성). 플러그인 래퍼 (개발을 위한 잠긴 버전)과 루트 래퍼 (잠시 다른 버전, 플러그인 연습을 위해).&amp;lt;/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;/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;비용? 한 주문`publishToMavenLocal`플러그인 빌드 사이 그리고 루트 빌드. 이점은? 아키텍처의 명확함을 제공하는 하류 디버깅에 소요되는 시간을 없앤다&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;이 패턴을 배포한 이후로`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;, 그리고 다른 플러그인의`foundry/public/&amp;lt;code&amp;gt;, 저는 더 이상 주저하지 않고 내 저장소 중 하나에서 터미널을 열 때. 그 첫 번째 반사 —&amp;lt;/code&amp;gt;./gradlew tasks`— 항상 걷고, 항상 준다 좋은 작업들, 그리고 모든 것이 정상인지 즉시 알려줍니다.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;그게 바로 좋은 아키텍처다. README에서는 읽을 수 없다. 터미널에서 체험되며, 10초 미만에 이루어집니다.&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;_참고문헌&amp;quot;&amp;gt;참고문헌&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;문서 0105 — Gradle 워크플로에 Graphify 통합하기&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;0117_granularisation_oss_css_repositorie_workspace_post.html&amp;quot;&amp;gt;Article 0117 — OSS/CSS 세분화&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;0123_knowledge_graph_moteur_recommandation_articles_connexes_post.html&amp;quot;&amp;gt;Article 0123 — Knowledge Graph을(를) 추천 엔진으로서&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: 시간 순서의 « 관련 기사 »를 죽인 방법</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;#_장면_화요일_5월_12일_오후_5시_30분&amp;quot;&amp;gt;장면 : 화요일 5월 12일, 오후 5시 30분&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_진단_어떤_템플릿도_보지_못하는_세_가지_유형의_관계&amp;quot;&amp;gt;진단 : 어떤 템플릿도 보지 못하는 세 가지 유형의 관계&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_해결책_gradle_파이프라인_뜨거운_llm_호출이_아님&amp;quot;&amp;gt;해결책: Gradle 파이프라인, 뜨거운 LLM 호출이 아님&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;#_아키텍처_베이커리가_그래피파이를_가져오고_엔진이_아님&amp;quot;&amp;gt;아키텍처 : 베이커리가 그래피파이를 가져오고, 엔진이 아님&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_파이프라인_스캔_그래프_템플릿&amp;quot;&amp;gt;파이프라인 : 스캔 → 그래프 → 템플릿&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_연대순의_폴백&amp;quot;&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;#_신흥_온톨로지_기사들이_서로를_알지_못한_채_모일_때&amp;quot;&amp;gt;신흥 온톨로지: 기사들이 서로를 알지 못한 채 모일 때&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_dag_계약_누가_누구에게_중요한가&amp;quot;&amp;gt;DAG 계약: 누가 누구에게 중요한가&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_우리가_얻는_것_그리고_우리가_얻지_못하는_것&amp;quot;&amp;gt;우리가 얻는 것 (그리고 우리가 얻지 못하는 것)&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_전망_블로그가_공개된_지식_그래프이다&amp;quot;&amp;gt;전망: 블로그가 공개된 지식 그래프이다&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_결론_파일_시스템은_템플릿이_무시하는_것을_알고_있다&amp;quot;&amp;gt;결론: 파일 시스템은 템플릿이 무시하는 것을 알고 있다.&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_참고문헌&amp;quot;&amp;gt;참고문헌&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;읽기 시간 : 14 minutes&amp;lt;/div&amp;gt;
&amp;lt;p&amp;gt;pgvector와 LangChain4j 통합에 관한 기사를 읽고 계십니다. 페이지 하단에 JBake 연관 문서를 제안합니다. 클릭하면&amp;amp;#8230;&amp;amp;#8203; 구성에 관한 문서입니다. Kitty Terminal의. 두 가지 사이의 유일한 공통점은? 그들은 미만으로 출판되었습니다. 15일 간격. 이것은 권고가 아닙니다. 이는 위장한 캘린더입니다. 사설.&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;_장면_화요일_5월_12일_오후_5시_30분&amp;quot;&amp;gt;장면 : 화요일 5월 12일, 오후 5시 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;나는 내 기사 중 하나를 다시 읽고 있다 — &amp;lt;a href=&amp;quot;0106_knowledge_graph_outil_comprehension_codebase_post.html&amp;quot;&amp;gt;Knowledge Graph를 코드베이스 이해 도구로 하는 것에 관하여&amp;lt;/a&amp;gt; 내용이 밀집되어 있습니다: 노드, 엣지, 커뮤니티, PlantUML, 온보딩. 한 기사 15분 읽기 기술은 참여를 유도한다`graphify-gradle`, &amp;lt;code&amp;gt;plantuml-gradle&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;페이지 하단에, ‘관련 기사’가 나에게 제시한다:&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;Firebase Contact Form (0113)에 대한 기사&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Eager/Lazy 메커니즘에 관한 기사 (0108)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Gradle 스크립트 → 플러그인 마이그레이션에 대한 기사 (0102)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;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;이 네 개의 기사 중 세 개는 &amp;lt;strong&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;저는 템플릿을 보고 있어요.&amp;lt;code&amp;gt;post.thyme&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-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`연대순 목록입니다.`postStat.index lt 4`잡는다 현재 게시물이 아닌 처음 네 개. 그게 다야. 논리가 없습니다. 유사성. 내용에 대한 개념이 없다. 단지 루프일 뿐이다.`for`로 가장한 추천.&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;JBake의 버그가 아닙니다. 모든 것의 기본 동작입니다. 정적 사이트 생성기: 게시물 목록은 플랫하고 날짜순으로 정렬되어 있습니다. 템플릿은 가진 것을 가지고 가능한 한 최선을 다합니다.&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;/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;_진단_어떤_템플릿도_보지_못하는_세_가지_유형의_관계&amp;quot;&amp;gt;진단 : 어떤 템플릿도 보지 못하는 세 가지 유형의 관계&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;내 기사 코퍼스 — 4개월 동안 23개의 게시물 — 풍부한 구조를 가진 시간순 템플릿이 완전히 압도한다 :&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;명시적 참조&amp;lt;/strong&amp;gt; : 저는 사용합니다`xref:`내 기사에 대량으로&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;문서 0122는 0106(knowledge graph)과 0116(compartimentage)를 참조합니다. (에피스테믹적). 이러한 링크들은 편집상의 &amp;lt;strong&amp;gt;hard linking&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;&amp;lt;strong&amp;gt;공유된 태그&amp;lt;/strong&amp;gt; : 각 기사는 몇 가지의`:jbake-tags:`. 기사에 대한&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) 있다`gradle, graphify, plantuml, knowledge-graph`. Knowledge Graph (0106) 문서는 &amp;lt;code&amp;gt;knowledge-graph, graphify, plantuml&amp;lt;/code&amp;gt;. 일곱 중 세 개의 태그가 일치합니다 — 43% 일치율.&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;공동 발생하는 명명된 엔티티&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;네 개의 다른 기사에 함께 나타납니다. « LangChain4j », Ollama&amp;quot;, &amp;quot;플러그인 Gradle&amp;quot;는 여섯 다른 곳에 있습니다. 이것들은 태그가 아닙니다. 선언된 — 그것들은 코퍼스의 *patterns émergents*이며, 오직 한 NLP만 감지할 수 있다.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;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 기사들 사이의 관계의 세 계층
package &amp;quot;층 1 — 명시적 참조&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;레이어 2 — 공유된 태그&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;3계층 — 발생적 온톨로지&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;오늘, 내 사이트는 이 세 레이어 중 &amp;lt;strong&amp;gt;아무것도&amp;lt;/strong&amp;gt; 사용하지 않습니다. 그것은 사용 제로 레이어: 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;_해결책_gradle_파이프라인_뜨거운_llm_호출이_아님&amp;quot;&amp;gt;해결책: Gradle 파이프라인, 뜨거운 LLM 호출이 아님&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;첫 번째 유혹은 bake 시점에 LLM을 호출하는 것일 것이다 : « Pour 이 문서에서 corpus에서 가장 유사한 세 개의 문서를 찾습니다.  그것을 하지 마세요.&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;각각에 대한 LLM 호출`./gradlew bake`시간과 돈이 들고, 그리고 비결정성을 빌드에 도입합니다. LLM의 응답은 두 빌드 사이에서 내용이 변경되지 않은 채 전환할 수 있습니다. 귀하의 CI가 됩니다. 재현할 수 없습니다, 여러분의 테스트는 불안정해집니다.&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;해결책은 결정적입니다: 그래프를 사전 계산하는 Gradle 파이프라인 유사성과 그것을 안에 저장합니다`graph.json`. 템플릿이 결과를 읽는다 그는 절대로 계산을 시작하지 않는다.&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;_아키텍처_베이커리가_그래피파이를_가져오고_엔진이_아님&amp;quot;&amp;gt;아키텍처 : 베이커리가 그래피파이를 가져오고, 엔진이 아님&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;이것은 건축 설계에서 가장 중요한 점입니다. 유혹이 될 것이다 협업을 연결하여 안에`engine/build.gradle.kts`: 엔진이 적용됩니다 graphify는 스캔을 위해, bakery는 베이크를 위해, engine은 사이에 다리를 놓는다 둘 다&amp;lt;/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은 *소비자 터미널*입니다 — 적용합니다 플러그인이지만 비즈니스 로직을 구현하지 않습니다. 규칙은 : &amp;lt;strong&amp;gt;증명 책임은 독점 플러그인에 있다.&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;올바른 아키텍처 — Bakery가 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 베이커리가 Graphify를 가져옵니다 — 엔진은 아무것도 모릅니다.
package &amp;quot;N3 — 엔진&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 — 빵집&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 — 그래파이&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;플러그인 { 빵집 }&amp;quot;
BAK -down-&amp;amp;gt; GRF : &amp;quot;플러그인 { graphify } ← N2→N0 확인&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;DAG 계약이 준수됩니다: bakery (N2)는 graphify (N0)를 임포트합니다, N2 &amp;amp;gt; N0, 위반 없음. Engine (N3)는 bakery (N2)를 가져옵니다. N3 &amp;amp;gt; N2, 정상.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;엔진에는 참조하는 코드 줄이 *aucune*개 없습니다.&amp;lt;code&amp;gt;graph.json&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;relatedPosts&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;_파이프라인_스캔_그래프_템플릿&amp;quot;&amp;gt;파이프라인 : 스캔 → 그래프 → 템플릿&amp;lt;/h3&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;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;스캔&amp;lt;/strong&amp;gt; (그래프화) :`graphify-plugin`워크스페이스를 스캔합니다. 그의&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;현재 형태로는 이미 그것들을 감지합니다`xref:`사이`.adoc`그리고 에 노출하다`graph.json`유형 테두리처럼`reference`.&amp;lt;br&amp;gt;
편집 콘텐츠를 위해 이를 풍부하게 합니다: * 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;) 각의`.adoc`블로그의 * 태그 공출현 계산 → 간선`tag_cooccurrence`무게와 함께 * TF-IDF를 통해 설명에서 명명된 엔티티 추출 → edges`entity_overlap` * 섹션을 삽입합니다`blog_articles`안에`graph.json`존재하는&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; (베이커리) : 순간의`./gradlew bake`, `BakeryPlugin`침대&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`관련 글을 각 포스트에 대해 해결합니다.&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;) : 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;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;플랫한 연대순 목록 대신 구조화된 형태&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;템플릿은 동일합니다 — 데이터가 &amp;lt;strong&amp;gt;어디서&amp;lt;/strong&amp;gt; 오는지 모릅니다. 베이커리와 JBake 사이의 계약만 변경되었습니다 :`published_posts`(목록) 순차적인) 된다`relatedPosts`(그래프 가중 맵).&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;배지 « 이유 » (예:`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;) 설명해 줍니다: 독자에게 &amp;lt;strong&amp;gt;왜&amp;lt;/strong&amp;gt; 이 기사가 관련되는지. 이것은 아니다 투명성만으로는 — 토폴로지에 대한 교육이야 당신의 내용. (Empty)&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;_연대순의_폴백&amp;quot;&amp;gt;연대순의 폴백&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Si `graph.json`없습니다 (로컬 빌드, 사전 스캔 없이, 첫 배포, 아직 graphify 스캔을 통합하지 않은 CI), 템플릿 우아하게 저하되어야 합니다 :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;기본 동작은 현재 동작과 동일합니다. Le 추천 엔진은 &amp;lt;strong&amp;gt;점진적인 개선&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;_신흥_온톨로지_기사들이_서로를_알지_못한_채_모일_때&amp;quot;&amp;gt;신흥 온톨로지: 기사들이 서로를 알지 못한 채 모일 때&amp;lt;/h2&amp;gt;
&amp;lt;div 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;strong&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;구체적인 예를 들어 봅시다. 내 코퍼스에 있는 세 개의 기사:&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 — 벤치마크 DGX Spark vs Cloud LLM 구독&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;0121 — Gradle 플러그인 Ollama Pro 두 인스턴스 제어&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;0122 — 효율 비율 27x 45x 함대 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;이 세 개의 기사 사이에는 xref가 &amp;lt;strong&amp;gt;아무것도&amp;lt;/strong&amp;gt; 없습니다. 그들의 태그는 하지 않다 20% (« ollama », « llm » 일반적)으로만 일치합니다. 그러나 가벼운 NLP 설명에 대해 명백한 클러스터가 드러납니다 : « 비용 », « 구독 »,  클라우드 », « GPU », « API 키 », « Ollama Pro », « 효율성 », « 비율&amp;lt;/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;LLM 자체 호스팅의 경제 vs cloud&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 packageBackgroundColor #E8F8F5

title Cluster Ontologique Émergent — &amp;quot;경제적 LLM Self-Hosted&amp;quot;

package &amp;quot;감지된 클러스터: 경제-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;대규모 언어 모델&amp;quot;.
    Cluster découvert par NLP :
    co-occurrences &amp;quot;비용&amp;quot;, &amp;quot;GPU&amp;quot;,
    &amp;quot;구독&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;이 온톨로지 클러스터는 복합 엣지가 된다`graph.json`[No output - empty string]&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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는 의도적으로 가볍습니다 — TF-IDF + 코사인 유사성 (대상에 대한) 설명. BERT가 필요하지 않습니다, 언어 모델이 필요하지 않습니다. 코퍼스는 23개의 문서로 구성되어 있으며, 유사도 행렬은 한 50 Ko 크기의 JSON 파일.&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;NLP의 힘은 알고리즘의 정교함에서 비롯되지 않습니다, 하지만 *코퍼스 크기*와 *설명 품질&amp;amp;gt;. 한 `:jbake-description:`bien 쓰여진 150자에는 더 많은 TF-IDF에 대한 신호: 3000단어 전체 기사&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;_dag_계약_누가_누구에게_중요한가&amp;quot;&amp;gt;DAG 계약: 누가 누구에게 중요한가&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;이것이 건축 학문이 보상을 받는 지점이다. 이 DAG N0→N3 정의된`engine/build.gradle.kts`간단한 규칙을 제시한다 : 어떤 프로젝트도 상위 수준의 프로젝트를 임포트하지 않습니다.&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;소비자 플러그인&amp;lt;/th&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;소비자 수준&amp;lt;/th&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;유효합니까 ?&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;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;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;엔진&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;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;엔진&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;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;✅ 기술 OK, 그러나 &amp;lt;strong&amp;gt;개념적으로 틀림&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;Engine 적용하다 bakery. Bakery 적용하다 graphify. 이건데 다. manifests 언젠가 저는 연구를 위해 벡터 스토어 코드베이스 (N1)를 추가하고 싶어요. 의미 교차 코퍼스, 베이커리도これを인포트합니다. 엔진은 변하지 않습니다.&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;패턴은 : &amp;lt;strong&amp;gt;N2 플러그인은 자체 의존성의 허브입니다&amp;lt;/strong&amp;gt; Engine N3은 허브가 아니라 허브를 적용하는 터미널이다.&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;_우리가_얻는_것_그리고_우리가_얻지_못하는_것&amp;quot;&amp;gt;우리가 얻는 것 (그리고 우리가 얻지 못하는 것)&amp;lt;/h2&amp;gt;
&amp;lt;div 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;/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;이전&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;이후&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;관련 기사 = 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;관련 글 = Knowledge Graph에서 가중치 상위 4개&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;리더가 RAG를 읽음 → 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;독자가 RAG를 읽는다 → 추천 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;왜에 대한 투명함이 전혀 없습니다.&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;뱃지`xref 0122`, &amp;lt;code&amp;gt;tag:gradle&amp;lt;/code&amp;gt;, `cluster:rag`보이는&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 표준, 노력 제로, 가치 제로&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;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;/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;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;독자는 내 내용의 *topologie*를 발견한다.&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;/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;quot;참된&amp;quot; 추천 엔진이 아닙니다 (협업 기반이 아닌&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;필터링, A/B 테스트 없음, 피드백 루프 없음)&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;품질은 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;설명이 비어 있습니다, TF-IDF는 아무것도 보지 못합니다&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는 오프라인 상태이며 — 새 기사들은 여기서만 클러스터화됩니다&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;/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;_전망_블로그가_공개된_지식_그래프이다&amp;quot;&amp;gt;전망: 블로그가 공개된 지식 그래프이다&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;이 기능은 더 넓은 관점을 열어줍니다: 그리고 만약`cheroliv.com` 그 자신 *knowledge graph navigable*가 되고 있었는가?&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;기사는 노드입니다. 태그는 커뮤니티입니다. xref는 간선들. 독자는 더 이상 고립된 기사를 읽지 않는다 — 그는 에서 탐색한다 *지식 그래프*에서 현재 문서는 진입점입니다.&amp;lt;/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;corpus 지도&amp;lt;/strong&amp;gt; : 온톨로지 클러스터, 피벗 항목 (가장 가장자리가 많은), 읽기 경로 추천 (« Graphify에 대한 글을 좋아하셨다면, 다음을 읽어보세요 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;이제 블로그가 아닙니다. 이것은 *시맨틱 아틀라스*입니다.&amp;lt;/p&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`이미 존재한다 그에게는 탐색 인터페이스만 부족합니다.&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;_결론_파일_시스템은_템플릿이_무시하는_것을_알고_있다&amp;quot;&amp;gt;결론: 파일 시스템은 템플릿이 무시하는 것을 알고 있다.&amp;lt;/h2&amp;gt;
&amp;lt;div 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;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;템플릿`post.thyme`변하지 않는다. 바뀌는 것은 우리가 하는 것이다. 그에게 먹이를 준다. 전 : 정렬된 Java 목록`date`. 후 : 세 층 분석에서 유도된 가중 그래프 — xrefs, 동시 발생 태그와, 그리고 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;루프가 닫혔습니다. graphify가 워크스페이스를 스캔합니다. Bakery가 읽습니다 그래프가 템플릿을 먹입니다. 독자는 추천을 봅니다 관련된. 그리고 엔진 — 지휘자 — 는조차도 모르는 그 모든 것이 있다.&amp;lt;/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&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_참고문헌&amp;quot;&amp;gt;참고문헌&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;문서 0105 — Graphify를 Gradle 워크플로에 통합&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;Article 0106 — 지식 그래프를 이해 도구로&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;0117_granularisation_oss_css_repositorie_workspace_post.html&amp;quot;&amp;gt;Article 0117 — La Granularisation OSS/CSS&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;0121_plugin_gradle_piloter_deux_instances_ollama_pro_post.html&amp;quot;&amp;gt;Article 0121 — Gradle 플러그인 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;문서 0122 — 27배에서 45배 더 효율적 : AI 전문가 함대&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×에서 45×까지 Claude와 GPT보다 더 효과적이다: 23명의 파인튜닝된 AI 전문가로 구성된 함대가 시장을 어떻게 장악하는가</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;#_문제_왜_파인튜닝을_하고_rag를_하지_않는가&amp;quot;&amp;gt;문제: 왜 파인튜닝을 하고 RAG를 하지 않는가?&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_아이디어_전문가들의_함대_단일_모델이_아니다&amp;quot;&amp;gt;아이디어: 전문가들의 함대, 단일 모델이 아니다.&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_현미경_아래_코드베이스_7_900_파일_144_000_줄&amp;quot;&amp;gt;현미경 아래 코드베이스: 7 900 파일, 144 000 줄&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;읽는 시간 : 22 minutes&amp;lt;/div&amp;gt;
&amp;lt;p&amp;gt;일상적인 소프트웨어 개발에서 LLM을 사용할 때, 결국 항상 같은 벽에 부딪히게 됩니다: 모델은 &amp;lt;strong&amp;gt;당신의&amp;lt;/strong&amp;gt; 코드베이스를 알지 못합니다. 당신이 그런 패턴의를 사용하고 있다는 것을 알지 못합니다.https://fr.wikipedia.org/wiki/Graphe_orient%C3%A9_acyclique[DAG], 그런 Gradle 관례, 그런 인식론적 계층화 당신의https://fr.wikipedia.org/wiki/AsciiDoc[.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;그래서 나는 직설적으로 질문을 던졌다: 내 워크스페이스에서 fine-tuned된 모델 팀이 내 스타일로 코드를 뱉어내며 매번 설명할 필요 없이 작동하는 데 드는 비용은 얼마나 될까? 그리고 무엇보다: 이 접근 방식이 기존의 전용 솔루션인 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;그 대답이 저를 놀라게 했어요. 내가 생각한 것보다 훨씬 적었어요. 그리고 해당 업계 거인들에 대한 비용 대비 효율성 비율은 비열합니다. 자세한 내용은 다음과 같습니다.&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;_문제_왜_파인튜닝을_하고_rag를_하지_않는가&amp;quot;&amp;gt;문제: 왜 파인튜닝을 하고 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는 괜찮습니다. &amp;lt;a href=&amp;quot;0106_knowledge_graph_outil_comprehension_codebase_post.html&amp;quot;&amp;gt;Mon knowledge graph&amp;lt;/a&amp;gt; 및 &amp;lt;a href=&amp;quot;0116_compartimentage_epistemique_automatise_llm_post.html&amp;quot;&amp;gt;mon compartimentage épistémique&amp;lt;/a&amp;gt;가 작동합니다. 하지만 RAG에는 근본적인 결함이 있습니다: 매 쿼리마다 관련 컨텍스트를 다시 주입해야 합니다. 프롬프트가 길어지고, 지연이 급증하며, 모델은 절대 당신의 패턴을 내면화하지 않습니다 — 매번 외부 문서처럼 읽습니다.&amp;lt;/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;/strong&amp;gt; 있습니다 당신이 쓰고 있다는 것입니다.&amp;lt;code&amp;gt;build.gradle.kts`통해 기록된 작업을 가진`tasks.register&amp;lt;/code&amp;gt;, 당신의`AGENTS.adoc`표준화된 구문이 뒤따르고, 당신의 워크스페이스가 버러로 구조화되어 있다는 것을. 그는 상기시킬 필요가 없습니다.&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;파인튜닝은 RAG의 대체가 아닙니다. 이는 보완적인 계층입니다. RAG는 자주 변경되는 데이터(로그, 티켓, 최신 커밋 등)에 여전히 관련이 있습니다. 파인튜닝은 장기적인 &amp;lt;strong&amp;gt;구조적&amp;lt;/strong&amp;gt; 및 &amp;lt;strong&amp;gt;스타일리틱&amp;lt;/strong&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&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_아이디어_전문가들의_함대_단일_모델이_아니다&amp;quot;&amp;gt;아이디어: 전문가들의 함대, 단일 모델이 아니다.&amp;lt;/h2&amp;gt;
&amp;lt;div 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;/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 플러그인 (Kotlin, 컨벤션 플러그인, 복합 빌드)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;DAG 엔진 (Kotlin, 계획, 실행)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;LLM 에이전트 (Python, LangChain4j, RAG)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;아키텍처 문서 (AsciiDoc, 거버넌스)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;인프라/데브옵스 (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;하나의 모델은 어디서든 좋지만, 어디에서도 뛰어나지 않아야 한다. 해결책? 하나&amp;lt;strong&amp;gt;특수 모델 함대&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 #FAFAFA
skinparam defaultFontSize 13

package &amp;quot;Ollama 클라우드&amp;quot; {
  rectangle &amp;quot;DeepSeek V4-Pro
1.6T / 49B 자산&amp;quot; as V4Pro #FF6B6B
  rectangle &amp;quot;6× V4-Flash-FT
292B / 13B 활성&amp;quot; as Flash #4ECDC4
  rectangle &amp;quot;6× Gemma4 26B-FT\n26B / 3.8B 활성&amp;quot; as Gemma #FFE66D
  rectangle &amp;quot;5× Qwen3.6 35B-FT
35B / 3B 활성&amp;quot; as Qwen36 #A8E6CF
  rectangle &amp;quot;5× Qwen3.5 35B-FT
35B / 3B 활성&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;복잡한 Gradle 작업&amp;quot;
V4Pro --&amp;amp;gt; Gemma : &amp;quot;아스키독 포맷&amp;quot;
V4Pro --&amp;amp;gt; Qwen36 : &amp;quot;코틀린 리팩터링&amp;quot;
V4Pro --&amp;amp;gt; Qwen35 : &amp;quot;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;전체 함대 :&amp;lt;strong&amp;gt;1 오케스트레이터 + 22 전문가 = 23 모델&amp;lt;/strong&amp;gt;. 그리고 월간 추론 비용은 얼마인가요?&amp;lt;strong&amp;gt;17€/월&amp;lt;/strong&amp;gt;연간 Ollama Pro 구독 — 할당량이 초과되면 계정을 누적하여 확장 가능하며, 이는 &amp;lt;a href=&amp;quot;0120_cumuler_deux_abonnements_ollama_pro_docker_post.html&amp;quot;&amp;gt;다중 계정 Docker 방법&amp;lt;/a&amp;gt;에서 보여준 바와 같이입니다. 17유로에서 시작하여 실제 필요에 따라 단계별로 20유로를 추가합니다. 낭비 없음.&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;_현미경_아래_코드베이스_7_900_파일_144_000_줄&amp;quot;&amp;gt;현미경 아래 코드베이스: 7 900 파일, 144 000 줄&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;훈련 비용을 추정하기 전에, 어떤 것을 훈련해야 하는지 알아야 합니다. 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/
----

추적된 파일에 대한 결과는 다음과 같습니다 :

[cols=&amp;quot;2,2&amp;quot;]
|===
|미터법 |가치

|추적된 소스 파일 |**7,896** |코드 라인 (LOC) |**143 805** |Git 저장소 |**26** |주요 언어 |Kotlin (1 903), AsciiDoc (2 765), Python (5 029)
|===

[IMPORTANT]
====
이 5,000+ 파이썬 파일들은 포함됩니다`runtimes/`노트북과 유틸리티 스크립트. 애플리케이션 핵심(Gradle 플러그인, DAG 엔진, 에이전트)은 주로 Kotlin과 AsciiDoc으로 작성되었습니다.
====

이 질량을 기준으로, 의 합성 데이터셋을 생성할 수 있다고 추정합니다**~1억 7천만 토큰**인스트럭터 LLM을 통해 (코드베이스를 instruction/response 쌍으로 증류).

=== 전문가 유형별 데이터 세트 세부 사항

[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
----

== 모델 선택: 100% MoE, 밀도 제로

여기가 건축이 진정으로 효율적으로 변하는 곳이다.

*밀집* 모델은 모든 토큰에 대해 모든 가중치를 활성화합니다. *MoE* (Mixture of Experts) 모델은 매개변수의 일부만 활성화합니다. 추론 비용 — 그리고 파인튜닝 비용 — 은 매개변수에 비례합니다.**활성화된**, 파라미터에 적용되지 않음**저장된**.

올라마 클라우드에서 오늘 시장에서 MoE가 제공하는 것을 살펴봅시다 :

[cols=&amp;quot;3,2,2,1,3&amp;quot;]
|===
|모델 |총 파라미터 |활성화된 |활성화 |올라마 클라우드

|DeepSeek V4-Pro |1.6T |49B |3.1% |✅ 토착의 |DeepSeek V4-Flash |292B |13B |4.5% |✅ 토착의 |Gemma 4 26B-A4B |26B |3.8B |14.6% |✅ 토착의 |Qwen3.6-35B-A3B |35B |3B |8.6% |❌ (아직 통합되지 않음) |Qwen3.5-35B-A3B |35B |3B |8.6% |✅ 토착의
|❌ Qwen3.5-27B |27B |27B |100% 밀집된 |제거된 |❌ Qwen3.5-4B/9B |4B/9B |4B/9B |100% 조밀한 |제외된 |❌ Gemma 4 31B |31B |31B |100%밀집된 |제거된
|🧪 Gemma 4 E2B/E4B |5B/8B |5B/8B |100% 밀도 |검증 파이프라인
|===

[CAUTION]
====
활성화 비율은 판별 기준이다. Gemma 4 26B-A4B는 전체 26B 매개변수 중 3.8B만 활성화한다 — 이는 밀집형 27B에 비해 7배 적으며, 코딩 에이전트 성능은 비슷하다(LiveCodeBench v6에서 77.1% 대 80%).
====

=== 왜 나는 밀집 모델들을 포기하는가?

예외: Gemma 4 E2B와 E4B 모델. 이 두 모델은 Apache 2.0 라이선스 하에 있으며, 비용을 최소화하여 학습 파이프라인을 테스트하고 검증하기에 적합합니다. E2B에서 2-3시간 실행 비용은 $5 미만 — 데이터셋이나 하이퍼파라미터가 문제가 있다면 몇 달러만으로도 알 수 있습니다. 파이프라인이 안정되면 심지어 초미세 작업(트리ivial 재포맷팅, 이진 분류)에도 사용할 수 있습니다. 그러나 주력 함대의 전문가들에 대해서는 저는 여전히 MoE를 선호합니다. 그 이유는:

.しかしだけではない: これらのGemma 4 E2B/E4B, 細かい匿名化の専門家

[NOTE]
====
Google은 이러한 작은 모델들을 네이티브 툴 호출에서 경쟁력 있게 설계했습니다 — 그들은 함수를 호출하고, 정규 표현식을 조작하며, 패턴을 검색할 수 있습니다. 익명화 예시(이메일, 토큰, UUID, 파일 경로)로 파인튜닝한 후, 그들은**데이터셋 익명화 도구**초고속. 그들에게 원시 텍스트 블록을 주면, 그들은 이를 호출한다.`sed`/`regex`툴 호출을 통해 정리된 블록을 반환합니다. 추론 비용은 미미합니다: 5B가 활성화되어 보급형 GPU 한 장이면 충분합니다. 이 모델들은 비즈니스 전문가가 아닙니다 — 이들은**숙련된 노동자**데이터셋의 생산 체인에서
====

* 그 훈련 VRAM(QLoRA에서도 마찬가지)는 다음과에 비례한다**총 27B**
* 그의 훈련 시간은 비례한다**27B 활성화된**

Qwen3.6-35B-A3B (MoE)와 비교해 봅시다:

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

|VRAM QLoRA |~55 GB |~35 GB |1.6× |GPU 시간 (8M 토크) |~12시간 |~4시간 |**3배** |파인 튜닝당 비용 |~$24 |~$8 |**3×** |SWE-bench 검증됨 |75.0% |73.4% |비교 가능한
|===

MoE는 에이전트 코딩 성능을 5% 차이로 훈련하는 데 3배 더 저렴합니다. 22명의 전문가 사이에서 차이가 수천 유로에 달합니다.

== 트레이닝 추정 : 811 H100-시간

나는 위에서 설명한 데이터셋을 기반으로 QLoRA 4-bit을 이용해 모델별 훈련 비용을 세분화했다.

=== 경량 마이크로 전문가 MoE (3-3.8B 활성화된)

이 모델은 실행당 H100 1개(80 GB)가 필요합니다. 모델의 Q4 VRAM은 대략 13-18 GB이며, LoRA 옵티마이저 상태와 활성화에 충분한 공간을 남깁니다.

[cols=&amp;quot;4,2,3,2&amp;quot;]
|===
|모델 |실행 횟수 |GPU-시간/실행 |총 GPU-h

|Gemma 4 26B-A4B-FT |6 |3.5 h |**21시** |Qwen3.6-35B-A3B-FT |5 |4.0 h |**20시** |Qwen3.5-35B-A3B-FT |5 |4.0 h |**20시간** |소계 마이크로 | | |*61 h*
|===

=== 임원용 V4-Flash (13B 활성화 / 292B 저장)

DeepSeek V4-Flash는 특별한 경우이다. 토큰당 13B만 활성화되더라도 Q4의 전체 모델은 약 142 GB로 — 훈련 중에 모든 전문가들이 VRAM에 있어야 합니다. 따라서**3× H100**달리면서 편안해지기 위해

[cols=&amp;quot;4,2,3,3,2&amp;quot;]
|===
|모델 |데이터셋 |그래픽 처리 장치 |시간 |총 GPU-시간

|V4-Flash-Base 범용 |50M 토큰 |3배 H100 |50 h |**150시간** |V4-Flash 전문가 ×5 |15M 토큰 |3배 H100 |40 h |**600시간** |임원 소계 | | | |*750 h*
|===

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

[NOTE]
====
비교를 위해, QLoRA 없이 V4-Flash의 전체 파인튜닝은 약 4000 H100시간이 6개 모델에만 소요됩니다. QLoRA는 비용을 5로 나눕니다. 이는 이 규모에서 프로젝트를 경제적으로 실행 가능하게 만드는 유일한 방법입니다.
====

=== GPU 가격 : 2026년 5월에 누가 무엇을 대여하고 있나요?

저는 주요 H100 대여 업체를 벤치마크했습니다. 계량 요금은 811시간 기준입니다.

[cols=&amp;quot;3,2,2,2&amp;quot;]
|===
|임대인 |$/H100·h |총 811h |신뢰성

|**Vast.ai 스팟** |$1.60 |**$1,298** |★★☆ 중단 가능한 |**크루소 클라우드** |$2.00 |**$1,622** |★★★★ 신뢰할 수 있는 데이터센터 |유동 스택 |$1.98 |$1 606 |★★★ |TensorDock |$1.80 |$1 460 |★★☆ |런팟 |$2.29 |$1 857 |★★★ 좋은 타협 |코어웨이브 |$2.25 |$1 825 |★★★★ 기업 |람다 랩스 |$2.49 |$2 019 |★★★★ 프리미엄
|===

[TIP]
====
Vast.ai spot은 가장 저렴하지만, tus runs(실행) 은 언제든지 중단될 수 있습니다. 학습에는 위험합니다. 시간당 $2.00인 Crusoe은 신뢰성 대비 가격 측면에서 최적입니다. Vast와의 차액 $324는 마음의 평화를 가질 가치가 있습니다.
====

=== Wall-clock : 전체를 훈련시키는 데 얼마나 걸리나요?

단일 3× H100 노드만 사용하면 31개의 실행(1개 범용 + 30개 전문가)은 약**12일**순차적으로. 2개의 노드를 병렬로 임대하면, 다음과 같이 떨어집니다.**6일**.

실제로, 마이크로 전문가는 별도의 노드(각각 1 H100)에서 실행될 수 있으며, V4-Flash는 3× H100 노드를 점유합니다. 지능적인 스케줄링을 통해 :

----

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일간 대여, 모두 접혀 있습니다.

== 추론: Ollama Cloud에서 23개 모델, 월 17유로부터 시작

이것은 건축의 가장 우아한 부분입니다.

Ollama Cloud에서는 Pro 플랜 기능으로 비공개 모델을 업로드할 수 있습니다. 내 22개의 파인튜닝된 모델은 ... 로_push되고 있습니다.`.gguf`내 Ollama 공간에 있으며, 비공개로 표시되어 있고, 표준 API를 통해 접근할 수 있습니다.

[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
----

=== 월 17유로부터 시작하며, 필요 시 확장합니다.

Ollama Pro 플랜 (연간 17유로/월)은 3개의 경쟁 클라우드 모델을 허용합니다. 이 아키텍처에서는 동시에 2-3명 이상의 전문가가 필요하지 않습니다.

* (V4-Pro) 분류 단계는 1 슬롯을 차지하며, 몇 초 정도 소요됩니다.
* 대상 전문가는 1개의 슬롯을 차지하며, 응답을 생성하는 데 걸리는 시간
* 3번째 슬롯은 병렬 작업을 위해 예약되어 있습니다.

사용량이 증가하면, xref:0120_cumuler_deux_abonnements_ollama_pro_docker_post.adoc[구독 합산에 대한 내 글]에서 설명한 Docker 방법을 통해 추가 Pro 계정을 추가합니다. 월 100유로 Max 플랜은 없습니다 — 실제로 필요한 것만 추가합니다.

유일한 제한 요인은**GPU 시간 할당량**Ollama 월간(무료 플랜 대비 50배 Pro 플랜). 저는 하루에 몇백 건 정도 사용하기에 충분합니다.

[IMPORTANT]
====
V4-Pro (49B 활성화) 호출의 마진 비용과 Gemma 4 26B-A4B (3.8B 활성화) 비교 시의 비용은**나에게조차도: 0**. 이건 Ollama 플랜이 compute 차이를 흡수하는 것입니다. 내 유일한 지표는 지연 시간입니다.
====

=== 라우팅 실무&amp;lt;/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;트래픽의 대부분이 작은 것을 통해 흐릅니다. 큰 모델은 최후의 수단으로만 개입한다.

== 아픈 비교 : Claude, GPT, Gemini, Grok vs 내 함대

이곳에서 격차가 급격히 벌어집니다. 같은 양의 작업(하루 500건의 요청, 평균적으로 입력 300 토큰 + 출력 2,000 토큰)이 2026년 5월 시장의 독점 솔루션으로 얼마나 드는지 살펴봅시다.

=== Anthropic — Claude (2026년 5월 요금)

Anthropic는 최근 가격 *인하*를 단행했습니다. DeepSeek과 Qwen과 같은 오픈 웨이트 솔루션의 압력이 느껴지고 있습니다.

[cols=&amp;quot;3,2,2,2,2&amp;quot;]
|===
|모델 |$/MTok |$/MTok |비용/일 |**월 비용**

|Opus 4.7 (가장 지능적인) |$5 |$25 |$25.75 |**$772** |소네트 4.6 (균형 잡힌) |$3 |$15 |$15.45 |**$463** |Haiku 4.5 (가장 빠른) |$1 |$5 |$5.15 |**154달러**
|===

[CAUTION]
====
이 요금은 *후* Anthropic의 마지막 가격 인하(2026년 5월) 이후의 것입니다. 그 이전에는 Opus가 MTok당 $15/$75였습니다. 오픈-웨이트에 대한 두려움은 현실입니다.
====

Claude의 가장 저렴한 모델조차 — Haiku 4.5에**154€/월**— 17유로의 Ollama Pro 구독료보다 9배 더 비쌉니다. 그리고 Haiku는 내 코드베이스를 알지 못하며, 내 스타일로 생성하지도 않고, 체계적인 프롬프트 엔지니어링이 필요합니다.

가장 똑똑한 — Opus 4.7 — 예산을 초과하는**772유로/월**하루에 500건 정도의 소규모 요청량에 대해. 즉**45배 더 비싸다**그 내 해결책.

=== OpenAI — GPT (2026년 5월 요금)

[cols=&amp;quot;3,2,2,2,2&amp;quot;]
|===
|모델 |$/MTok |출력 $/MTok |비용/일 |**월 비용**

|GPT 5.5 (가장 똑똑한) |$5 |$30 |$30.90 |**$927** |GPT 5.4 (표준) |$2.50 |$15 |$15.45 |**$463** |GPT 5.4 미니 |$0.75 |$4.50 |$4.64 |**$139**
|===

OpenAI의 « 경제적 » 모델인 GPT 5.4 Mini(월 139유로)조차도 내 구독료의 8배에 달하며, 내 규칙에 대해 *rien* 을 아는 바가 없고, 무거운 프롬프트 엔지니어링이 필요합니다.

GPT 5.5, 그들의 주력 제품 :**927유로/월**. *zero-shot*을 내 코드베이스에. 스타일이 미숙하여 작업당 2-3번의 반복이 필요하면, 실제 월간 비용이 쉽게 두 배로 늘어납니다.

=== Google — Gemini (2026년 5월 요금)

[cols=&amp;quot;3,2,2,2,2&amp;quot;]
|===
|모델 |$/MTok |$/MTok |일당 비용 |**비용/월**

|Gemini 3.1 Pro (고급) |$3.50 |$17.50 |$18.00 |**540달러** |Gemini 3.1 Pro |$2 |$10 |$10.30 |**$309** |제미니 3 플래시 |$0.30 |$1.50 |$1.54 |**46달러**
|===

Gemini 3 Flash 월 46유로는 순수 가격 면에서 경쟁력이 있지만 — 하지만 파인튜닝 없이, 일반적 코드를 생성합니다. *Cheroliv* 코드가 아닙니다.

=== Grok — xAI (2026년 5월 요금)

[cols=&amp;quot;3,2,2,2,2&amp;quot;]
|===
|모델 |$/MTok |$/MTok |일일 비용 |**월 비용**

|그록 4 |$4 |$16 |$16.48 |**$494** |그로크 4 미니 |$0.80 |$3.20 |$3.30 |**$99**
|===

=== Microsoft — Azure OpenAI (2026년 5월 요금)

Azure는 직접 OpenAI API와 동일한 가격을 적용하지만, 인프라와 네트워크 비용을 추가로 청구합니다. 관리형 서비스에 대해 10-20%의 추가 비용을 예상해야 합니다.

[cols=&amp;quot;3,2,2,2,2&amp;quot;]
|===
|모델 |$/MTok |$/MTok |비용/일 |**월 비용**

|GPT 5.4 (Azure, 글로벌) |$2.50 |$15 |$15.45 |**$463** |GPT 5.4 + 인프라스트럭처 |15% 추가 비용 |15%의 추가 비용 |$17.77 |**$533**
|===

[NOTE]
====
위에 명시된 요금은 API용이며, &amp;#39;채팅&amp;#39; 구독은 아닙니다(Claude Pro 월 $20, ChatGPT Plus 월 $20). 여기서 비교하는 것은 개발자 사용법이며, 이는 하루에 수백 건의 요청을 API를 통해 이어서 생성, 리팩터링, 문서화를 하는 경우입니다. 일반 사용자 구독은 500 요청/일 이전에 할당량이 빠르게 소진됩니다.
====

=== 최종 비교 표

[cols=&amp;quot;3,2,2,2,2,2&amp;quot;]
|===
|해결 |지식 코드베이스 |통합된 스타일 |비용/개월 |반복 |**실제 비용**

|**내 함대 (Ollama Pro)** |✅ 세부 조정된 |무게에서 |**17유로** |0-1 |**17유로** |클로드 오퍼스 4.7 |❌ 제로 샷 |❌ 필요한 프롬프트 |$772 |2-3 |$1 500-2 300 |클로드 하이쿠 4.5 |❌ 제로샷 |필요한 프롬프트 |$154 |3-4 |$460-615 |GPT 5.5 |❌ Zero-shot |❌ 필요한 프롬프트 |$927 |2-3 |$1 854-2 781 |GPT 5.4 |❌ 제로샷 |필요한 프롬프트 |$463 |2-3 |$925-1 390 |GPT 5.4 미니 |❌ 제로샷 |❌ 필수 프롬프트 |$139 |3-5 |$417-695 |제미니 3.1 프로 하이 |❌ 제로 샷 |필요한 프롬프트 |$540 |2-3 |$1 080-1 620 |Grok 4 |제로샷 |❌ 필수 프롬프트 |$494 |2-3 |$988-1 482 |**애저 GPT 5.4** |❌ 제로샷 |❌ 필요한 프롬프트 |$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 비용 효율성 보고서 — 내 함대 대 시장 (2026년 5월)
rectangle &amp;quot;17유로/월&amp;quot; as OURS #4ECDC4
rectangle &amp;quot;Opus 4.7
772€&amp;quot; as OPUS #FF6B6B
rectangle &amp;quot;Sonnet 4.6
463유로&amp;quot; as SONNET #FF8B94
rectangle &amp;quot;GPT 5.5\n927€&amp;quot; as GPT #FFD93D
rectangle &amp;quot;Gemini 3.1 Pro\n540€&amp;quot; as GEMINI #6C5CE7
rectangle &amp;quot;Grok 4
494유로&amp;quot; as GROK #A8E6CF
rectangle &amp;quot;Haiku 4.5
154유로&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;미세조정된 AI 전문가 23명에게 월 17유로. Claude Opus 4.7 플래그십은 비용이 듭니다.**45배 더 비싸다**내 *mon* 코드베이스에 낮은 품질로. GPT 5.5의 비용은**54배 더 비싸다**. 가장 저렴한 모델조차 (Gemini Flash 46유로) 비용이 발생한다**2.7배 더**내 워크스페이스를 알지 못한 채.

판단은 확정적이다: 2026년 MoE 아키텍처에 대한 파인튜닝은 단순히 « 더 저렴한 » 것이 아니다. 이것은 효율성의 요인이다**27×에서 54×까지**비용 대비 품질 비율에서 독점 솔루션과 비교할 때. Anthropic, OpenAI, Google — 모두가 이것을 알고 있으며, 그래서 그들은 패닉에 빠져 가격을 내리고 있다.

== 효율 인자: 5-8× vs V4-Pro 단독 사용

두 극단을 비교해 봅시다.

=== 시나리오 A : V4-Pro 단독으로 모든 것

[cols=&amp;quot;3,2&amp;quot;]
|===
|미터법 |가치

|코드베이스에 대한 지식 |0 (제로샷) |협약 명명 |40-60% 첫 시도 정확한 |필요한 프롬프트 엔지니어링 |예, 체계적인 |작업당 반복 |2-3 |인퍼런스 비용 |$100/월 (Ollama Max) |평균 지연 |~20 tok/s + 네트워크
|===

=== 시나리오 B : 오케스트레이션된 MoE 함대

[cols=&amp;quot;3,2&amp;quot;]
|===
|미터법 |값

|코드베이스 지식 |통합된(세밀 조정된) |명명 규칙 |90-95%가 첫 시도에서 정확함 |프롬프트 엔지니어링 |아니요 (스타일은 가중치에 있습니다) |작업당 반복 횟수 |0-1 |추론 비용 |$60/월 (3배 Ollama Pro) |평균 지연 |~50 tok/초 (경량 모델)
|===

[cols=&amp;quot;4,2,2,2&amp;quot;]
|===
|차원 |V4-Pro만 |함대 MoE |우편배달부

|월간 클라우드 비용 |$100 |**$60** |1.7× |주입할 컨텍스트 |높은 (prompt) |**영** |∞ |평균 반복 횟수 |2-3 |**0-1** |2-3× |느껴지는 지연 |초당 20토큰 |50 tok/s |**2.5배** |첫 번째 샷이 정확합니다 |50% |90%+ |**1.8×**
|===

**전체 효율 인자 : 5-8×**— 개발자 생산성에서 느끼는

[NOTE]
====
요인은 토큰이나 달러로 측정되지 않는다. 그것은 « 모델에게 이미 알고 있어야 할 것을 설명하는 데 내가 쓰는 시간 »으로 측정된다. 파인튜닝된 모델에서는 이 시간이 0에 수렴한다.
====

== 전체 예산 : 일회성 투자, 터무니없는 패키지

[cols=&amp;quot;3,2&amp;quot;]
|===
|직책 |금액

|학습 GPU (811 H100-h, Crusoe $2/h) |**$1,622** |합성 데이터셋 생성 (LLM API) |**400달러** |추론 (Ollama Pro 연간, $17/월) |**$204** |**첫 해 총계** |**$2 226** |**월 등가** |**$186** |**쿼리당 예상 비용** |&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;예산 1년차: $2 226&amp;quot; as Total #E8E8E8 {
  rectangle &amp;quot;GPU 훈련
$1 622 (73%)&amp;quot; as Train #FF6B6B
  rectangle &amp;quot;데이터셋
증류
$400 (18%)&amp;quot; as Data #FFE66D
  rectangle &amp;quot;Ollama Pro
$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/월 첫해,**$17/월**2년 차부터, 넷플릭스 프리미엄 구독 가격, 24시간 7일 가용한 23명의 AI 전문가 팀을 위해, 그들이 *내 스타일*로 코드를 작성하는 데 내가 그들에게 내 규칙을 설명할 필요가 없이.

=== 수익성 계산

edster.cloud와 약 95%의 마진을 가지고, 손익분기점은**~$2 340 추가 CA**연간. 실제로는 :

* 1 소프트웨어 파일럿 미션 → $3 000-5 000 → 투자금 회수 + 이익
* 1 기업 교육 → $5 000-10 000 → 편안한
* 암시적인 업셀 : &amp;quot;내 AI 팀이 내 소프트웨어 공장을 장악한다&amp;quot; → 상업적 차별화

2년 차부터는 트레이닝 비용을 더 이상 지불할 필요가 없습니다(데이터셋 업데이트 제외). 연간 비용이 떨어집니다**$204**($17/월). 비용 대비 가치 비율은 유니콘과 같다 : 거의 없음.

== 운동의 한계

이것은 여전히 *대략적인 추정*입니다. 여러 변수가 예산 변동을 일으킬 수 있습니다:

[cols=&amp;quot;2,3&amp;quot;]
|===
|변수 |영향

|합성 데이터셋 생성 |증류 API가 엉망이면(환각, 잘못된 쌍) 인간 시간을 들여 정리해야 합니다. 숨은 비용. |Ollama GPU 시간 할당량 |사용량이 프로 플랜 할당량을 초과하면 플랜을 업그레이드하거나 전용 GPU로 전환해야 합니다. |파인 튜닝의 실제적인 품질 |실패한 파인튜닝 → 다시 해야 함. 진짜 위험은 하이퍼파라미터이며, GPU 위치가 아님. |모델의 빠른 진화 |Qwen3.6/Gemma 4는 1개월 미만 전에 출시되었습니다. 6개월 후 다음 세대는 더 좋고 파인튜닝하기에 더 저렴할 것입니다.
|===

[WARNING]
====
단일 마이크로 모델에서 파이프라인을 먼저 테스트하지 않고 811시간의 학습을 시작하지 마세요. Qwen 35B-A3B에서 3-4시간 실행은 $8 비용이 듭니다. 데이터셋이 망가졌다면 $8로 알 수 있고, $1,622로는 알 수 없습니다.
====

== 결론 : 파인튜닝이 접근 가능한 럭셔리가 되었습니다.

1년 전, 292B 파라미터 모델을 파인튜닝하는 것(QLoRA를 사용하더라도)은 연구에 해당했습니다. 오늘날, 2026년 5월, 이는 독립 개발자에게 가능한 프로젝트가 되었습니다:

* **811 H100-시간**트레이닝의 — $1,622 크루소에서
* **23개의 특화된 모델**Ollama Cloud에 로드됨 —**$17/개월**
* **0 서버, 0 로컬 GPU, 0 유지보수 인프라**

민주화는 세 가지 수렴하는 요인에서 비롯되었다:

1. **MoE 아키텍처**— 효과적으로 fine-tuning할 수 있게 해 주는, 가중치의 일부만 활성화된 모델들
2. **Ollama Cloud의 고정 플랜**— 변동 비용(per-token)을 고정 비용(abonnement)으로 바꾸는
3. **고품질 오픈-웨이트 모델**— DeepSeek V4, Qwen3.6, Gemma 4는 MIT/Apache 2.0 라이선스 하에 있습니다.

문명은 우리가 생각하지 않고 수행할 수 있는 중요한 작업의 수를 늘림으로써 발전한다.

파인튜닝, 그게 바로 그것이야: 기계를 위임해서 더 이상 신경을 쓰지 않아도 되는 작업이지. 그리고 2026년에는 신입 개발자 월급보다 저렴해.

---

_게시된 글 {jbake-date}_

== 함대의 미세 조정된 모델

[cols=&amp;quot;2,2,2,2,3&amp;quot;]
|===
|활성화된 크기 |숫자 |기본 모델 |도메인 |데이터셋

|13B |1 |디프시크-V4-플래시-베이스 |일반 워크스페이스 |50M 토큰 |13B |5 |딥시크-V4-플래시 |그레이들, 코틀린, DAG, 문서, 인프라 |15M 토큰 |3.8B |6 |Gemma 4 26B-A4B |마이크로 작업, 포맷팅, 스크립트 |5M 토큰 |3B |5 |Qwen3.6-35B-A3B |리팩터링, 리뷰, 중간 문서 |5M 토큰 |3B |5 |Qwen3.5-35B-A3B |레거시 작업, 호환성 |5백만 토큰
|===

== 자원

=== HuggingFace의 open-weight 모델

* https://huggingface.co/deepseek-ai/DeepSeek-V4-Flash-Base[DeepSeek V4-Flash-Base — 292B 파라미터, 라이선스 MIT]
* https://huggingface.co/Qwen/Qwen3.6-35B-A3B[Qwen3.6-35B-A3B — 35B/3B 활성화됨, 라이선스 Apache 2.0]
* https://huggingface.co/Qwen/Qwen3.5-35B-A3B[Qwen3.5-35B-A3B — 35B/3B 활성화된, Apache 2.0 라이선스]
* https://huggingface.co/google/gemma-4-26B-A4B-it[Gemma 4 26B-A4B — 26B/3.8B 활성화된, Apache 2.0 라이선스]

=== 요금 출처 (2026년 5월)

* https://ollama.com/pricing[Ollama Cloud 요금제 — Pro 플랜 $17/월 연간]
* https://www.anthropic.com/pricing[Anthropic 요금 — Claude API (Opus 4.7 $5/$25, Sonnet 4.6 $3/$15, Haiku 4.5 $1/$5)]
* https://openai.com/api/pricing/[OpenAI 요금 — GPT API (GPT 5.5 $5/$30, GPT 5.4 $2.50/$15)]
* https://deepinfra.com/pricing[DeepInfra 요금 — GPU 인스턴스 H100 $1.79/h, B200 $2.79/h]
* Vast.ai, Crusoe Cloud, FluidStack, TensorDock, RunPod, CoreWeave, Lambda Labs — 수동 가격 기록, 2026년 5월

=== 공식 발표

* https://qwen.ai/blog?id=qwen3.6-35b-a3b[공식 발표 Qwen3.6-35B-A3B — Qwen 블로그]— 에이전틱 코딩, Apache 2.0, 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>두 개의 Ollama Pro 인스턴스를 병렬로 실행하는 집에서 만든 Gradle 플러그인</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;#_소개&amp;quot;&amp;gt;소개&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_왜_인프라에_gradle를_사용해야_할까요&amp;quot;&amp;gt;왜 인프라에 Gradle를 사용해야 할까요?&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_전체_build_gradle_kts&amp;quot;&amp;gt;전체 build.gradle.kts&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_핵심_업무의_해부&amp;quot;&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;#_startollamab_런처&amp;quot;&amp;gt;startOllamaB` — 런처&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_signinb_대화형&amp;quot;&amp;gt;signInB` — 대화형&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_pullmodelb_조정_가능한&amp;quot;&amp;gt;pullModelB` — 조정 가능한&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_statusollamaall_진단&amp;quot;&amp;gt;statusOllamaAll` — 진단&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_cleanollamab_정체성을_보존하는_청소&amp;quot;&amp;gt;cleanOllamaB` — 정체성을 보존하는 청소&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;#_완전한_워크플로우_제로에서_두_세션까지&amp;quot;&amp;gt;완전한 워크플로우 — 제로에서 두 세션까지&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_왜_이것을_진짜_gradle_플러그인으로_만들지_않을까요&amp;quot;&amp;gt;왜 이것을 진짜 Gradle 플러그인으로 만들지 않을까요 ?&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_배운_교훈&amp;quot;&amp;gt;배운 교훈&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_결론&amp;quot;&amp;gt;결론&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_자원&amp;quot;&amp;gt;자원&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;읽기 시간 : 18 minutes&amp;lt;/div&amp;gt;
&amp;lt;p&amp;gt;도커 명령어를 직접 입력할 필요가 있나요? 모든 것을 Gradle 작업에 캡슐화할 수 있는데요. 외부 셸 스크립트 없이 내 듀얼-Ollama Pro 설정을 산업화한 방법을 보여드리겠습니다. 시작, 중지, 식별, 로그인, 풀 — 모든 것이`./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;_소개&amp;quot;&amp;gt;소개&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;이전 글에서는 Ollama Pro 계정 두 개를 동일한 머신에서 공존시키기 위해 두 번째 인스턴스를 Docker 컨테이너에 격리하는 방법을 보여 주었습니다. 설정은 작동하지만 일상적인 관리가 빨리 번거로워집니다: 컨테이너 이름을 기억하고 올바른 명령을 다시 실행해야 합니다.&amp;lt;code&amp;gt;docker exec&amp;lt;/code&amp;gt;, checker 어떤 포트가 활성화되어 있는지&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;당신은 14개의 터미널이 열려 있는 순간, 그때 입력하고 있는 것을 알고 있죠`docker exec ollama-instance-b ollama list`네 번째로, 그리고 당신은 « 더 깨끗한 방법이 반드시 있어야 해 »라고 생각하나요?&amp;lt;/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;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;코드를 빌드하는 것만이 아니다. Gradle은 로컬 인프라스트럭처의 지휘자처럼. 프로비저닝, 검증, 테스트, 정리를 수행하는 작업들. 모두가 하나의 &amp;amp;#8230;&amp;amp;#8203;에 버전 관리됨.&amp;lt;code&amp;gt;build.gradle.kts`옆에 사는`docker-compose.yml&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&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;개발자&amp;quot; as Dev

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

  usecase &amp;quot;OllamaB 시작&amp;quot; as Start
  usecase &amp;quot;stopOllamaB&amp;quot; as Stop
  usecase &amp;quot;신원 확인&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 데몬&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 네이티브&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;_왜_인프라에_gradle를_사용해야_할까요&amp;quot;&amp;gt;왜 인프라에 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은 빌드 도구입니다. 보통, 그것은 컴파일하고, 테스트하고, 패키징합니다. 그것은 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;그런데 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;우리는 원한다 :&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;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;모델 풀 전에 신원이 확인되어야 합니다&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;컨테이너가 다운되면 API 테스트가 조용히 실패합니다&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;순수한 의존성 그래프에서. Gradle은 그에 맞게 설계되었습니다. Kotlin DSL은 태스크 작성을 유연하게 만들고, 그리고`doLast`시스템 명령어를 애플리케이션 코드처럼 실행할 수 있게 해줍니다.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;그리고 무엇보다: 유지할 bashrc도 없고, 나뒹구는 스크립트도 없으며`~/bin`, docker-compose 옆에 Makefile이 없습니다. 모든 것이 하나의 파일에 있으며, 프로젝트 코드와 같은 위치에 있습니다.&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&amp;quot;&amp;gt;전체 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;이게 내가 결국 작성한 파일이예요. 볼륨 프로비저닝부터 검증 curl까지, 대화형 로그인을 거치며 모든 과정을 처리합니다.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;자. 열두 가지 작업, 하나의 진입점, 그리고 마찰이 전혀 없습니다.&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;_핵심_업무의_해부&amp;quot;&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;_startollamab_런처&amp;quot;&amp;gt;startOllamaB` — 런처&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;별거 아냐 : 하나`docker run -d`올바른 매개변수와 함께. 비결은`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;컨테이너가 이미 존재하면 명령`docker run`실패합니다 (exit code ≠ 0). 이 속성이 없으면 Gradle은 전체 빌드를 오류로 중단합니다. 여기서 실패는 조용합니다 — 그리고`doLast`이미 존재하는&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;Le `doLast`그는 명령어 실행 후에 &amp;lt;strong&amp;gt;après&amp;lt;/strong&amp;gt; 실행된다, 이로 인해 검사할 수 있게 된다`executionResult.get().exitValue`그리고 적절한 메시지를 로그합니다.&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_대화형&amp;quot;&amp;gt;signInB` — 대화형&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;이것은 인간의 상호작용이 필요한 유일한 작업입니다. Gradle은 전송합니다.`System.in`프롬프트가 되도록 프로세스에`ollama signin`정상적으로 표시될 수 있도록 :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;그렇지 않으면 stdin이 닫혀서 sign-in이 차단될 것이고, 프롬프트를 결코 볼 수 없을 것입니다.&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_조정_가능한&amp;quot;&amp;gt;pullModelB` — 조정 가능한&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;일반적인 작업이 매개변수를 받는 것을 통해`-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;폴백은 중요합니다 :`project.findProperty(&amp;quot;model&amp;quot;) as? String ?: &amp;quot;qwen3:0.6b&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;_statusollamaall_진단&amp;quot;&amp;gt;statusOllamaAll` — 진단&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;두`curl`항구에서의 건강`11434` et &amp;lt;code&amp;gt;11435&amp;lt;/code&amp;gt;, fallback을(를) 사용하는`runCatching`다운된 인스턴스가 있어도 크래시되지 않도록. 출력은 구조화되어 있습니다 :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;한 번 쳐다보면, 정확히 무엇이 돌아가고, 무엇이 잘못됐는지 알 수 있다.&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_정체성을_보존하는_청소&amp;quot;&amp;gt;cleanOllamaB` — 정체성을 보존하는 청소&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;컨테이너는 삭제되었지만 볼륨은`~/ollama-b-data`이다&amp;lt;strong&amp;gt;보존된&amp;lt;/strong&amp;gt;. SSH 신원은 정리를 견딘다. 다음에`startOllamaB`, 동일한 키가 재사용됩니다 — sign‑in을 다시 할 필요도 없고 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`볼륨을 건드리지 마세요. 당신이 B의 정체성을 삭제하고(다시 처음부터 시작하고 싶다면) 삭제해야 합니다&amp;lt;/code&amp;gt;~/ollama-b-data`수동으로. 이것은 자발적인 — un`clean`는 자격 증명을 파괴해서는 안 됩니다.&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;_완전한_워크플로우_제로에서_두_세션까지&amp;quot;&amp;gt;완전한 워크플로우 — 제로에서 두 세션까지&amp;lt;/h2&amp;gt;
&amp;lt;div 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;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;2분도 채 안 되어 모든 것이 프로비저닝됩니다. 읽을 README도 없고, 설정할 환경 변수도 없습니다. 작업`helpOllama`내장된 설명서 역할을 합니다.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;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\n(build.gradle.kts)&amp;quot; as Gradle
participant &amp;quot;Docker
Daemon&amp;quot; as Docker
participant &amp;quot;Ollama
인스턴스 B&amp;quot; as OllamaB
participant &amp;quot;Ollama
클라우드&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;_왜_이것을_진짜_gradle_플러그인으로_만들지_않을까요&amp;quot;&amp;gt;왜 이것을 진짜 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;정당한 질문. 이를 추출`build.gradle.kts`독립적인 Gradle 플러그인이라면 다음과 같은 장점이 있습니다: 프로젝트 간 재사용, 선언적 구성, 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;하지만 지금은 의미가 없습니다. 이 빌드에는 복잡한 비즈니스 로직이 없습니다 — 이는 Docker 및 curl 명령을 둘러싼 래퍼일 뿐입니다. 완전한 Gradle 플러그인(확장, 단위 테스트, publish 포함)의 추가 계층은 12개의 유틸리티 작업에 비해 과도할 것입니다.&amp;lt;/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는 필요한 프로젝트에 존재한다. 다른 곳에 복사하기는 매우 간단하다. 만약 언젠가 세 개의 프로젝트가 이에 의존하게 된다면, 나는 이를 플러그인으로 바꿀 것이다. 지금은 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;세 개를 가진 날`build.gradle.kts`세 프로젝트에서 동일한 코드가 반복될 때, 플러그인을 추출해야 한다는 신호입니다. 그 전에, 중복이 추상화보다 비용이 적게 듭니다. 개발자의 지혜.&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;_배운_교훈&amp;quot;&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;strong&amp;gt;Gradle은 단순한 빌드 도구가 아니다.&amp;lt;/strong&amp;gt;— 의존성 그래프 엔진 덕분에 이는 두려울 정도로 강력한 인프라 오케스트레이터가 된다.&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;: 이 세 가지 원시 기능은 CI 파이프라인과 동일한 신뢰성으로 Docker 컨테이너를 제어하는 데 충분합니다.&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`는 네 친구야&amp;lt;/strong&amp;gt;— 이상적인 세계에서,&amp;lt;code&amp;gt;docker run`이미 존재하는 컨테이너에 대해 실행하면 오류 종료 코드 대신 경고가 반환됩니다. 실제 상황에서 너는`isIgnoreExitValue = true`그리고 너는 진단을 관리하고 있는`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;구성 가능한 작업은 모호함을 없앤다&amp;lt;/strong&amp;gt; — Un &amp;lt;code&amp;gt;pullModelB`제네릭과 함께&amp;lt;/code&amp;gt;-Pmodel=&amp;amp;#8230;&amp;amp;#8203;`N개의 전문 작업을 대체합니다. 하나의 작업, 하나의 문서, 하나의 동작.&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` 헬스 체크를 위해&amp;lt;/strong&amp;gt;— N개의 엔드포인트를 테스트할 때, 첫 번째의 크래시가 다른 것을 테스트하는 것을 막아서는 안 된다.&amp;lt;code&amp;gt;runCatching&amp;lt;/code&amp;gt;+`getOrElse`엔드포인트당이며, 부분적인 장애가 발생해도 진단은 여전히 완전합니다.&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;helpOllama` 작업을 실행 가능한 문서처럼&amp;lt;/strong&amp;gt; — Un `println()`잘 포맷된 형식은 세 문단짜리 README를 대체합니다. 자동 문서화되어 있으며, 항상 최신 상태를 유지하고 터미널을 나가지 않고도 접근할 수 있습니다.&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;_결론&amp;quot;&amp;gt;결론&amp;lt;/h2&amp;gt;
&amp;lt;div 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 명령어에서 완전하고 버전 관리되고 재사용 가능한 Gradle 워크플로우로 전환했습니다. 12개의 작업이 전체 수명 주기를 포괄합니다: 프로비저닝, 아이덴티티, 로그인, pull, 테스트, 진단, 정리.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;docker-compose + build.gradle.kts + opencode.json 조합은 깔끔한 삼위일체를 이룹니다: Docker는 격리를 위해, Gradle은 오케스트레이션을 위해, OpenCode는 소비를 위해 사용됩니다. 각 레이어는 각각의 책임을 가지고 있으며, 어느 하나도 다른 영역을 침범하지 않습니다.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;다음 단계? CI에서 라이프사이클을 자동화하여 임시 개발 환경에서 Ollama 인스턴스를 사용할 수 있게 하는 것입니다. 하지만 이건 또 다른 글입니다.&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;_자원&amp;quot;&amp;gt;자원&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;사용자 매뉴얼 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://docs.gradle.org/current/userguide/custom_tasks.html&amp;quot;&amp;gt;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://hub.docker.com/r/ollama/ollama&amp;quot;&amp;gt;Docker Hub의 Ollama Docker 이미지&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 — 문서&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;게시된 기사 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>Docker를 사용해 동일한 머신에서 Ollama Pro 구독 두 개 누적하기</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;#_소개&amp;quot;&amp;gt;소개&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_왜_두_번_같은_것인가요&amp;quot;&amp;gt;왜 두 번 같은 것인가요?&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_문제의_핵심_디바이스_키&amp;quot;&amp;gt;문제의 핵심: 디바이스 키&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_준비_단계별로&amp;quot;&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;#_선행_조건&amp;quot;&amp;gt;선행 조건&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_단계_1_볼륨_생성&amp;quot;&amp;gt;단계 1 : 볼륨 생성&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_단계_2_도커_컨테이너_실행&amp;quot;&amp;gt;단계 2: 도커 컨테이너 실행&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_단계_3_공개_키를_가져오기&amp;quot;&amp;gt;단계 3: 공개 키를 가져오기&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_4단계_ollama_com에_키_저장하기&amp;quot;&amp;gt;4단계: Ollama.com에 키 저장하기&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_단계_5_작은_무료_모델로_테스트하기&amp;quot;&amp;gt;단계 5 : 작은 무료 모델로 테스트하기&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;#_api_함정_v1models&amp;quot;&amp;gt;API 함정 `/v1/models&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_결과_두_세션_두_계정_갈등_없음&amp;quot;&amp;gt;결과 : 두 세션, 두 계정, 갈등 없음&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_배운_교훈&amp;quot;&amp;gt;배운 교훈&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_더_나아가기&amp;quot;&amp;gt;더 나아가기&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;읽는 데 걸리는 시간 : 14 minutes&amp;lt;/div&amp;gt;
&amp;lt;p&amp;gt;두 개의 Pro 구독, 한 대의 기계, 한 장의 신용카드, 그리고 네트워크 충돌 없음. Docker가 두 번째 PC를 구매하지 않고도 내 클라우드 추론 용량을 두 배로 늘릴 수 있도록 해준 방법.&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;_소개&amp;quot;&amp;gt;소개&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;저는 Ollama Pro를 몇 달 동안 사용해 왔으며, 이를 통해 OpenCode 세션에 클라우드 모델을 공급하고 있습니다. 문제는, Pro 구독을 가지고 있더라도 동시에 여러 에이전트를 실행하면 &amp;lt;strong&amp;gt;rate limiting&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;/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는 각 머신을 고유한 SSH 키로 식별합니다 — 유명한 &amp;lt;strong&amp;gt;Device Key&amp;lt;/strong&amp;gt;. 같은 OS 상의 두 인스턴스는 동일한 정체성을 공유하게 되며, 동일한 기기에 두 개의 Pro 계정을 연결하는 것은 불가능합니다. 그리고 예상하셨겠지만, 저는 단순히 그것을 위해 두 번째 랩톱을 구매하지는 않을 겁니다.&amp;lt;/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에게 두 대의 다른 기계에서 실행되고 있다고 믿게 하다, 사실 같은 CPU, 같은 RAM, 같은 네트워크 연결을 공유하고 있다. 도커는 우리에게 완벽한 격리 버블을 제공할 것이다.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;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;물리적 기계&amp;quot; {
  frame &amp;quot;Ollama 인스턴스 A (네이티브)&amp;quot; as Native {
    database &amp;quot;신원
/usr/share/ollama/.ollama/id_ed25519&amp;quot; as KeyA
    portin &amp;quot;API\n:11434&amp;quot; as PortA
  }

  frame &amp;quot;Ollama 인스턴스 B (Docker)&amp;quot; as Docker {
    database &amp;quot;정체성
~/ollama-b-data/id_ed25519&amp;quot; as KeyB
    portin &amp;quot;API
:11435&amp;quot; as PortB
  }
}

cloud &amp;quot;올라마 클라우드&amp;quot; {
  actor &amp;quot;프로 A 계정
(이메일 1)&amp;quot; as AccountA
  actor &amp;quot;프로 B 계정
(이메일 2)&amp;quot; as AccountB
}

KeyA --&amp;amp;gt; AccountA : &amp;quot;SSH 키 A&amp;quot;
KeyB --&amp;amp;gt; AccountB : &amp;quot;SSH 키 B&amp;quot;
PortA --&amp;amp;gt; AccountA : &amp;quot;요청 A&amp;quot;
PortB --&amp;amp;gt; AccountB : &amp;quot;요청 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;_왜_두_번_같은_것인가요&amp;quot;&amp;gt;왜 두 번 같은 것인가요?&amp;lt;/h2&amp;gt;
&amp;lt;div 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;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;하나의 Pro 구독만 있으면 모델을 실행할 수 있어`deepseek-v4-pro:cloud`OpenCode 세션에서. 문제가 &amp;lt;strong&amp;gt;두&amp;lt;/strong&amp;gt; 개의 동시 세션을 원할 때 발생합니다. Rate limiting quotas 때문에 두 번째 세션이 느려지거나 완전히 거부됩니다.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;두 개의 독립된 Pro 구독:&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;세션 OpenCode 1 → 프로 계정 A`localhost:11434`)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;세션 OpenCode 2 → 계정 프로 B (&amp;lt;code&amp;gt;localhost:11435&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;각 세션은 자체 할당량과 자체 컨텍스트를 가지고 있으며, 서로 방해하지 않습니다. 이것은 인간의 멀티프로세싱입니다.&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;두 개의 Pro 구독 = 서로 다른 두 개의 이메일. 동일한 은행 카드가 작동합니다 — Ollama 결제 시스템은 동일한 결제 수단에서 여러 구독을 차단하지 않습니다. 저는 확인했습니다.&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;_문제의_핵심_디바이스_키&amp;quot;&amp;gt;문제의 핵심: 디바이스 키&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Ollama를 설치하면, ed25519 SSH 키 쌍이 identity 디렉터리에 생성됩니다 :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;이 키는 Ollama 서버로 업로드됩니다 동안의`ollama signin`. 그녀가 이렇게 말한다: « 이 기계는 해당 Pro 계정에 속합니다 ». 두 인스턴스가 같은 파일을 공유하면`id_ed25519`, 그들은 같은 정체성을 공유합니다. 게임 끝.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;퍼레이드: 우리 두 번째 인스턴스에 자체 ID 디렉터리를 제공하고, 이를 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;_준비_단계별로&amp;quot;&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;_선행_조건&amp;quot;&amp;gt;선행 조건&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;기능적인 네이티브 Ollama 설치 (공식 스크립트`curl | sh`)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;도커가 설치되어 작동 중&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;배포를 위한 Portainer 또는 docker-compose&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;두 개의 Ollama 계정, 두 개의 다른 이메일 주소&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;나는 버전을 사용했다`0.20.2`— 공식 스크립트가 제공하는 것과 해당하는 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;_단계_1_볼륨_생성&amp;quot;&amp;gt;단계 1 : 볼륨 생성&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;호스트의 간단한 디렉터리가 인스턴스 B의 ID에 대한 영구 볼륨으로 사용됩니다.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;이 폴더는 Docker 컨테이너에 마운트될 것입니다.&amp;lt;code&amp;gt;/root/.ollama&amp;lt;/code&amp;gt;, Ollama가 신원 키, 기록 및 모델을 저장하는 곳.&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_도커_컨테이너_실행&amp;quot;&amp;gt;단계 2: 도커 컨테이너 실행&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;여기서 일어나는 일 :&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;11435&amp;lt;/strong&amp;gt;호스트의 &amp;amp;#8230;&amp;amp;#8203;에 매핑됨&amp;lt;strong&amp;gt;11434&amp;lt;/strong&amp;gt;컨테이너 내부. 따라서 Docker 인스턴스는 에서 수신 대기합니다.&amp;lt;code&amp;gt;:11435`네이티브 인스턴스가 점유하고 있는 것과 충돌 없이&amp;lt;/code&amp;gt;:11434`.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;볼륨`~/ollama-b-data`장착된`/root/.ollama`— 여기서 SSH 신분이 저장됩니다.&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`컨테이너가 외부 연결을 수락할 수 있게 합니다.&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;나는 Portainer를 통해 이 스택을 배포했지만, 간단한`docker compose up -d`같은 방식으로도 잘 작동합니다.&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_공개_키를_가져오기&amp;quot;&amp;gt;단계 3: 공개 키를 가져오기&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;컨테이너가 처음 시작할 때 비어 있으므로, Ollama는 볼륨 내에서 새 SSH 키 쌍을 자동으로 생성합니다. 공개 키를 가져옵니다 :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;저는 이 키가 기본 인스턴스의 키와 &amp;lt;strong&amp;gt;다른&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;# 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;두 개의 서로 다른 열쇠, 두 개의 별개 정체성. 다 끝났어.&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;컨테이너가 다시 시작되면, 그것은 *재사용*하는 볼륨에 있는 키를 재사용합니다. 정체성은 지속됩니다. 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;_4단계_ollama_com에_키_저장하기&amp;quot;&amp;gt;4단계: Ollama.com에 키 저장하기&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://ollama.com/settings/keys&amp;quot; class=&amp;quot;bare&amp;quot;&amp;gt;https://ollama.com/settings/keys&amp;lt;/a&amp;gt; → SSH 키 추가. 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;그다음에, 이 신분을 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;브라우저가 열립니다, 계정 B의 이메일로 로그인합니다, 그리고 토큰은 컨테이너의 SSH 키와 연결됩니다. 모든 것이 정상인지 확인합니다 :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;_단계_5_작은_무료_모델로_테스트하기&amp;quot;&amp;gt;단계 5 : 작은 무료 모델로 테스트하기&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;프로 구독 가격을 얘기하기 전에, 파이프가 잘 작동하는지 확인하고 싶어요. 연결성을 검증하기 위해 작은 로컬 무료 모델을 pull합니다 :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;항구`11435`답하고, 모델은 제공됩니다. 인스턴스 B는 살아 있습니다.&amp;lt;/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-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 바이트 — 정상입니다. 추론은 로컬이 아닌 서버 쪽에서 이루어집니다. 그리고 이것이 궁극적인 테스트입니다:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;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;_api_함정_v1models&amp;quot;&amp;gt;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;바보 같은 디테일에 20분을 허비했다. provider를 설정했을 때`ollama-b`OpenCode에서는 모델 선택기에 아무것도 나타나지 않았습니다. 아무것도요. 전혀 없었어요.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;이유는? API`/v1/models`인스턴스 B에서 돌아가고 있었다`{&amp;quot;object&amp;quot;:&amp;quot;list&amp;quot;,&amp;quot;data&amp;quot;:null}`대신에`{&amp;quot;object&amp;quot;:&amp;quot;list&amp;quot;,&amp;quot;data&amp;quot;:[]}`어떤 모델도 끌어오지 않았을 때. 값`null`OpenCode 측에서 파싱을 중단시켰고, 제공자를 표시하지 않았습니다.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;솔루션: OpenCode 구성에서 모델을 &amp;lt;strong&amp;gt;명시적으로&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;~/.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;이 명시적 선언과 함께, OpenCode는 즉시 provider B와 그 모델을 확인합니다. 세션을 재시작하고, 셀렉터`/models`두 인스턴스를 나란히 표시합니다.&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;사용자 지정 공급자를 보지 못하면`/models`, 세 시간 동안 터미널을 재시작하는 데 시간을 낭비하지 마세요. 수동으로 모델을 선언하세요 안에`opencode.json`— 이것이 문제를 즉시 해결합니다.&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;_결과_두_세션_두_계정_갈등_없음&amp;quot;&amp;gt;결과 : 두 세션, 두 계정, 갈등 없음&amp;lt;/h2&amp;gt;
&amp;lt;div 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 세션을 동시에 시작할 수 있습니다:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;각 세션마다 자체 할당량과 자체 레이트 제한이 있으며 서로 간섭하지 않습니다. 같은 기기, 같은 신용카드, 두 개의 다른 이메일.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;그리고 가장 좋은 점은? Docker 컨테이너는 진행 중`restart: always`. 시스템 재부팅에도 수동 개입 없이 살아남습니다.&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;_배운_교훈&amp;quot;&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;strong&amp;gt;Docker는 모든 것을 격리합니다, 심지어 정체성까지도&amp;lt;/strong&amp;gt;— 볼륨에 대한 간단한 바인드 마운트만으로도 컨테이너에 자체 SSH 키 세트를 제공할 수 있어, 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;두 개의 이메일, 동일한 CB&amp;lt;/strong&amp;gt;— 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;무료로 테스트해보다 결제하기&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`와 &amp;lt;code&amp;gt;data: null`은 OpenCode을 깨뜨립니다.&amp;lt;/strong&amp;gt;— 커스텀 제공자를 구성하면`&amp;quot;models&amp;quot;: {}&amp;lt;/code&amp;gt;, OpenCode는 API를 통해 모델을 발견하려고 시도합니다. 만약 API가 응답하면`data: null`, provider가 나타나지 않습니다. 모델을 명시적으로 선언하세요.&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;두 개의 계정은 인간 다중 처리다.&amp;lt;/strong&amp;gt;— Pro 계정 = 활성화된 OpenCode 세션. 두 개의 계정 = 두 개의 병렬 세션. 여러 Gradle 프로젝트를 동시에 다루는 사람에게 이것은 게임 체인저입니다.&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;_더_나아가기&amp;quot;&amp;gt;더 나아가기&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 설정 — 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;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;Docker Hub의 Docker 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://opencode.ai/docs/providers/#ollama&amp;quot;&amp;gt;OpenCode — 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;게시일: 2026-05-08&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>Xubuntu에서 Kitty 터미널을 설치하고 Zsh를 기본 쉘로 설정합니다.</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;#_왜_kitty와_zsh를_선택해야_할까요&amp;quot;&amp;gt;왜 Kitty와 Zsh를 선택해야 할까요?&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_시스템_사전_요구사항&amp;quot;&amp;gt;시스템 사전 요구사항&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_kitty_다운로드_및_설치&amp;quot;&amp;gt;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;#_설치_스크립트를_다운로드하세요&amp;quot;&amp;gt;설치 스크립트를 다운로드하세요&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_심볼릭_링크_생성&amp;quot;&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;#_xubuntu에_kitty_통합&amp;quot;&amp;gt;Xubuntu에 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;#_데스크톱_파일을_복사&amp;quot;&amp;gt;데스크톱 파일을 복사&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_데스크톱_파일_수정&amp;quot;&amp;gt;데스크톱 파일 수정&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_kitty를_기본_터미널로_설정_선택_사항&amp;quot;&amp;gt;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;#_zsh_설치&amp;quot;&amp;gt;Zsh 설치&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_zsh를_기본_셸로_설정&amp;quot;&amp;gt;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;#_전역_방법_모든_터미널에_대해&amp;quot;&amp;gt;전역 방법 (모든 터미널에 대해)&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_kitty_전용_방법&amp;quot;&amp;gt;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;#_zsh_구성_선택_사항&amp;quot;&amp;gt;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;#_파일_zshrc_생성&amp;quot;&amp;gt;파일 .zshrc 생성&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_최소_구성_예시&amp;quot;&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;#_완전한_워크플로_요약&amp;quot;&amp;gt;완전한 워크플로 요약&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_최종_검증&amp;quot;&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;#_구성_파일_열기&amp;quot;&amp;gt;📂 구성 파일 열기&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_️_추가하거나_수정_font_size&amp;quot;&amp;gt;🖋️ 추가하거나 수정 `font_size&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_저장하고_kitty_다시_시작&amp;quot;&amp;gt;💾 저장하고 Kitty 다시 시작&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_동적_조정_임시_줌&amp;quot;&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;#_oh_my_zsh_설치_및_구성&amp;quot;&amp;gt;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;#_oh_my_zsh_설치&amp;quot;&amp;gt;Oh My Zsh 설치&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_테마_변경&amp;quot;&amp;gt;테마 변경&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_검증&amp;quot;&amp;gt;검증&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_유스_케이스_다이어그램&amp;quot;&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;#_kitty에서_투명도_활성화&amp;quot;&amp;gt;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;#_투명성을_위한_사전_조건&amp;quot;&amp;gt;투명성을 위한 사전 조건&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_kitty_conf에서_투명도_구성&amp;quot;&amp;gt;kitty.conf에서 투명도 구성&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_kitty_재시작&amp;quot;&amp;gt;Kitty 재시작&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_가능한_보완&amp;quot;&amp;gt;가능한 보완&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_시퀀스_다이어그램_kitty_투명도_초기화&amp;quot;&amp;gt;시퀀스 다이어그램 - Kitty 투명도 초기화&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_알려진_제한&amp;quot;&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;#_벨_소리_비활성화_청각적_벨&amp;quot;&amp;gt;벨 소리 비활성화 (청각적 벨)&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_kitty_업데이트&amp;quot;&amp;gt;Kitty 업데이트&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_참고문헌&amp;quot;&amp;gt;참고문헌&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_결론&amp;quot;&amp;gt;결론&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;이 가이드는 Xubuntu에서 &amp;lt;strong&amp;gt;kitty&amp;lt;/strong&amp;gt; 터미널을 단계별로 설치하고 올바르게 구성하며, bash를 기본 셸로 *zsh*로 바꾸는 방법을 설명합니다. kitty 프로젝트는 여기에서 이용할 수 있습니다: &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는 요구가 높은 개발자에게 특히 적합한 현대적이고 빠르며 구성 가능한 터미널입니다. (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;_왜_kitty와_zsh를_선택해야_할까요&amp;quot;&amp;gt;왜 Kitty와 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은(는) 다음과 같이 특징이 있습니다:&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;GPU 가속 덕분에 향상된 성능,&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;단일 텍스트 파일을 기반으로 완전히 구성된,&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&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;Zsh는 그에 반해 강력한 인터랙티브 셸을 제공합니다:&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;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;맞춤형 프롬프트,&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;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;Kitty와 Zsh를 함께 사용하면 생산적이고 미적인 환경을 누릴 수 있습니다.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;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 간소화된 아키텍처 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;이 가이드는 스크립트를 통한 설치와 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;_시스템_사전_요구사항&amp;quot;&amp;gt;시스템 사전 요구사항&amp;lt;/h2&amp;gt;
&amp;lt;div 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;/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 설치됨,&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;인터넷 접속,&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;스크립트를 실행하기 위한 기능적인 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;환경을 확인하세요:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;_kitty_다운로드_및_설치&amp;quot;&amp;gt;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는 공식 스크립트를 통해 파일을 설치하는 것을 권장합니다. 파일을 넣는`~/.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;이 방법은 패키지 관리자에 독립적인(없는`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;_설치_스크립트를_다운로드하세요&amp;quot;&amp;gt;설치 스크립트를 다운로드하세요&amp;lt;/h3&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-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;이 명령은 수행합니다 :&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;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;추출에서`~/.local/kitty.app`,&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&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
title 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;_심볼릭_링크_생성&amp;quot;&amp;gt;심볼릭 링크 생성&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;소환하기 위해`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;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;추가하세요`~/.local/bin`votre PATH에 추가하지 않았다면 :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;설치를 확인할 수 있습니다:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;_xubuntu에_kitty_통합&amp;quot;&amp;gt;Xubuntu에 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가 응용 프로그램 메뉴에 나타나도록:&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;_데스크톱_파일을_복사&amp;quot;&amp;gt;데스크톱 파일을 복사&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;파일을 복사하세요`.desktop`제공 :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;_데스크톱_파일_수정&amp;quot;&amp;gt;데스크톱 파일 수정&amp;lt;/h3&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-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;다음 줄을 확인하거나 수정하세요:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;바꿔`YOURUSER`사용자 이름으로&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;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 Xubuntu 메뉴에 Kitty 통합 프로세스
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;_kitty를_기본_터미널로_설정_선택_사항&amp;quot;&amp;gt;Kitty를 기본 터미널로 설정 (선택 사항)&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;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;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;선택하세요`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;_zsh_설치&amp;quot;&amp;gt;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;Zsh가 아직 설치되지 않았다면, 실행하세요 :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;설을 확인하세요:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;_zsh를_기본_셸로_설정&amp;quot;&amp;gt;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 내에서만 변경할 수 있습니다.&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;_전역_방법_모든_터미널에_대해&amp;quot;&amp;gt;전역 방법 (모든 터미널에 대해)&amp;lt;/h3&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-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;일반적으로`/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;기본 셸을 변경하세요 :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;로그아웃하고 다시 로그인하세요.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;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 전역 셸 변경
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;_kitty_전용_방법&amp;quot;&amp;gt;Kitty 전용 방법&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;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;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;이 줄을 추가하세요:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;저장하고 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;_zsh_구성_선택_사항&amp;quot;&amp;gt;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;Zsh를 사용자 지정하려면 :&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;_파일_zshrc_생성&amp;quot;&amp;gt;파일 .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;_최소_구성_예시&amp;quot;&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-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;_완전한_워크플로_요약&amp;quot;&amp;gt;완전한 워크플로 요약&amp;lt;/h2&amp;gt;
&amp;lt;div 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;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;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 설치 및 구성 워크플로우
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;_최종_검증&amp;quot;&amp;gt;최종 검증&amp;lt;/h2&amp;gt;
&amp;lt;div 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;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;당신은 그것을 보아야 합니다.`zsh`활성 상태입니다 :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;예상 결과 :&amp;lt;/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;글꼴 크기를 변경하려면&amp;lt;strong&amp;gt;키티&amp;lt;/strong&amp;gt;, 매개변수를 조정해야 합니다`font_size`구성 파일에서. 자세한 절차는 다음과 같습니다:&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;_구성_파일_열기&amp;quot;&amp;gt;📂 구성 파일 열기&amp;lt;/h3&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-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;이 파일이 아직 존재하지 않는 경우, 그것을 생성할 수 있습니다. 예시 :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;_️_추가하거나_수정_font_size&amp;quot;&amp;gt;🖋️ 추가하거나 수정 `font_size&amp;lt;/h3&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-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;여기`14.0`원하는 크기에 맞습니다. 원하는 소수 값을 입력할 수 있습니다, 예를 들어`12.0`, &amp;lt;code&amp;gt;15.5&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;_저장하고_kitty_다시_시작&amp;quot;&amp;gt;💾 저장하고 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;저장 (&amp;lt;code&amp;gt;Ctrl + O&amp;lt;/code&amp;gt;, 그 다음에`Entrée`) 그리고 떠나세요 (&amp;lt;code&amp;gt;Ctrl + X&amp;lt;/code&amp;gt;) 사용하신다면`nano`.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Kitty의 모든 창을 닫은 다음, 변경 사항이 적용되도록 터미널을 다시 시작하세요.&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;_동적_조정_임시_줌&amp;quot;&amp;gt;✅ 동적 조정 (임시 줌)&amp;lt;/h3&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;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;글꼴 크기를 늘리다&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;글꼴 크기 줄이기&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;크기 재설정&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;이 설정은 터미널을 닫은 후에도 유지되지 않습니다.&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;_oh_my_zsh_설치_및_구성&amp;quot;&amp;gt;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;Zsh가 설치되고 기본 셸로 설정된 후, *Oh My Zsh*를 사용하여 환경을 풍부하게 할 수 있습니다. Zsh 구성을 관리하는 프레임워크인 *Oh My Zsh*는 다음과 같은 기능을 제공합니다:&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;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;많은 플러그인,&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&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;_oh_my_zsh_설치&amp;quot;&amp;gt;Oh My Zsh 설치&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Zsh가 활성화되어 있는지 확인하세요 :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;필요하면 실행하세요:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;그런 다음 공식 설치 스크립트를 다운로드하고 실행하세요.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;이 스크립트 :&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;폴더를 만들어`~/.oh-my-zsh`,&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;가능하면 파일을 백업해 주세요`~/.zshrc`,&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&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 class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_테마_변경&amp;quot;&amp;gt;테마 변경&amp;lt;/h3&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-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;변수를 수정하세요`ZSH_THEME`다른 테마를 선택하기 위해 (예를 들어`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;저장하고 구성을 다시 로드하세요:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;_검증&amp;quot;&amp;gt;검증&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Kitty를 실행하고 프롬프트가 제대로 변경되었는지 확인하세요. 다음과 같이 실행하여 Oh My Zsh가 활성화되어 있는지 확인할 수 있습니다:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;결과가 비슷해야 합니다 :&amp;lt;/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/당신의_사용자/.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;_유스_케이스_다이어그램&amp;quot;&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&amp;gt;@startuml
title 사용 사례 Oh My Zsh 설치 및 사용
actor Utilisateur as User

package &amp;quot;키티 터미널&amp;quot; {
  component &amp;quot;Zsh 쉘&amp;quot; as Zsh
}

component &amp;quot;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;_kitty에서_투명도_활성화&amp;quot;&amp;gt;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의 가장 좋아하는 기능 중 하나는 터미널 배경에 *투명도*를 추가할 수 있는 기능입니다. 이를 통해 창, 바탕화면 등 아래 환경을 약간 볼 수 있어 멀티태스킹 시 특히 유용합니다.&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;_투명성을_위한_사전_조건&amp;quot;&amp;gt;투명성을 위한 사전 조건&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;윈도우 컴포지터&amp;lt;/strong&amp;gt;그래픽 환경에서 사용됩니다. Xubuntu (XFCE 기반에서), &amp;amp;#8230;&amp;amp;#8203;인지 확인하세요&amp;lt;strong&amp;gt;구성이 활성화되었습니다.&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;확인해야 할 :&amp;lt;/div&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;바탕화면에서 오른쪽 클릭 &amp;amp;gt; 설정 &amp;amp;gt;&amp;lt;strong&amp;gt;창 관리자 설정&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;작곡가&amp;lt;/strong&amp;gt;상자 : &amp;lt;strong&amp;gt;&amp;quot;컴포지션 활성화&amp;quot;&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
title 사용 사례 - Kitty에서 투명도 활성화
actor Utilisateur

rectangle &amp;quot;그래픽 환경 (XFCE/Xubuntu)&amp;quot; {
  usecase &amp;quot;구성 활성화&amp;quot; as UC1
}

rectangle &amp;quot;고양이 터미널&amp;quot; {
  usecase &amp;quot;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;_kitty_conf에서_투명도_구성&amp;quot;&amp;gt;kitty.conf에서 투명도 구성&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;설정 파일을 열어 주세요`~/.config/kitty/kitty.conf`(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;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;다음 줄을 추가하거나 수정하세요 :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;이 매개변수는 다음 사이의 값을 허용합니다:&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;→ 불투명 (투명도 없음),&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;→ 완전히 투명한 (사용할 수 없는),&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;중간 값과 같은`0.90`, &amp;lt;code&amp;gt;0.75&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;좋은 시각적 타협은 보통 사이에`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;_kitty_재시작&amp;quot;&amp;gt;Kitty 재시작&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;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&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_가능한_보완&amp;quot;&amp;gt;가능한 보완&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;사용자 지정 터미널 배경을 사용하고 있는 경우, 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;마지막 구성 요소 (&amp;lt;code&amp;gt;ff&amp;lt;/code&amp;gt;) indique l&amp;amp;#8217;opacité (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;_시퀀스_다이어그램_kitty_투명도_초기화&amp;quot;&amp;gt;시퀀스 다이어그램 - Kitty 투명도 초기화&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 투명성을 갖춘 Kitty 시작 시퀀스
actor User
participant &amp;quot;키티&amp;quot; as Kitty
participant &amp;quot;XFCE 작곡가&amp;quot; as XFCE
participant &amp;quot;GPU/윈도우 관리자&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;_알려진_제한&amp;quot;&amp;gt;알려진 제한&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;투명도가 작동하지 않을 경우: - XFCE 컴포지터가 비활성화됨, - 호환되지 않는 윈도우 관리자가 사용되고 있습니다, - 당신은 Kitty를 &amp;lt;strong&amp;gt;tmux&amp;lt;/strong&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&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_벨_소리_비활성화_청각적_벨&amp;quot;&amp;gt;벨 소리 비활성화 (청각적 벨)&amp;lt;/h2&amp;gt;
&amp;lt;div 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는 &amp;lt;strong&amp;gt;bip&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;이 동작을 비활성화하려면 파일에 다음 지시문을 추가`~/.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-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;또한 *시각 피드백*을 소리 대신 활성화할 수 있습니다:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;_kitty_업데이트&amp;quot;&amp;gt;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가 스크립트를 통해 설치되었고 패키지 관리자가 아니기 때문에, 업데이트는 공식 설치 스크립트를 다시 실행하여 수행됩니다. 이를 통해 최신 버전을 다운로드하고 기존 설치를 업데이트할 수 있습니다 안에`~/.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;접미사`.app`길에서`~/.local/kitty.app`Kitty의 Linux 설치 방식은 공식 스크립트를 통해 이루어지며, 이는 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;업데이트 명령을 실행하기 전에, 개인 디렉터리에 있는지 확인하세요 (&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;다음 명령을 홈 디렉터리에서 실행하세요:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;이 스크립트는 다음을 담당할 것입니다 :&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;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;파일을 추출하여`~/.local/kitty.app`.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&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;업데이트 후, Kitty의 모든 인스턴스를 종료하고 다시 시작하여 새로운 버전이 제대로 적용되도록 하는 것이 권장됩니다.&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;_참고문헌&amp;quot;&amp;gt;참고문헌&amp;lt;/h2&amp;gt;
&amp;lt;div 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;/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://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;깃허브: &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;_결론&amp;quot;&amp;gt;결론&amp;lt;/h2&amp;gt;
&amp;lt;div 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;/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;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;강력하고 사용자 지정 가능한 셸 (Zsh)의&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;시스템 패키지와 독립적인 깨끗한 구성&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;아름다우면서도 기능적인 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 LLM 구독 — 지역 하드웨어의 꿈을 죽이는 경제적 벤치마크</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;#_컨텍스트_변동된_시장&amp;quot;&amp;gt;컨텍스트 : 변동된 시장&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_옵션_패널&amp;quot;&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;#_제가_제외시킨_것_그리고_왜&amp;quot;&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;#_간단한_추론_로컬_하드웨어의_죽음&amp;quot;&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;#_계산&amp;quot;&amp;gt;계산&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_집중적인_사용을_하더라도&amp;quot;&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;#_파인튜닝_클라우드는_절대_지지_않는다&amp;quot;&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;#_fine_tune의_실제_비용&amp;quot;&amp;gt;Fine-Tune의 실제 비용&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_특수_배우&amp;quot;&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;#_유일한_진짜_이유_데이터_주권&amp;quot;&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;#_진정한_프라이버시_계산&amp;quot;&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;#_요약_결정_매트릭스&amp;quot;&amp;gt;요약: 결정 매트릭스&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_특별_사례_중국_모델에_맞서는_프로_구독&amp;quot;&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;#_왜_중국_모델들이_모든_것을_바꾼다&amp;quot;&amp;gt;왜 중국 모델들이 모든 것을 바꾼다?&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_하드웨어를_위한_의미&amp;quot;&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;#_그리고_화웨이&amp;quot;&amp;gt;그리고 화웨이?&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_결론_근접의_럭셔리&amp;quot;&amp;gt;결론: 근접의 럭셔리&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;읽는 시간 : 14 minutes&amp;lt;/div&amp;gt;
&amp;lt;p&amp;gt;NVIDIA는 DGX Spark를 &amp;#39;데스크톱 AI 슈퍼컴퓨터&amp;amp;#8217;로 $4,699에 판매합니다. Ollama는 클라우드 모델을 $20/월에 제공합니다. Vast.ai에서 H100은 $1.38/시간에 대여할 수 있습니다. 그리고 중국 오픈소스 모델인 DeepSeek, Qwen, GLM은 어디서든 무료로 이용 가능하며, 카드를 구매할 필요가 없습니다. 그렇다면 이 Spark는 정확히 어떤 용도로 사용됩니까? 저는 계산을 해봤습니다.&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;/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;_컨텍스트_변동된_시장&amp;quot;&amp;gt;컨텍스트 : 변동된 시장&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;2026년 5월, 인공지능 환경은 2025년 CES에서 발표된 DGX Spark 발표와 비교해 근본적으로 바뀌었습니다. 세 가지 힘이 모여 로컬 하드웨어를 점점 더 정당화하기 어렵게 만들었습니다:&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;클라우드 GPU 가격 폭락&amp;lt;/strong&amp;gt;H100은 시간당 $1.38에서 $3 사이로 대여되며, 18개월 전에는 시간당 $5~$8이었습니다.&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;중국 오픈소스 모델의 폭발&amp;lt;/strong&amp;gt;: DeepSeek-V4, Qwen-3, GLM-5는 서구 모델과 경쟁력이 있으며 무료로 이용 가능합니다.&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;관리형 추론 플랫폼의 부상&amp;lt;/strong&amp;gt;Ollama, Groq, Together AI는 공격적인 가격의 API를 제공하며, 관대한 무료 티어도 제공합니다.&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;$2,999에 발표된 후 $4,699로 인상된 DGX Spark는 더 이상 필요하지 않은 세상에 도착한다.&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;_옵션_패널&amp;quot;&amp;gt;옵션 패널&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;2026년 5월에 이용 가능한 LLM을 실행하는 솔루션, 가장 가벼운 것부터 가장 무거운 것까지:&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;옵션&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;VRAM / 용량&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;strong&amp;gt;Ollama 무료&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;경량 클라우드&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;제한된 사용, 동시에 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;클로드 프로&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/월 (~$240/년)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;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 닫힘)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;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 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;유일한 모델, 오픈소스가 아님&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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/월 (~$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;클라우드 매체&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;최대 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;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/월 (~$1 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;클라우드 무거운&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;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;&amp;lt;strong&amp;gt;클라우드 GPU (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;모든 모델, 파인튜닝&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;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 (구매)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;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;느림(~10배 더 낮음, 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;&amp;lt;strong&amp;gt;맥 스튜디오 M3 울트라&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;CUDA가 아님, 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;인용된 클라우드 GPU 가격은 2026년 5월에 Vast.ai와 RunPod의 가격이다 — 스팟/온디맨드 가격이며, 예약 가격이 없습니다.&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;_제가_제외시킨_것_그리고_왜&amp;quot;&amp;gt;제가 제외시킨 것 (그리고 왜)&amp;lt;/h3&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;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;에이피아이 오픈AI / 앤스로픽 / 구글&amp;lt;/strong&amp;gt;여기서 주제는 선택할 수 있고, fine-tuning 할 수 있으며, 단일 벤더에 의존하지 않는 오픈소스 모델을 실행하는 것입니다.&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;_간단한_추론_로컬_하드웨어의_죽음&amp;quot;&amp;gt;간단한 추론 : 로컬 하드웨어의 죽음&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;가장 일반적인 사용 사례부터 시작해 보겠습니다: 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;_계산&amp;quot;&amp;gt;계산&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;DGX Spark는 $4,699. 각 구독의 연간 비용으로 나누어 봅시다:&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 손익분기점 — DGX Spark ($4 699) vs 구독
rectangle &amp;quot;DGX Spark
$4 699&amp;quot; as spark #FFCDD2

rectangle &amp;quot;Ollama Pro
$200/년&amp;quot; as opro #C8E6C9
rectangle &amp;quot;Claude Pro
$240/년&amp;quot; as cpro #C8E6C9
rectangle &amp;quot;Ollama Max
$1 200/년&amp;quot; as omax #FFF9C4
rectangle &amp;quot;Cloud H100
시간당 $3 × 주당 10시간&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;판결은 잔혹합니다. 추론을 위해:&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;당신이 필요하실 겁니다&amp;lt;strong&amp;gt;23세&amp;lt;/strong&amp;gt;DGX Spark를 상각하기 위해. Spark는 20년 후에는 낡은 기술이 될 것입니다.&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년&amp;lt;/strong&amp;gt;— Spark 24/7을 사용하는 것을 조건으로, 아무도 하지 않는 일&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;클로드 프로&amp;lt;/strong&amp;gt;: 같은 문제,&amp;lt;strong&amp;gt;20년&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;em&amp;gt;&amp;lt;em&amp;gt;_ 데스크톱용 AI 슈퍼컴퓨터의 약속은 기초적인 회계 현실과 충돌합니다: $4 699 / $20/월 = 개인 사용으로는 절대 수익이 나지 않습니다. &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;_집중적인_사용을_하더라도&amp;quot;&amp;gt;집중적인 사용을 하더라도&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;클라우드 최악의 시나리오: 하루 8시간, 주 5일, 연간 52주 동안 H100을 가동합니다.&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 시간/년&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;2 080 × $2/h (H100 스팟 평균 가격) =&amp;lt;strong&amp;gt;$4 160/년&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;스파크($4 699)는 &amp;amp;#8230;&amp;amp;#8203;에서 회수됩니다&amp;lt;strong&amp;gt;13개월&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;H100은&amp;lt;strong&amp;gt;~10x 더 빠르다&amp;lt;/strong&amp;gt;GB10의 Spark와 비교하면, 1시간에 Spark가 10시간 하는 일을 해냅니다. 동일한 작업량 기준으로 H100은 연간 $416 정도 비용이 듭니다, $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;H100 1시간과 Spark 1시간을 비교하는 것은 의미가 없습니다. H100은 작업을 10배 더 빨리 수행합니다. 따라서 특정 워크로드에 대한 실제 클라우드 비용은 시간 기반 순수 계산보다 약 10배 낮습니다.&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;_파인튜닝_클라우드는_절대_지지_않는다&amp;quot;&amp;gt;파인튜닝: 클라우드는 절대 지지 않는다&amp;lt;/h2&amp;gt;
&amp;lt;div 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;/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;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_fine_tune의_실제_비용&amp;quot;&amp;gt;Fine-Tune의 실제 비용&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Unsloth에서 7B 모델에 대한 LoRA/QLoRA 파인튜닝 :&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 H100에서 =&amp;lt;strong&amp;gt;$5에서 $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;스파크에서도 똑같다 =&amp;lt;strong&amp;gt;20-30시&amp;lt;/strong&amp;gt;(10배 느리게), 기계를 정지시킵니다&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로 다음을 자금 조달할 수 있습니다 :&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에서 ~900개의 fine-tunes&amp;lt;/strong&amp;gt;클라우드 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세&amp;lt;/strong&amp;gt;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;~4년&amp;lt;/strong&amp;gt;ollama 맥스&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;그리고 더 큰 모델(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;풀 파인튜닝을 위해서는 어쨌든 멀티 GPU가 필요합니다 — 스파크는 경쟁에서 벗어났습니다.&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;_특수_배우&amp;quot;&amp;gt;특수 배우&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;2026년에, Unsloth, Modal, Replicate, Together AI와 같은 플랫폼은 서버리스 파인튜닝을 제공합니다:&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;GPU 관리가 없습니다&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;환경 구성이 없습니다&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;분당 결제&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;통합 최적화 (flash attention, 자동 양자화)&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;LoRA 7B 파인튜닝 비용 — 비교&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;_유일한_진짜_이유_데이터_주권&amp;quot;&amp;gt;유일한 진짜 이유: 데이터 주권&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;추론, 파인튜닝, 비용 대비 성능을 모두 제거한 후 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;절대적인 기밀.&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;비밀유지 문제라기보다는 — “내 프롬프트가 OpenAI 직원에 의해 읽히길 원하지 않는다” — 이것은 2026년에 잘못된 우려입니다: Ollama, Groq 및 대부분의 신뢰할 수 있는 서비스 제공업체는 무로그 및 무보존 정책을 가지고 있습니다.&amp;lt;/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;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;규제 에어 갭&amp;lt;/strong&amp;gt;défense, diplomatie, santé — les données ne peuvent &amp;lt;strong&amp;gt;physiquement&amp;lt;/strong&amp;gt; pas quitter le bâtiment&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;중요한 사유 데이터&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;네트워크 복원력&amp;lt;/strong&amp;gt;: AI는 인터넷이 끊어져도 계속 작동해야 한다&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;하지만 솔직히 말하자면 : DGX Spark 구매자 중 실제로 해당되는 비율이 몇 퍼센트인가? 1%인가? 나머지는 단순히 « nice to have »가 $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;_진정한_프라이버시_계산&amp;quot;&amp;gt;진정한 프라이버시 계산&amp;lt;/h3&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;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;올라마 프로&amp;lt;/strong&amp;gt;$20/월 + 중국 오픈소스 모델 = 실용적 솔루션&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Ollama에서 프롬프트가 유출되면 매일 수백만 건의 요청 속에서 사라집니다. 이것은 바다 속의 한 방울 물과 같습니다.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&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;&amp;lt;em&amp;gt;_  distiller son prompt dans le LLM »에 대한 두려움은 개인적인 정보 전파 수준이 너무 희석되어 실제 위협보다는 불안에 가깝다. 산업 규모에서는 다르지만, 개인 수준에서는 단지 소음이다. &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;_요약_결정_매트릭스&amp;quot;&amp;gt;요약: 결정 매트릭스&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 2026년에 어떤 AI 솔루션을 선택해야 할까요?
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;기준&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;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 무료&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;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;클라우드 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;추론&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;✅ 과잉&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;세밀한 조정&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;✅ 무적&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;연간 비용&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;변수 (~$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 선불&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;기밀성&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;⚠️ 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;✅ 전체&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;유지보수&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;고유한 설정&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;노후화&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;없다&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;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;td class=&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;소프트웨어 생태계&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;모든 모델 오픈&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;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(지만 느림)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;평결&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;추천된&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;/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;*작업량에 따라 변하는 변수 ; H100 속도 ~10x &amp;amp;gt; Spark, 주어진 워크로드에 대한 실제 비용은 낮다.&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;_특별_사례_중국_모델에_맞서는_프로_구독&amp;quot;&amp;gt;특별 사례: 중국 모델에 맞서는 프로 구독&amp;lt;/h2&amp;gt;
&amp;lt;div 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;strong&amp;gt;Ollama Pro $20/월 + 중국 오픈소스 모델&amp;lt;/strong&amp;gt;이는おそらく 소비자 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;_왜_중국_모델들이_모든_것을_바꾼다&amp;quot;&amp;gt;왜 중국 모델들이 모든 것을 바꾼다?&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 파라미터, CSA+HCA, 1M 컨텍스트, Claude 4 및 GPT-5와 코드 측면에서 경쟁력 있음&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 컨텍스트, 프랑스어와 추론에서 뛰어남&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 컨텍스트, 에이전틱에 최적화된&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;모두 Ollama에서 이용할 수 있습니다. 모두 오픈소스(허가형 라이선스)이며, $20/월 구독을 통해 제한 없이 이용할 수 있습니다.&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;Ollama Pro를 사용하면 닫힌 모델 하나(예: Claude Pro)의 가격으로 오픈 소스 &amp;#39;frontier&amp;#39; 모델 ~5개에 접근할 수 있습니다. 그 가치가 너무 비대칭적이어서 거의 터무니없다.&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;_하드웨어를_위한_의미&amp;quot;&amp;gt;하드웨어를 위한 의미&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;최고의 가격 대비 성능이 월 $20인 클라우드 구독이라면, 로컬 하드웨어는 &amp;lt;strong&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;4,699달러의 DGX Spark는 다음과 같습니다:&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개월 Ollama Pro&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;23개의 AAA 게임 출시일&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;리용의 T2 아파트 1년치 임대료&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;/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;_그리고_화웨이&amp;quot;&amp;gt;그리고 화웨이?&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;질문이 연구 중에 제기되었기 때문에: 아닙니다, Huawei는 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;존재하는 것 :&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;Ascend 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;아틀라스 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;_결론_근접의_럭셔리&amp;quot;&amp;gt;결론: 근접의 럭셔리&amp;lt;/h2&amp;gt;
&amp;lt;div 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은 나쁜 제품이 아닙니다. 이는 잠재 구매자 99%에게 나쁜 투자입니다.&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 2026년 AI 풍경 — 누가 지배하고 있나요?
left to right direction

package &amp;quot;개인 사용 / 중소기업&amp;quot; #C8E6C9 {
  rectangle &amp;quot;🏆 Ollama Pro
$20/월&amp;quot; as pro
  rectangle &amp;quot;Ollama Free
$0/월&amp;quot; as free
  rectangle &amp;quot;클라우드 GPU 스팟
$1-3/h&amp;quot; as spot
}

package &amp;quot;집중 사용 / 프로&amp;quot; #FFF9C4 {
  rectangle &amp;quot;🏆 Ollama Max
$100/월&amp;quot; as max
  rectangle &amp;quot;🏆 전용 클라우드 GPU
$2-4/h&amp;quot; as dedicated
  rectangle &amp;quot;Modal / Together AI\n서버리스&amp;quot; as serverless
}

package &amp;quot;필수 에어 갭&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;_ 2026년에 DGX Spark를 구매하는 것은 2010년에 메일 서버를 구매하는 것과 같다: 기술적으로는 가능하고, 철학적으로는 만족스럽지만, 경제적으로는 개인에게 비합리적이다. &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;계산은 간단하고, 그것은 거짓말을 하지 않아요 :&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;Ollama Free에서 DeepSeek과 채팅하기&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/년&amp;lt;/strong&amp;gt;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;클라우드 GPU에서의 일시적 파인튜닝을 위해&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;&amp;amp;#8230;&amp;amp;#8203; NVIDIA 케이스를 책상 위에 두고 느껴보는 경험은?&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;/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는 수익성이 좋은 도구가 아닙니다. 그것은 선언입니다. “나는 나만의 AI를 집에서 원하고, 남의 클라우드는 원하지 않아.” 그건 존중할 만합니다. 하지만 2026년, Ollama가 클라우드에서 DeepSeek-V4-Pro를 월 $20에 제공할 때, 그것은 주로 매우 비싼 선언입니다. &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;이 글은 DGX Spark의 경제적 타당성에 관한 나의 AI 에이전트와의 대화에서 비롯되었으며, 클라우드 GPU 가격(Vast.ai, RunPod) 시장 모니터링, Ollama/Claude 가격표, 그리고 중국 오픈소스 모델 생태계와 결합되었습니다. 인용된 가격은 2026년 5월의 것이며, 이 글을 읽으실 때는 아마도 아직 더 낮아졌을 것입니다.&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>LLM 거버넌스 매트릭스 — 왜 귀하의 AI 보안은 트리 구조에서 시작됩니까?</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;#_문제_프롬프트_엔지니어링은_모래성이다&amp;quot;&amp;gt;문제: 프롬프트 엔지니어링은 모래성이다&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_내_답변_44_행렬&amp;quot;&amp;gt;내 답변: 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;#_다섯_개_구역_공간_차원_rgpd&amp;quot;&amp;gt;다섯 개 구역 (공간 차원 — RGPD)&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_인식론적_분류_두_번째_차원&amp;quot;&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;#_llm이_이_행렬을_어떻게_소비하는가&amp;quot;&amp;gt;LLM이 이 행렬을 어떻게 소비하는가&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_구현_타입이_지정된_gradle_작업&amp;quot;&amp;gt;구현 : 타입이 지정된 Gradle 작업&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_왜_이것이_선언문이지_단순한_사양이_아닌가&amp;quot;&amp;gt;왜 이것이 선언문이지, 단순한 사양이 아닌가?&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_완전한_폭포_brain_dump에서_블로그_글까지&amp;quot;&amp;gt;완전한 폭포 — Brain Dump에서 블로그 글까지&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_찬성반대&amp;quot;&amp;gt;찬성/반대&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_결론_파일을_정리하고_프롬프트를_정리하지_마세요&amp;quot;&amp;gt;결론: 파일을 정리하고, 프롬프트를 정리하지 마세요.&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_참고문헌&amp;quot;&amp;gt;참고문헌&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;읽기 시간 : 14 minutes&amp;lt;/div&amp;gt;
&amp;lt;p&amp;gt;몇 달 동안 나는 에이전트 거버넌스 규칙을 쌓아 올렸다. 파일들 EAGER, LAZY 체크리스트, 여섯 단계 세션 종료 프로토콜. 그리고, 여전히 해결되지 않은 질문이 하나 남아 있었다: &amp;lt;strong&amp;gt;comment le LLM sait-il ce qu&amp;amp;#8217;il 파일에 대해 할 권리가 있다 ?&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;/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;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;_문제_프롬프트_엔지니어링은_모래성이다&amp;quot;&amp;gt;문제: 프롬프트 엔지니어링은 모래성이다&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;LLM에 파일을 주면 그 모든 것을 주는 것 같아요. 내용이죠 — 그리고 보호 장치의 부재도 있다. 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;고전적인 퍼레이드, 그것은 프롬프트:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;이 프롬프트에는 세 가지 문제가 있습니다.&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의 선의에 의존합니다.&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;복잡한 버그를 해결하는 것은 충분히 커서 명령어를 우회할 수 있다 보안의 200k 토큰의 맥락 속에 잠긴&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;이것은 문맥적이며 구조적이지 않다.&amp;lt;/strong&amp;gt; 프롬프트를 바꾸고, 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;세션을 바꾸면 — 규칙이 사라집니다.&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;확장되지 않습니다.&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;개인정보 보호는 새로운 조항이 필요합니다. 당신의 프롬프트는 아무도 다 읽지 않는 규정 텍스트&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;/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;_내_답변_44_행렬&amp;quot;&amp;gt;내 답변: 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;내가 워크스페이스에서 구현한 솔루션은 &amp;lt;strong&amp;gt;영역 행렬 × LLM&amp;lt;/strong&amp;gt;. 그녀는 LLM에게 주의하도록 요구하지 않는다. 그녀는 구조적으로 불가능한 무모함.&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 LLM 거버넌스 매트릭스 — 영역 × 접근 권한
package &amp;quot;작업 공간&amp;quot; {

  rectangle &amp;quot;RACINE
(원 0)
━━━━━━
뇌 덤프
자유로운 생각
[LLM 사용 금지]&amp;quot; as Z0 #FFB3B3
  rectangle &amp;quot;configuration/
(원 1)
━━━━━━
비밀, 토큰
프라이빗 인프라
[금지 LLM]&amp;quot; as Z1 #FFB3B3
  rectangle &amp;quot;office/
(원 2)
━━━━━━
편집 데이터
기사, 프레임
[필터링된 Vision]&amp;quot; as Z2 #FFF3B3
  rectangle &amp;quot;foundry/private/
(원 3)
━━━━━━
Code closed source
[DATASET 비공개]&amp;quot; as Z3 #B3D9FF
  rectangle &amp;quot;foundry/public/
(3번째 원)
━━━━━━
오픈 소스 코드
Apache 2.0
[자유 접근]&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;_다섯_개_구역_공간_차원_rgpd&amp;quot;&amp;gt;다섯 개 구역 (공간 차원 — RGPD)&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;워크스페이스의 각 영역에는 파일이 저장될 수 있는 *어디*를 결정하는 GDPR 수준이 있습니다. 라우팅되다 :&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;구역&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;공개 RAG 접근 권한&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;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;브레인 덤프, 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;금지&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;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;시크릿, 토큰, 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;금지&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;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;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;필터링된&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/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 — 조건부 개방&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;소스 코드가 비공개인, 공개되지 않은 SaaS&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;금지&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/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 — 네이티브 대중&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;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.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;자유&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;규칙은 간단합니다: &amp;lt;strong&amp;gt;파일 경로가 LLM이 그것을 할 수 있는 것을 결정합니다.&amp;lt;/strong&amp;gt; 유지해야 할 메타데이터도 없고, frontmatter에 추가할 태그도 없습니다, 각 커밋마다 수동 분류는 없습니다. 파일이 &amp;amp;#8230;&amp;amp;#8203;에 있습니다.&amp;lt;code&amp;gt;OSS/&amp;lt;/code&amp;gt;? 공개되어 있습니다. 안에 있습니다`configuration/`? 그는 손댈 수 없다.&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;_인식론적_분류_두_번째_차원&amp;quot;&amp;gt;인식론적 분류 (두 번째 차원)&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;어디*로 라우트할지를 말한다. 하지만 수직인 두 번째 축이 있습니다: *뭐&amp;lt;/strong&amp;gt; 라우터. 일부 파일은 허가된 구역에 있는 정보를 포함하고 있습니다. 희석해서는 안 되는 것들:&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 이중 캐스케이드 — 공간 필터 + 인식론적 필터
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;에피스테믹 분류 그리드, 나의 통치에 새겨진 규칙 2bis 에이전트 :&amp;lt;/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 신호 | 목적지 | | 비전 | 안정된 아키텍처, 테스트된 패턴 | 선언적 언어, 세션/테스트 참조 | 완전한 희석 + 블로그 | | 전략 | 비즈니스 포지셔닝, 가격 | 시장 어휘, 경쟁 | 루트 문서만 | | OPINION | 추측, 검증되지 않은 가설 | 가설적 언어, 직감 | 격리 구역 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;두 차원의 결합 — 물리적 영역 × 인식론적 분류 — 형의&amp;lt;strong&amp;gt;완전한 행렬&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 완전한 매트릭스 — 구역 × 분류
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;_llm이_이_행렬을_어떻게_소비하는가&amp;quot;&amp;gt;LLM이 이 행렬을 어떻게 소비하는가&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;행렬은 LLM이 읽는 문서가 아닙니다. 이것은 &amp;lt;strong&amp;gt;제약 암시된&amp;lt;/strong&amp;gt; 인코딩된 컨텍스트 복합 벡터에 (EPIC 9 — 진행 중 구현의)&amp;lt;/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이 각 세션에서 받는 내용 :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;구체적으로, LLM이 내게 말할 때:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;영역 메타데이터는 LLM이 문장을 마치기도 전에 응답합니다 : &amp;lt;code&amp;gt;edster/`에 있다`CSS/&amp;lt;/code&amp;gt;, 레벨 3 →&amp;lt;strong&amp;gt;공개 지식 그래프에 대한 접근이 금지되었습니다.&amp;lt;/strong&amp;gt; RAG는 그것을 보지 못한다. 그래프는 그것을 만지지 않는다. 사용자 응답은 그는 그것을 인용하지 않는다.&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;공간 온톨로지는처럼 작동한다&amp;lt;strong&amp;gt;유닉스 권한&amp;lt;/strong&amp;gt;LLM용. 당신은 프로세스가 조심하기를 요구하지 않는다`/etc/shadow`— 그에게 읽기 접근을 거부합니다. 같은 원칙입니다.&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;_구현_타입이_지정된_gradle_작업&amp;quot;&amp;gt;구현 : 타입이 지정된 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 :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;작업은 *testable*입니다. 검증할 수 있는 테스트를 작성할 수 있습니다:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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 테스트, 380 통과. plantuml-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;_왜_이것이_선언문이지_단순한_사양이_아닌가&amp;quot;&amp;gt;왜 이것이 선언문이지, 단순한 사양이 아닌가?&amp;lt;/h2&amp;gt;
&amp;lt;div 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;/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;치수&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;결정론적 규칙&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;Un &amp;lt;strong&amp;gt;과업 계약&amp;lt;/strong&amp;gt;(타입된 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;참조 구현&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;strong&amp;gt;선언문&amp;lt;/strong&amp;gt;그는 논쟁에 입장을 취하기 때문에 더 큰: &amp;lt;strong&amp;gt;LLM의 데이터 접근을 어떻게 관리할 수 있을까?&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;br&amp;gt;
사후 분류기. 내 답변은 : &amp;lt;strong&amp;gt;당신의 파일을 안에 올바른 파일들.&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;이 선언문은 &amp;quot;LLM은 정렬되어야 한다&amp;quot;고 말하지 않습니다. 그것은 말합니다 : &amp;quot;정렬 당신의 보안 규칙에 대한 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 class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_완전한_폭포_brain_dump에서_블로그_글까지&amp;quot;&amp;gt;완전한 폭포 — Brain Dump에서 블로그 글까지&amp;lt;/h2&amp;gt;
&amp;lt;div 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;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;세션 중에 이루어진 관찰로 시작된 것 (&amp;quot;야, RAG은 몰라 에드스터가 closed source가 되었다&amp;quot;) 아키텍처 사양이 되었다 한 세션 미만으로 발표되었습니다. 이것이 STIMULUS 패턴이 동작 중입니다.&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;_찬성반대&amp;quot;&amp;gt;찬성/반대&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;이 접근법 (공간 온톨로지 + 행렬)&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;클래식 접근법 (프롬프트 + 가드레일)&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;기계&amp;lt;/strong&amp;gt; — 파일 시스템이 접근을 차단하고, 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;선언적&amp;lt;/strong&amp;gt; — 프롬프트가 LLM에게 신중하도록 요청합니다&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; — Gradle 태스크 계약은 380개 이상의 테스트에 의해 검증됩니다.&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;테스트할 수 없는&amp;lt;/strong&amp;gt; — &amp;quot;LLM이 규칙을 따랐나요?&amp;quot;는 개방형 질문입니다.&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;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;LLM에 의존하는&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;&amp;lt;strong&amp;gt;확장 가능&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;확장 불가능한&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;&amp;lt;strong&amp;gt;Auditable&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;감사할 수 없는&amp;lt;/strong&amp;gt; — 프롬프트는 기록이 없고, diff가 없고, 비난이 없다&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;제약&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;제약&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&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_결론_파일을_정리하고_프롬프트를_정리하지_마세요&amp;quot;&amp;gt;결론: 파일을 정리하고, 프롬프트를 정리하지 마세요.&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;내가 방금 설명한 LLM 거버넌스 매트릭스는 제품이 아닙니다. 이것은 하나의&amp;lt;strong&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;그녀를 강건하게 만드는 것은, 그녀가 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;내가 내 LLM에게 &amp;#39;모든 것을 인덱싱할 수 있어&amp;amp;#8217;라고 말할 때`OSS/&amp;lt;code&amp;gt;, 아무것도 `configuration/&amp;lt;/code&amp;gt;, et &amp;lt;code&amp;gt;office/&amp;lt;/code&amp;gt; 그것 Vision으로 분류된 경우에만&amp;quot; — 이것은 지시가 아닙니다. 이것은 &amp;lt;strong&amp;gt;필터&amp;lt;/strong&amp;gt; — Kotlin에서 구현된, 380 테스트에 의해 검증됨, 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;그 사람에게 &amp;quot;이 서랍을 보지 마&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;_참고문헌&amp;quot;&amp;gt;참고문헌&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;논문 0116 — 자동화된 인식론적 구획&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;제 0114조 — 신뢰의 원과 공간 온톨로지&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;문서 0108 — AsciiDoc을 사용하여 AI 에이전트 관리하기&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;Article 0115 — 익명화 도구 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>OSS/CSS 과립화 — 왜 내가 foundry/를 두 개로 나누었나?</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;#_일어나기를_기다리던_사고&amp;quot;&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;#_진짜_질문&amp;quot;&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;#_해결책_oss_및_css&amp;quot;&amp;gt;해결책 : OSS/ 및 CSS/&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_왜_oss와_css이며_public과_private가_아닌가&amp;quot;&amp;gt;왜 &amp;quot;OSS&amp;quot;와 &amp;quot;CSS&amp;quot;이며 &amp;quot;Public&amp;quot;과 &amp;quot;Private&amp;quot;가 아닌가&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;#_완전한_나무_네_구역_세_구역이_아님&amp;quot;&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;#_특별한_경우_cheroliv_com&amp;quot;&amp;gt;특별한 경우: 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_신경_쓰지_않는다&amp;quot;&amp;gt;bakery-gradle 신경 쓰지 않는다&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;#_거버넌스_에이전트에_미치는_영향&amp;quot;&amp;gt;거버넌스 에이전트에 미치는 영향&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_우리가_얻는_것_그리고_우리가_잃는_것&amp;quot;&amp;gt;우리가 얻는 것 (그리고 우리가 잃는 것)&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_결론_물리적_신호가_소프트웨어_신호를_이긴다&amp;quot;&amp;gt;결론 : 물리적 신호가 소프트웨어 신호를 이긴다&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_참조&amp;quot;&amp;gt;참조&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;읽는 시간: 10 minutes&amp;lt;/div&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;code&amp;gt;foundry/&amp;lt;/code&amp;gt;— 내 워크스페이스 코드를 호스팅하는 기능 영역 — 18개의 프로젝트를 포함하고 있습니다. 여덟 개는 Apache 2.0 라이선스 하에 오픈 소스입니다. 단 하나만 closed source — SaaS Edster. 몇 달 동안, 이 아홉 개의 프로젝트가 공존했습니다. 같은 폴더에 있으며, 아무것도 없이 단순히&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;그리고 나서 RAG를 연결하고 싶었어요. 그러자 모든 것이 무너졌습니다.&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;_일어나기를_기다리던_사고&amp;quot;&amp;gt;일어나기를 기다리던 사고&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;2026년 4월에, 저는 slider-gradle를 위해 RAG pgvector 구현을 시작했습니다. 원칙 : 모든 저장소를 색인하는`foundry/&amp;lt;code&amp;gt;, 생산하다 embeddings, 이를 LLM의 컨텍스트에 주입하여 그것이 하나의 perception&amp;quot; 파일에 대한 인식`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;파이프라인은 간단했습니다 :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;간단함. 효과적. 그리고&amp;lt;strong&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;왜냐하면 이것이`fileTree`사이에 차이를 두지 않다`plantuml-gradle/` (Apache 2.0, public) 그리고`edster/`(closed source, private).그는 tudo를 다 삼킨다. 그리고 언젠가 내가 이 임베딩을 게시한다면 — 대시보드에서, 응답에서 LLM, 훈련 데이터셋에서 — Edster의 독점 코드가 유출됩니다.&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;문제는 LLM이 닫힌 소스 코드를 읽는 것이 아니다. 문제는, 이 코드가 &amp;lt;strong&amp;gt;공개된&amp;lt;/strong&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;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_진짜_질문&amp;quot;&amp;gt;진짜 질문&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;이건 &amp;quot;LLM이 코드를 누출하는 것을 어떻게 막을 수 있나요?&amp;quot;가 아닙니다. 이건 &amp;quot;어떻게&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;답변은 프롬프트가 아닙니다. 답변은 폴더를 두 조각으로 자르는 것입니다.&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;_해결책_oss_및_css&amp;quot;&amp;gt;해결책 : OSS/ 및 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;이전:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;다음 :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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`된다:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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은 결코 보지 않는다.&amp;lt;code&amp;gt;CSS/&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&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_왜_oss와_css이며_public과_private가_아닌가&amp;quot;&amp;gt;왜 &amp;quot;OSS&amp;quot;와 &amp;quot;CSS&amp;quot;이며 &amp;quot;Public&amp;quot;과 &amp;quot;Private&amp;quot;가 아닌가&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;OSS(Open Source Software)와 CSS(Closed Source Software)라는 약어 선택 의논된 :&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;는&amp;lt;strong&amp;gt;가시성&amp;lt;/strong&amp;gt;(GitHub이 보는 것)&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; 는 설명하는&amp;lt;strong&amp;gt;자연&amp;lt;/strong&amp;gt;코드 (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;GitHub 가시성은 원격 저장소의 메타데이터입니다. 코드의 성격은 내용의 속성. LLM은 GitHub API에 접근할 수 없지만 — 그러나 접근할 수 있습니다. 파일 시스템에.&amp;lt;code&amp;gt;CSS/`그는 그에게 &amp;quot;주의, 이 코드는 라이선스가 없습니다&amp;quot;라고 말했다. 자유&amp;quot; 를 읽을 필요 없이`LICENSE`또는 하나를 파싱하는`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;폴더 이름은 당신이 줄 수 있는 가장 강력한 메타데이터입니다 한 LLM. 잘못 파싱될 수 없고, 무시될 수도 없고, 잘못 해석될 수도 없습니다. 이 LLM 본다`CSS/edster/`파일 경로에서 — 그는 알고 있다.&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;_완전한_나무_네_구역_세_구역이_아님&amp;quot;&amp;gt;완전한 나무 — 네 구역, 세 구역이 아님&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;OSS/CSS 세분화는 3영역 온톨로지를 완성한다. 영역 `foundry/`기능적 하위 구역이 1에서 2로 변경됩니다 :&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 foundry 조직 — OSS/CSS 세분화
package &amp;quot;주조소/&amp;quot; {

  package &amp;quot;원 0 — 근원&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;원 1 — 설정/&amp;quot; #FFD9CC {
    rectangle &amp;quot;비밀, 토큰&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;원 2 — 사무실/&amp;quot; #FFFFCC {
    rectangle &amp;quot;기사, 구도&amp;quot; as DATA
    rectangle &amp;quot;훈련, SPG/SPD&amp;quot; as FORM
    note right of DATA
      Privé-CVS
      [LLM : filtré Vision/Opinion]
    end note
  }

  package &amp;quot;원 3 — 주조/&amp;quot; #CCE5FF {
    package &amp;quot;OSS/ — 아파치 2.0&amp;quot; #CCFFCC {
      rectangle &amp;quot;그레이들 플러그인&amp;quot; as PLUGINS
      rectangle &amp;quot;엔진&amp;quot; as ENGINE
    }
    package &amp;quot;CSS/ — 폐쇄 소스&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;| 구역 | 개인정보 보호 규정 | 공개 RAG 인덱싱 | 데이터셋 파인튜닝 | | 뿌리 | 수준 0 — 친밀한 | ✗ | ✗ | | &amp;lt;code&amp;gt;configuration/&amp;lt;/code&amp;gt;| 레벨 1 — 제한됨 | ✗ | ✗ | | &amp;lt;code&amp;gt;office/&amp;lt;/code&amp;gt;| 레벨 2 — 협업적 | ✓ 필터된 | ✓ 필터된 | | &amp;lt;code&amp;gt;foundry/private/&amp;lt;/code&amp;gt;| 레벨 3 — 조건부 개방 | ✗ | ✓ 비공개 | | &amp;lt;code&amp;gt;foundry/public/&amp;lt;/code&amp;gt;| 레벨 4 — 원어민 | ✓ 무료 | ✓ 공개 |&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;_특별한_경우_cheroliv_com&amp;quot;&amp;gt;특별한 경우: 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;거기 있을 때, 나는 또 다른 불일치를 수정했다. 내 사이트 &amp;lt;code&amp;gt;cheroliv.com`살고 있던`foundry/`소프트웨어 프로젝트처럼 완전한 — 자체 Gradle 빌드, 자체 CI, 자체 거버넌스&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;하지만 블로그 글은 코드가 아닙니다. &amp;lt;strong&amp;gt;데이터&amp;lt;/strong&amp;gt;입니다 사설들** 의 Cercle 2 — 같은 기준으로 프레이밍 의`office/pilotage/` 또는 형성의`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;변경되는 것:&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;→ Cercle 2의 기밀성&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;작업`publishSite`의 하나의 능력이 된다`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;통해`bakery-gradle`— 플러그인이 하나를 받는다`FileTree`, 그는 모른다 기사들이 오는`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;한 개의 CI, 한 개의 거버넌스 — 중복 없음&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Vision/Opinion 분류기(규칙 2bis)는 자동으로 적용됩니다:&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;기사들 안에`office/`출판 전에 필터링됩니다&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_신경_쓰지_않는다&amp;quot;&amp;gt;bakery-gradle 신경 쓰지 않는다&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;그리고それが 아름다워요. 플러그인`bakery-gradle`수정되지 않았습니다. 그는 AsciiDoc 콘텐츠 디렉터리를 받고, HTML을 생성하고, 푸시한다. GitHub Pages에서. 이 디렉터리가`site/jbake/content/` ou &amp;lt;code&amp;gt;office/sites/cheroliv/&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;// 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;인터페이스 계약은 Gradle 작업입니다. REST API가 아닙니다. 웹훅이 아닙니다. 한 작업 — 타입이 지정된, 테스트 가능한, 로컬 및 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;_거버넌스_에이전트에_미치는_영향&amp;quot;&amp;gt;거버넌스 에이전트에 미치는 영향&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;OSS/CSS 세분화는 세 가지 측면에서 에이전트 거버넌스를 변경합니다:&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; : 색인 범위가 이동됩니다`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;. 폐쇄 소스 코드 작업 구성에 의해 인덱스에서 제외되며, 프롬프트에 의해 제외되지 않습니다.&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 두 개의 그래프를 생성합니다 :&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;(public) — OSS 아티팩트와 office/Vision 사이의 관계 *&amp;lt;code&amp;gt;configuration/graph_private.json&amp;lt;/code&amp;gt;(gitignored, 비공개의) — 관계 사이 CSS 아티팩트, 절대 게시되지 않음&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;데이터셋 파인튜닝&amp;lt;/strong&amp;gt; :`codebase-gradle`이제 두 개의 출처가 있습니다:&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;→ 공개 데이터세트 (커뮤니티 모델, 벤치마크) *&amp;lt;code&amp;gt;CSS/&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;_우리가_얻는_것_그리고_우리가_잃는_것&amp;quot;&amp;gt;우리가 얻는 것 (그리고 우리가 잃는 것)&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;이전 (평면 폴더)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;다음에 (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;→ 공공/사적 혼합&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;→ 모든 것이 게시 가능합니다&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;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는 차별 없이 모든 것을 인덱싱합니다.&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;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은 인덱싱합니다`OSS/`+`office/Vision`오직&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;길`OSS/` ou `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;cheroliv.com은 자체 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;CI와 거버넌스는 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;블로그 글은 &amp;quot;code&amp;quot; 저장소에 있습니다.&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;블로그 게시물은`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;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;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/`범위를 벗어난 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;유일한 비용 : 하나`git mv`전 세계 17개 OSS 저장소에 대한 + 리팩터링된 &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;. 이것은 콜드 마이그레이션 — 비행 중인 데이터도 없고, 영향을 받은 사용자도 없습니다.&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;_결론_물리적_신호가_소프트웨어_신호를_이긴다&amp;quot;&amp;gt;결론 : 물리적 신호가 소프트웨어 신호를 이긴다&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;오늘 오후에 내가 한 일은 보이지 않는 관행을 대체한 것이었다. 구역 안에서 보이는 분리로`foundry/&amp;lt;code&amp;gt;.이전에는, 당신은 해야 했습니다. &amp;lt;strong&amp;gt;알다&amp;lt;/strong&amp;gt; 고`edster/`폐쇄 소스였습니다. 이제 당신이 그것을 &amp;lt;strong&amp;gt;봅니다&amp;lt;/strong&amp;gt; — 폴더 이름은`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;이것은 Unix 권한, 네트워크 VLAN, 또는 그것과 동일한 원리입니다. 데이터베이스의 방화 구획. 보안은 해서는 안 된다 당신이 기억해야 할 정보여야 합니다. 그것은 속성이어야 합니다. &amp;lt;strong&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;OSS/CSS 세분화는 이 분야에서의 이 원칙의 번역이다. LLM 거버넌스. LLM은 Edster가 무엇인지 알 필요가 없습니다. 기밀. 그는 그것을 색인할 수 없어야 한다.&amp;lt;/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/`보장한다&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;_참조&amp;quot;&amp;gt;참조&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;문서 0117 — LLM 거버넌스 매트릭스&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 — 신뢰의 원&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;0116_compartimentage_epistemique_automatise_llm_post.html&amp;quot;&amp;gt;문서 0116 — 자동화된 인식론적 구분&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 — AsciiDoc으로 IA 에이전트를 관리하기&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>자동화된 인식론적 구분 — LLM이 내 마음의 정원을 어떻게 보호하는가</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;#_문제_자유롭게_생각하기_아무것도_게시하지_않기&amp;quot;&amp;gt;문제 : 자유롭게 생각하기, 아무것도 게시하지 않기&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_stimulus_패턴_태어나다_고정하다_희석하다_죽다&amp;quot;&amp;gt;STIMULUS 패턴 — 태어나다, 고정하다, 희석하다, 죽다&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_llm에_위임_왜_llm이_필터링을_하는가&amp;quot;&amp;gt;LLM에 위임: 왜 LLM이 필터링을 하는가&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;읽기 시간 : 10 minutes&amp;lt;/div&amp;gt;
&amp;lt;p&amp;gt;내 LLM은 단순히 내 Gradle 작업을 실행하는 것에 만족하지 않는다. 그것은 내 비공개 생각과 공개 표현 사이의 경계를 지키는 수호자이다. 모든 아이디어는 라우팅되기 전에 — 비전, 의견, 전략 — 으로 분류된다. 이것이 자동화된 인지론적 구분이 나의 정신 정원을 침묵시키지 않으면서 보호하는 방식이다.&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;/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;_문제_자유롭게_생각하기_아무것도_게시하지_않기&amp;quot;&amp;gt;문제 : 자유롭게 생각하기, 아무것도 게시하지 않기&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;혼자 일하는 개발자가 LLM과 함께 자신의 사고 과정을 문서화할 때, 그는 하나의 역설에 직면한다 :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;이 두 극단 사이에 하나가&amp;lt;strong&amp;gt;필터&amp;lt;/strong&amp;gt;. 이 질문은 : 누가 이것을 하는가? 피곤한 인간(세션 끝에)? 순진한 regex 스크립트? 아니면 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;내 답변: 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;_stimulus_패턴_태어나다_고정하다_희석하다_죽다&amp;quot;&amp;gt;STIMULUS 패턴 — 태어나다, 고정하다, 희석하다, 죽다&amp;lt;/h2&amp;gt;
&amp;lt;div 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는 파일입니다.&amp;lt;code&amp;gt;.adoc`워크스페이스 루트(서클 0)에 임시로 위치하고, 그 이름은 느낌을 주는 명명 규칙을 따릅니다 —`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;— 현재의 탐색적 의도를 포착하는.&amp;lt;/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-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;2회 이상의 세션을 거치지 않고 살아남는 자극은 안티-패턴입니다. 정보가 임시 파일에서 썩히기엔 너무 중요하다면(→ 희석), 존재할 가치가 있을 만큼 명확하지 않다면(→ 삭제).&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;_llm에_위임_왜_llm이_필터링을_하는가&amp;quot;&amp;gt;LLM에 위임: 왜 LLM이 필터링을 하는가&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;나는 Kotlin에서 Vision/Opinion 분류를 하드코딩하지 않습니다. 나는 그것을 안에 주입한다.&amp;lt;strong&amp;gt;시스템 프롬프트&amp;lt;/strong&amp;gt;LLM을 통해`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은 이 지시를 읽는다**이전**세션을 처리하는 것. 그것은 별도의 파일이 아닌 즉시적인 컨텍스트에 있습니다. *결정적 프롬프트 엔지니어링* : 행동은 외부 분류자가 아닌 주입된 텍스트에서 발생합니다.

=== 왜 작동하는가 — 그리고 왜 직관과 반대되는가

표준 반응 : « AI에게 분류를 맡기나요? 실수하면 어쩌려고?

답변: 인간은 새벽 2시에 더 잘하지 못한다. 차이점은 LLM이 모든 것을 읽는다는 점이다. 세션과 대상 문서의 전체 맥락을 가지고 있다. 그것은 알고 있다`VPS ECO-16 12€ HT`이는 인프라 의사결정(VISION)이며`« le TTS ElevenLabs sonne plus naturel que Coqui »`이는 벤치마크에 의해 검증되지 않은 의견입니다.

인간은 세션이 끝날 무렵 대각선으로 읽으며 최선을 다한다. LLM은 모든 것을 읽고 체계적인 격자를 적용한다. 결과는**더 신뢰할 수 있는**, 적어도.

그리고 20%의 애매한 경우 — 심지어 LLM조차 주저하는 회색 지대 — 에 대한 답은 &amp;#39;코드를 더 추가하는 것&amp;#39;이 아니다. 답은**LangGraph4j**(EPIC 13) : 모호한 경우에 Human-in-the-Loop 노드를 활성화하고 인간에게 결정을 맡기는 상태 그래프이지만, 명확한 경우의 80%에서는 LLM만으로 충분합니다.

== 완전한 파이프: 브레인 덤프에서 블로그 게시물까지

다음은 아이디어 초안에서 게시된 기사까지 전체 흐름 — LLM만이 유일한 필터인 — :

[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
----

개발자는 기사를 작성하지 않는다. 그는 도전하고, 검토하고, 검증한다. LLM이 작성한다. 루프는 :

1. 브레인 덤프 자유 → 검열 없음
2. LLM에 의한 인식론적 분류 → 누수 없음
3. LLM에 의한 작성 → 미루지 않기
4. 인간 검토 → 오류 없음

역사적인 병목 현상 — « 시간을 낼 수 없다 » — 은 해소되었다. LLM이 대부분의 작업을 수행한다. 인간은 오직 인간만이 할 수 있는 일, 즉 판단한다.

== 세 가지 루트 문서 — 정보가 제자리를 찾는 방법

각 대상 문서는 희석 캐스케이드에서 정확한 역할을 수행합니다:

[cols=&amp;quot;1,3,2&amp;quot;]
|===
|문서 |그가 받는 것 |콘텐츠 유형 |`WORKSPACE_AS_PRODUCT.adoc` |아키텍처 결정, 기술 스택, 로드맵, 비즈니스 모델 |비전 + 전략 |`WHAT_THE_GAMES_BEEN_MISSING.adoc` |패턴, 수업, 안티패턴, 온톨로지, 형식적인 규칙 |비전 (테스트된 패턴) |`WORKSPACE_VISION.adoc` |세션 컨텍스트, 결정 기록, 인프라 포인트 |비전 + 전략 (맥락) |`AGENT_GOVERNANCE.adoc` |거버넌스 규정, 프로젝트 포트폴리오, 절차 |비전 (지배구조) |**블로그 cheroliv.com** |공개된 기사, 외부 독자를 위한 가치 |비전만
|===

같은 자극이 4개의 문서와 블로그에 영양을 공급할 수 있다 — 각 목적지는 자신에게 해당하는 부분을 받으며, 중복도 모순도 없다.

이게 일어난 일`PICTURE_ME_ROLLIN.adoc`(2026년 5월 3일) :

| Information | Classification | Destinations | | 인벤토리의 13+ 프로젝트 파운드리/ | 비전 | WORKSPACE_AS_PRODUCT | | VPS-2 OVH, edster.cloud, 월 8.49유로 | VISION | WORKSPACE_AS_PRODUCT + WORKSPACE_VISION |  아프리카 시장은 모바일 머니를 위해 준비되어 있다 » | STRATÉGIE | WORKSPACE_AS_PRODUCT (section business) | « DeepSeek &amp;amp;gt; Kimi &amp;amp;gt; GLM 장기 컨텍스트 » | OPINION → VISION | 제한되었다가 벤치마크 후 승진됨 (문서 0112) | | 합성 직원을 최종 제품으로 | 전략 | WORKSPACE_AS_PRODUCT (블로그 없음) |

== Baby-Step 패턴 + TestContainer + 살아있는 문서 — 스스로 테스트하는 코드

이 패턴 `STIMULUS/Dilution`은 생각을 관리한다. 코드에서의 대응은 패턴 `Baby-Step/TestContainer/Doc Vivante`, 검증된 `plantuml-gradle (380/380 tests PASS, 137 sessions sans fuite Docker)` :

[cols=&amp;quot;1,3&amp;quot;]
|===
|원칙 |규칙 |**아기 발걸음** |US당 3점을 초과하지 마시오. US가 두렵다면 나누시오. 통과하는 1점 US가 지연되는 8점 US보다 낫다. |**TestContainer 먼저** |비즈니스 코드 이전에 컨테이너. 인프라 테스트 통과 → 안정적인 환경 보장 |**커밋된 데이터셋** |최소한이면서도 대표적인 부분집합에서`src/test/resources/`, PII 없이. 복제 가능, 실행 가능. |**살아있는 문서** |각 Cucumber 테스트는 자연어 문장입니다. 소스 코드가 실행 가능한 명세입니다. 마크다운 문서를 유지할 필요가 없습니다. |**자동 정리** |자원 누수 없음. 고아 컨테이너,`/tmp/gradle-test-*`: 모든 것이 청소되었습니다.
|===

이 패턴이 138회 세션 동안 plantuml-gradle이 Docker 회귀를 한 번도 겪지 않은 이유입니다. 이것은 외부 인프라(PostgreSQL, Redis, Kafka)와 통신하는 코드가 항상 프로덕션과 동일한 환경에서 버전 관리되고, 재현 가능하며, 한 명령으로 실행 가능한 상태로 테스트된다는 것을 보장합니다.

== 결론:출판에 대한 두려움 없이 생각의 자유

자동화된 인식론적 분할은 검열 도구가 아닙니다. 그것은**해방자**. 자유롭게 생각하고, 필터 없이 추측하며, 머리 속에 떠오르는 모든 것을 뇌 덤프한다 — 언제든 미성숙한 아이디어가 블로그에 올라올까 봐 두려워하지 않는다.

LLM은 이 경계의 수호자이다. 그것은 모든 것을 읽는다. 그것은 분류한다. 그것은 생존할 가치가 있는 것을 희석시킨다. 그것은 비공개로 남아 있어야 할 것을 가두어 놓는다. 그것은 공개 가능한 것을 작성한다.

그리고 개발자는 마지막 말을 가진다 : 다시 읽고, 도전하고, 검증한다. 판단한다.

혼자 일하는 개발자에게 부족했던 것은 업무 분업이었습니다. 정신적인 paperwork을 처리하는 합성 직원이 있다면, 인간은 자신이 가장 잘하는 일—설계하고, 아키텍처를 구성하고, 결정을 내리는 것—에 집중할 수 있습니다.

== 참고문헌

* 에이전트 Eager/Lazy 거버넌스에 대한 기사 :link:../2026/0108_gouvernance_agent_opencode_eager_lazy_post.html[AsciiDoc을 사용한 AI 에이전트 관리]
* 공간 온톨로지와 신뢰 원에 관한 기사 :link:../2026/0114_gouvernance_cercles_confiance_ontologie_spatiale_alignement_llm_post.html[공간 온톨로지, 정렬 메커니즘으로서]
* Anonymiseur MVP0에 대한 문서:link:../2026/0115_anonymiseur_dataset_mvp0_realite_augmentee_llm_post.html[우리 첫 채용자: 데이터셋 익명화 도구]
* 긴 컨텍스트 LLM 비교 기사 :link:../2026/0112_comparaison_kimi_glm_deepseek_long_contexte_plugin_gradle_opencode_post.html[DeepSeek-V4-Pro, Kimi K2.6, GLM-5.1]
* 백업 에이전트 메커니즘에 관한 기사:link:../2026/0110_mecanisme_backup_contexte_agent_post.html[슬라이딩 윈도우와 한파]
----&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>우리 첫 채용자: 데이터셋 익명화 도구 — MVP0 시범 기간</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;#_모두를_도망치게_하는_문제&amp;quot;&amp;gt;모두를 도망치게 하는 문제&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;읽는 시간 : 12 minutes&amp;lt;/div&amp;gt;
&amp;lt;p&amp;gt;나는 LLM을 채용하지 않는다. 나는 익명화자를 채용한다. pgvector RAG를 수행하기 전에, CDA/FPA 전문가를 파인튜닝하기 전에, 클라이언트 워크스페이스를 프로비저닝하기 전에 — 누군가는 데이터의 화장실을 청소해야 한다. 이 직원은 익명화 전문가다 (MVP0, codebase-gradle의 EPIC 2). 그리고 그는 제품이 시장에서 판매 가능하도록 만든다.&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;/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;_모두를_도망치게_하는_문제&amp;quot;&amp;gt;모두를 도망치게 하는 문제&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;훈련 데이터를 다루는 모든 AI 스타트업은 동일한 벽에 부딪힙니다:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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
----

결과: RAG를 오염된 데이터에 사용할 경우(개인정보 환각, GDPR 위반, 법적 리스크)가 발생하거나, 아무것도 하지 않을 경우(제품 없음)이 됩니다. 세 번째 방법 — 수동으로 정리하는 것 — 은 확장 가능하지 않다.

내 답변 : 문제를 피하지 마라. L&amp;#39;**자동화하다**.

== MVP0 : 익명화 도구, 시험 기간 중 첫 번째로 고용된

MVP0는 기능이 아닙니다. 그것은**편의 전문가**— 더러운, 멍청하고 사악한 일을 하는 직원, 그리고 누구도 피치 덱에서 말하고 싶어 하지 않는.

그가 하는 일:

[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
----

먼저 가장 명백한 패턴을 감지합니다 (emails`.*@.*`, 토큰`sk-*`, `ghp_*`, IPs), 그 후 그것이 배운다. 수습 기간은 이것이죠 : 우리는 위음성이 통과되지 않는지 확인한 후 그것을 정직원으로 채용합니다.

== 이 MVP가 잠금 해제하는 것(그리고 이것이 단순히 규제 준수가 아니다)

익명화는 MVP0가 아니라 GDPR 고속도로 앞의 통행료가 아닙니다. 그것은 고속도로 자체입니다. 다음은 그것이 열어주는 것 :

=== 현재: 프롬프트의 증강 현실

[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는 오염된 데이터에서 검색하지 않는다 — 그것은 공간에서 검색한다**구성으로 깨끗한**. 이것은 답변의 품질에 모든 것을 바꾼다 : LLM은 인식한 PII를 우회하려 시간을 낭비하지 않지만 언급해서는 안 된다.

=== 미래 : 건강한 데이터에 대한 파인 튜닝

[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)
----

원시 데이터에서의 파인튜닝은 잠재적인 법적 재앙입니다. 깨끗한 데이터에서는 그것은**재현 가능한 방법**. 각 클라이언트는 정제된 데이터셋을 축적하고, 자신의 데이터를 기반하여 비즈니스 전문가를 파인 튜닝할 수 있으며, 개인 데이터를 절대 노출하지 않습니다.

== 왜 이것이 시장성이 있나요?

이것은 GDPR 준수 도구가 아닙니다. 이것은**보편적인 문제를 자산으로 바꾸는 재현 가능한 방법**:

1. *모든 교육 기업은 더러운 데이터를 가지고 있습니다* — 이것이 문제입니다
2. *자동화된 다중 소스 정제 파이프라인이 있는 것은 하나도 없습니다* — 이것이 시장입니다
3. *루프: 익명화 → RAG → 파인튜닝은 독점적이다* — 이것이 장벽이다

Edster SaaS (MVP3)는 « Gradle 워크스페이스 »를 판매하지 않을 것입니다. 이 폐쇄 루프를 판매할 것입니다. 그리고 MVP0는 입구입니다.

== SQL 형식은 세 번째 컨텍스트 경로

구조화된 데이터 가져오기는 단순히 교환 형식이 아닙니다. SQL 스키마(DDL)는**도메인 엔티티-관계 모델**:

[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은 익명 처리된 후(4줄) LLM의 컨텍스트 소스가 된다. 텍스트가 아니라 — du**비즈니스 모델**. RAG은 내용(벡터 유사도)을 제공하고, Graphify는 관계(정확한 구조)를 제공하며, SQL 스키마는 제공한다**암시적 도메인 주도 설계**: bounded contexts, aggregates, value objects. LLM은 자신의 스키마를 읽음으로써 업계를 이해한다.

== 로드맵: MVP0부터 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
----

각 MVP는 이전 MVP에 의존합니다. MVP0 없이는 어떤 MVP도 제공할 수 없습니다. 이것이 바로 백로그에서 &amp;#39;P0&amp;#39;가 아닌 이유가 익명화 도구가 아닌 이유이며 — 그것은**MVP0**. 이것은 첫 번째 상업적 산출물이며, 나머지는 그 성공에 달려 있다.

== Conclusion : 모든 것을 가능하게 하는 보이지 않는 일

일반적인 스타트업에서는 데이터 정리가 외주화되거나 수작업으로 이루어지거나 간과됩니다. 이 아키텍처에서는 이것이**제품의 핵심**. 익명화하는 사람은 첫 번째 직원이기 때문에, 그에 없이는 아무것도 작동하지 않는다 :

* MVP0 없이는 → 법적 RAG이 없습니다 (MVP1)
* MVP0 없이는 → 데이터 유출이 없으면 미세 조정도 없습니다 (EPIC 5)
* MVP0 없이는 → 신뢰할 수 있는 SaaS 없음 (MVP3)
* MVP0 없음 → 프롬프트에서 증강 현실 없음
* MVP0 없음 → 컨텍스트의 세 번째 경로 없음 (SQL DDD)

그는 더러운 일을 한다. 그는 피치에서 결코 언급되지 않을 것이다. 하지만 그가 공장을 움직인다.

그게 바로 우리가 그들을 먼저 고용하는 이유다.

== 참고문헌

* 공간적 온톨로지와 신뢰의 원에 관한 기사 :link:../2026/0114_gouvernance_cercles_confiance_ontologie_spatiale_alignement_llm_post.html[공간 온톨로지로서의 정렬 메커니즘]
* Eager/Lazy 에이전트 거버넌스에 관한 기사 :link:../2026/0108_gouvernance_agent_opencode_eager_lazy_post.html[AsciiDoc을 사용한 AI 에이전트 관리]
* Hot/Warm/Cold 메커니즘에 대한 기사 :link:../2026/0110_mecanisme_backup_contexte_agent_post.html[슬라이딩 윈도우와 한파]
* 세 개의 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>우리 첫 채용자 : 데이터셋 익명화 도구 — MVP0 시험 기간 중</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;#_모두를_도망치게_하는_문제&amp;quot;&amp;gt;모두를 도망치게 하는 문제&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;읽는 시간 : 12 minutes&amp;lt;/div&amp;gt;
&amp;lt;p&amp;gt;저는 LLM을 고용하지 않습니다. 저는 익명화자를 고용합니다. RAG pgvector를 수행하기 전, CDA/FPA 전문가를 fine-tuning하기 전, 클라이언트 워크스페이스를 프로비저닝하기 전 — 누군가는 데이터의 화장실을 청소해야 합니다. 그 직원은 익명화 전문가(MVP0, codebase-gradle의 EPIC 2)입니다. 그리고 그가 제품을 시장가능하게 만듭니다.&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;/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;_모두를_도망치게_하는_문제&amp;quot;&amp;gt;모두를 도망치게 하는 문제&amp;lt;/h2&amp;gt;
&amp;lt;div 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;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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
----

결과: 더러운 데이터에서 RAG를 수행한다(개인정보(PII) 환각, GDPR 비준수, 법적 위험)와 그렇지 않으면 아무것도 하지 않는다(제품 없음). 세 번째 방식 — 수작업으로 정리하기 — 는 확장할 수 없다.

내 답변: 문제를 피하지 마라. L&amp;#39;**자동화하다**.

== MVP0 : 익명화자, 시험 기간 중 첫 번째 채용

MVP0는 기능이 아닙니다. 그것은**편의 전문가**— 더럽고 멍청하고 사악한 일을 하는 직원, 그리고 누구도 피치 덱에서 이야기하고 싶어하지 않는 사람.

그가 하는 것:

[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
----

가장 명백한 패턴부터 먼저 감지합니다 (이메일`.*@.*`, 토큰`sk-*`, `ghp_*`, IPs), 그는 배운다. 시험 기간은 이것이죠: 우리는 그가 거짓 음성을 놓치지 않도록 확인한 뒤 그를 정규직으로 채용한다.

== 이 MVP가 열어젖히는 것(그리고 이것이 단지 규정이 아님)

MVP0 형태의 익명화는 고속도로 앞의 GDPR 통행세가 아니다. 그것은 고속도로 자체이다. 다음은 그것이 열어주는 것:

=== 현재 : 프롬프트에서의 증강 현실

[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은 오염된 데이터에서 검색하지 않고 — 공간에서 검색합니다**구축에 따라 깨끗한**. 이것이 답변 품질의 판도를 바꾼다: LLM은 인식하지만 언급해서는 안 되는 PII를 우회하는 데 시간을 보내지 않는다.

=== 미래 : 건강한 데이터에 대한 파인튜닝

[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)
----

원시 데이터에서의 파인튜닝은 잠재적인 법적 재앙이다. 깨끗한 데이터에서는 이것이**재현 가능한 방법**. 각 클라이언트는 정제된 데이터셋을 축적하고, 각 클라이언트는 개인 데이터를 노출시키지 않고 자신의 비즈니스 전문가를 자신의 데이터에 대해 미세 조정할 수 있습니다.

== 왜 그게 시장성이 있나요?

이건 “GDPR 준수 도구”가 아니에요. 이건**보편적인 문제를 자산으로 바꾸는 재현 가능한 방법** :

1. *모든 교육 기업에는 더러운 데이터가 있습니다* — 이것이 문제입니다
2. *아무것도 자동화된 다중 소스 정제 파이프라인이 없습니다* — 이것이 시장입니다
3. *루프: 익명화 → RAG → 파인튜닝은 전용입니다* — 이것이 장벽이다

SaaS Edster (MVP3)는 « Gradle 워크스페이스 »를 판매하지 않을 것입니다. 그는 이 닫힌 루프를 판매할 것입니다. 그리고 MVP0는 진입점입니다.

== SQL 형식은 컨텍스트의 세 번째 방법

구조화된 데이터의 가져오기는 단순히 교환 형식이 아닙니다. SQL 스키마(DDL)는 이를 가지고 있다**도메인 엔티티-관계 모델** :

[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은 한 번 익명 처리된(4번 라인 이후) 후, LLM의 컨텍스트 소스가 됩니다. 텍스트가 아니라 —**비즈니스 모델**. RAG는 내용(유사도 벡터)를 제공합니다, Graphify는 관계(정확한 구조)를 제공합니다, 그리고 SQL 스키마는**암시적 Domain-Driven Design**: bounded contexts, agrégats, value objects. LLM은 자신이 읽은 스키마를 통해 해당 도메인을 이해한다.

== 로드맵: MVP0부터 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
----

각 MVP는 이전 MVP에 의존합니다. MVP0 없이는 어떠한 MVP도 제공할 수 없습니다. 이것이 백로그에서 익명화자가 « P0 »가 아닌 이유입니다 — 그것은**MVP0**. 이것은 첫 번째 상업적 산출물이자, 나머지는 그것의 성공에 달려 있다.

== 결론: 모든 것을 가능하게 하는 보이지 않는 일

일반적인 스타트업에서는 데이터 정리가 외주화되거나, 수작업으로 수행되거나, 간과됩니다. 이 아키텍처에서는 이것이**제품의 핵심**. 익명 처리자는 첫 번째 직원입니다. 그가 없이는 아무것도 작동하지 않기 때문에:

* MVP0 없이는 합법적인 RAG이 없습니다 (MVP1)
* MVP0 없이는 → 데이터 유출 없이 미세 조정 불가능 (EPIC 5)
* MVP0 없이 → 신뢰할 수 있는 SaaS가 없음 (MVP3)
* MVP0 없이 → 프롬프트에서의 증강 현실 없음
* MVP0가 없음 → 컨텍스트의 세 번째 방법이 없습니다 (SQL DDD)

그는 더러운 일을 한다. 그는 피치에서 결코 언급되지 않을 것이다. 하지만 그는 공장을 움직이게 하는 사람이다.

그리고 이것이 그가 먼저 고용되는 이유다.

== 참고문헌

* 공간 온톨로지와 신뢰의 원에 대한 기사 :link:../2026/0114_gouvernance_cercles_confiance_ontologie_spatiale_alignement_llm_post.html[공간 온톨로지, 정렬 메커니즘으로서]
* Eager/Lazy 에이전트 거버넌스에 관한 기사 :link:../2026/0108_gouvernance_agent_opencode_eager_lazy_post.html[AsciiDoc을 이용해 AI 에이전트를 운영하다]
* 핫/웜/콜드 메커니즘에 관한 기사:link:../2026/0110_mecanisme_backup_contexte_agent_post.html[슬라이딩 윈도우와 차가운 파도]
* 세 개의 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>개발자의 비밀의 정원 : 공간 온톨로지가 프롬프트 엔지니어링 없이 LLM을 어떻게 정렬하는지</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;#_관찰_프롬프트_엔지니어링은_모래성이다&amp;quot;&amp;gt;관찰 : 프롬프트 엔지니어링은 모래성이다&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_네_개의_신뢰_원_공간_온톨로지&amp;quot;&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;#_비밀의_정원_cvs_외부_공간&amp;quot;&amp;gt;비밀의 정원: 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를_거버넌스_구성으로&amp;quot;&amp;gt;.gitignore`를 거버넌스 구성으로&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_스냅샷_메커니즘&amp;quot;&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;#_금고_configuration처럼_spring_cloud_config&amp;quot;&amp;gt;금고 : `configuration/`처럼 Spring Cloud Config&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_도서관_office_로_소모성_데이터&amp;quot;&amp;gt;도서관: &amp;lt;code&amp;gt;office/&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;#_주물공장_foundry_구현으로&amp;quot;&amp;gt;주물공장 : &amp;lt;code&amp;gt;foundry/&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;#_procontra_공간_정렬_vs_프롬프트_기반_정렬&amp;quot;&amp;gt;Pro/Contra : 공간 정렬 vs 프롬프트 기반 정렬&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;#_클래식_접근법_사전_프롬프트를_통한_정렬&amp;quot;&amp;gt;클래식 접근법: 사전 프롬프트를 통한 정렬&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_이_접근법_공간_온톨로지_기반_정렬&amp;quot;&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;#_워크스페이스의_관계_대수와_델타_옵저버블&amp;quot;&amp;gt;워크스페이스의 관계 대수와 델타 옵저버블&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_컨텍스트_복합_벡터_rag_pgvector_graphify&amp;quot;&amp;gt;컨텍스트 복합 벡터 : 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;#_컴포넌트_1_rag_langchain4j_postgresql_pgvector&amp;quot;&amp;gt;컴포넌트 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;#_구성_요소_2_knowledge_graph_graphify&amp;quot;&amp;gt;구성 요소 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;#_구성_요소_3_graphify_증분_희소_집계_가능한_소비_가능한&amp;quot;&amp;gt;구성 요소 3 — Graphify 증분 : 희소, 집계 가능한, 소비 가능한&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;#_자동_gdpr_분류_llm은_라우터로_사용&amp;quot;&amp;gt;자동 GDPR 분류: LLM은 라우터로 사용&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_로드맵_asciidoc에서_langgraph4j로&amp;quot;&amp;gt;로드맵: AsciiDoc에서 LangGraph4j로&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_이_아키텍처가_해결하는_것_그리고_해결하지_못하는_것&amp;quot;&amp;gt;이 아키텍처가 해결하는 것 (그리고 해결하지 못하는 것)&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_결론_건축을_담론으로&amp;quot;&amp;gt;결론 : 건축을 담론으로&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_참고문헌&amp;quot;&amp;gt;참고문헌&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;읽는 시간 : 20 minutes&amp;lt;/div&amp;gt;
&amp;lt;p&amp;gt;프롬프트 엔지니어링은 섬세합니다. 80,000 토큰에서 정렬 규칙은 소음에 묻히고, LLM은 대화 초반에 당신에게 요청한 것을 잊어버립니다. 내 해결책? 텍스트로 정렬하지 말고, 대신&amp;lt;strong&amp;gt;공간&amp;lt;/strong&amp;gt;. 나는 개발 워크스페이스를 네 개의 동심 신뢰 원으로 구성했다 — 비밀스러운 내부 정원에서 공개적인 제련소까지 — 그리고 각 원은 파일 시스템의 물리적 영역이다. LLM은 규칙을 상기시킬 필요가 없다; 파일 경로가 그것들을 포함하고 있다. 이것이 어떻게 작동하는지와, 왜 이 공간 기반 정렬 아키텍처가 더 탄력적인지`system prompt`500줄의.&amp;lt;/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;br&amp;gt;
편안히 앉아 주세요.&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;/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;_관찰_프롬프트_엔지니어링은_모래성이다&amp;quot;&amp;gt;관찰 : 프롬프트 엔지니어링은 모래성이다&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;3주 동안, Opencode를 사용해 Gradle 플러그인을 개발했으며, 세 가지 다른 LLM — Kimi K2.6, GLM-5.1, 그리고 DeepSeek-V4-Pro (유일한 생존자지만, 이는 또 다른 이야기&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;)를 사용했습니다. 에이전트 거버넌스 방법&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;는 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;— 규칙, 백로그, 기록 약 30,000 토큰을 세션 시작 시마다 로드합니다.&amp;lt;/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;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이 잊어버립니다.&amp;lt;/strong&amp;gt; EAGER 파일의 각 파일에 절대 규칙을 가지고 있더라도, 누적 토큰이 60,000을 초과하면 (초기 컨텍스트 + 대화), Kimi K2.6은 제안하기 시작했습니다.`Write`구성 파일에 압도적인. GLM-5.1은 Firebase 토큰을 교체해야 할 플레이스홀더와 혼동했다. 규칙은 작성되어 있었지만 — LLM은それを 더 이상 &amp;lt;strong&amp;gt;보지&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;거버넌스 자체도 문제가 된다.&amp;lt;/strong&amp;gt; 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; 메커니즘을 구현한 후, 컨텍스트 폭발을 방지하기 위해 내 EAGER 파일은 여전히 1414줄이었고&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; . 거버넌스 — LLM의 과부하를 방지하기 위해 설계되었지만 — 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;프롬프트의 토큰 수에 의존하지 않는 정렬 메커니즘이 필요했습니다.&amp;lt;/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;/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;_네_개의_신뢰_원_공간_온톨로지&amp;quot;&amp;gt;네 개의 신뢰 원 : 공간 온톨로지&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;내 폴더`workspace/&amp;lt;code&amp;gt;(안에&amp;lt;/code&amp;gt;~/workspace/`) 이것은 Git 저장소가 아닙니다. 이것은 내가 하는 모든 작업의 근간이다 — 코드, 문서, 교육, 인프라. 그리고 이것은 네 가지 영역으로 구조화되어 있는데, 이 영역들은 정리의 관례가 아니라 하지만 그것들은&amp;lt;strong&amp;gt;동심원 형태의 신뢰&amp;lt;/strong&amp;gt;&amp;amp;lt;/think&amp;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;레벨&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;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;가시성&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;비밀의 정원&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/`뿌리&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;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;금고&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 (혼자)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;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;도서관&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 (확장된)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;교육 데이터, SPG/SPD, JSON 스키마. 신뢰할 수 있는 원이 식별되었습니다.&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;공공 단조소&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 공개 (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;소스 코드, 플러그인, 테스트, 기술 문서.&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;참고: 테이블에 수준 3이 없습니다. 수준 3은 &amp;lt;strong&amp;gt;전이&amp;lt;/strong&amp;gt; 수준이며: 내용의 일부입니다.`office/`익명 처리되어 오픈 데이터로 공개할 준비가 된 상태입니다. 자체적인 물리적 영역이 없으며 — 데이터의 상태일 뿐, 위치가 아닙니다.&amp;lt;/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;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;아직 공유하기 준비되지 않은 아이디어를 어디에 두어야 할까요? 심지어 제한된 그룹이라도?&amp;lt;/strong&amp;gt; → 비밀의 정원. Git 없음. 압력 없음.&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;API 토큰을 어디에 저장해야 유출되지 않을까요?&amp;lt;/strong&amp;gt; → 금고 (&amp;lt;code&amp;gt;configuration/&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;어디서 파일럿 OF와 함께 교육 카탈로그를 구축할 수 있나요?&amp;lt;code&amp;gt;office/&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;오픈소스 Gradle 플러그인을 어디에서 산업화할 수 있나요?&amp;lt;code&amp;gt;foundry/&amp;lt;/code&amp;gt;). 공개, 포크 가능, 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;이 온톨로지는&amp;lt;strong&amp;gt;LLM에 의해 소비 가능한&amp;lt;/strong&amp;gt;. 파일을 읽을 때`foundry/plantuml-gradle/src/`, 그는 암시적으로 알고 있다 : &amp;quot;나는 4번째 원 안에 있다 — 공개 코드, 필수 테스트, 비밀 없음, 교육 데이터 없음&amp;quot;. 그는 그것을 상기시켜줄 프롬프트가 필요하지 않다.&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;_비밀의_정원_cvs_외부_공간&amp;quot;&amp;gt;비밀의 정원: CVS 외부 공간&amp;lt;/h3&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;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;뿌리`workspace/&amp;lt;code&amp;gt;없습니다&amp;lt;/code&amp;gt;.git/&amp;lt;code&amp;gt;. 거이에 존재하는 문서들 (`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;— 그리고 지금 당신이 읽고 있는 것, 그것에서 비롯된 것) 은 버전 관리, 공유, 혹은 심지어 나 이외의 다른 사람이 다시 읽는 것을 목적으로 하지 않습니다. 이것들은&amp;lt;strong&amp;gt;발아 중인 생각들&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;비밀의 정원은 본래적으로 *out-of-CVS*입니다. 버전 관리가 없는 것은 생각하는 자유의 조건이다. 거기에서 쓰는 것은 읽히기 위한 것이 아니라 — 거기에서 쓰는 것은 자신의 관점을 명확히 하기 위한 것이다.&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;하지만 이 자유에는 비용이 따른다: 하나`rm`우발적인 것이고, 이는 전략적 사고의 몇 달이 사라지는 것을 의미합니다. 해결책은 정원을 버전 관리하는 것이 아닙니다(그것을 파괴할 뿐입니다) — 그것은 그것을 하는 것&amp;lt;strong&amp;gt;반사하다&amp;lt;/strong&amp;gt;가장 좁은 원 안에서.&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를_거버넌스_구성으로&amp;quot;&amp;gt;.gitignore`를 거버넌스 구성으로&amp;lt;/h4&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;의 근원에서`workspace/&amp;lt;code&amp;gt;, 하나의 파일&amp;lt;/code&amp;gt;.gitignore`미니멀은 선언한다&amp;lt;strong&amp;gt;히스토리화 정책&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;.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`일반적인 Git 기능이 없습니다 ( 없습니다&amp;lt;/code&amp;gt;.git/`루트에서). 그것은 ~처럼 작용한다&amp;lt;strong&amp;gt;거버넌스 구성 파일&amp;lt;/strong&amp;gt;특정 질문에 답하는 : &amp;lt;strong&amp;gt;비밀 정원의 어떤 유물들이 역사화되어야 하고, 어떤 것들은 순전히 일시적인 것인가?&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;목록에 나열된 파일들`.gitignore`—&amp;lt;code&amp;gt;.goosehints&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;.goose&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;파일들`.adoc`뿌리의 —&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;— 아닙니다&amp;lt;strong&amp;gt;안&amp;lt;/strong&amp;gt;안에`.gitignore`. 이것은 역사화해야 할 유물들입니다.&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`은/는&amp;lt;strong&amp;gt;거버넌스 스키마&amp;lt;/strong&amp;gt;: 여기에 나열되지 않은 모든 것은 스냅샷 후보가 됩니다. 그리고 LLM은 이 파일을 읽면서 정확히 무엇이 아카이브되어야 하고 무엇이 무시될 수 있는지를 알고 — 프롬프트에서 이를 상기시킬 필요가 없습니다.&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;_스냅샷_메커니즘&amp;quot;&amp;gt;스냅샷 메커니즘&amp;lt;/h4&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;만들었습니다`configuration/vision-archive/&amp;lt;code&amp;gt;: 모든 것의 날짜가 있는 스냅샷&amp;lt;/code&amp;gt;.adoc`루트에서, 개인 저장소에 커밋됨`configuration/`. 각 브레인스토밍 세션은 스냅샷을 생성합니다 :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;절차는 세션 종료 시 LLM 자체에 의해 트리거되며, 이 LLM이 전체 라우팅 작업을 수행합니다. 각 스냅샷은 시간 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;의 Git 기록`configuration/`되는&amp;lt;strong&amp;gt;나의 전략적 사고의 전기&amp;lt;/strong&amp;gt;. 저는 만들 수 있는`git log&amp;amp;#8201;&amp;amp;#8212;&amp;amp;#8201;vision-archive/`그리고 내 시력의 변화를 세션별로, 날짜별로 보다.&amp;lt;/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-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;하루에 다섯 개의 스냅샷이 있다. 각각은 복구 지점이다. 각각은 전략의 발생에서 이정표이다.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;폴더`configuration/vision-archive/latest/`항상 포함하고 있는&amp;lt;strong&amp;gt;작업 사본&amp;lt;/strong&amp;gt;마지막 스냅샷의, 빠른 참조를 제공하여 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;그리고 LLM에 대해, 그것은&amp;lt;strong&amp;gt;일관성 오라클&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;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_금고_configuration처럼_spring_cloud_config&amp;quot;&amp;gt;금고 : `configuration/`처럼 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/`시각의 아카이브만을 포함하지 않는다. 그 주요한 — 그리고 미래의 — 기능은 되는 것이다.&amp;lt;strong&amp;gt;Spring Cloud Config 서버&amp;lt;/strong&amp;gt;. 모든 비밀, 토큰, 자격 증명, 인프라 디스크립터는 여기, 단독 소유자에게만 접근 가능한 비공개 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;왜 별도의 저장소가 아니라 파일을 사용하나요?`.env`각 프로젝트에서?&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;의도치 않은 비밀 푸시&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;감사&amp;lt;/strong&amp;gt; : Git 기록은 각 구성 변경의 이력을 제공합니다. 누가 무엇을 바꾸었는지, 언제였는지, 어떤 SHA 해시였는지.&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;롤백&amp;lt;/strong&amp;gt; :`git revert`손상된 구성에 대해. 스냅샷, 수동 백업 없음.&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;_도서관_office_로_소모성_데이터&amp;quot;&amp;gt;도서관: &amp;lt;code&amp;gt;office/&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;`office/`블로그 글, 기술 사양, 교육 자료(38개의 FPA, SPG/SPD 강의 디렉터리, JSON 스키마, Bloom/Harrow/Krathwohl 분류체계)를 포함하며 — 이를 구성하는 모든 것&amp;lt;strong&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;하지만`office/`그 문서 서랍만이 아닙니다. 그것은&amp;lt;strong&amp;gt;구조화된 데이터 소스&amp;lt;/strong&amp;gt;그 Gradle 플러그인들이`foundry/`소비한다. 하나의 블로그 글 안에서`office/`는 플러그인이 HTML로 변환할 입력입니다. AsciiDoc SPG 안에`office/metiers/FPA/`는 오케스트레이터가 파싱하여 슬라이드, 퀴즈, 비디오 캡슐을 생성하는 아티팩트입니다.&amp;lt;/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`의 근본에서`office/`비즈니스 코드가 아닙니다 — 그것은&amp;lt;strong&amp;gt;플러그인 에코시스템의 소비 스크립트&amp;lt;/strong&amp;gt;. 그는 말합니다: &amp;quot;다음은 내가 사용하는 Gradle 플러그인이고 이것은 내 워크스페이스의 컨텍스트입니다&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;_주물공장_foundry_구현으로&amp;quot;&amp;gt;주물공장 : &amp;lt;code&amp;gt;foundry/&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;&amp;lt;code&amp;gt;foundry/`이 포함된&amp;lt;strong&amp;gt;산업화된 코드&amp;lt;/strong&amp;gt;. 54 Git 저장소, 중 7개는 현재 에 의해 관리되고 있는&amp;lt;/code&amp;gt;.agents/`. 여기서 비전이 실행 가능해집니다 — Gradle 플러그인, CI/CD, 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;관계와`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;&amp;lt;code&amp;gt;office/&amp;lt;/code&amp;gt;→`foundry/`교육 데이터는 플러그인이 소비하는 원료이다.&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/&amp;lt;/code&amp;gt;: 플러그인은 데이터를 생산해 풍부하게 만든다`office/` — le `graph.json`graphify-gradle, 컴파일된 슬라이더 데크, 빌드 보고서.&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_공간_정렬_vs_프롬프트_기반_정렬&amp;quot;&amp;gt;Pro/Contra : 공간 정렬 vs 프롬프트 기반 정렬&amp;lt;/h2&amp;gt;
&amp;lt;div 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;/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;_클래식_접근법_사전_프롬프트를_통한_정렬&amp;quot;&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;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;✅ 프로&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;구현하기 간단합니다. 시스템 프롬프트 내의 텍스트 블록.&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;장문 컨텍스트에 취약함&amp;lt;/strong&amp;gt; : 규칙은 80K 토큰 이후에 묻힙니다.&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;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;검증할 수 없는&amp;lt;/strong&amp;gt; : 아무것도 LLM이 규칙을 위반하는 것을 막지 못한다.&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;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;양도 불가&amp;lt;/strong&amp;gt; : 각 repo가 규칙을 재정의합니다.&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;순전히 선언적&amp;lt;/strong&amp;gt; : 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 class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_이_접근법_공간_온톨로지_기반_정렬&amp;quot;&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;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;✅ 프로&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;&amp;lt;strong&amp;gt;장문 컨텍스트에 탄력적&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;초기 인프라 비용&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;&amp;lt;strong&amp;gt;기계적으로 검증 가능한&amp;lt;/strong&amp;gt; : 비밀이 있는`foundry/&amp;lt;code&amp;gt;탐지할 수 있는`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;필요한 규율&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;&amp;lt;strong&amp;gt;양도 가능한&amp;lt;/strong&amp;gt; : 하나`AGENT.adoc`표준은 모든 새 프로젝트에 대해 원을 노출합니다.&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;공간 제약 조건만 다룹니다&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;&amp;lt;strong&amp;gt;설계부터 보안&amp;lt;/strong&amp;gt; : 하나`git push`부터`foundry/&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;/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;비LLM 에이전트에서 소비 가능&amp;lt;/strong&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;/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;strong&amp;gt;창의적인&amp;lt;/strong&amp;gt;. LLM이 온톨로지에 의해 구조화된 공간에서 작업할 때, 그것은 어떤 프롬프트도 허용하지 않는 것을 수행할 수 있다: &amp;lt;strong&amp;gt;델타를 관찰한다&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;_워크스페이스의_관계_대수와_델타_옵저버블&amp;quot;&amp;gt;워크스페이스의 관계 대수와 델타 옵저버블&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;LLM이 순회할 때`foundry/&amp;lt;code&amp;gt;, 그것은 가지고 있다&amp;lt;strong&amp;gt;구조화된 데이터 격자&amp;lt;/strong&amp;gt;: 노드 (플러그인, 파일, 테스트, 의존성), 타입이 지정된 엣지 (`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;), 구성 및 순서 관계 (`training-gradle`저장소를 추출`slider-gradle`슬라이드를 생성한다 →`capsule-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;이 대수학은 어디서도 명문화되어 있지 않다 — 그러나 그것은 &amp;lt;strong&amp;gt;관찰 가능하다&amp;lt;/strong&amp;gt;. 각`build.gradle.kts`종속성을 노출합니다. 각`INDEX.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;LLM은 이 대수학을 관찰하고 이를 감지합니다&amp;lt;strong&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;이 델타부터, 그는 일부를 정의할 수 있다&amp;lt;strong&amp;gt;전문가들의 비즈니스 지도&amp;lt;/strong&amp;gt;— CDA (응용 프로그램 설계·개발자, Kotlin/Gradle/JHipster) 그리고 FPA (성인 전문 강사, 교수법/Qualiopi/Bloom) — 그리고 각 전문가에게 부족한 점을 식별하다.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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은 무엇을 해야 할지 알려줄 필요가 없습니다. 델타는 구조에서 발생합니다.&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;_컨텍스트_복합_벡터_rag_pgvector_graphify&amp;quot;&amp;gt;컨텍스트 복합 벡터 : 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;관계 대수는 이론적 구성이 아닙니다. 그녀는 세 가지 구성 요소에 의해 *구체화*되며, 하나를 형성합니다&amp;lt;strong&amp;gt;컨텍스트 복합 벡터&amp;lt;/strong&amp;gt;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;_컴포넌트_1_rag_langchain4j_postgresql_pgvector&amp;quot;&amp;gt;컴포넌트 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;저는 이미 LangChain4j를 두 개의 플러그인에서 프로덕션 환경에 사용하고 있습니다:`slider-gradle`(4 providers LLM)와`plantuml-gradle`(7개의 제공자). ONNX 임베딩 (AllMiniLmL6V2)은 데이터의 색인을 생성합니다`office/`그리고 소스 코드`foundry/`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는 두 차원에서 동작합니다 :&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; : 문서들`office/`(SPG, 문서, 교육, 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;Dimension 코드&amp;lt;/strong&amp;gt; : 코드베이스`foundry/`(소스 Kotlin, 테스트 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;Intersection은 &amp;lt;strong&amp;gt;무엇&amp;lt;/strong&amp;gt; (비즈니스 도메인)와 &amp;lt;strong&amp;gt;어떻게&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;_구성_요소_2_knowledge_graph_graphify&amp;quot;&amp;gt;구성 요소 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가 통합되어 있습니다`plantuml-gradle`(109 테스트, 380/380 PASS)를 생성한다`graph.json`— 구조화된 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;RAG은 벡터 유사성(흐림)으로 작동하는 반면, 지식 그래프는 &amp;amp;#8230;&amp;amp;#8203;로 작동한다.&amp;lt;strong&amp;gt;정확한 관계&amp;lt;/strong&amp;gt;(결정론적):`graphify query`의미적인 질문에 대해 (~50 토큰),`graphify path`탐색하기 위해,`graphify explain`설명하기 위해.&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_graphify_증분_희소_집계_가능한_소비_가능한&amp;quot;&amp;gt;구성 요소 3 — Graphify 증분 : 희소, 집계 가능한, 소비 가능한&amp;lt;/h3&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;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Graphify는 단일 저장소에 살면 안 됩니다. 그것은 방식으로 살아야 합니다.&amp;lt;strong&amp;gt;흩어진&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;*각 플러그인*은 Gradle 작업을 포함합니다`updateKnowledgeGraph`Graphify를 호출하는&amp;lt;strong&amp;gt;스코프 자체에서&amp;lt;/strong&amp;gt;그리고 하나 생산한다`graph.json`현지의.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;* 빌드 스크립트의`office/&amp;lt;code&amp;gt;* 소비한다`graphify-gradle`와/과`rootDir = /home/cheroliv/workspace`그리고 하나를 생산한다`graph.json&amp;lt;/code&amp;gt; &amp;lt;strong&amp;gt;전역&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;각 플러그인의 RAG&amp;lt;/strong&amp;gt; 을 주입합니다`graph.json`글로벌처럼&amp;lt;strong&amp;gt;컨텍스트 필터&amp;lt;/strong&amp;gt;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;결과 : LLM은 빈 공간을 찾지 않고 — 지식 그래프에 구조화된 공간을 탐색한다. &amp;quot;다이어그램 생성&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&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_자동_gdpr_분류_llm은_라우터로_사용&amp;quot;&amp;gt;자동 GDPR 분류: LLM은 라우터로 사용&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;공간 온톨로지는 LLM을 맞추는 데 만족하지 않고 — 그에게 하나를 준다&amp;lt;strong&amp;gt;RGPD 분류 표&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: 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;기준&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;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;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;개인정보 (이름, 이메일, 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;패턴`@`, 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;익명화하다 →`[OF_PILOTE]`또는 라우터 레벨 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;토큰 / 비밀 / 자격 증명&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;패턴`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;라우터 →&amp;lt;code&amp;gt;configuration/&amp;lt;/code&amp;gt;. 에 의해 참조된`${VAR}`코드 안에.&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;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&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;포함한다`localhost`, 사설 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;익명화 →&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;교육 데이터 (SPG, 수업)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;구조 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;라우터 →&amp;lt;code&amp;gt;office/&amp;lt;/code&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;/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;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;확장`.kt`, &amp;lt;code&amp;gt;.kts`안에`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;라우터 →`foundry/`비밀이 없는지 확인합니다.&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;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;세션이 끝날 때, LLM은 하나를 실행합니다&amp;lt;strong&amp;gt;전체 작업&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;읽기의`.gitignore`임시 아티팩트를 식별하기 위한 기준 (스냅샷에서 제외) vs 전략적 아티팩트 (히스토리화)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;모든 파일의 인벤토리`.adoc`뿌리의`workspace/`&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;필터링: 나열된 파일 제외`.gitignore`, 모든 다른 것들의 포함&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;복사 안에`configuration/vision-archive/$DATE/&amp;lt;code&amp;gt;와 심볼릭 링크 업데이트`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;커밋이 저장소에 있습니다`configuration/`구조화된 메시지와 함께&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;각 수정된 파일에 대한 자동 GDPR 분류 (모든 원)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;라우팅 : 데이터`office/&amp;lt;code&amp;gt;→ 개인 커밋, 코드`foundry/&amp;lt;/code&amp;gt;→&amp;lt;code&amp;gt;./gradlew check&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;GDPR 경보와 보관 확인이 포함된 구조화된 보고서&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`루트는 Git 구성 파일이 아닙니다 — 그것이&amp;lt;strong&amp;gt;히스토리 관리를 위한 거버넌스 파일&amp;lt;/strong&amp;gt;. 그는 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;LLM은 더 이상 단순한 텍스트 생성기가 아닙니다. 그것은&amp;lt;strong&amp;gt;정보 관리자&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;_로드맵_asciidoc에서_langgraph4j로&amp;quot;&amp;gt;로드맵: AsciiDoc에서 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;오늘날, 이 거버넌스는 &amp;lt;strong&amp;gt;결정론적&amp;lt;/strong&amp;gt; : LLM은 AsciiDoc 파일에 설명된 절차를 적용합니다. 이것은&amp;lt;strong&amp;gt;1단계&amp;lt;/strong&amp;gt;— 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;단계 2&amp;lt;/strong&amp;gt;각 단계의 절차를 추출하여&amp;lt;strong&amp;gt;타입이 지정된 Gradle 작업&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;3단계&amp;lt;/strong&amp;gt;세션 종료 과정을 하나의 모델링할 것이다.&amp;lt;strong&amp;gt;상태 그래프&amp;lt;/strong&amp;gt;와https://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;이 그래프는 CI/CD에서 버전 관리되고, Gradle에 의해 실행되며, 플러그인의 GitHub Secrets를 통해 구성됩니다. 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;_이_아키텍처가_해결하는_것_그리고_해결하지_못하는_것&amp;quot;&amp;gt;이 아키텍처가 해결하는 것 (그리고 해결하지 못하는 것)&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;공간 온톨로지는 모든 것을 다루지 않습니다. 코드 스타일 규칙, 명명, 아키텍처 디자인 선택 — 모든 것이 프롬프트에 남아 있습니다. 공간 온톨로지가 해결하는 것은&amp;lt;strong&amp;gt;보안 및 가시성 계층&amp;lt;/strong&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;그녀가 구체적으로 제공하는 것은 다음과 같습니다:&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;없습니다`git push`우발적인 비밀&amp;lt;/strong&amp;gt; : 비밀들은 안에 있다`configuration/&amp;lt;code&amp;gt;(cercle 1). 공공 프로젝트는 안에`foundry/&amp;lt;/code&amp;gt;(원 4). 두 곳을 모두 지나는 경로는 없습니다.&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;데이터에 비즈니스 코드가 없습니다&amp;lt;/strong&amp;gt; :`office/&amp;lt;code&amp;gt;포함하는&amp;lt;/code&amp;gt;.adoc`, YAML, JSON 스키마—그러나`.kt`. Le `build.gradle.kts`거기 사는 사람은 소비 스크립트이지, 업무 코드가 아닙니다.&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;전략적 사고가 드러나지 않음&amp;lt;/strong&amp;gt; : 비전 문서들은 비밀 정원에 거주합니다 (CVS 외 루트). 스냅샷은`configuration/`(개인 금고). 신뢰 범위가 확장된 경우에도 아무도 전략의 기원을 읽지 않는다.&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;자동 우선순위 지정&amp;lt;/strong&amp;gt; : 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;_결론_건축을_담론으로&amp;quot;&amp;gt;결론 : 건축을 담론으로&amp;lt;/h2&amp;gt;
&amp;lt;div 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에 정치적 선언문을 넣지 않는다. 나는 내 프롬프트에 윤리적 규칙을 넣지 않는다. 정렬은 텍스트에 있지 않다 — 그것은 안에 있다&amp;lt;strong&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;LLM이 이 공간에서 작업할 때는 비밀을 유출할 수 없습니다(경로가 이를 막기 때문입니다). 교육 데이터와 소스 코드를 혼동할 수 없습니다(물리적 영역이 다르기 때문입니다). 80,000 토큰의 보안 규칙을 잊을 수 없습니다—규칙이 프롬프트에 있지 않고 ___에 있습니다.&amp;lt;code&amp;gt;workspace/&amp;lt;/code&amp;gt;→&amp;lt;code&amp;gt;configuration/&amp;lt;/code&amp;gt;→&amp;lt;code&amp;gt;office/&amp;lt;/code&amp;gt;→`foundry/`각 파일 읽기마다 반응적입니다.&amp;lt;/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;secure by design&amp;lt;/strong&amp;gt; 원칙이다: 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;그 사이에, 그것은 프롬프트가 LLM에게 줄 수 없는 것을 LLM에게 줍니다: 능력을&amp;lt;strong&amp;gt;인식하다&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;_참고문헌&amp;quot;&amp;gt;참고문헌&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;Eager/Lazy 에이전트 거버넌스에 관한 기사 :&amp;lt;a href=&amp;quot;../2026/0108_gouvernance_agent_opencode_eager_lazy_post.html&amp;quot;&amp;gt;AsciiDoc으로 AI 에이전트 관리하기&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;Hot/Warm/Cold 메커니즘에 대한 기사 :&amp;lt;a href=&amp;quot;../2026/0110_mecanisme_backup_contexte_agent_post.html&amp;quot;&amp;gt;슬라이딩 윈도우와 한파&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/0111_audit_contexte_agent_post.html&amp;quot;&amp;gt;당신만의 지배구조가 문제가 될 때&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;세 가지 LLMs 비교 글 :&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>Supabase → Firebase 마이그레이션: Firestore에 백엔드 없이 연락처 양식 연결</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;#_장면_아무것도_저장하지_않는_폼&amp;quot;&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;#_supabase의_유산&amp;quot;&amp;gt;Supabase의 유산&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_유령_이중_제출&amp;quot;&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;#_왜_firebase가_supabase_대신인가&amp;quot;&amp;gt;왜 Firebase가 Supabase 대신인가?&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_1단계_firebase_프로젝트_만들기&amp;quot;&amp;gt;1단계: 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;#_초기화&amp;quot;&amp;gt;초기화&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_firestore_보안_규칙&amp;quot;&amp;gt;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;#_phase_2_제출_javascript_다시_쓰기&amp;quot;&amp;gt;Phase 2 : 제출 JavaScript 다시 쓰기&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;#_의존성_window_firebase&amp;quot;&amp;gt;의존성 `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;#_새로운_contact_js&amp;quot;&amp;gt;새로운 `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;#_3단계_supabase의_죽은_코드_정리&amp;quot;&amp;gt;3단계: Supabase의 죽은 코드 정리&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_단계_4_푸터_구성&amp;quot;&amp;gt;단계 4: 푸터 구성&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_phase_5_최종_아키텍처&amp;quot;&amp;gt;Phase 5 : 최종 아키텍처&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_이_이동이_도그푸딩에_대해_말하는_것&amp;quot;&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;#_수정_사항_요약&amp;quot;&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;#_다음_단계_백로그&amp;quot;&amp;gt;다음 단계 (백로그)&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_참고문헌&amp;quot;&amp;gt;참고문헌&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;읽기 시간 : 12 minutes&amp;lt;/div&amp;gt;
&amp;lt;p&amp;gt;개월 동안, 이 사이트의 연락처 양식은 모의 JavaScript에서 작동했습니다 — 85% 성공을 약속하는, 가짜 Firestore, 저장된 데이터가 전혀 없었습니다. 초기 계획은 이메일 알림을 위한 Google Apps Script와 함께 Supabase 백엔드를 사용하는 것이었습니다. 포기되었습니다. 오늘, 저는 Firebase Firestore로의 마이그레이션에 대해 이야기합니다: 프로젝트 생성, 보안 규칙, JS 재작성, Supabase 죽은 코드 정리. 그리고 왜 이 선택이 개발 철학에 대해 더 넓은 무언가를 말하는지.&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;/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;_장면_아무것도_저장하지_않는_폼&amp;quot;&amp;gt;장면: 아무것도 저장하지 않는 폼&amp;lt;/h2&amp;gt;
&amp;lt;div 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로 생성된 내 Gradle 플러그인입니다.&amp;lt;code&amp;gt;bakery&amp;lt;/code&amp;gt;. 그것은 100% 정적입니다 — 백엔드도 없고 데이터베이스도 없습니다. 그런데 저는 연락처 폼이 있습니다. 페이지`contact.html`있어요, HTML이 준비되었습니다(이름, 이메일, 전화번호, 제목, 메시지, HTML5 검증, 스팸 방지 하니팟 필드), Bootstrap 스타일이 적용되었습니다. 시각적으로 모든 것이 완벽합니다.&amp;lt;/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-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;모의. 가짜 약속. 사용자는 스피너를 보고, 이어서 &amp;#39;메시지가 성공적으로 전송되었습니다!&amp;#39; 라는 메시지를 봅니다. 하지만 데이터는 허공으로 사라집니다. 어디에도 메시지가 저장되지 않습니다.&amp;lt;/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;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_supabase의_유산&amp;quot;&amp;gt;Supabase의 유산&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;초기 계획은, &amp;amp;#8230;&amp;amp;#8203;에 문서화된`content/draft/integration_formulaire_contact_supabase.adoc`, 예정하고 있었다 :&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;테이블이 있는 Supabase 데이터베이스`contacts`와 Row Level Security&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;한 RPC`handle_contact_form`서버 측&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Google Apps Script 웹훅을 호출하는 SQL 트리거&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Gmail 알림 메일을 보내는 Google Apps Script&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;해당 JavaScript 코드는 여전히 존재합니다 안에`script.js`. 반이 있다`SupabaseManager`전역 변수를 이용해 Supabase 클라이언트를 초기화하는`SUPABASE_URL` et &amp;lt;code&amp;gt;SUPABASE_KEY&amp;lt;/code&amp;gt;, 그리고 한 클래스`ContactFormHandler`폼의 submit 이벤트를 듣고 호출한다`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;문제 : 이 전역 변수들은 더 이상 푸터에 주입되지 않습니다. 그`&amp;amp;lt;script src=&amp;quot;supabase-js&amp;quot;&amp;amp;gt;`제거되었습니다. 코드가 호출합니다.`supabase.createClient()`변수에서`supabase`더 이상 존재하지 않습니다. 그래서:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;데이터가 저장되지 않을 뿐만 아니라 제출 코드도 죽었습니다.&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;_유령_이중_제출&amp;quot;&amp;gt;유령 이중 제출&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;침묵적인 경쟁&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;`contact.js`제출을 듣고, 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;— 경유`ContactFormHandler`— 또한 제출을 들어라, 호출해`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;둘 다 한다`event.preventDefault() + event.stopPropagation()&amp;lt;code&amp;gt;.처럼`contact.js`먼저 로드됩니다`footer.thyme&amp;lt;/code&amp;gt;, 그 핸들러가 먼저 연결됩니다. 그것은 전파를 차단합니다.`ContactFormHandler`결코 트리거되지 않을 것입니다.&amp;lt;/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 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 초기 상태 — 연락처 양식
rectangle &amp;quot;contact.thyme
(HTML Bootstrap, honeypot)&amp;quot; as FORM
rectangle &amp;quot;footer.thyme
(Firebase SDK with config placeholder)&amp;quot; as FOOT
rectangle &amp;quot;contact.js\n(모의 Firebase, 85% 성공)&amp;quot; as CONT
rectangle &amp;quot;script.js
(SupabaseManager + ContactFormHandler, 죽은)&amp;quot; as SCRIPT
rectangle &amp;quot;사용자&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;_왜_firebase가_supabase_대신인가&amp;quot;&amp;gt;왜 Firebase가 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;마이그레이션 결정은 다음에 문서화되어 있습니다.&amp;lt;code&amp;gt;AGENT.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;Firebase는 이제 다음 이유로 선택됩니다: 더 나은 무료 플랜, 네이티브 Firestore, 통합된 Cloud Functions, 더 적합한 Google 생태계. 기존 Supabase 구현은 &amp;#39;⚠️ 폐기됨&amp;amp;#8217;으로 표시됩니다.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;무료 플랜을 넘어 건축적인 이유가 있습니다. 이 사이트는 Google 생태계 : 대상 저장소는`cheroliv.github.io`, CNAME은 GitHub Pages를 가리키고, Gradle 빌드는 JGit을 통해 GitHub에 푸시됩니다. Firebase와 같은 Google 서비스를 타사 서비스(Supabase) 대신 사용하면 배포 표면이 줄어듭니다.&amp;lt;/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를 네이티브 모드(데이터스토어 모드가 아님)로 사용하면 내가 생각하는 NoSQL 문서 모델에 더 가깝습니다: 컬렉션, 문서, 타입이 지정된 필드, 서버 타임스탬프, 내장된 보안 규칙.&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단계_firebase_프로젝트_만들기&amp;quot;&amp;gt;1단계: 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;_초기화&amp;quot;&amp;gt;초기화&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;내 머신에 Firebase CLI가 설치되어 있지 않아서 웹 콘솔을 사용합니다 :&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;가다https://console.firebase.google.com/[Firebase Console]&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;프로젝트 생성`cheroliv-contact`( 또는 기존 프로젝트 재사용)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;네이티브 모드에서 Firestore를 활성화(데이터스토어가 아님)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;지역에서 데이터베이스 생성`eur3`(유럽)&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;우리처럼 단순한 용도(단일 컬렉션, 공개 쓰기)라면 네이티브 모드가 올바른 선택입니다. 복잡한 Datastore 규칙이 필요하지 않습니다.&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;_firestore_보안_규칙&amp;quot;&amp;gt;Firestore 보안 규칙&amp;lt;/h3&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-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;핵심 포인트:&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;— 인증된 사용자만이 메시지를 읽을 수 있습니다 (저, 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;— 누구나 문서를 만들 수 있지만, 필드 검증과 함께&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;created_at`해야 한다`request.time&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;`user_agent`추적을 위해 전송됨 (중요하지 않지만 유용함)&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;이 규칙들은 더 엄격합니다보다 간단한`allow write: if true;`. 그들이 공격자가 엄청난 페이로드나 잘못된 필드를 주입하는 것을 방지합니다.&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;_phase_2_제출_javascript_다시_쓰기&amp;quot;&amp;gt;Phase 2 : 제출 JavaScript 다시 쓰기&amp;lt;/h2&amp;gt;
&amp;lt;div 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;/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;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;허니팟 확인 (필드`hp_name`— 채워져 있으면 봇이며, 아무것도 보내지 않고 성공을 시뮬레이션합니다)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;전화하다`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;성공 또는 오류 표시&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;_의존성_window_firebase&amp;quot;&amp;gt;의존성 `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;안에`footer.thyme`, 스크립트 모듈이 Firebase SDK를 초기화하고 전역 객체를 노출합니다 :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;모듈 스크립트는 먼저 실행됩니다`DOMContentLoaded`, 따라서`window.&amp;lt;em&amp;gt;FIREBASE&amp;lt;/em&amp;gt;`핸들러가 있을 때 사용 가능하도록 보장됩니다`contact.js`발생합니다. 예방 차원에서, 5초 폴링을 추가로 설정합니다. CDN이 느릴 경우에 대비해.&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;_새로운_contact_js&amp;quot;&amp;gt;새로운 `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;모의와 비교한 변경 사항 :&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;— 타임아웃이 있는 폴링, CDN이 느려도 견고함&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;— 숨은 필드면`hp_name`가 채워졌습니다, 파이어스토어에서 무조건적인 성공을 시뮬레이션합니다. 봉은 성공했다고 생각하지만 아무것도 저장되지 않습니다&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;— 실제 Firestore 호출`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;오류 처리와 함께`try/catch`비동기&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;청소하는`finally`(버튼 복원)&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;왜`user_agent`? 이것은 선택사항이지만 진단에 유용합니다. 이상한 메시지가 도착했을 때, 그것이 데스크톱 브라우저, 모바일, 또는 curl 스크립트에서 왔는지 아는 것이 분류에 도움이 됩니다.&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;_3단계_supabase의_죽은_코드_정리&amp;quot;&amp;gt;3단계: 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`250줄의 dead 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;&amp;lt;code&amp;gt;SupabaseManager&amp;lt;/code&amp;gt;(라인 417-481) — 65 라인&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;(490-551번 줄) — 62줄&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;초기화 블록 (줄 645-654) — 10줄&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;총 : 약 140줄 삭제&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;블록`DOMContentLoaded`만든`SupabaseManager`그리고 하나`ContactFormHandler`양식에 연결됨. 앞서 설명한 바와 같이, 이 코드는 결코 실행되지 않습니다(차단된`contact.js`), 심지어 실행되더라도 실패할 것입니다 (Supabase SDK가 로드되지 않음).&amp;lt;/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;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;클래스`SupabaseManager`&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;수업`ContactFormHandler`&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;초기화 블록 안에`DOMContentLoaded`(줄 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`정상입니다 :`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;_단계_4_푸터_구성&amp;quot;&amp;gt;단계 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;`footer.thyme`이미 Firebase boilerplate을 가지고 있지만 값이 플레이스홀더인 상태입니다. 나는 교체합니다:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;실제 값을 가져온 이래&amp;lt;strong&amp;gt;프로젝트 설정 &amp;amp;gt; 일반 &amp;amp;gt; 앱 &amp;amp;gt; 웹 앱&amp;lt;/strong&amp;gt;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;값은 민감합니다`apiKey`Firebase에서는 설계상 공개되어 있지만, 저는 평문으로 커밋하는 것을 선호하지 않습니다). 저는 그것을 저장합니다`site.yml`(이미 안에`.gitignore`) 및 플러그인`bakery`그들은 빌드 측에 추가해야 할 로직을 통해 템플릿에 주입한다.&amp;lt;/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;footer.thyme&amp;lt;/code&amp;gt;— 빌드`./gradlew serve`로컬에서 로드할 것입니다. 배포 시, 저는 인젝션을 이동시킬 것입니다.`site.yml`또는 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는 아닙니다.&amp;lt;strong&amp;gt;아니&amp;lt;/strong&amp;gt;비밀입니다. 설계상 공개되어 있습니다. 당신의 데이터를 보호하는 것은&amp;lt;strong&amp;gt;Firestore 보안 규칙&amp;lt;/strong&amp;gt;, API 키가 아닙니다. 그 안에 넣지 마세요`.env`서버 측에서 로드된 — 브라우저에 노출되도록 의도되었습니다.&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;_phase_5_최종_아키텍처&amp;quot;&amp;gt;Phase 5 : 최종 아키텍처&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 최종 아키텍처 — 문의 폼 Firebase
actor Utilisateur as USER

package &amp;quot;브라우저&amp;quot; #E8F5E9 {
  rectangle &amp;quot;연락.타임\n(HTML, 허니팟)&amp;quot; as FORM
  rectangle &amp;quot;contact.js\n(유효성 검사 + Firestore)&amp;quot; as CONT
  rectangle &amp;quot;footer.thyme\n(Firebase SDK 초기화)&amp;quot; as FOOT
}

cloud &amp;quot;파이어베이스&amp;quot; #BBDEFB {
  rectangle &amp;quot;Firestore
(연락_메시지)&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;_이_이동이_도그푸딩에_대해_말하는_것&amp;quot;&amp;gt;이 이동이 도그푸딩에 대해 말하는 것&amp;lt;/h2&amp;gt;
&amp;lt;div 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 플러그인으로 생성됩니다.&amp;lt;code&amp;gt;bakery&amp;lt;/code&amp;gt;. 연락처 양식은 사이트 내부에 존재합니다. Supabase → Firebase 마이그레이션은 다음에 문서화되어 있습니다.&amp;lt;code&amp;gt;AGENT.adoc&amp;lt;/code&amp;gt;, 이 이야기는 백로그에서 논의되며, 이 이야기는 를 통해 테스트됩니다`./gradlew serve`, 그리고 그것은 블로그 포스트(당신이 읽고 있는 글)를 생성합니다.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;이것은 순수한 dogfooding이다. 사이트는 플러그인의 제품이고, 플러그인은 개발자의 제품이며, 개발자는 사이트 자체에서 과정을 문서화한다.&amp;lt;/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;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;몇 달 동안 모의를 끌고 다녔던 일(양식은 조용히 거짓말을 했던 전체 세션들) 때문에 나는 뭔가를 깨달았다: 개인 정적 사이트의 백로그는 결코 ‘완료’되지 않는다. 항상 우선순위가 있는 US가 있고, 항상 초안인 글이 있고, 항상 템플릿에 주석 처리된 섹션이 있다. 규율은 모든 것을 끝내는 것이 아니라 사용자에게 보이는 것을 끝내는 것이다.&amp;lt;/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;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_수정_사항_요약&amp;quot;&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;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;파일&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;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;기사 작성&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;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;재작성 (mock → 실제 Firestore)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;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;삭제 SupabaseManager + ContactFormHandler + 초기화 블록&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;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;구성 요소 플레이스홀더 대체 → 실제 값&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_다음_단계_백로그&amp;quot;&amp;gt;다음 단계 (백로그)&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;알림 이메일&amp;lt;/strong&amp;gt;: 하나의 Cloud Function`onCreate`위에`contact_messages`SendGrid을 통해 메일을 보내는. 폼은 저장되지만 알림을 받지 못합니다. 중위 우선순위 — 메시지가 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;클라이언트 측 요청 제한&amp;lt;/strong&amp;gt;: &amp;lt;code&amp;gt;localStorage&amp;lt;/code&amp;gt; 타임스탬프를 추가하여 연속적인 다중 제출을 방지합니다. 허니팟은 naïve 봇을 차단하고, 레이트 리미터는 좀 더 교묘한 봇도 차단할 것입니다.&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;테스트&amp;lt;/strong&amp;gt;: Playwright 테스트가 폼을 제출하고 Firestore에서 문서가 나타나는지 확인합니다. 현재는 수동으로 테스트하고 있습니다 via`./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;_참고문헌&amp;quot;&amp;gt;참고문헌&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;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;Firestore addDoc 문서 — Web v9 모듈형&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;에이전트 거버넌스 시리즈 — 파트 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;거버넌스 에이전트 시리즈 — 파트 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;에이전트 거버넌스 시리즈 — 파트 3: 감사&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: Long Context Vibe Coding에서 시험에 오른 세 LLMs</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;#_맥락_시험대가_아닌_공사_현장&amp;quot;&amp;gt;맥락 : 시험대가 아닌 공사 현장&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_내_테스트_환경&amp;quot;&amp;gt;내 테스트 환경&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_평가_그리드&amp;quot;&amp;gt;평가 그리드&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_round_1_kimi_k2_6_거짓_출발&amp;quot;&amp;gt;Round 1 : Kimi K2.6 — 거짓 출발&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;#_세션_9_호전&amp;quot;&amp;gt;세션 9: 호전&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_세션_10_침묵의_난파&amp;quot;&amp;gt;세션 10 : 침묵의 난파&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;#_라운드_2_glm_5_1_명예로운_전투원&amp;quot;&amp;gt;라운드 2 : GLM-5.1 — 명예로운 전투원&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;#_아키텍처_dsa_대_순수_mla&amp;quot;&amp;gt;아키텍처 : DSA 대 순수 MLA&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_세션_11_13_단단하지만_짜증나는&amp;quot;&amp;gt;세션 11-13: 단단하지만 짜증나는&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_deepseek_v4_pro_전쟁_기계&amp;quot;&amp;gt;라운드 3 : DeepSeek-V4-Pro — 전쟁 기계&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;#_아키텍처_csa_hca_이중_필렛&amp;quot;&amp;gt;아키텍처 : CSA + HCA, 이중 필렛&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_세션_1_8_완화&amp;quot;&amp;gt;세션 1-8 : 완화&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_결정적인_테스트_100k_토큰_규모의_리팩터링&amp;quot;&amp;gt;결정적인 테스트: 100K 토큰 규모의 리팩터링&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;#_대면_비교_지표&amp;quot;&amp;gt;대면: 비교 지표&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_왜_deepseek_v4_pro가_소프트웨어_개발에서_승리하는가&amp;quot;&amp;gt;왜 DeepSeek-V4-Pro가 소프트웨어 개발에서 승리하는가&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_csahca_아키텍처는_코드에_맞게_설계되었습니다&amp;quot;&amp;gt;1. CSA+HCA 아키텍처는 코드에 맞게 설계되었습니다.&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_2_eager_컨텍스트는_그의_편안한_영역이다&amp;quot;&amp;gt;2. EAGER 컨텍스트는 그의 편안한 영역이다&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_3_품질지연_비율이_흐름에_최적입니다&amp;quot;&amp;gt;3. 품질/지연 비율이 흐름에 최적입니다.&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;#_배운_교훈_vibe_coding을_위한_llm_선택_방법&amp;quot;&amp;gt;배운 교훈 : Vibe Coding을 위한 LLM 선택 방법&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_그리고_거버넌스_에이전트는_이_모든_상황에서_어떤가요&amp;quot;&amp;gt;그리고 거버넌스 에이전트는 이 모든 상황에서 어떤가요?&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_기술적_출처&amp;quot;&amp;gt;기술적 출처&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_결론_장인의_선택&amp;quot;&amp;gt;결론 : 장인의 선택&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;읽기 시간 : 18 minutes&amp;lt;/div&amp;gt;
&amp;lt;p&amp;gt;LLM 전쟁도 개발자 터미널에서 이루어진다. 무의미한 학술적 벤치마크가 아니라. 실제 생활에서는: 30,000 토큰 프롬프트, AsciiDoc 기반 에이전트 거버넌스, 디버그해야 하는 Gradle Kotlin DSL 플러그인, 그리고 3주 동안 이어지는 세션. 저는 여러분을 위해 DeepSeek-V4-Pro, Kimi K2.6, GLM-5.1을 테스트해 보았습니다. 여기서 기술적 증거를 바탕으로 한 평결을 제시합니다.&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;/dt&amp;gt;
&amp;lt;dd&amp;gt;
&amp;lt;p&amp;gt;&amp;amp;lt;/think&amp;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;_맥락_시험대가_아닌_공사_현장&amp;quot;&amp;gt;맥락 : 시험대가 아닌 공사 현장&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;3주 전, 나는 &amp;amp;#8230;&amp;amp;#8203;에 대해 일하고 있었다`codebase-gradle`, 내 메타빌드 시스템은 YAML 구성 중앙화하여 네 개의 프로젝트 — PlantUML README 생성기, AsciiDoc 슬라이드 빌더, 정적 JBake 사이트, LLM 챗봇 — 을 관리합니다. Opencode 에이전트는 AsciiDoc 형식의 내 에이전트 파일 방법론에 따라 매 세션 시작 시 약 30K 토큰의 EAGER 컨텍스트를 로드했습니다 — 절대 규칙, 백로그, 마지막 10개 세션 기록.&amp;lt;/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;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 파라미터 / 32B 활성화, 256K 최대 컨텍스트, MLA) - &amp;lt;strong&amp;gt;GLM-5.1&amp;lt;/strong&amp;gt;(Zhipu AI/Tsinghua, 744B params / DSA, 200K 컨텍스트 max) - &amp;lt;strong&amp;gt;DeepSeek-V4-Pro&amp;lt;/strong&amp;gt;(DeepSeek, 1.6T 파라미터 / 49B 활성화, 1M 최대 컨텍스트, 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;모든 것이 클라우드 서버의 Ollama를 통해 제공되며, 모두 thinking 모드(반사 단계 활성화)로 실행됩니다. 과제 : 올바른 코드를 생산하고, 긴 세션 동안 일관성을 유지하며, 컨텍스트가 80K 토큰을 초과할 때 환각을 일으키지 않는 것입니다.&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;_내_테스트_환경&amp;quot;&amp;gt;내 테스트 환경&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 테스트 환경 — Opencode 세션 × 3 LLMs
left to right direction

package &amp;quot;🖥️ 개발자 터미널&amp;quot; #E8F5E9 {
  rectangle &amp;quot;AGENT.adoc
(규칙, 백로그)&amp;quot; as AG
  rectangle &amp;quot;PROMPT_REPRISE
(임무)&amp;quot; as PR
  rectangle &amp;quot;INDEX.adoc
(로드맵)&amp;quot; as IDX
}

package &amp;quot;☁️ 올라마 클라우드&amp;quot; #BBDEFB {
  rectangle &amp;quot;DeepSeek-V4-Pro\n1.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;⚙️ 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줄)&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;코드베이스&amp;quot; : &amp;quot;TDD, 리팩터링,\n스냅샷&amp;quot;
KIMI --&amp;amp;gt; &amp;quot;코드베이스&amp;quot; : &amp;quot;성능 저하
60K 토큰부터&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;각 세션은 약 30K 토큰의 EAGER 컨텍스트로 시작했습니다 :`AGENT.adoc`(287줄)&amp;lt;code&amp;gt;PROMPT_REPRISE.adoc&amp;lt;/code&amp;gt;(51줄),&amp;lt;code&amp;gt;.agents/INDEX.adoc&amp;lt;/code&amp;gt;(218줄),&amp;lt;code&amp;gt;LAZY_EAGER_ESSENTIALS.adoc&amp;lt;/code&amp;gt;(50줄). 컨텍스트가 교환과 함께 빠르게 커졌습니다 — 일반적인 10 메시지 세션은 누적 프롬프트에 15-20K 토큰을 추가했습니다.&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;_평가_그리드&amp;quot;&amp;gt;평가 그리드&amp;lt;/h2&amp;gt;
&amp;lt;div 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;/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;도끼&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;장거리 맥락 일관성&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;에이전트는 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;생산된 코드의 품질&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;건축적 추론&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;환각 저항&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;몇 토큰부터 에이전트가 존재하지 않는 API 또는 클래스를 만들어내기 시작하나요?&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;strong&amp;gt;회복 계수&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;_round_1_kimi_k2_6_거짓_출발&amp;quot;&amp;gt;Round 1 : Kimi K2.6 — 거짓 출발&amp;lt;/h2&amp;gt;
&amp;lt;div 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는 나의 첫 번째 선택이었다. 그의 SWE-Bench Verified (80.2)와 Terminal-Bench 2.0 (66.7) 벤치마크는 훌륭하다. 그의 MLA 아키텍처는 긴 시퀀스에 대한 좋은 효율성을 약속한다.&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;_세션_9_호전&amp;quot;&amp;gt;세션 9: 호전&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Kimi와의 첫 번째 세션. 작업: 메서드 구현`resolveActiveKey()&amp;lt;code&amp;gt;안에`codebase.kt&amp;lt;/code&amp;gt;— API 키를 해결하는 함수이며 CLI 폴백을 갖는 함수. 컨텍스트는 30K 토큰이며, Kimi는 빠르게 추론하고 키 만료 관리가 있는 깨끗한 코드를 생성합니다.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;코드가 컴파일됩니다. 7개의 테스트 케이스가 통과됩니다. 저는 낙관적입니다.&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;_세션_10_침묵의_난파&amp;quot;&amp;gt;세션 10 : 침묵의 난파&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;두 번째 세션. 컨텍스트가 교환으로 인해 약 90K 토큰까지 증가합니다. 나는 Kimi에게 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;여기서부터 일이 꼬이기 시작해. 기미는 존재하지 않는 클래스를 발명하기 시작한다. 그는 나에게 제안한다.&amp;lt;code&amp;gt;AnonymizedObjectMapper&amp;lt;/code&amp;gt;— 가상의 클래스. 그는 혼동한다`ReadmeYmlAnonymizer` et &amp;lt;code&amp;gt;CodebaseYmlAnonymizer&amp;lt;/code&amp;gt;. 그가 내게 수입하라고 제안한다`com.fasterxml.jackson.anonymize.*`— 존재한 적이 없는 패키지.&amp;lt;/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;quot;기억한다&amp;quot;고 한다`GitConfig`필드가 있습니다`anonymizedToken`— 아니, 그것이`resolvedToken()&amp;lt;code&amp;gt;.그는 &amp;amp;#8230;&amp;amp;#8203;에 돌린다`SiteYmlAnonymizer`방법`maskSupabaseCredentials()&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;&amp;lt;em&amp;gt;&amp;lt;em&amp;gt;_ 이게 버그가 아니었다. 그것은 일관성의 점진적인 소멸이었다. 마치 컨텍스트에 추가된 각 토큰이 처음 30,000개의 메모리를 점점 더 희석시키는 것처럼. &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;두 세션 후에 Kimi를 멈춥니다. 진단은 명확합니다: MLA는 KV 캐시를 잘 압축하지만, 미세한 희소 선택 메커니즘이 없으면 주의가 60K 토큰을 넘어가면서 기계적으로 희석됩니다. 각 토큰은 먼 컨텍스트를 점점 더 잘 볼 수 없게 되고 — 빈틈을 소음으로 채우기 시작합니다.&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;_라운드_2_glm_5_1_명예로운_전투원&amp;quot;&amp;gt;라운드 2 : GLM-5.1 — 명예로운 전투원&amp;lt;/h2&amp;gt;
&amp;lt;div 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은 다른 아키텍처와 함께 제공됩니다: 기본 모델에 대한 MLA, 이어서 DSA(DeepSeek Sparse Attention)를 사용한 &amp;lt;strong&amp;gt;continued pre-training&amp;lt;/strong&amp;gt; — 전체 기록에서 관련 있는 상위 2048 토큰을 동적으로 선택하는 가벼운 인덱서.&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;_아키텍처_dsa_대_순수_mla&amp;quot;&amp;gt;아키텍처 : DSA 대 순수 MLA&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;차이는 근본적이다. Kimi가 기록을 단일 잠재 공간에 압축할 때(그리고 점차적으로 관련 정보를 구분하는 능력을 잃을 때), GLM은 사후 훈련된 인덱서를 삽입하여 명시적인 희소 선택을 수행한다.&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 어텐션 아키텍처 — MLA vs MLA+DSA
left to right direction

rectangle &amp;quot;Kimi K2.6 — MLA 순수한&amp;quot; as MLA #FFCDD2 {
  rectangle &amp;quot;KV 캐시
완전&amp;quot; as KV1
  rectangle &amp;quot;잠재 압축&amp;quot; as CL1
  rectangle &amp;quot;디코딩
잠재 공간에서&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 캐시
완전&amp;quot; as KV2
  rectangle &amp;quot;잠재적 압축
(MLA)&amp;quot; as CL2
  rectangle &amp;quot;경량 인덱서\n(DSA, top-k=2048)&amp;quot; as IL
  rectangle &amp;quot;디코딩
선택된 토큰에 대한&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;구조상 무손실&amp;quot;
    Sélection explicite
    des tokens pertinents
  end note
}

MLA --&amp;amp;gt; DSA : &amp;quot;이득 : 희소 선택
희석을 피하는&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;기술 보고서는 명시적으로 다음과 같이 말합니다: DSA는 &amp;lt;em&amp;gt;&amp;quot;lossless by construction&amp;quot;&amp;lt;/em&amp;gt; — SWA (패턴 검색), Gated DeltaNet 또는 SimpleGDN과 같은 대안들과 달리, 이들은 RULER@128K에서 최대 5.69포인트를 잃습니다.&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;_세션_11_13_단단하지만_짜증나는&amp;quot;&amp;gt;세션 11-13: 단단하지만 짜증나는&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;GLM-5.1이 거리를 더 잘 유지합니다. 세션 11 (~60K 토큰)에서 일관성을 유지합니다. 그것은 생산하는`SnapshotManager`네 개의 익명화 도구를 올바르게 관리하는 기능적인&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;하지만 지연은 문제입니다. DSA 반사 단계는 순수 MLA보다 더 무겁습니다 — 인덱서는 각 단계에서 기록을 다시 스캔해야 합니다. Kimi로는 8초가 걸리던 응답이 GLM에서는 15초가 걸립니다. 30개의 메시지로 구성된 세션에서는 이것이 느껴집니다.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;그리고 미묘한 오류들이 있습니다. GLM은 Kimi처럼 환각을 일으키지 않지만, &amp;lt;strong&amp;gt;이름 지정&amp;lt;/strong&amp;gt; 오류를 합니다. 그것은 호출한다`toAnonymizedYaml()&amp;lt;code&amp;gt;호출되어야 하는 메서드`anonymize()`python print(&amp;quot;.  뒤집합니다&amp;quot;, end=&amp;#39;&amp;#39;)`loadReadmeConfiguration()&amp;lt;/code&amp;gt; et &amp;lt;code&amp;gt;loadCodebaseConfiguration()`안에`renderFileSection()&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;GLM과 세 번의 세션을 가졌다. 그는 Kimi보다 확실히 더 낫다. 하지만 이름 지정 세부 사항에 대한 수작업 세 세션은 지치게 만든다.&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_deepseek_v4_pro_전쟁_기계&amp;quot;&amp;gt;라운드 3 : DeepSeek-V4-Pro — 전쟁 기계&amp;lt;/h2&amp;gt;
&amp;lt;div 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는 세 가지 중 가장 야심찬 아키텍처를 탑재하고 출시됩니다: 서로 보완적인 두 주의 메커니즘을 결합하는 하이브리드 시스템입니다.&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;_아키텍처_csa_hca_이중_필렛&amp;quot;&amp;gt;아키텍처 : CSA + HCA, 이중 필렛&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 — 하이브리드 아키텍처 CSA + HCA
left to right direction

rectangle &amp;quot;KV 캐시
1M 토큰&amp;quot; as KV #E3F2FD

rectangle &amp;quot;CSA
압축 희소 어텐션&amp;quot; as CSA #C8E6C9 {
  rectangle &amp;quot;압축
m 토큰 → 1&amp;quot; as CC
  rectangle &amp;quot;희소 어텐션
(DSA, 상위 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
매우 압축된 주의&amp;quot; as HCA #BBDEFB {
  rectangle &amp;quot;극한 압축\nm&amp;#39; &amp;amp;gt;&amp;amp;gt; m → 1&amp;quot; as ECC
  rectangle &amp;quot;밀집된 주의
잔여&amp;quot; as EDA
  ECC --&amp;amp;gt; EDA

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

rectangle &amp;quot;융합
하이브리드&amp;quot; as FUSION #FFF9C4
rectangle &amp;quot;디코딩
일관된&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;압축의 두 단계, 주의의 두 가지 세밀도:&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;: KV 캐시를 모두 압축합니다`m`토큰, 그다음 DeepSeek Sparse Attention(희소 주의) 방식을 적용합니다 — 압축된 입력의 상위 k개만이 조회됩니다. 정확하고, 지역적이며, 효율적입니다. - &amp;lt;strong&amp;gt;HCA&amp;lt;/strong&amp;gt;: 극도의 압축 (요인`m&amp;#39;`훨씬 더 큰`m`), 하지만 압축된 잔여물에 대한 촘촘한 주의 — 제곱 폭발 없이 전역 연결 유지&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;결과 : 1M 토큰에서 DeepSeek-V4-Pro는 단지 소비한다&amp;lt;strong&amp;gt;27%의 추론 FLOPs&amp;lt;/strong&amp;gt; et &amp;lt;strong&amp;gt;10%의 KV 캐시 크기&amp;lt;/strong&amp;gt;DeepSeek-V3.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;그리고 무엇보다, 기술 보고서(그림 9)는 다음과 같이 발표합니다: &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;_세션_1_8_완화&amp;quot;&amp;gt;세션 1-8 : 완화&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;나는 DeepSeek-V4-Pro와 함께 여덟 번의 세션을 보냈고 그것에 대해`codebase-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;세션 1: 구현`CodebaseYmlConfig` et &amp;lt;code&amp;gt;CodebaseYmlAnonymizer&amp;lt;/code&amp;gt;. 350줄의 Kotlin, 인라인 테스트, 모두 컴파일됨. 세션 4: 추가`SnapshotManager`트리 뷰, 파일 수집, 파일당 AsciiDoc 렌더링. 279줄, 오류 없음. 세션 7: 디버그 du`renderFileSection()`네 가지 유형의 다른 익명화 도구를 해결 모호함 없이 관리해야 했고, 세 개의 메시지로 해결되었습니다.&amp;lt;/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보다 더 높습니다 — 생각 모드에서 응답당 약 10-12초 정도입니다. 하지만 수정률은 거의 제로입니다. 저는 에이전트의 오류를 고치는 데 시간을 보내지 않습니다. 저는 코딩합니다 &amp;lt;strong&amp;gt;avec&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;_결정적인_테스트_100k_토큰_규모의_리팩터링&amp;quot;&amp;gt;결정적인 테스트: 100K 토큰 규모의 리팩터링&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;8회차 세션에서 누적된 컨텍스트가 100K 토큰을 초과합니다. 저는 무거운 리팩터링을 요청합니다: 인라인 검증 작업 네 가지를 추출하는`build.gradle.kts`JUnit5 테스트 파일 쪽으로 안에`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;대리인은 3단계 계획을 제안합니다:&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;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;JUnit5와 Kotest 의존성을 추가`buildSrc/build.gradle.kts`&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;인라인 코드를 삭제하다 의`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;계획이 정확합니다. 실행이 깔끔합니다. 그조차도 내가 놓친 엣지 케이스를 식별합니다: 중복된 Jackson 의존성 사이`buildscript {}` et `buildSrc/build.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;100K 컨텍스트 토큰, 그리고 에이전트는 기억합니다`CodebaseYmlAnonymizer.TOKEN_MASK`이다`&amp;quot;&amp;lt;strong&amp;gt;*&amp;lt;/strong&amp;gt;&amp;quot;`, 라고`GitConfig.resolvedToken()&amp;lt;code&amp;gt;는에서`readme.kt&amp;lt;/code&amp;gt;, 그리고`SnapshotManager.PRUNED_DIRS`제외한다`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;그게 차이야.&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;_대면_비교_지표&amp;quot;&amp;gt;대면: 비교 지표&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;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;최대 문맥&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;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;주의 메커니즘&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;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 순수한&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;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 하이브리드&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;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;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.6테라&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;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;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;게시되지 않은&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;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;관측된 열화 임계치&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;~60K 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;~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;행동을 넘어&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;안정성 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;미공개된&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;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;128K까지 안정적 (도표 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;미발표&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;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;포기 전에 진행된 세션&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;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 (계속)&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;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;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;평균 지연 시간 (think 모드)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;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;신뢰도 계수*&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;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;*신뢰 계수 = 에이전트의 코드와 커밋을 줄 단위로 검토하지 않고 가져올 수 있는 나의 능력에 대한 주관적 측정.&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 레이더 비교 — 3개의 LLM 지원된 소프트웨어 개발
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;일관성
80K+ 토큰&amp;quot; as coh #F5F5F5
rectangle &amp;quot;품질\n코드&amp;quot; as qual #F5F5F5
rectangle &amp;quot;건축적
사고&amp;quot; as arch #F5F5F5
rectangle &amp;quot;저항
환각&amp;quot; as hall #F5F5F5
rectangle &amp;quot;속도\n(개발 경험)&amp;quot; as speed #F5F5F5
rectangle &amp;quot;지식
Gradle/Kotlin&amp;quot; as kg #F5F5F5
rectangle &amp;quot;수정\n비율&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;_왜_deepseek_v4_pro가_소프트웨어_개발에서_승리하는가&amp;quot;&amp;gt;왜 DeepSeek-V4-Pro가 소프트웨어 개발에서 승리하는가&amp;lt;/h2&amp;gt;
&amp;lt;div 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의 우수성은 단일 요인 때문이 아닙니다 — 그것은 수렴입니다:&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_csahca_아키텍처는_코드에_맞게_설계되었습니다&amp;quot;&amp;gt;1. CSA+HCA 아키텍처는 코드에 맞게 설계되었습니다.&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;극한&amp;lt;/strong&amp;gt; 사용 사례입니다. 다음과 같이 필요합니다: - 현지 정확도 (이 클래스는 무엇을 상속받았나요? 이 메서드는 어디서 정의되었나요?) →&amp;lt;strong&amp;gt;CSA&amp;lt;/strong&amp;gt; - 전체적인 비전 (이 모듈이 왜 존재하는가? 네 개의 익명화 도구가 어떻게 상호작용하는가?) →&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는 MLA만으로 로컬 정확성을 관리하지만 60K 이상에서는 글로벌 시각을 잃습니다. GLM은 DSA로 글로벌 시각을 개선하지만 단일 수준에 머무릅니다. 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;_2_eager_컨텍스트는_그의_편안한_영역이다&amp;quot;&amp;gt;2. EAGER 컨텍스트는 그의 편안한 영역이다&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;내 거버넌스 시스템은 시작 시 약 30K 토큰의 규칙과 백로그를 로드합니다. 128K까지 안정적인 창을 가지면, DeepSeek은 세션 교환을 위한 약 100K 토큰의 여유를 갖습니다. 이는 Kimi가 성능 저하 없이 처리할 수 있는 양의 3~4배입니다.&amp;lt;/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;_ 완벽한 안정성을 위한 128K 창은 정확히 내 필요에 맞습니다: EAGER 30K + 교환 70K = 녹색 구역을 벗어나지 않는 20-30 메시지의 생산적인 세션. &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_품질지연_비율이_흐름에_최적입니다&amp;quot;&amp;gt;3. 품질/지연 비율이 흐름에 최적입니다.&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;예, DeepSeek-V4-Pro는 Kimi보다 느립니다 (11초 vs 8초). 하지만 작업의 &amp;lt;strong&amp;gt;total&amp;lt;/strong&amp;gt; 시간이 훨씬 짧습니다. 왜냐하면 에이전트의 환각을 수정하는 데 20분을 쓰지 않기 때문입니다.&amp;lt;/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;#39;질문을 던지는 순간&amp;amp;#8217;과 &amp;#39;코드가 내 저장소에 들어 있고 작동하는&amp;#39; 사이의 시간을 측정합니다. 이 지표에서 DeepSeek-V4-Pro가 세 가지 중 가장 빠릅니다.&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;_배운_교훈_vibe_coding을_위한_llm_선택_방법&amp;quot;&amp;gt;배운 교훈 : Vibe Coding을 위한 LLM 선택 방법&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;순위를 넘어 이 경험은 보조 소프트웨어 개발을 위한 LLM을 평가하는 방법을 가르쳐 주었는데, 이는 어떤 벤치마크에도 포함되지 않은 기준으로 평가하는 것입니다 :&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;아키텍처를 봐, 발표된 컨텍스트 크기는 보지 마.&amp;lt;/strong&amp;gt;— 모델이 256K 컨텍스트를 발표하지만 MLA 하나만 있다면 Kimi처럼 끝날 것이다: 이론적으로는 가능하지만 실제로 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;당신의 컨텍스트에서 테스트하고, 일반적인 벤치마크에서는 테스트하지 마세요&amp;lt;/strong&amp;gt;내 초기 30K 토큰 AsciiDoc 프롬프트, 절대 규칙과 백로그를 갖춘 것은 HLE 또는 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;지연이 적이 아니며, 품질이 따라올 때&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;공개된 기술 보고서가 없는 모델에 주의해&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 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 결정 트리 — Dev Assisté를 위한 LLM 선택하기
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;_그리고_거버넌스_에이전트는_이_모든_상황에서_어떤가요&amp;quot;&amp;gt;그리고 거버넌스 에이전트는 이 모든 상황에서 어떤가요?&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;이 경험은 내가 Eager/Lazy 거버넌스에 관한 나의 기사에서 주장해 온 점을 검증한다:&amp;lt;strong&amp;gt;LLM의 품질과 거버넌스의 품질은 곱셈적이며, 가법적이지 않다&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 K2.6를 사용했을 때, 나의 거버넌스는 완벽했지만 — 모델이 정보를 희석시켰다. 결과: 완벽한 거버넌스 × 환각 = 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;DeepSeek-V4-Pro와 함께, EAGER 거버넌스 (규칙 토큰 30K) + LAZY (세션 아카이브, 기술 참조) + Hot/Warm/Cold (회전 백업)은 각 층이 서로를 증폭시키는 생태계를 형성합니다. 에이전트는 규칙을 바로 볼 수 있습니다 (EAGER), 기록을 확인할 수 있습니다 (LAZY), 그리고 컨텍스트가 포화되지 않습니다 (회전 백업).&amp;lt;/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;_ 거버넌스가 없는 좋은 LLM은 핸들이 없는 Ferrari 엔진이다. 좋은 LLM이 없는 좋은 거버넌스는 엔진이 없는 핸들이다. DeepSeek-V4-Pro + 나의 거버넌스 에이전트 = 운전을 한다는 느낌을 처음으로 갖게 된 순간. &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;회전식 백업 메커니즘(10 세션마다 또는 EAGER 라인 &amp;amp;gt;500줄일 때 회전)은 128K 컨텍스트를 유지하는 모델과 함께 의미가 생깁니다: 활성 세션 10개의 슬라이딩 윈도우는 컨텍스트를 최신 상태로 유지하면서 절대 degradation 영역을 초과하지 않습니다.&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;_기술적_출처&amp;quot;&amp;gt;기술적 출처&amp;lt;/h2&amp;gt;
&amp;lt;div 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;/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; — 에서 다운로드된 PDFhttps://huggingface.co/deepseek-ai/DeepSeek-V4-Flash[허깅페이스]. 그림 9 : &amp;quot;검색 성능은 128K 컨텍스트 창 내에서 매우 안정적으로 유지됩니다. 128K 지점을 넘어가면 성능 저하가 보이기 시작하지만, 1M 토큰에서의 모델의 검색 기능은 놀랍도록 강력하게 유지됩니다.&amp;quot; CSA+HCA 아키텍처 문서화 섹션 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 기술 보고서&amp;lt;/strong&amp;gt; —https://arxiv.org/abs/2602.15763[arXiv:2602.15763]. DSA는 continued pre-training을 통해 도입되었으며, &amp;quot;lossless by construction&amp;quot;. 최대 컨텍스트 202,752 토큰. 표 3/5/6은 장기 컨텍스트 성능을 대안(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;Kimi K2.6 모델 카드&amp;lt;/strong&amp;gt; —https://huggingface.co/moonshotai/Kimi-K2.6[허깅페이스]. MLA, 256K 최대 컨텍스트. 에이전트 작업에서 임계값을 초과한 discard-all 전략(장기 컨텍스트에서 MLA의 실질적 한계를 암시적으로 확인).&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;키미 K2 기술 보고서&amp;lt;/strong&amp;gt; —https://arxiv.org/abs/2507.20534[arXiv:2507.20534]. 아키텍처 MoE, MuonClip 옵티마이저, 에이전트 성능 (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;가장 드러나는 점: 자체 평가에서 Moonshot은 컨텍스트 창이 초과되자마자 &amp;lt;strong&amp;gt;discard-all&amp;lt;/strong&amp;gt;(오래된 컨텍스트 삭제) 전략을 적용합니다. 이는 제가 관찰한 바를 정확히 확인해 줍니다: MLA만으로는 256K 창 전체에서 일관성을 유지할 수 없습니다. 아키텍처가 따라가지 못합니다.&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;_결론_장인의_선택&amp;quot;&amp;gt;결론 : 장인의 선택&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;3주와 13번의 실제 개발 세션 후, 판결은 확정적이다:&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;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;최대 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;어디서나 지배한다&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;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 세션&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;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세션, 채택된&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`는 `Opencode`을 사용한 모든 소프트웨어 개발 지원 세션의 기본 LLM이 되었습니다. 최신이라서가 아닙니다. 학술적 벤치마크가 최고라서도 아닙니다. 하지만 개발자가 30K 토큰 에이전트 컨텍스트와 함께 Gradle Kotlin 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;CSA+HCA는 아키텍처적 게임 체인저입니다. 이중 레벨 압축 + 스파시피케이션은 구현 세부 사항이 아니라 — 코드 어시스턴트와 신뢰할 수 있는 팀원 사이의 차이를 만드는 요소입니다.&amp;lt;/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; 나는 DeepSeek-V4-Pro를 선택했는데, 이는 세 개 중 오직 하나만이 내 에이전트 거버넌스를 방어적 제약(&amp;quot;에이전트가 잊지 않도록 어떻게 방지할까?&amp;quot;)에서 공격적 장점(&amp;quot;에이전트가 모든 것을 기억한다면 지금 무엇을 건설할 수 있을까?&amp;quot;)으로 바꾸기 때문이다. (empty)&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;이 글은 프로젝트에서 진행된 실제 개발 13세션의 결과이다.&amp;lt;code&amp;gt;codebase-gradle&amp;lt;/code&amp;gt;, 문서화된`.agents/sessions/`내 에이거/레이즈/핫/워머/콜드 에이전트 거버넌스 방법론에 따라. 인용된 기술 출처는 HuggingFace와 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>에이전트 컨텍스트 감사: 당신의 자체 거버넌스가 문제가 될 때 — 그리고 아무것도 잃지 않고 이를 복구하는 방법</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;#_장면_세션_051_뭔가_이상해&amp;quot;&amp;gt;장면: 세션 051, 뭔가 이상해&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_감사_627줄_분석하기&amp;quot;&amp;gt;감사: 627줄 분석하기&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_공학을_잃는_두려움&amp;quot;&amp;gt;공학을 잃는 두려움&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_해결책_분할_백과사전&amp;quot;&amp;gt;해결책: 분할 백과사전&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_essentials_파일의_내용&amp;quot;&amp;gt;ESSENTIALS 파일의 내용&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_결과_eager_컨텍스트_50&amp;quot;&amp;gt;결과: EAGER 컨텍스트 -50%&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_왜_폴더_이름이_encyclopedies&amp;quot;&amp;gt;왜 폴더 이름이 `encyclopedies/&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_시리즈의_논리적_연속&amp;quot;&amp;gt;시리즈의 논리적 연속&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_내가_다르게_했더라면&amp;quot;&amp;gt;내가 다르게 했더라면&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_자신의_컨텍스트를_감사하는_가이드&amp;quot;&amp;gt;자신의 컨텍스트를 감사하는 가이드&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_살아_있는_거버넌스&amp;quot;&amp;gt;살아 있는 거버넌스&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_링크&amp;quot;&amp;gt;링크&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;읽기 시간 : 14 minutes&amp;lt;/div&amp;gt;
&amp;lt;p&amp;gt;몇 주를 보내며 완벽한 에이전트 거버넌스를 구축했습니다. Eager/Lazy, 세션 종료 절차, 순환 백업. 시스템이 가동 중입니다. 그러던 어느 날, 에이전트가 느려졌습니다. 응답이 희석되었습니다. 측정 결과: 자동으로 로드된 1414줄. 그리고 가장 나쁜 점은, 원인이 코드가 아니며, 백로그도 아니며, 세션 아카이브도 아니라는 것입니다. 원인은 당신의 방법을 문서화한 파일입니다.&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;/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;_장면_세션_051_뭔가_이상해&amp;quot;&amp;gt;장면: 세션 051, 뭔가 이상해&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;2026년 4월 30일, 16시 00분. 저는 현재 진행 중인 세션에서`magic-stick`, 내 프로젝트의 Linux 라이브 ISO 빌드. Opencode 에이전트가 평소처럼 자동으로 내 Eager 파일들을 로드했어요 —&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;, `.agents/INDEX.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;하지만 답변이 미지근해. 에이전트는 추론에 두 초 더 걸린다. 그는 세 메시지 전에 눈앞에 있던 세부 사항을 잊어버린다. 이는 충돌도 오류도 아닌 — 서서히 나빠지는 현상, 바로 눈치채지 못하는 종류다.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;이미 세션 048에서 그런 상황을 경험한 적이 있었는데, 그때 지배 파일들이 누적으로 5200줄에 달한다는 사실을 알게 되었습니다. 그러고 나서 10세션 슬라이딩 윈도우를 가진 Hot/Warm/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;하지만 여기, 우리는 세션 051에 있다. 백업 회전이 정상적으로 이루어졌다 — EAGER 파일은 2087줄에서 500줄로 줄었다. 그런데 상황은 여전히 무겁다. 뭔가 놓치고 있는 것 같다.&amp;lt;/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-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줄. 백업 로테이션이 INDEX, SESSIONS_HISTORY, COMPLETED_TASKS를 깔끔하게 정리했습니다 — 이들은 깨끗합니다. 하지만 방 안에 코끼리가 있는데 내가 보지 못했어 :&amp;lt;strong&amp;gt;627줄&amp;lt;/strong&amp;gt;한 파일에`AGENT_MODUS_OPERANDI.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;이 파일은 내가 세션 1에서 Eager/Lazy 전략을 문서화하기 위해 쓴 파일입니다. 이것은 방법의 매뉴얼입니다. 그리고 이것은 단독으로 EAGER 컨텍스트의 44%가 되었습니다.&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 컨텍스트 EAGER — 1414줄
rectangle &amp;quot;AGENT_MODUS_OPERANDI
**627줄 (44%)**&amp;quot; as MOD #FFCDD2
rectangle &amp;quot;AGENT.adoc\n287 줄 (20%)&amp;quot; as AG #BBDEFB
rectangle &amp;quot;INDEX.adoc
218줄 (15%)&amp;quot; as IDX #C8E6C9
rectangle &amp;quot;COMPLETED_TASKS\n213 줄 (15%)&amp;quot; as ARC #FFF9C4
rectangle &amp;quot;PROMPT_재개
51줄 (4%)&amp;quot; as PRO #E1BEE7
rectangle &amp;quot;SESSIONS_HISTORY
18 줄 (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;아이러니가 완전하다. &amp;lt;strong&amp;gt;컨텍스트를 절약하기 위해 설계된&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;_감사_627줄_분석하기&amp;quot;&amp;gt;감사: 627줄 분석하기&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;섹션별로 감사를 진행하기로 결정했습니다. 삭제하기 위해서가 아니라, *이해*하기 위해 EAGER에 있어야 할 것과 다른 곳에 거주해도 지식 손실 없이 살 수 있는 것을 구분하기 위해서입니다.&amp;lt;/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_MODUS_OPERANDI.adoc`그리고 내가 거기서 찾는 것 :&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;섹션&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;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 — 개요&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;문제 해결, 기본 원칙, 대시보드와 매뉴얼의 비유&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;(No source text provided, so nothing to translate.)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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 — 파일 구조&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;트리 구조, 로딩 정책, 언제 무엇을 로드할지&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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 — 절대 규칙&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 금지, 파괴적인 명령어 금지, 비밀 금지&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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 — 세션 생명 주기&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;템플릿 개시, 작업 규칙, 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;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;P5 — 메트릭스와 임계값&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;이상적인 세션 (15-30분, 1-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;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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 — 세션 유형&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;탐지 표, 제안 형식, 예외&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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 — 지속적인 개선&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;추적 지표, 주간 리뷰&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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 — 시작 체크리스트&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;Bootstrap 새 프로젝트&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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 — 참고문헌&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;참조 파일, 외부 리소스&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;(파일 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;부록 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;용어 사전, 버전 기록&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;판결은 최종적 :&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;완전한 중복&amp;lt;/strong&amp;gt;(167 줄) : P3, P4, P9 — 모든 것이 이미 안에 있다`AGENT.adoc` ou &amp;lt;code&amp;gt;INDEX.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;&amp;lt;strong&amp;gt;실전에서 assoluta 참조된 적이 없음&amp;lt;/strong&amp;gt;(215 줄) : P5, P6, P7, P8, 부록 — 메타 거버넌스가 어느 세션에서도 사용된 적이 없는&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;유일한 지식&amp;lt;/strong&amp;gt;(161 lignes) : P1과 P2 — 어휘 LAZY/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;627줄에&amp;lt;strong&amp;gt;382는 소음&amp;lt;/strong&amp;gt;. 이 382줄은 &amp;lt;strong&amp;gt;세션 시작 시마다&amp;lt;/strong&amp;gt; 로드되고, 에이전트에 의해 소비되며, 내가 인사도 하기 전에 그의 주의를 희석시킵니다.&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 AGENT_MODUS_OPERANDI.adoc 감사 — 627 줄
left to right direction

rectangle &amp;quot;🟡 **중복**\n167줄 (27%)&amp;quot; as DUP #FFF9C4 {
  card &amp;quot;P3 — 절대 규칙
(34 줄)&amp;quot; as D1
  card &amp;quot;P4 — 생명 주기
(133 줄)&amp;quot; as D2
  card &amp;quot;P9―참고문헌
(30줄)&amp;quot; as D3
}

rectangle &amp;quot;🔴 **절대 확인되지 않음**
215줄 (34%)&amp;quot; as JAM #FFCDD2 {
  card &amp;quot;P5 — 메트릭
(49 줄)&amp;quot; as J1
  card &amp;quot;P6 — 탐지
(72줄)&amp;quot; as J2
  card &amp;quot;P7 — 개선
(39줄)&amp;quot; as J3
  card &amp;quot;P8 — Bootstrap
(13줄)&amp;quot; as J4
  card &amp;quot;부록 — 용어집
(42 줄)&amp;quot; as J5
}

rectangle &amp;quot;🟢 **유일한 지식**
161줄 (26%)&amp;quot; as UNI #C8E6C9 {
  card &amp;quot;P1 — 개요
(64줄)&amp;quot; as U1
  card &amp;quot;P2 — 파일 구조
(97 줄)&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;이 다이어그램이 모든 것의 핵심이다. 중복(노란색)은 순수한 노이즈이다 — 에이전트가 이를 두 번 읽으며, 두 개의 다른 파일에서이다. 절대 참조되지 않은 (빨간색)은死んだ 문서이다 — 주의 깊게 작성되었지만 결코 사용되지 않았다. 오직 초록색만이 에이전트가 다른 곳에서 찾을 수 없는 지식을 포함한다.&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;_공학을_잃는_두려움&amp;quot;&amp;gt;공학을 잃는 두려움&amp;lt;/h2&amp;gt;
&amp;lt;div 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;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;이 파일은 손으로 썼습니다. 각 섹션은 실제 세션에서 배운 교훈의 결과입니다. P3 부분은 &amp;amp;#8230;&amp;amp;#8203;에서 태어났다.`rm -rf`우발적인 위에`bakery-plugin`실제 Firebase 토큰이 소모되었습니다. P4 부분은 아카이빙을 잊고 흐름을 잃은 15번의 세션 결과입니다. 6단계 절차는 책에서 나온 것이 아니라 고통에서 나온 것이다.&amp;lt;/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;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;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;절대 삭제하지 마십시오. 항상 재배치하십시오.&amp;lt;/strong&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&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_해결책_분할_백과사전&amp;quot;&amp;gt;해결책: 분할 백과사전&amp;lt;/h2&amp;gt;
&amp;lt;div 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;/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_MODUS_OPERANDI.adoc`, 그 결과는 :&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;추출하다&amp;lt;/strong&amp;gt;161개의 고유한 지식 라인 (P1 + P2)를 새로운 파일에`LAZY_EAGER_ESSENTIALS.adoc`— 약 50줄로 압축, 엄격히 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;이름 바꾸기&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;— 원래 627줄 파일, &amp;lt;strong&amp;gt;손상되지 않은&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;절대로 충전하지 마&amp;lt;/strong&amp;gt;encyclopedia 파일을 자동으로 — 인간을 위해 존재하며, 미래의 증류를 위해, 그리고 6개월 후에 더 나은 모델로 호출할 에이전트를 위해 존재합니다&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 분할된 백과사전 — AGENT_MODUS_OPERANDI
left to right direction

rectangle &amp;quot;**AGENT_MODUS_OPERANDI.adoc**
627줄
(분할 전)&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**\n**~50줄 — EAGER**&amp;quot; as ESS #C8E6C9 {
  card &amp;quot;P1 압축된
(15줄)&amp;quot; as E1
  card &amp;quot;P2 압축된
(35줄)&amp;quot; as E2
}

rectangle &amp;quot;.agents/encyclopedies/**
**LAZY_EAGER_ENCYCLOPEDIE.adoc**
627줄 — COLD&amp;quot; as ENC #BBDEFB {
  card &amp;quot;P1부터 부록까지
(완전하고 손상되지 않은)&amp;quot; as C1
}

BEFORE -[#4CAF50]-&amp;amp;gt; ESS : &amp;quot;추출의
비판적 지식&amp;quot;
BEFORE -[#2196F3]-&amp;amp;gt; ENC : &amp;quot;완전한 보존
공학의&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;이름`encyclopedies/`그것은 우연이 아닙니다. 백과사전은 아카이브 파일이 아니라 — 조직화된 지식의 집합체이며, 조회할 수 있지만 휴대할 수 없습니다. 지하철에서는 Universalis 백과사전을 읽지 않습니다. 우리는 그것을 도서관에 보관하고, 특정한 질문이 있을 때 거기에 가서 찾습니다.&amp;lt;/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&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_essentials_파일의_내용&amp;quot;&amp;gt;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;여기서 새 파일이 구체적으로 어떻게 보이는지입니다.&amp;lt;code&amp;gt;LAZY_EAGER_ESSENTIALS.adoc&amp;lt;/code&amp;gt;, 추출 및 압축된 P1과 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;오십 줄. 에이전트가 메커니즘을 이해하기 위해 필요한 것은 이것뿐이다. 나머지는 — 수업 기록, 상세한 비유, 단계별 절차, 부록 — 모두 백과사전에 있다.&amp;lt;/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;/strong&amp;gt;. 627개의 엔지니어링 라인이 여전히 여기에 있습니다, 안에`.agents/encyclopedies/LAZY_EAGER_ENCYCLOPEDIE.adoc`. 그들은 단지 도서관에 정리되어 있을 뿐, 작업대 위에 놓여 있지 않다.&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;_결과_eager_컨텍스트_50&amp;quot;&amp;gt;결과: EAGER 컨텍스트 -50%&amp;lt;/h2&amp;gt;
&amp;lt;div 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;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;분할 후 :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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 백과사전 분할 이후 EAGER 컨텍스트 — 837줄 (-41%)
rectangle &amp;quot;AGENT.adoc\n287줄 (34%)&amp;quot; as AG #BBDEFB
rectangle &amp;quot;INDEX.adoc
218 줄 (26%)&amp;quot; as IDX #C8E6C9
rectangle &amp;quot;COMPLETED_TASKS\n213 줄 (25%)&amp;quot; as ARC #FFF9C4
rectangle &amp;quot;PROMPT_REPRISE
51줄 (6%)&amp;quot; as PRO #E1BEE7
rectangle &amp;quot;필수
50줄 (6%)&amp;quot; as ESS #A5D6A7
rectangle &amp;quot;SESSIONS_HISTORY\n18 줄 (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;가장 큰 소비자 (AGENT_MODUS_OPERANDI, 627줄)가 50줄 파일로 대체되었습니다. 이득은 즉각적이며: 577줄의 컨텍스트가 해제되었습니다. 에이전트가 숨을 쉰다.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;그리고 안에`.agents/encyclopedies/`, 원본 파일에 627줄이 기다리고 있습니다. 변함없이. 모든 섹션과 함께 — 중복된 섹션도 포함하고, 한 번도 사용되지 않은 섹션도 포함해서. 어느 날, 더 나은 모델이나 인간 데이터 과학자가 이러한 각도, 가정되는 중복, 배운 교훈을 교차하고 싶어하기 때문입니다. 그날에는 원본 데이터가 있을 것이다.&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;_왜_폴더_이름이_encyclopedies&amp;quot;&amp;gt;왜 폴더 이름이 `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;이름 선택은 장식이 아닙니다. 그것은 메커니즘의 철학을 인코딩합니다.&amp;lt;/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;/strong&amp;gt; (&amp;lt;code&amp;gt;archives/`포함합니다 연대순으로 정리된 과거 데이터 — 월별 COMPLETED_TASKS처럼. 하나의 &amp;lt;strong&amp;gt;backup&amp;lt;/strong&amp;gt; (`backup/&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;&amp;lt;strong&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;이것은 정확히 이 파일의 계약입니다 :&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;폴더&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;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;역사적 데이터 (월별 완료된 작업)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;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;차가운 스냅샷 (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;콜드, 전체 복사&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;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;주제에 대한 포괄적인 지식 (방법론, 패턴)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;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, 자동이 아님&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;이 구분은 잡탕을 피합니다. 각 파일은 *무엇을 포함하는지*에 따라 어디에 있어야 하는지를 알고, *언제 만들어졌는지*에 따라 결정되지 않습니다.&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;_시리즈의_논리적_연속&amp;quot;&amp;gt;시리즈의 논리적 연속&amp;lt;/h2&amp;gt;
&amp;lt;div 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;/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 에이전트 거버넌스 삼부작
left to right direction

package &amp;quot;📖 **Article 0108**
전략 Eager/Lazy&amp;quot; as A1 #E8F5E9 {
  card &amp;quot;메모리 에이전트
세션 간&amp;quot; as M1
  card &amp;quot;EAGER 파일
(자동 로드됨)&amp;quot; as M2
  card &amp;quot;LAZY 파일
(요청 시)&amp;quot; as M3
  card &amp;quot;6단계 절차&amp;quot; as M4
}

package &amp;quot;🧊 **Article 0110**
핫/웜/콜드 메커니즘&amp;quot; as A2 #FFF9C4 {
  card &amp;quot;노화
컨텍스트의&amp;quot; as V1
  card &amp;quot;슬라이딩 윈도우
10 세션&amp;quot; as V2
  card &amp;quot;차가운 파도
동일한&amp;quot; as V3
  card &amp;quot;센서와\n두 개의 트리거&amp;quot; as V4
}

package &amp;quot;🔍 **Article 0111**
감사 + 백과사전 솔루션&amp;quot; as A3 #BBDEFB {
  card &amp;quot;파일 감사
EAGER&amp;quot; as E1
  card &amp;quot;식별
폐기물&amp;quot; as E2
  card &amp;quot;Split
필수/백과사전&amp;quot; as E3
  card &amp;quot;엔지니어링
보존&amp;quot; as E4
}

A1 --&amp;amp;gt; A2 : &amp;quot;메모리가 커집니다 → 노화 메커니즘이 필요합니다&amp;quot;
A2 --&amp;amp;gt; A3 : &amp;quot;노화만으로는 부족합니다
→ 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;답변: « 에이전트에게 두 세션 사이에 메모리를 어떻게 줄 수 있나요?&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;답변 : « 이 기억이 에이전트를 질식시키는 것을 어떻게 막을 수 있나요?&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; 답변 : « 그리고 문제가 아카이브의 크기가 아니라 우리가 *선택*하여 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;첫 번째 논문은 구조를 구축했다. 두 번째 논문은 노화 메커니즘을 추가했다. 세 번째 논문은 내용을 감사하고 — 방법론 자체가 문제라는 것을 발견한다.&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;_내가_다르게_했더라면&amp;quot;&amp;gt;내가 다르게 했더라면&amp;lt;/h2&amp;gt;
&amp;lt;div 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_MODUS_OPERANDI.adoc`이는 단일 문서로 만들어졌으며 — 하나의 선언문이었습니다. 이는 생각을 형식화하는 데 올바른 접근 방식이었습니다. 하지만 생각이 형식화된 후에는 문서를 즉시 분할해야 했습니다: 핵심은 EAGER에, 포괄적인 내용은 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;나는 문서에 자랑스러워서 그것을 하지 않았다. 627줄의 순수 공학, 손으로 쓴 것이며, 각 부분은 세션 수업의 결과물이다. 그것은 나의 작품이었다. 그리고 모든 작가처럼, 나는 그것을 자르는 데 어려움을 겪었다.&amp;lt;/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;/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;오늘, 규칙은 간단합니다: EAGER 컨텍스트에서 100줄이 넘는 문서는 모두 의심 대상입니다. 감사를 받아야 합니다. 처벌이 아니라 감사입니다. 그리고 질문은 결코 &amp;#39;삭제해야 하는가?&amp;#39;가 아니라 &amp;#39;매 세션마다 로드해야 하는가?&amp;#39;입니다.&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;_자신의_컨텍스트를_감사하는_가이드&amp;quot;&amp;gt;자신의 컨텍스트를 감사하는 가이드&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;첫 번째와 두 번째 기사를 따르고 자체적인 Eager/Lazy 거버넌스를 구축했다면, 여기 5단계 감사 절차가 있습니다:&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;측정&amp;lt;/strong&amp;gt; : `wc -l`모든 EAGER 파일에. 총 수는 1000줄 미만이어야 합니다.&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;가장 큰 것 식별&amp;lt;/strong&amp;gt;: 전체 용량의 20%보다 큰 파일이 첫 번째 용의자입니다.&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;섹션별로 감사하기&amp;lt;/strong&amp;gt;: 각 섹션마다 자신에게 묻습니다. « 이 정보가 이미 다른 곳에 있나요? 해당 대리인이 이미 다른 파일에서 이를 읽었나요? 이 정보가 지난 5번의 세션에서 사용되었나요?&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;분류기&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;분할하거나 재배치&amp;lt;/strong&amp;gt;: 고유하고 중요한 것 → ESSENTIALS 압축된. 그 외의 모든 것 →&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 에이전트 컨텍스트 감사 절차 — 5단계
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;이 절차는 15분이 소요됩니다. 50세션 프로젝트에서는 향후 세션당 수백 토큰을 절약하며, ROI는 즉시 발생합니다.&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;_살아_있는_거버넌스&amp;quot;&amp;gt;살아 있는 거버넌스&amp;lt;/h2&amp;gt;
&amp;lt;div 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;strong&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;세션 048은 아카이브가 부풀어 오르고 있음을 드러냈다. 세션 051은 방법론 자체가 부풀어 오르고 있음을 드러냈다. 세션 060은 아마도 다른 것을 드러낼 것이다. 정상이다. 건강하다. 자신을 결코 의심하지 않는 통치는 죽은 통치이다.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;설정된 세 가지 메커니즘 — Eager/Lazy, Hot/Warm/Cold, 백과사전식 분할 — 은 맥락 포화에 대한 심층 방어 체계를 형성합니다. 단독으로는 충분하지 않습니다. 함께 사용하면 서로 보완됩니다:&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;즉시/지연&amp;lt;/strong&amp;gt;정보를 &amp;lt;strong&amp;gt;가용성별로&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;뜨거운/따뜻한/차가운&amp;lt;/strong&amp;gt;정보를 &amp;lt;strong&amp;gt;신선도&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;백과사전식 감사&amp;lt;/strong&amp;gt;정보 구조화 &amp;lt;strong&amp;gt;밀도별로&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 컨텍스트 포화에 대한 깊이 있는 방어
node &amp;quot;**컨텍스트 에이전트**
~800 줄
건전한&amp;quot; as CTX #C8E6C9

node &amp;quot;층 1
**EAGER / LAZY**&amp;quot; as C1 #BBDEFB
node &amp;quot;레이어 2
**더운 / 따뜻한 / 추운**&amp;quot; as C2 #BBDEFB
node &amp;quot;3층
**필수 / 백과사전**&amp;quot; as C3 #BBDEFB

CTX --&amp;amp;gt; C1 : &amp;quot;구조별\n**가용성**&amp;quot;
CTX --&amp;amp;gt; C2 : &amp;quot;구조는
**신선함**&amp;quot;
CTX --&amp;amp;gt; C3 : &amp;quot;구조에 따른\n**밀도**&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;이 세 층으로, 에이전트 컨텍스트가`magic-stick`2087줄(최적화 이전)에서 약 800줄로 줄어들어 — 2.6배로 감소했습니다. 문서 한 줄도, 세션 아카이브 하나도, 배운 교훈 하나도 잃지 않았습니다.&amp;lt;/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&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_링크&amp;quot;&amp;gt;링크&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 — Eager/Lazy 전략 :&amp;lt;a href=&amp;quot;/blog/2026/0108_gouvernance_agent_opencode_eager_lazy_post.html&amp;quot;&amp;gt;AsciiDoc로 AI 에이전트 관리하기&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 0110 — Hot/Warm/Cold 메커니즘 :&amp;lt;a href=&amp;quot;/blog/2026/0110_mecanisme_backup_contexte_agent_post.html&amp;quot;&amp;gt;Sliding Window과 차가운 파도&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://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;프로젝트`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;지식은 사라질 필요가 없습니다. 그것은 필요하지 않을 때는 단순히 로드되지 않아도 됩니다.&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와 차가운 파도 : 에이전트 컨텍스트가 폭발하고 손실 없이 아카이브해야 할 때</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;_요약&amp;quot;&amp;gt;요약&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;단일 프로젝트에 대해 48회 세션을 진행하고 전체적으로 150+회를 한 후, 내가 신중히 구축한 Eager/Lazy 거버넌스 시스템이 답답해지기 시작했다. 가벼워야 할 에이전트 파일들이 누적 5200줄에 달했다. 자동으로 로드되는 컨텍스트는 내가 제어할 수 있는 속도보다 빠르게 증가했다. 이 글에서는 활성 컨텍스트를 가볍게 유지하면서도 절대 손실을 입히지 않도록, 슬라이딩 윈도우와 동일한 콜드 웨이브 사이에 위치한 백업 메커니즘을 어떻게 개념화했는지 설명한다.&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;_시그널_5200_줄&amp;quot;&amp;gt;시그널: 5200 줄&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;저는 현재 세션 048 중이에요`magic-stick`, 내 Linux 라이브 ISO 빌드 프로젝트. Opencode가 나를 바라보고 있다. 항상처럼, 그것은 세션 시작時に 내 Eager 파일을 자동으로 로드했다 —&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;. 별일 없습니다.&amp;lt;/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;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-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;INDEX용 260줄. SESSIONS_HISTORY용 55줄.&amp;lt;strong&amp;gt;1756 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;폴더`sessions/`또 약 3200줄을 추가합니다. 전체 :&amp;lt;strong&amp;gt;5200줄&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;이전 기사에서 내가 이론화한 Eager/Lazy 전략은 작동한다 — 하지만 그녀가 예상하지 못한 탄생 결함이 있다 : 그녀는 없다&amp;lt;strong&amp;gt;노화 메커니즘이 없습니다.&amp;lt;/strong&amp;gt;. 각 세션은 INDEX에 한 줄을 추가합니다, COMPLETED_TASKS에 한 단락을 추가합니다, sessions/ 폴더에 하나의 파일을 추가합니다. 아무것도 절대 나오지 않습니다. 컨텍스트는 매 새로운 세션마다 커지는 눈덩이와 같습니다.&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 컨텍스트 에이전트 성장 — 세션 1에서 48까지
rectangle &amp;quot;세션 1-10&amp;quot; as S10 #E8F5E9
rectangle &amp;quot;세션 11-20&amp;quot; as S20 #C8E6C9
rectangle &amp;quot;세션 21-30&amp;quot; as S30 #A5D6A7
rectangle &amp;quot;세션 31-40&amp;quot; as S40 #81C784
rectangle &amp;quot;세션 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;이건 버그가 아니라 세션 종료 절차의 직접적인 결과입니다. 이 절차는 세부 사항을 하나하나 꼼꼼히 보관합니다. 시스템은 자신의 성공의 희생자입니다.&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;_진단_삼중_중복&amp;quot;&amp;gt;진단 : 삼중 중복&amp;lt;/h2&amp;gt;
&amp;lt;div 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;/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;파일&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;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;열정적 (자동 충전)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;001부터의 모든 세션 테이블 —&amp;lt;strong&amp;gt;+1 줄당 세션&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;열망하는&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;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;열망 (묵시적)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;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;.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;게으른 (추정되는)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;사용되지 않은&amp;lt;/strong&amp;gt;왜냐하면 모든 것이 이미 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;에이전트는 네 가지 근본 원인을 식별한다 :&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 모든 것을 흡수합니다&amp;lt;/strong&amp;gt;— 아카이브를 가리키는 대신`.sessions/`, 그는 각 세션을 완전히 복사합니다.&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는 완전한 기록으로 기능합니다.&amp;lt;/strong&amp;gt;— 표 &amp;quot;Sessions Récentes&amp;quot;에는 30+개의 항목이 있습니다.&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 중복&amp;lt;/strong&amp;gt;— 같은 정보가 INDEX이고, 형식이 다릅니다.&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 규칙이 준수되지 않았습니다.&amp;lt;/strong&amp;gt;— COMPLETED_TASKS는 모든 것을 포함하기 때문에 암시적으로 EAGER입니다.&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`를 10세션으로 제한, `COMPLETED_TASKS`를 비우다, `SESSIONS_HISTORY`를 순수한 LAZY로 이동. 즉시 얻을 수 있는 이득 :&amp;lt;strong&amp;gt;~1900줄 절감&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;/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;_왜_나는_명백한_해결책을_거절한_걸까&amp;quot;&amp;gt;왜 나는 명백한 해결책을 거절한 걸까?&amp;lt;/h2&amp;gt;
&amp;lt;div 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;/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;quot;중복&amp;quot;은 아닙니다. 각 거버넌스 파일은 하나를 포착합니다.&amp;lt;strong&amp;gt;다른 각도&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;색인&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;SESSIONS_HISTORY&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;완료된 작업 아카이브&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;sessions/*.adoc&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;strong&amp;gt;다중 구조화된 관점&amp;lt;/strong&amp;gt;이것이 정확히 우리가 증류하기 위해 필요한 것이며, 나중에 인간(또는 향후 더 잘 훈련된 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;데이터 과학자가 당신에게 말한다고 상상해 보세요: « 6개 컬럼 중 3개를 삭제합시다, 이들은 상관관계가 있습니다. » 당신에게 뭐라고 답변하시겠습니까? 상관관계는 각 컬럼이 같은 현상의 서로 다른 차원을 포착할 때 중복이 아닙니다. 바로 이러한 차원의 풍부함이 데이터셋을 사용 가능하게 만드는 것입니다.&amp;lt;/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;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;em&amp;gt;&amp;lt;em&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;에이전트는 충격을 받는다. 그리고 스스로를 바로잡는다.&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;_제안_동일한_추위_파도&amp;quot;&amp;gt;제안 : 동일한 추위 파도&amp;lt;/h2&amp;gt;
&amp;lt;div 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;/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;/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;뜨거운 (열망하는)&amp;lt;/strong&amp;gt;= INDEX 안의 지난 10개의 세션, 세션 N+1의 PROMPT_REPRISE, 마지막 2개의 세션 파일&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;따뜻한 (게으른)&amp;lt;/strong&amp;gt;= SESSIONS_HISTORY 최근, SCRIPT_VERIFICATION, 모든 참조 문서&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;콜드 (backup/)&amp;lt;/strong&amp;gt;= 나머지 전부, 이동된&amp;lt;strong&amp;gt;손상되지 않은&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 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...
----

메커니즘의 열쇠 :**백업은 중앙 인덱스가 아니라 과거 파도의 정확히 동일한 복사본이다.**. 활성 세션 10을 초과하면 아무것도 삭제하지 않고, 아무것도 재인덱싱하지 않고, 아무것도 병합하지 않습니다. 우리는 N-10 세션 때의 에이전트 파일 패키지를 그대로 취해서 그것을 안으로 이동합니다`backup/`.

활성화된 파일들은 잘립니다:

* INDEX : 최근 10줄만 (슬라이딩 윈도우)
* SESSIONS_HISTORY : 동일
* COMPLETED_TASKS_ARCHIVE : 현재 기간에 대한 새 파일
* sessions/ : 마지막 2개의 세션만

[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 컨텍스트 에이전트의 핫/웜/콜드 아키텍처
package &amp;quot;HOT (EAGER)
자동으로 충전됨
약 300줄&amp;quot; as HOT #FFCDD2 {
  file &amp;quot;INDEX.adoc\n(10 세션)&amp;quot; as IDX_HOT
  file &amp;quot;PROMPT_REPRISE\n(세션 N+1)&amp;quot; as PRO_HOT
  file &amp;quot;AGENT.adoc
(절대 규칙)&amp;quot; as AG_HOT
}

package &amp;quot;WARM (LAZY)
요청 시 로드
~500 줄&amp;quot; as WARM #FFF9C4 {
  file &amp;quot;SESSIONS_HISTORY
(최근 10개)&amp;quot; as HIS_WARM
  file &amp;quot;SCRIPT_VERIFICATION\n(마지막)&amp;quot; as VER_WARM
  file &amp;quot;PROCEDURES.adoc
(템플릿)&amp;quot; as PRO_WARM
  file &amp;quot;*_REFERENCE.adoc
(기술 문서)&amp;quot; as REF_WARM
}

package &amp;quot;콜드 (backup/)
절대 충전되지 않음
인간 전용 읽기&amp;quot; as COLD #BBDEFB {
  folder &amp;quot;Y2026-001-039/&amp;quot; as WAVE {
    file &amp;quot;색인 (완전)&amp;quot; as IDX_COLD
    file &amp;quot;SESSIONS_HISTORY\n(전체)&amp;quot; as HIS_COLD
    file &amp;quot;COMPLETED_TASKS\n(완료)&amp;quot; as ARCH_COLD
    folder &amp;quot;세션/ (001-039)&amp;quot; as SESS_COLD
  }
  folder &amp;quot;Y2026-040-???
(future)&amp;quot; as FUTURE
}

HOT --&amp;amp;gt; WARM : &amp;quot;에이전트가 다시 올라갑니다
필요하면&amp;quot;
WARM --&amp;amp;gt; COLD : &amp;quot;절대 자동이 아니다
인간만이 그곳으로 간다&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
----

즉시 이익은 엄청납니다 : EAGER 컨텍스트가**~5200줄에서 ~300줄**. 17로 나누기. 역사적 데이터를 한 줄도 잃지 않고.

== 왜 이것이 만능이 아닌가

에이전트는 첫 번째 반복에서 두려워했습니다.`backup/`검은 상자가 되는 — 아무도 다시 보지 않을 파일을 버리는 폴더이다. 이는 정당한 두려움이다. 하지만 이는 오해에 기반한다.

잡동사니는 파일을 버릴 때다**구조도 없고, 관례도 없고, 그룹화 논리도 없이**. 여기서는 백업이 기간별(Y2026-001-039)으로 구성되어 있으며, 각 백업 폴더에는**같은 구조**그 파일`.agents/`활성 : INDEX, SESSIONS_HISTORY, COMPLETED_TASKS_ARCHIVE, sessions/.

이것은 만능 상자가 아닙니다. 이것은**타임스탬프가 찍힌 스냅샷**. 그렇다고 해도 이것이 간소화된 버전 관리 메커니즘이라고 말할 수 있다 — 다만 개별 파일을 버전 관리하는 것이 아니라 특정 시점 T에서의 거버넌스 전체 패키지를 버전 관리한다는 차이점이 있다.

과거 정보를 찾아야 할 때, 중앙 인덱스가 필요하지 않습니다. 두 가지 옵션이 있습니다 :

1. **grep` 목표된**:`grep -r &amp;quot;zsh&amp;quot; backup/Y2026-001-039/`— 그리고 zsh를 언급하는 모든 것을 기간 동안 찾으세요, 어떤 관점이든 (INDEX, SESSIONS_HISTORY, 세션 아카이브).
2. **수동 재통합**: 당신은 백업 폴더를 활성 컨텍스트에 일시적으로 복사하고, 해당 특정 기간을 분석하도록 에이전트에게 요청합니다.

인덱스는 암시적이다. 그것은 파일의 구조 자체에 있다 — 각각은 이미 그 자체의 관점에서 인덱스이다.

== 선반의 은유

기구를 직관적으로 만들기 위해, 저는 그것을 세 개의 선반으로 개념화했습니다 :

* **선반 1 (EAGER)**— 작업 계획. 내가 지금 필요한 것 *지금*. INDEX 최근, PROMPT_REPRISE, absolue적인 규칙. 가벼운, 즉각적인, 중요한.
* **선반 2 (LAZY)**— 상담용 도서관. 요청 시 내가 가져올 수 있는 것. 기술 참조, 최근 기록, 절차. 더 크지만 메모리에 로드되지 않음.
* **동굴 (backup/)**— 차가운 아카이브. 지난 것들이지만 버리기를 원하지 않는 모든 것. 에이전트는 그곳에 들어가지 않는다. 인간은 증류하고 싶을 때 그곳으로 내려간다.

에이전트는 처음에 네 번째 선반 — 하나`index-backup.adoc`그것은 LAZY이면서 전체 백업의 목차를 포함할 것이었습니다. 나는 거절했습니다. 그것은 이미 선형 성장에 시달리는 시스템에 또 하나의 중복이 될 것입니다. 아카이브된 파일의 구조는 이미 인덱스입니다.

== 두 트리거 센서

개념화는 탄탄했지만 여전히 사각지대가 있었다:**정확히 언제 회전을 트리거해야 하나요?**기본 문서는 이를 개방된 질문으로 식별했습니다. 2일 후, 답변은 6개 프로젝트의 거버넌스 파일에 코드화되었습니다: 두 트리거를 가진 센서.

=== 자동 트리거 — `N % 10 == 0

첫 번째 트리거는 수학적입니다. 세션 번호가 10의 배수일 때 — 세션 10, 20, 30, 40 — 백업 로테이션이 세션 종료 절차 내에서 자동으로 실행되며, 이는 단계 6 직후입니다.

왜 10일까? 두 상반된 힘 사이의 타협점이다: 너무 짧은 윈도우(5세션)는 연속성에 필요한 맥락을 잃고; 너무 긴 윈도우(20세션)는 맥락 비대 문제를 해결하지 못한다. 하루에 한두 세션 정도를 유지하면 10세션은 약 일주일分の 작업 시간을 커버한다 — 에이전트가 최근 결정을 기억하기에는 충분하지만, 맥락이 폭주하기에는 부족하다.

=== 임계치 트리거 — 500 라인 EAGER

두 번째 트리거는 동적입니다. 세션 번호에 관계없이 누적된 EAGER 파일이 초과하면**500 줄**, 회전이 시작됩니다.

이 임계값은 세션이 예외적으로 생산적인 시나리오 — 즉, 짧은 세션에 많은 콘텐츠가 작성됨 — 에 대비합니다. 120줄의 편집 콘텐츠를 생성하는 세션은 COMPLETED_TASKS_ARCHIVE를 두 줄을 수정하는 디버그 세션보다 훨씬 빠르게 증가시킵니다. 500줄의 임계값은 측정된 via`wc -l .agents/INDEX.adoc .agents/archives/COMPLETED_TASKS_ARCHIVE_*.adoc PROMPT_REPRISE.adoc`, 이 비대칭을 포착한다.

=== 수동 트리거 — &amp;quot;회전 백업

마침내, 인간이 주도권을 유지한다. 키워드`rotation backup`, `backup rotation` ou `lance la rotation backup`요청에 따라 세션 종료와 무관하게 절차가 시작됩니다. 컨텍스트가 무거워지는 것을 느끼지만 아직 10의 배수가 아닌 경우이거나, 새로운 작업을 시작하기 전에 현재 작업 단계를 보관하고 싶을 때 유용합니다.

[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 백업 회전 센서 — 세 가지 트리거
start

:Procédure de fin de session;
note right: Mots-clés &amp;quot;세션 종료&amp;quot;\nou &amp;quot;여기서 멈추자&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
----

이 다이어그램은 세션 종료 절차에서 센서의 정확한 삽입을 보여줍니다. 7단계는 선택적입니다 — 두 조건 중 하나가 참일 때만 실행되지만 — 그러나 항상 *검증*됩니다. 최종 체크리스트에는`[✅] 7. Backup roté (si applicable)`.

=== 폐쇄된 루프

이 센서는 이전 기사에서 열린 루프를 닫습니다. Eager/Lazy 거버넌스는 두 세션 간의 에이전트 메모리 문제를 해결했습니다. Hot/Warm/Cold 메커니즘은 증가하는 메모리 문제를 해결했습니다. 두 트리거가 있는 센서는 *quand* 문제를 해결하여 — 인간이 컨텍스트 크기를 모니터링하는 정신적 부담을 덜어줍니다.

[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 에이전트 거버넌스의 세 계층
left to right direction

package &amp;quot;층 1 — 메모리
(조항 0108)&amp;quot; as C1 #E8F5E9 {
  rectangle &amp;quot;**EAGER**
대시보드
자동 충전&amp;quot; as EAG
  rectangle &amp;quot;**LAZY**\n소유자 매뉴얼\n요청 시 로드&amp;quot; as LAZ
  EAG -[hidden]right-&amp;amp;gt; LAZ
}

package &amp;quot;층 2 — 노화
(제0110조)&amp;quot; as C2 #FFF9C4 {
  rectangle &amp;quot;HOT
10개의 활성 세션
~300줄&amp;quot; as HOT
  rectangle &amp;quot;**WARM**
참조
절차&amp;quot; as WRM
  rectangle &amp;quot;**COLD**
backup/
차가운 파도&amp;quot; as CLD
  HOT -[hidden]right-&amp;amp;gt; WRM
  WRM -[hidden]right-&amp;amp;gt; CLD
}

package &amp;quot;3층 — 트리거
(오늘)&amp;quot; as C3 #BBDEFB {
  rectangle &amp;quot;**자동**
N % 10 == 0&amp;quot; as AUTO
  rectangle &amp;quot;**임계값**
&amp;amp;gt; 500 라인&amp;quot; as SEUIL
  rectangle &amp;quot;**Manuel**
회전 백업&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;메모리 크기가 증가한다\n→ 메커니즘이 필요합니다\n노화&amp;quot;
C2 --&amp;amp;gt; C3 : &amp;quot;노화
→ 트리거가 필요합니다
실행하기 위해&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
----

세 층이 논리적으로 쌓입니다. 첫 번째 층은 에이전트에게 기억을 제공합니다. 두 번째 층은 이 기억이 에이전트를 압도하는 것을 방지합니다. 세 번째 층은 이 기억의 유지 관리를 자동화하여 인간이 그에 대해 생각하지 않아도 되도록 합니다.

=== cheroliv.com에서의 효과적인 마이그레이션

메커니즘은 이론적으로 남아 있지 않았습니다. 위에서`cheroliv.com`, 첫 번째 백업 로테이션은 2026년 4월 29일에 실행되었습니다 — 세션 -6부터 2까지가 이동했습니다.`.agents/backup/Y2026-sessions-neg6-a-002/` :

|===
|파일 |회전 전 |회전 후 |이득 |`.agents/INDEX.adoc` |19개 세션 나열 |10회 (3-12) |-9 항목 |`.agents/SESSIONS_HISTORY.adoc` |18 세션 |10 세션 |-8개 항목 |`COMPLETED_TASKS_ARCHIVE` |161줄 (세션 1-12) |135 라인 (세션 3-12) |-26 줄 |`sessions/` |20개의 파일 |10 파일 |-10 파일 |**백업/** |존재하지 않는 |1차 한파 (보관된 10개 세션) |+1 냉동 팩
|===

라인 수의 증가량은 작았습니다 — 프로젝트는 아직 어리고, 12세션이었습니다 — 중요한 것은 그것이**메커니즘이 마련되어 있습니다.**. 다음 자동 회전은 세션 20에서 트리거되거나, 500개의 EAGER 라인에 도달하면 더 일찍 트리거됩니다.

== 인간-에이전트 수업

이 세션 048은 IA 에이전트와의 협업에서 근본적인 것을 배웠습니다.

에이전트는 자연스러운 편향이 있다: 그것은 시도한다**최적화하다**, à **간소화하다**, à **중복을 제거하다**. 이것은 깨끗하고 간결한 응답을 생성하도록 훈련된 시스템의 편향이다. 데이터셋이 풍부하고 다차원적이면, 그의 첫 번째 반응은 그것을 가장 간단한 형태로 줄이는 것이다.

인간은 그와 달리 다른 직관을 가지고 있다: 그는 구조적 중복이 무언가임을 직감한다**장점**, 그것이 결함이 아닙니다. 같은 현실에서의 다양한 관점이 바로 나중에 품질 높은 증류를 가능하게 할 것입니다.

에이전트가 틀린 것이 아니다.
그의 &amp;quot;optimum&amp;quot;은 내 것이 아니다.
에이전트는 를 위해 최적화한다**현재**— 즉각적인 맥락, 제기된 질문에 대한 빠른 답변. 인간은 이를 위해 최적화한다**미래**— 3개월 혹은 3년 이내에 찾아내고, 교차시키고, 증류하는 능력

____ 내가 원하는 것은 원본 자료야. 너의 생각, 주저함, 그리고 나의 질문에 대한 답변. 네 요약은 아니야. 요약은 내가 너보다 더 잘 만들 수 있어. 내가 원하는 것은 원본 자료야. ____

세션 끝에 내가 그에게 한 이 말이 모든 것을 요약한다. 에이전트는 생산 도구다. 인간은 증류 도구다. 거버넌스는 에이전트가 모든 것을 스스로 이해하도록 하기 위한 것이 아니라 — 인간이 나중에 생산된 물질을 다룰 수 있도록 하기 위한 것이다.

같은 차가운 파도는 이 철학의 건축적 번역이다: 아무것도 버리지 않고, 아무것도 합치지 않고, 아무것도 재인덱스하지 않는다. 패키지를 그대로 이동한다. 증류는 나중에 수작업으로 인간이 수행한다.

== 이전 글의 논리적 연속

읽으셨다면link:/blog/2026/0108_gouvernance_agent_opencode_eager_lazy_post.html[Eager/Lazy 전략에 관한 기사], 당신은 자연스러운 진행을 인식하게 될 것입니다 :

1. **제108조**— Eager/Lazy 거버넌스 : *comment* 에이전트 컨텍스트를 두 가지 가용성 수준으로 구성하는 방법
2. **이 글 0110**— 백업 메커니즘 : *어떻게* 이 컨텍스트를 잃지 않고 오래 보존할 수 있을까, 너무 커질 때

첫 번째 기사는 다음과 같은 질문에 답했습니다: « 에이전트는 두 세션 사이에서 아무것도 기억하지 못합니다. 어떻게 그에게 기억을 줄 수 있을까요?

이것은 첫 번째에서 불가피하게 이어지는 질문에 답한다: « 메모리는 각 세션마다 증가하는데, 이를 지우지 않고 에이전트를 질식시키는 것을 어떻게 막을 수 있을까?

답은 하나의 패턴으로 요약됩니다:**더운/따뜻한/추운**, 거버넌스 파일에 적용된. 그리고 하나의 원칙 :**절대 아무것도 잃지 말고, 항상 모든 것을 재배치하라**.

== 링크

* 이전 글 :link:/blog/2026/0108_gouvernance_agent_opencode_eager_lazy_post.html[AsciiDoc를 사용해 AI 에이전트 관리하기]
* 내 사이트 : https://cheroliv.com
* 프로젝트`magic-stick`: https://github.com/cheroliv/magic-stick

---

*좋은 거버넌스 시스템은 결코 데이터를 삭제하지 않습니다. 그것을 보관합니다.*
----&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>Gradle 9, Docker 29, Testcontainers이 속이 끓을 때 : 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;#_현장_jhipster_프로젝트_더_이상_로드되지_않는_빌드&amp;quot;&amp;gt;현장 : JHipster 프로젝트, 더 이상 로드되지 않는 빌드&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_오류_1_유령_변수_reportsdir&amp;quot;&amp;gt;오류 1 : 유령 변수 `reportsDir&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_오류_2_main_더_이상_gradle_9에서_존재하지_않습니다&amp;quot;&amp;gt;오류 2 : &amp;lt;code&amp;gt;main = &amp;quot;&amp;amp;#8230;&amp;amp;#8203;&amp;quot;&amp;lt;/code&amp;gt; 더 이상 Gradle 9에서 존재하지 않습니다.&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_오류_3_sourcecompatibility는_더_이상_프로젝트_속성이_아닙니다&amp;quot;&amp;gt;오류 3: `sourceCompatibility`는 더 이상 프로젝트 속성이 아닙니다.&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_오류_4_세_피연산자_groovy_어서션&amp;quot;&amp;gt;오류 4: 세 피연산자 Groovy 어서션&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_오류_5_암묵적_의존성_compilekotlin_openapigenerate&amp;quot;&amp;gt;오류 5 : 암묵적 의존성 &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;#_오류_6_generategitproperties가_filteroutputstream_write에서_실패합니다&amp;quot;&amp;gt;오류 6 : `generateGitProperties`가 `FilterOutputStream.write()`에서 실패합니다.&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_마지막으로_testcontainers가_무대에_등장합니다&amp;quot;&amp;gt;마지막으로: Testcontainers가 무대에 등장합니다.&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_phase_1_docker_java_트랙&amp;quot;&amp;gt;Phase 1 : docker-java 트랙&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_2단계_웹_검색_및_github_브레드크럼&amp;quot;&amp;gt;2단계 : 웹 검색 및 GitHub 브레드크럼&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_3단계_이름이_바뀐_아티팩트의_함정&amp;quot;&amp;gt;3단계 : 이름이 바뀐 아티팩트의 함정&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_4단계_해결_강제&amp;quot;&amp;gt;4단계 : 해결 강제&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_최종_결과&amp;quot;&amp;gt;최종 결과&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_수정_사항_요약표&amp;quot;&amp;gt;수정 사항 요약표&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_결론&amp;quot;&amp;gt;결론&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;읽기 시간 : 15 minutes&amp;lt;/div&amp;gt;
&amp;lt;p&amp;gt;당신은 이미 그 순간을 경험했습니다. 단순한`./gradlew build`당신이 하루에 100번 실행하는 것이 갑자기 한 번도 본 적 없는 오류로 인해 멈추기 시작한다면? 이를 연속 11번의 오류로 곱하고, Docker Engine 29 호환성 문제를 충분히 추가한 뒤, 10년 동안 사용해 온 API를 삭제하는 Gradle 9을 뿌리세요. 여기서 전체 로그를 확인할 수 있습니다.&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;다이어그램 1 — Gradle 9 마이그레이션 (오류 체인)&amp;lt;/div&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;title&amp;quot;&amp;gt;👉 명료하고 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 Gradle 9 마이그레이션
rectangle &amp;quot;1
reportsDir&amp;quot; as A
rectangle &amp;quot;2
mainClass&amp;quot; as B
rectangle &amp;quot;3\nsourceCompatibility&amp;quot; as C
rectangle &amp;quot;4
Groovy 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;다이어그램 2 — 호환되지 않는 JHipster 플러그인&amp;lt;/div&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;title&amp;quot;&amp;gt;👉 의존성에 대한 명확한 비전.&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 수정해야 할 플러그인
skinparam shadowing false
skinparam roundcorner 18

rectangle &amp;quot;1
openApiGenerate&amp;quot; as A #FFF3E0
rectangle &amp;quot;2
gitProperties
2.5.7&amp;quot; as B #FFF3E0
rectangle &amp;quot;3
버전
수정된&amp;quot; as C #E3F2FD
rectangle &amp;quot;4
조립 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;도표 3 — Docker 29 / Testcontainers&amp;lt;/div&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;title&amp;quot;&amp;gt;👉 명확한 런타임 기록.&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
조립 OK&amp;quot; as A #E8F5E9
rectangle &amp;quot;2\n테스트 Cucumber 실패&amp;quot; as B #FFEBEE
rectangle &amp;quot;3
Testcontainers 1.32
거부&amp;quot; as C #FFF3E0
rectangle &amp;quot;4
업그레이드
버전&amp;quot; as D #E3F2FD
rectangle &amp;quot;5\n빌드 최종 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;그것을 표현하는 또 다른 방법&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 안정화 로드맵 — Gradle 9부터 최종 빌드까지
left to right direction

rectangle &amp;quot;⚙️ Gradle 9 마이그레이션&amp;quot; as A #E3F2FD
rectangle &amp;quot;🧩 JHipster 플러그인&amp;quot; as B #FFF3E0
rectangle &amp;quot;🐳 Docker 29\nTestcontainers&amp;quot; as C #FCE4EC
rectangle &amp;quot;✅ 안정적인 빌드&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;_현장_jhipster_프로젝트_더_이상_로드되지_않는_빌드&amp;quot;&amp;gt;현장 : JHipster 프로젝트, 더 이상 로드되지 않는 빌드&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;프로젝트`edster`는 2024년에 생성된 JHipster 애플리케이션입니다. 루트 빌드 스크립트`build.gradle`개월 동안 안정 상태를 유지했습니다. 그러고 나서 버전 업그레이드를 결정했습니다 :&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;컴포넌트&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;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;자바&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;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 (프레임워크`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;스프링 부트&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;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;코틀린&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;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;도커 엔진&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;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;자바 버전 변경은 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;첫 번째 발사:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;우리는 빌드 스크립트 로딩조차 벗어나지 못한다. 문제는`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;_오류_1_유령_변수_reportsdir&amp;quot;&amp;gt;오류 1 : 유령 변수 `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;스크립트`jhipster.cucumber-conventions.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;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;정의된 변수는`cucumberReportsDir`. 사용된 것은`reportsDir`-- 어디에서도 정의되지 않은.&amp;lt;/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;/strong&amp;gt;:`outputs.dir(cucumberReportsDir)`&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;_오류_2_main_더_이상_gradle_9에서_존재하지_않습니다&amp;quot;&amp;gt;오류 2 : &amp;lt;code&amp;gt;main = &amp;quot;&amp;amp;#8230;&amp;amp;#8203;&amp;quot;&amp;lt;/code&amp;gt; 더 이상 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;즉시 재시작 :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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는 속성을 제거합니다`main`의 이익을 위해`mainClass`과제에`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;수정&amp;lt;/strong&amp;gt; : &amp;lt;code&amp;gt;mainClass = &amp;quot;org.junit.platform.console.ConsoleLauncher&amp;quot;&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;_오류_3_sourcecompatibility는_더_이상_프로젝트_속성이_아닙니다&amp;quot;&amp;gt;오류 3: `sourceCompatibility`는 더 이상 프로젝트 속성이 아닙니다.&amp;lt;/h2&amp;gt;
&amp;lt;div 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;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;루트 스크립트는 다음과 같았습니다 :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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은 루트 수준에서 이러한 속성을 제거합니다. 이러한 속성은 블록 내에서 존재해야 합니다.&amp;lt;code&amp;gt;java&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;&amp;lt;strong&amp;gt;수정&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;_오류_4_세_피연산자_groovy_어서션&amp;quot;&amp;gt;오류 4: 세 피연산자 Groovy 어서션&amp;lt;/h2&amp;gt;
&amp;lt;div 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;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;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;`진실한 문자열이다. 표현은 &amp;amp;#8230;&amp;amp;#8203;로 해결됩니다&amp;lt;/code&amp;gt;(&amp;amp;#8230;&amp;amp;#8203; == &amp;quot;17&amp;quot;) || true || true`, 항상 참인 것&amp;amp;#8201;&amp;amp;#8212;&amp;amp;#8201;하지만 원하는 엄격한 주장에 대한 구문은 유효하지 않습니다.&amp;lt;/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;/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;_오류_5_암묵적_의존성_compilekotlin_openapigenerate&amp;quot;&amp;gt;오류 5 : 암묵적 의존성 &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;빌드가 진행 중입니다. 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-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은 태스크 사이의 암시적 의존성을 거부합니다. 왜냐하면`compileKotlin`생성된 소스를 읽는`openApiGenerate`, 링크를 선언해야 합니다.&amp;lt;/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;/strong&amp;gt;안에`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`필요합니다 왜냐하면`openApiGenerate`OpenAPI Generator 플러그인(확장 기능)이며, 구성 단계에서 직접 접근할 수 있는 작업이 아닙니다.&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_generategitproperties가_filteroutputstream_write에서_실패합니다&amp;quot;&amp;gt;오류 6 : `generateGitProperties`가 `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;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;&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;플러그인`gradle-git-properties`version 2.5.0는 Java 21 / Gradle 9와 호환되지 않습니다. 내부 버그가 호출을 시도합니다`write(int)`그루비를 통해 스트림이 닫힌 것.&amp;lt;/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;/strong&amp;gt;안에`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;_마지막으로_testcontainers가_무대에_등장합니다&amp;quot;&amp;gt;마지막으로: Testcontainers가 무대에 등장합니다.&amp;lt;/h2&amp;gt;
&amp;lt;div 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;quot;build script debugging&amp;quot;이었다. 각 오류는 빌드 스크립트에서의 Gradle 9 또는 Java 21 호환성 문제였다. 위에서 언급한 여섯 가지 수정을 마친 후,`./gradlew assemble`성공적으로 통과합니다.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;하지만`./gradlew build`테스트도 실행하고, Cucumber 테스트는 Testcontainers를 사용하여 임시 PostgreSQL을 시작합니다.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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은 Docker와 통신할 수 없습니다. 서로 다른 두 가지 전략(환경 변수 + Unix 소켓)이 동일한 오류에서 실패합니다.&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\n1.20.6&amp;quot; as TC
participant &amp;quot;docker-java\n3.4.1&amp;quot; as DJ
participant &amp;quot;도커 엔진
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;_phase_1_docker_java_트랙&amp;quot;&amp;gt;Phase 1 : 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;첫 번째 반응: Testcontainers에서 사용하는 Java Docker 클라이언트가 API 버전을 전송`1.32`그의 HTTP 핸드셰이크에서, 하지만 Docker Engine 29.4.1은 최소한을 요구한다`1.40`. 문제는 클라이언트 측에 있으며, 데몬 측이 아닙니다.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Testcontainers 1.20.6에 의해 해결된 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-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, Testcontainers 1.20.6에서 유래한 버전입니다. docker-java의 최신 공개 버전은 3.5.1입니다. 우리는 버전 강제 업그레이드를 시도해 봅니다.&amp;lt;code&amp;gt;resolutionStrategy`블록 안에`configurations&amp;lt;/code&amp;gt; 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;빌드를 다시 시작합니다. 같은 오류:`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;Gradle 캐시 확인: JAR이 다시 다운로드되었지만 오류가 계속됩니다. docker-java 3.5.1은 여전히 보냅니다`1.32`. 이 버전은 따라서 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;정화 작용&amp;lt;/strong&amp;gt;: 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-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;정리 후 전체 재시작. 같은 오류.&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;_2단계_웹_검색_및_github_브레드크럼&amp;quot;&amp;gt;2단계 : 웹 검색 및 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은 부족합니다. 이제 누군가가 이 정확한 문제를 겪었는지 찾아볼 수밖에 없습니다.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;testcontainers-java 및 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;즉시 결과:&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;—&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;아리아드네의 실은 명확합니다: Docker Engine 29.x는 최소 API 버전을 올렸다`1.24`(이전 결함) 에`1.40`. Testcontainers 1.20.x는 docker-java 3.4.x를 사용하여 API 버전을 협상하여 보내는`1.32`. 데몬은 예의 바르게 그러나 단호히 거부합니다.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;이슈 #11491에서, 유지관리자가 답변합니다 :&amp;lt;/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;_ 안녕하세요, 저는 GH 러너에서 엔진 29.1을 사용하는 버전 2.0.3으로 실행되는 프로젝트가 있는데, 잘 작동합니다. 모두 버전 충돌이 없는지 확인해 주실 수 있나요? 모든 모듈이 무언가로 prefix되었음을 기억해 주세요`testcontainers-&amp;lt;code&amp;gt;. 그래서 버전 2.x부터 우리는 &amp;amp;#8230;&amp;amp;#8203;부터 시작했습니다`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;이 답변에는&amp;lt;strong&amp;gt;두 개의 중요한 정보&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+은 문제를 해결합니다&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;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;_3단계_이름이_바뀐_아티팩트의_함정&amp;quot;&amp;gt;3단계 : 이름이 바뀐 아티팩트의 함정&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;이름 변경은 가혹합니다. Testcontainers 2.x에서는 더 이상 과거 이름 중 어느 것도 해당 이름으로 작동하지 않습니다:&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;이전 이름 (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;새 이름 (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;변경되지 않은&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;첫 번째 시도: Testcontainers BOM 2.0.5를 적용하고 새로운 아티팩트 이름을 안에`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;testcontainers-bom의 BOM은 충분하지 않다.&amp;lt;/strong&amp;gt;왜? Spring Boot 3.4.5가 자체적인 의존성 관리 BOM을 노출하기 때문에 (&amp;lt;code&amp;gt;spring-boot-dependencies&amp;lt;/code&amp;gt;) 누구&amp;lt;strong&amp;gt;이긴다&amp;lt;/strong&amp;gt;Testcontainers BOM의 전달적 해결에서 Spring Boot이 고정합니다`testcontainers` à &amp;lt;code&amp;gt;1.20.6&amp;lt;/code&amp;gt;, 따라서 Spring Boot BOM에 명시적으로 들어 있지 않은 모든 아티팩트(예: 새로운`testcontainers-*&amp;lt;code&amp;gt;) 이전 이름으로 해결됩니다`1.20.6&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;점검하면서`dependencies --configuration testRuntimeClasspath`, 찾을 수 있습니다 :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;`나타나냅니다 대체 : Testcontainers 2.0.5가 *요구*됩니다, 하지만 Spring Boot이 강제합니다`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
(소비자)&amp;quot; as BG #E3F2FD {
    rectangle &amp;quot;요청
`testcontainers-bom:2.0.5&amp;quot; as REQ
    rectangle &amp;quot;요청
`testcontainers-postgresql&amp;quot; as REQ2
}

component &amp;quot;Maven 중앙&amp;quot; as MC #FFF3E0

component &amp;quot;선언&amp;quot; as DEC #E8F5E9 {
    rectangle &amp;quot;testcontainers-bom:2.0.5`
수정 2.0.5&amp;quot; as BOM2
    rectangle &amp;quot;spring-boot-dependencies:3.4.5`\n고정 1.20.6&amp;quot; as BOM1
}

component &amp;quot;Gradle 해결&amp;quot; as RES #FFEBEE {
    rectangle &amp;quot;우승 버전
`testcontainers:1.20.6&amp;quot; as WIN #EF9A9A
}

BG --&amp;amp;gt; DEC : &amp;quot;BOM을 분석하다&amp;quot;
BOM2 --&amp;amp;gt; RES : &amp;quot;제안 2.0.5&amp;quot;
BOM1 --&amp;amp;gt; RES : &amp;quot;제안하다 1.20.6&amp;quot;
WIN -&amp;amp;gt; MC : &amp;quot;다운로드
testcontainers: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;_4단계_해결_강제&amp;quot;&amp;gt;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;전략이 두 배가 됩니다 :&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;docker-java를 버전 3.7.1로 강제 지정 (Docker Engine 29와 호환됨으로 테스트됨)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Spring Boot BOM을 우회하여 Testcontainers core를 2.0.5로 강제&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
(resolutionStrategy와 함께)&amp;quot; as BG #E8F5E9 {
    rectangle &amp;quot;BOM Testcontainers
`2.0.5&amp;quot; as BOM2
    rectangle &amp;quot;resolutionStrategy\n.eachDependency&amp;quot; as RS #66BB6A
}

component &amp;quot;해결된 갈등&amp;quot; as RESOLVED #E3F2FD {
    rectangle &amp;quot;docker-java
`3.7.1` (강제)&amp;quot; as DJ
    rectangle &amp;quot;testcontainers
`2.0.5` (강제)&amp;quot; as TC #81C784
    rectangle &amp;quot;testcontainers-postgresql
`2.0.5&amp;quot; as TPG
    rectangle &amp;quot;testcontainers-jdbc
`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
(암시적, 재정의된)&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\n바로 통과합니다&amp;quot;
RS -&amp;amp;gt; DJ : &amp;quot;힘 3.7.1&amp;quot;
RS -&amp;amp;gt; TC : &amp;quot;힘 2.0.5&amp;quot;
RESOLVED --&amp;amp;gt; MC : &amp;quot;JARs를 다운로드하세요\nDocker 29와 호환되는&amp;quot;
BOM1 -[#gray,dashed]-&amp;amp;gt; RESOLVED : &amp;quot;testcontainers에서 무시됨\ndocker-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;최종 수정&amp;lt;/strong&amp;gt;안에`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;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`이것이 BOM을 우회하는 유일한 방법이다`spring-boot-dependencies`Spring Boot 플러그인에 의해 관리됨. 동등 테스트`details.requested.name == &amp;quot;testcontainers&amp;quot;`은 의도적으로 제한적 : 핵심 모듈만 강제합니다. 모듈들`testcontainers-*`Testcontainers 2.0.5의 BOM을 명시적으로 선언했습니다.&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;_최종_결과&amp;quot;&amp;gt;최종 결과&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은 드디어 Docker Engine 29.4.1을 통해 PostgreSQL 컨테이너를 생성할 수 있게 되었습니다. 최종 비즈니스 오류 (Cucumber 테스트에서의 HTTP 500)는 빌드 스크립트와 완전히 무관한 애플리케이션 문제입니다.&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;_수정_사항_요약표&amp;quot;&amp;gt;수정 사항 요약표&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;파일&amp;lt;/th&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;수정&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;변수`reportsDir`존재하지 않는&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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`지원 종료된 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 금지된 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;블록`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;구문적으로 잘못된 Groovy 어설션&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;암시적 의존성`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`호환되지 않는 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;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 테스트됨, 해결되었지만 부족함&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;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.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는 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;강제 Testcontainers 2.0.5 + 접두사`testcontainers-*`+ resolutionStrategy 대 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;_결론&amp;quot;&amp;gt;결론&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;JHipster/Gradle 빌드가 Gradle 9 + Docker Engine 29로 버전 업그레이드 후 실패하면 오류는 두 가지 범주로 나뉩니다:&amp;lt;/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;/strong&amp;gt;(첫 6개) : Gradle 9는 수년간 작동했던 속성과 구문을 제거합니다 (&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;). 이것들은 새로운 API를 알면 이루어지는 기계적 마이그레이션입니다.&amp;lt;/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;Docker 런타임 오류&amp;lt;/strong&amp;gt;(마지막 두) : Docker Engine 29.4.1은 API 클라이언트 버전 1.40 미만을 제외합니다. Testcontainers 1.20.6 (Spring Boot 3.4.5에 의해 강제됨)는 docker-java 3.4.1을 사용하며, 이는 버전 1.32를 전송합니다. Testcontainers 2.0.5와 docker-java 3.7.1만이 문제를 해결하며, 아티팩트 이름 변경 및 버전 강제를 통해`resolutionStrategy.eachDependency`Spring Boot의 내재된 BOM을 우회하는 유일한 방법.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;프로젝트 루트의 Gradle 빌드 스크립트가 이제 깨끗합니다.`./gradlew build`Java 21, Gradle 9.4.1, Spring Boot 3.4.5 및 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>AsciiDoc을 사용한 IA 에이전트 관리 : 나의 Eager/Lazy 전략, 컨텍스트 누출이 없는 Opencode 세션을 위한</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;_요약&amp;quot;&amp;gt;요약&amp;lt;/h2&amp;gt;
&amp;lt;div 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와 같은 IA 에이전트와 함께 여러 세션에 걸쳐 복잡한 프로젝트를 진행할 때 근본적인 문제에 직면하게 됩니다:&amp;lt;strong&amp;gt;컨텍스트 유출&amp;lt;/strong&amp;gt;. 에이전트는 이전 세션을 기억하지 못합니다. 그에게 설명된 모든 것&amp;amp;#8201;&amp;amp;#8212;&amp;amp;#8201;아키텍처, 규약, 백로그 상태&amp;amp;#8201;&amp;amp;#8212;&amp;amp;#8201;가 손실되었습니다. 매 세션마다 이 컨텍스트를 재구성하는 것은 비용이 많이 들고 느리며 오류의 원인이 됩니다.&amp;lt;/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 파일을 기반으로 한 지속 가능한 거버넌스 시스템, 그리고 이분법&amp;lt;strong&amp;gt;즉시/지연&amp;lt;/strong&amp;gt;컨텍스트 토큰 소비를 최적화하고, 그리고&amp;lt;strong&amp;gt;필수적인 세션 종료 절차&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;_장소_월요일_4월_21일_오전_9시&amp;quot;&amp;gt;장소 : 월요일 4월 21일, 오전 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;Opencode를 다시 열어 Gradle 플러그인 작업을 이어갑니다.&amp;lt;code&amp;gt;plantuml-plugin&amp;lt;/code&amp;gt;. 어제 저녁, 저는 API 키 풀의 아키텍트 담당자와 세 시간을 토론했습니다&amp;amp;#8201;&amp;amp;#8212;&amp;amp;#8201;라운드 로빈 로테이션, 할당량 관리, 자동 페일오버. 오늘 아침, 에이전트는 금붕어 눈으로 나를 바라본다.&amp;lt;/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;안녕하세요, 저는 Opencode 어시스턴트입니다. 오늘 어떻게 도와드릴까요? &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;아니요&amp;amp;#8201;&amp;amp;#8212;&amp;amp;#8201;아 맞다, API 키 풀, 우리는 YAML 구조에 있었죠. 아니요&amp;amp;#8201;&amp;amp;#8212;&amp;amp;#8201;주의,&amp;lt;code&amp;gt;PlantumlManager`은 Kotlin 싱글톤 객체이지 클래스가 아닙니다. 아니요&amp;amp;#8201;&amp;amp;#8212;&amp;amp;#8201;아니, 어제 우리가 결정한 것이`SyntaxValidationResult`중첩된 sealed class 상태로 남아 있었다`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;모든 것을 다시 해야 한다. 혹은 더 정확히 말하면, 모든 것을 다시 설명해야 한다. 나는 내 세션의 처음 20분을 들여 어제 에이전트가 이미 손에 넣었던 맥락을 재구성하는 데 쓸 것이다. 소모된 토큰으로 20분. 나는 코딩을 할 수도 있는 20분이지만, 대신 의무적인 교육을 하고 있다.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;이것은 Opencode의 버그가 아닙니다. 이것은 대화형 LLM의 근본적인 성질입니다: 두 세션 사이에서는 작업 기억이&amp;lt;strong&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;저는 그것을 수십 번 경험했습니다. 네 개의 동시에 진행되는 프로젝트에서요. 몇 주 동안 이어지는 세션과 함께요. 저는 계산해 보았습니다: 평균적으로,&amp;lt;strong&amp;gt;세션 시간의 30~40%&amp;lt;/strong&amp;gt;에이전트를 재맥락화하는 데 전념되었다. 프로젝트 세션 87에서`plantuml-plugin`, 나는 무너졌다. 나는 더 이상 그 문제를 열 번째로 설명하는 것을 감당할 수 없어서`AttemptEntry`는 최상위 레벨의 data class인`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;내가 필요로 했던 건 시스템이었다. 해킹이 아니라. 진정한 거버넌스였다.&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;_창세기_혼돈에서_방법으로&amp;quot;&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;_첫_번째_세션_어둠의_시대&amp;quot;&amp;gt;첫 번째 세션 : 어둠의 시대&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Opencode와 함께한 첫 번째 프로젝트,&amp;lt;code&amp;gt;plantuml-plugin&amp;lt;/code&amp;gt;, 아무런 지배 구조 없이 시작되었습니다. 저는 질문을 하고, 에이전트가 답변하고, 우리는 반복하고, 세션이 끝납니다. 그리고 다음 날 우리는 다시 제로에서 시작합니다. 세션 1이었고, 그다음 세션 2, 그다음 세션 3&amp;amp;#8230;&amp;amp;#8203; 결국 세션 62에 이르러서 저는 같은 아키텍처를 재설명하는 데 누적된 시간을 잃었다는 것을 깨달았습니다.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;세션 62에서, 숫자들이 여기 있습니다:&amp;lt;strong&amp;gt;198개의 단위 테스트가 통과합니다&amp;lt;/strong&amp;gt;, &amp;lt;strong&amp;gt;42개의 기능 테스트가 검증되었습니다&amp;lt;/strong&amp;gt;, 플러그인이 작동합니다. 하지만 인지 비용은 감당할 수 없습니다. 각 새로운 세션은 프로젝트 구조에 대한 20분 독백으로 시작됩니다.&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;_site_yml의_파괴된_에피소드_세션_2_bakery_plugin&amp;quot;&amp;gt;site.yml의 파괴된 에피소드 (세션 2, bakery-plugin)&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;이 방법도 재난에서 비롯됩니다. 프로젝트에서`bakery-gradle`, 세션 2에서, 저는 에이전트에게 파일을 수정하도록 요청합니다`site.yml`. 에이전트는 파일이 버전 관리됐는지 확인하지 않고 하나를 만든다`Write`내용이 완전히 덮어쓰는. 결과 : 실제 토큰(Firebase API 키, 배포 비밀)은 가짜 플레이스홀더로 대체됩니다. 파일은 git에 있지 않았습니다&amp;amp;#8201;&amp;amp;#8212;&amp;amp;#8201;그것은`.gitignore`비밀을 보호하기 위해.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;백업 없이. 없이`git restore`가능. 저는 막혔습니다. 수동으로 구성 파일을 다시 만들어야 하고, 비밀번호 관리자에서 토큰을 찾아서 모두 다시 붙여야 합니다.&amp;lt;/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;절대 규칙 1b&amp;lt;/strong&amp;gt;(Note: The output is a single space character, as the input text to translate was a space after the colon, and spaces are language-neutral in translation.)&amp;lt;/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;절대 짓밟지 마라&amp;lt;/strong&amp;gt;하나의 구성 파일과 하나의`Write`완료될 때 하나의`Edit`부분만으로 충분합니다.&amp;lt;strong&amp;gt;절대 교체하지 마세요&amp;lt;/strong&amp;gt;민감한 값을 가짜 값으로 대체합니다.&amp;lt;strong&amp;gt;확인 &amp;lt;code&amp;gt;git check-ignore&amp;lt;/code&amp;gt; 그리고 `git ls-files&amp;lt;/strong&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;이 규칙은 오늘날 모든 내 파일의 대리석에 새겨져`AGENT.adoc` et `INDEX.adoc`네 개 프로젝트에서 실제 오류가 발생하여 수작업에 한 시간을 소요했습니다.&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;_마크다운_애스키도크_마이그레이션_세션_1_cheroliv_com&amp;quot;&amp;gt;마크다운 → 애스키도크 마이그레이션 (세션 1, cheroliv.com)&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;2026년 4월 25일, 위에`cheroliv.com`, 나는 근본적인 결정을 내린다: 마크다운의 전체 거버넌스를 AsciiDoc으로 전환한다. 미적이지 않다. 기능적이다. AsciiDoc은 LLM이 더 잘 읽을 수 있는 의미적 구조를 제공한다: 계층적 섹션, 타입이 지정된 테이블, 어드몬트먼트(&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;), 기계가 읽을 수 있는 문서 속성&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;세션 1의`cheroliv.com`구조를 공식화 :&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;변환`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;전문 에이전트 생성 :`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;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;단일 커밋 :`90975e9 refactor: migrate agent governance from Markdown to AsciiDoc`. 그리고 사이트는 계속 작동합니다.&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 세션의 발전 — 세션 1부터 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;활성 세션&amp;quot; as S

@S
0 is &amp;quot;.md 원시&amp;quot;
1 is &amp;quot;마이그레이션
AsciiDoc&amp;quot;
10 is &amp;quot;Eager/Lazy
형식화된&amp;quot;
62 is &amp;quot;안전 규정\n(site.yml)&amp;quot;
87 is &amp;quot;크래킹\n맥락&amp;quot;
109 is &amp;quot;최적화
-60% 토큰&amp;quot;
133 is &amp;quot;133 세션
240 테스트 통과&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;위 타임라인은 실제 진행 상황을 보여줍니다. 전환점은 세션 87이며, 이때 반복적인 재컨텍스트화의 좌절감이 허용 한계를 초과하고, Eager/Lazy 방법은 더 이상 아이디어가 아니라 필수 조건이 됩니다.&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;_전략_eagerlazy_심층&amp;quot;&amp;gt;전략: Eager/Lazy 심층&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;_철학_인지에_적용된_컴퓨터_캐시&amp;quot;&amp;gt;철학 :인지에 적용된 컴퓨터 캐시&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;비판적이며 자주 사용되는&amp;lt;/strong&amp;gt;즉시 접근 가능해야 (&amp;lt;strong&amp;gt;열정적인&amp;lt;/strong&amp;gt;). 모든 것이&amp;lt;strong&amp;gt;문맥적 혹은 대용량&amp;lt;/strong&amp;gt;요청 시 로드해야 함&amp;lt;strong&amp;gt;게으른&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;열정적인 (대시보드)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;크기&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 줄, &amp;amp;lt; 10k 토큰&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;로딩&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;&amp;lt;strong&amp;gt;내용&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;&amp;lt;strong&amp;gt;역할&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;/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;_eager_파일_대시보드&amp;quot;&amp;gt;Eager 파일 : 대시보드&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;대시보드&amp;lt;/strong&amp;gt;-- 중요 정보, 즉시 접근 가능.&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;프로젝트 루트 (Eager - 자동 로드)&amp;quot; {
    component &amp;quot;&amp;amp;lt;b&amp;amp;gt;AGENT.adoc&amp;amp;lt;/b&amp;amp;gt;
절대 규칙
구조 및 관례&amp;quot; as AGENT
    component &amp;quot;&amp;amp;lt;b&amp;amp;gt;PROMPT_REPRISE.adoc&amp;amp;lt;/b&amp;amp;gt;
N 세션의 미션
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;
진입점
규칙 + 세션&amp;quot; as INDEX
    component &amp;quot;&amp;amp;lt;b&amp;amp;gt;*_ESSENTIALS.adoc&amp;amp;lt;/b&amp;amp;gt;\n중요한 비즈니스 컨텍스트&amp;quot; as ESS
}

package &amp;quot;.agents/ (Lazy - 요청 시 로드)&amp;quot; {
    component &amp;quot;&amp;amp;lt;b&amp;amp;gt;sessions/N-*.adoc&amp;amp;lt;/b&amp;amp;gt;\n상세 기록\n결정 및 출력&amp;quot; as SESS
    component &amp;quot;&amp;amp;lt;b&amp;amp;gt;SESSIONS_HISTORY.adoc&amp;amp;lt;/b&amp;amp;gt;
요약 테이블
날짜/유형/점수&amp;quot; as HIST
    component &amp;quot;&amp;amp;lt;b&amp;amp;gt;PROCEDURES.adoc&amp;amp;lt;/b&amp;amp;gt;
세션 종료 템플릿
6단계&amp;quot; as PROC
    component &amp;quot;&amp;amp;lt;b&amp;amp;gt;*_REFERENCE.adoc&amp;amp;lt;/b&amp;amp;gt;
완전한 아키텍처
기술 참조&amp;quot; as REF
    component &amp;quot;&amp;amp;lt;b&amp;amp;gt;COMPLETED_TASKS_ARCHIVE&amp;amp;lt;/b&amp;amp;gt;
완료된 작업
월별&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;
전략 문서
방법론&amp;quot; as MOD
    component &amp;quot;&amp;amp;lt;b&amp;amp;gt;*_REFERENCE.adoc&amp;amp;lt;/b&amp;amp;gt;
Boot tests, A/B partition
특정 컨텍스트&amp;quot; as SPEC
}

AGENT --&amp;amp;gt; PROMPT : &amp;quot;참고문헌&amp;quot;
AGENT --&amp;amp;gt; INDEX : &amp;quot;참고문헌&amp;quot;
INDEX --&amp;amp;gt; SESS : &amp;quot;색인하다&amp;quot;
INDEX --&amp;amp;gt; HIST : &amp;quot;색인하다&amp;quot;
INDEX --&amp;amp;gt; PROC : &amp;quot;참조&amp;quot;
INDEX --&amp;amp;gt; ARCH : &amp;quot;참조&amp;quot;
INDEX --&amp;amp;gt; REF : &amp;quot;참조&amp;quot;
PROMPT --&amp;amp;gt; SESS : &amp;quot;아카이브 N-1&amp;quot;
PROMPT --&amp;amp;gt; ESS : &amp;quot;비즈니스 컨텍스트 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;-- 마스터 파일. 에`cheroliv.com`, 그것은 200줄이며 포함합니다:&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;프로젝트의 절대적인 규칙 (허가 없이 커밋 금지,`rm`확인 없이)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&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;./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;에픽들과 제품 백로그(우선순위된 사용자 스토리)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&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;위에`bakery-plugin`, 규칙 0는 다릅니다 :&amp;lt;strong&amp;gt;./gradlew -q publishToMavenLocal` 소스 코드를 수정할 때마다 필수입니다.&amp;lt;/strong&amp;gt;. 로컬 JAR을 다시 패키징하지 않고 플러그인을 테스트했기 때문에 아직 패키징되지 않은 코드를 디버깅하는 데 한 시간을 허비하게 되었다.&amp;lt;/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;-- 현재 세션의 임무. 각 세션 종료 시 업데이트되며, 다음을 포함합니다:&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;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;이전 세션의 요약 (완료된 내용, 아직 해야 할 일)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;현재 세션의 수락 기준&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&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;.agents/INDEX.adoc&amp;lt;/strong&amp;gt;-- 진입점입니다. 절대 규칙, 최근 세션, 그리고 무엇보다&amp;lt;strong&amp;gt;프로젝트 포트폴리오&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;----
----
| 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`** -- 최근 추가된 사항(세션 109, plantuml-plugin)으로 Eager 컨텍스트를 더욱 최적화하기 위해, API 키 풀에 비즈니스 컨텍스트 200줄을 로드하는 대신, 필수 50줄만 로드하고 나머지 150줄은 LAZY 상태로 안에`*_REFERENCE.adoc`.

측정된 결과: 통과**~25k 토큰 EAGER부터 ~10k 토큰까지**(60% 증가). 이제 더 이상 에너지 소모되는 알림이 필요하지 않다.

==== 누락된 연결고리 : `opencode.json

나는 문서화하는 것을 거의 잊어버릴 뻔한 사실을 고백해야 한다. 이 모든 .adoc 파일 위에 작동에 필수적인 아주 작은 JSON 파일이 있다. 그의 이름은`opencode.json`그리고 그는 여섯 줄입니다. 글자 그대로 여섯 줄입니다.

[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;
  ]
}
----

이 파일은 Opencode에게 말한다: « 시작 시, 로드 `AGENT.adoc`자동으로. » 그 없이는 에이전트는 기사 시작 부분에서 제가 설명한 것처럼 빈 페이지와 같습니다. 그 있으면 에이전트는 이미 손에 쥔 절대 규칙, 프로젝트 아키텍처, 그리고 필수 명령을 가지고 있습니다 — 저조차도 인사하기 전에.

이 파일의 중요성을 우연히 발견했습니다. 위`bakery-plugin`, 존재하지 않았습니다. 나는 왜 에이전트가 이 프로젝트에서 다른 프로젝트들에 비해 체계적으로 더 « 잃어버린 » 상태였는지 궁금했다. 절대 규칙들은 잘 들어 있었다.`AGENT.adoc`— 하지만`AGENT.adoc`절대 충전되지 않았습니다. 에이전트는 내가 읽으라고 지시한 내용만 읽었고, 매 세션마다 수동으로 수행했습니다. 이것은 세션 11의`bakery-plugin`quand 나는 ...의 부재를 깨달았을 때`opencode.json`. 저는 그것을 만들었습니다 — 그리고 세션 12은 다른 세션들과 마찬가지로 시작되었습니다.

이 파일은 지금 나에게 너무나 명백해서 그를 전혀 생각하지 않는다. 도구를 너무 잘 아는 개발자가 흔히 저지르는 실수다. 오늘은 나는 그것을 체계적으로 *먼저* 만든다.`AGENT.adoc`. 이것은 첫 번째 돌이다.

==== 의 이중성 `INDEX.adoc

또 명시할 가치가 있는 또 다른 미묘함 :`INDEX.adoc`에 산다`.agents/`— 제가 `LAZY`라고 제시한 폴더. 하지만 저는 모든 테이블에서 그것을 `EAGER`라고 등록합니다. 여기에는 명백한 긴장이 있습니다.

현장 현실 : 파일들`.agents/INDEX.adoc`자동으로 시작 시에 잘 로드되며, 그와 마찬가지로`AGENT.adoc` et `PROMPT_REPRISE.adoc`그들은 안에`.agents/`조직상의 이유로 — 루트를 침범하지 않기 위해 — 하지만 그들의 동작은 EAGER.

위`plantuml-plugin`, `INDEX.adoc`200줄에 *완전한* 절대 규칙과 그 기록(지난 세션의 교훈)과 함께, EPIC과 점수, 그리고 프로젝트 포트폴리오가 포함되어 있습니다. 이것은 에이전트가 &amp;#39;현재 진행 상황&amp;#39;을 알기 위해 참조하는 문서입니다. 위`bakery-plugin`, 그는 로드맵과 최근 세션으로 150줄을 만든다.

의도적인 중복 사이`AGENT.adoc` et `INDEX.adoc`놀라울 수 있습니다. 절대 규칙은 양쪽에 존재합니다. 왜 ? 왜냐하면它们... : 안에`AGENT.adoc`, 그들은 *설명적인* (규칙의 이야기, 배운 교훈); 안에서`INDEX.adoc`,그녀들은 *집행적인* (벌거숭이 규칙, 정당화 없이, 빠른 참고를 위해). 에이전트는 읽는다`AGENT.adoc`한 번 *comprendre*를 위해; 그는 다시 읽는다`INDEX.adoc`각 세션에 *적용*하기 위해. 두 가지 사용법, 두 가지 형식.

[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 이중성 AGENT.adoc ←→ INDEX.adoc
left to right direction

rectangle &amp;quot;AGENT.adoc
(근원 — EAGER)&amp;quot; as AGENT #E3F2FD {
  rectangle &amp;quot;📖 **서사 형식**
규칙의 스토리텔링
배운 교훈, 맥락&amp;quot; as NARR
  rectangle &amp;quot;🏗️ **완전한 아키텍처**
프로젝트 구조, 구성 요소
상세한 US 백로그&amp;quot; as ARCHI
  rectangle &amp;quot;📋 **설명 규칙**
왜 규칙이 존재하는가
사건 기록&amp;quot; as EXPL
}

rectangle &amp;quot;INDEX.adoc\n(.agents/ — 열망하는)&amp;quot; as INDEX #E8F5E9 {
  rectangle &amp;quot;⚡ **실행 형식**
규칙 자체만, 정당화 없이
빠른 상담&amp;quot; as EXEC
  rectangle &amp;quot;📊 **Roadmap &amp;amp;amp; EPICs**\n요약 표\n진행 상황, 점수, 우선순위&amp;quot; as ROAD
  rectangle &amp;quot;🌐 **프로젝트 포트폴리오**
횡단 뷰
5개의 동기화된 프로젝트&amp;quot; as PORT
}

AGENT --&amp;amp;gt; INDEX : &amp;quot;에이전트가 AGENT.adoc를 읽음\n이해하기 위해 한 번&amp;quot;
INDEX --&amp;amp;gt; AGENT : &amp;quot;Agent relit INDEX.adoc\n각 세션에 **적용하기 위해**&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
----

이 명시적인 중복은 설계 선택입니다. 이는 EAGER 토큰에서 약 50줄을 추가로 소비하지만 — 에이전트가 항상 규칙을 눈앞에 두도록 보장합니다. 여기에는 즉각적인 순종을 촉진하는 간결한 형식도 포함됩니다.

=== LAZY 파일: 소유자 매뉴얼

이 파일들은 ...에 존재합니다`.agents/`그리고 에이전트가 필요할 때만 읽힙니다. 그들은 방법의 진정한 가치를 구성하며, 왜냐하면 그들은 현재 맥락을 오염시키지 않고 프로젝트의 지식을 축적하기 때문이다.

[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\n(EAGER -- 200 줄)&amp;quot; as IDX #E8F5E9
  file &amp;quot;AGENT_SESSION_MANAGER.adoc
템플릿 세션&amp;quot; as ASM
  file &amp;quot;SESSION_CHECKLIST.adoc
(변경 시점)&amp;quot; as CHK
  file &amp;quot;PROCEDURES.adoc
(6 단계 + LAZY/EAGER)&amp;quot; as PRO
  file &amp;quot;SESSIONS_HISTORY.adoc
(모든 세션)&amp;quot; as HIS

  folder &amp;quot;세션/&amp;quot; as SESS {
    file &amp;quot;1-일-마이그레이션.adoc&amp;quot; as S1
    file &amp;quot;109-정식화-게으른.adoc&amp;quot; as S109 #FFECB3
    file &amp;quot;133-epic11-article.adoc&amp;quot; as S133
    file &amp;quot;... +130 다른&amp;quot; as SMORE
  }

  folder &amp;quot;아카이브/&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;세션_요약/&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;프롬프트_아카이브/&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;색인&amp;quot;
IDX --&amp;amp;gt; HIS : &amp;quot;색인&amp;quot;
IDX --&amp;amp;gt; ARCH : &amp;quot;참조&amp;quot;

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

@enduml
----

위 트리 구조는 폴더의 실제 구조를 보여줍니다.`.agents/`위에`plantuml-plugin`, 가장 성숙한 프로젝트. 세 층의 깊이에 주의하세요: 루트 파일(메타데이터), 폴더`sessions/`(연대순 아카이브), 그리고 폴더`archives/`(집계 및 요약). 이 깊이는 단순한 TODO 파일의 거버넌스를 무엇인가로 변환한다.**완전한 조직 기억**.

**.agents/sessions/{N}-{titre}.adoc**-- 각 세션의 상세한 아카이브. 현재 :

* `plantuml-plugin` : **133개의 세션이 보관되었습니다**(세션 1부터 133까지)
* `bakery-plugin`:**11 세션**
* `magic-stick`:**23 세션**
* `cheroliv.com`:**9회 공식 세션**+ 7개의 사전 시스템 세션이 역으로 재구성되었습니다

각 아카이브에는 세션의 전체 컨텍스트, 이루어진 결정, 발생한 문제와 그 해결, 실행된 명령과 그 출력이 포함됩니다.

**.agents/SESSIONS_HISTORY.adoc**-- 모든 세션의 점수를 요약한 표. 예시에서`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_{월}.adoc**-- 완료된 작업들을 월별로 아카이브하여 활성 백로그를 과부하시키지 않도록 합니다. 사용자 스토리가 완료되면 여기서 이동합니다. 백로그는 읽기 쉬운 상태를 유지하며, 활성 항목은 최대 10개입니다.

**.agents/PROCEDURES.adoc**-- 세션 종료 절차의 상세 템플릿. 길지만, 에이전트가 방법을 배울 때 한 번만 읽음. 이후 절차는 기계적이 된다.

**.agents/AGENT_MODUS_OPERANDI.adoc**-- 전략적 문서 전체. 위에`plantuml-plugin`, 이 파일은**900+ 줄**그리고 실제로 불리는`AGENT_METHODOLOGIES.adoc`— 나는 이 기사를 쓰는 동안과 실제로 적용하는 사이에 이름을 바꾸었다. 이런 종류의 네이밍 차이는 진화하는 수공예 시스템에서는 피할 수 없다. 중요한 것은 이름 짓기 관습이다: 만약 파일이 *메서드*를 문서화한다면, 그것은 ...로 시작한다.`AGENT_`또는 명시적 접두사. Eager/Lazy 방법론을 문서화하고, 따라야 할 패턴과 피해야 할 안티패턴을 설명합니다. 이는 LAZY이기 때문인데, 에이전트는 매 세션마다 전체 전략을 다시 읽을 필요가 없고, 오직 애매함이 있을 때만 그렇게 한다.

**`*_REFERENCE.adoc`** -- 프로젝트에 특화된 기술 참조. 에`magic-stick`, 두 개의 LAZY 밀집 파일 :

* `AB_PARTITION_REFERENCE.adoc`(147줄) -- A/B 파티션 아키텍처 GPT, 스크립트`update-system.sh`, 추정 크기, 롤백 메커니즘
* `BOOT_TEST_REFERENCE.adoc`(144 줄) -- QEMU + VNC를 이용해 물리적 하드웨어 없이 ISO 부팅을 테스트하는 절차, BIOS/UEFI 체크리스트, CI/CD 제한

위`plantuml-plugin` :

* `ARCHITECTURE.adoc`(134줄) -- 11개의 데이터 클래스 구조, 주의 사항 (피해야 할 함정), 최적화된 테스트 명령어
* `API_KEY_POOL_REFERENCE.adoc`-- 키 풀의 전체 세부 정보 (LAZY 동안`ESSENTIALS`이다 EAGER)

== 전문 에이전트 : 가상 팀

거버넌스는 정적인 파일에만 한정되지 않습니다. 저는 일부를 형식화했습니다**전문 에이전트들의 역할**전용 LAZY 파일에서, 작업 유형에 따라 예상되는 워크플로우를 정의합니다.

|===
|에이전트 |파일 |역할 |프로젝트 |**코더** |`CODER.adoc` |FTL/CSS/JS 구현, 시맨틱 태그, 접근성 기준 |cheroliv.com |**스크럼 마스터** |`SCRUM_MASTER.adoc` |미국 계획, 하위 작업으로 분할, 의존성 감지 |cheroliv.com |**PlantUML 디자이너** |`PLANTUML_DESIGNER.adoc` |다이어그램 작성, PUML 구문, JBake 통합 |cheroliv.com
|===

파일`CODER.adoc`위`cheroliv.com`포함하는 구체적인 규칙이 있습니다: _하나`&amp;amp;lt;h1&amp;amp;gt;`페이지당_, _경로에 접두사 추가하기`${content.rootpath}`_, _언어를 선언`&amp;amp;lt;html lang=&amp;quot;${content.lang!&amp;quot;fr&amp;quot;}&amp;quot;&amp;amp;gt;`_. 이러한 협약은 한 번 작성되면 에이전트가 세션 1부터 자동으로 준수합니다.

파일`SCRUM_MASTER.adoc`산출물의 구조를 규정한다 :**목표**, **작업**(할당이 있는 좌표),**수용 기준**, **위험**. 행동 계획을 요청할 때, 에이전트는 내가 요청하지 않았음에도 이 구조를 생성합니다. 거버넌스**프로그램**요원.

==== 전문화된 에이전트가 필수적이 될 때

전문 에이전트의 생성은 자연스러운 곡선을 따릅니다. 프로젝트 초반에는 필요하지 않습니다 —`AGENT.adoc`충분합니다. 하지만 프로젝트가 커질 때 (예를 들어, 20세션을 넘어) 두 가지 신호가 당신을 경고해야 합니다 :

1. 에이전트는 서로 다른 두 분야의 규칙을 혼합합니다(예: PlantUML 구문과 CSS 규칙)
2. 당신은 이미 5번 설명한 관습에 대해 에이전트를 수정하는 데 더 많은 시간을 보냅니다.

위`cheroliv.com`, 세션에 도착했습니다... 1. 예, 처음부터. 왜냐하면 이 프로젝트는 세 가지 언어(`FTL`, `CSS`, `JS`)로 이루어진 웹사이트이며, `AsciiDoc` 콘텐츠와 `PlantUML` 다이어그램이 포함되어 있기 때문입니다 — 세 가지 분야가 전혀 관련이 없습니다. `CODER` 에이전트는 글꼴 크기와 미디어 쿼리를 알아야 하며, `PLANTUML_DESIGNER` 에이전트는 문법을 알아야 합니다.`@startuml`. 분리 없이, CODER 에이전트가 다이어그램을 제시해 주었고, 반대도 마찬가지였다. 혼란.

위에`jhipster-gradle-plugins`, 저는 Gradle 플러그인 개발에 맞춘 두 개의 전문화된 에이전트를 만들었습니다 :`PLUGIN_DEVELOPER.adoc` et `BACKLOG_MANAGER.adoc`. 첫 번째는 Kotlin/Gradle의 모든 규약을 인코딩합니다(아니오`!!`, 모델에 대한 데이터 클래스,`@TaskAction`작업들에 대해). 두 번째는 알고 있다`persistence`안정적이어야만`assistant`개발을 시작하지 않는다 — 모노레포에서의 중요한 의존성

실수하지 말아야 할 것 : 너무 많은 에이전트를 너무 일찍 만드는 것`plantuml-plugin`108번째 세션을 기다린 후 API 키 풀을 위한 전문 에이전트를 공식화했습니다. 그때까지 비즈니스 컨텍스트는`AGENT.adoc`. 경험칙 : 전문 에이전트는 그 업무 분야가 문서 100줄을 초과할 때 정당화된다.

==== 세션 명명 규칙

사소해 보이는 세부 사항이지만 100 세션에 도달하면 중요해집니다. 아카이브 파일 이름을 어떻게 지어야 하나요?

나는 힘들게 배웠는데, 하나의 관습이 필요하다는 것을 — 네 개의 프로젝트, 네 가지 다른 형식으로 시작했고, 나는 길을 잃었다. 오늘 내가 확립한 관습은:&amp;lt;/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;구체적인 예시 :

* `1-chore-migration-gouvernance-agent.adoc`— 세션 1, 타입 안무
* `10-solidification-tests.adoc`— 세션 10, 명시적 타입이 없음 (주어가 충분함)
* `036-debug-graphify-symlink-epic9.adoc`— 세션 36, 3자리 숫자로 정렬

세션 번호가 주요 정렬 기준입니다. 3자리 숫자(001, 036, 133)를 사용하는 프로젝트는 99를 초과할 때 사전식 정렬 문제를 피합니다. 이것이 제가 지금부터 사용하는`magic-stick`:`001-init-projet.adoc`, `036-debug-graphify-symlink-epic9.adoc`.

타입은 선택 사항이며, 세션의 키워드(debug, feature, refactor, docs, chore, test)에서 파생됩니다. kebab-case 형식의 주제는 가장 중요한 부분이며, 파일을 열지 않고 세션을 찾을 수 있어야 합니다. «통합 테스트의 타임아웃을 수정한 세션은 어느 것이었나요?», 답은`124-fix-timeout-integration-test.adoc`.

재구성된 역사적 세션(거버넌스 이전에 태어난 프로젝트)에는 음수를 사용합니다. 위`cheroliv.com`, 세션 -6부터 0까지는 프로젝트의 전체 사전 거버넌스 역사를 다룹니다. 그리고 문서화되지 않거나 잃어버린 세션에 대해, 나는 항목을 만든다`SESSIONS_HISTORY.adoc`해당하는 아카이브 없이, 점수가 있음`?`. 그거는 솔직해 보이는 것보다 더 솔직하다.

[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 세션 명명 규칙
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;세션 종료&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` — 5단계 세부 분석

세션 종료 절차의 단계 5가 가장 미스터리합니다. 그것은 &amp;#39;업데이트&amp;#39;라고 말합니다.`TEST_COVERAGE_ANALYSIS.adoc`테스트가 추가되거나 수정되었습니다. » 하지만 이 파일은 어떤 모습인가요?

위`plantuml-plugin`, 몇 줄에서 완전한 구조로 진화했습니다. 다음은 그 안정화된 형태입니다:

[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;관심은 파일 자체에 있는 것이 아니라 — 변경된 사항을 &amp;lt;strong&amp;gt;기록&amp;lt;/strong&amp;gt; 해야 하는 의무다. 이 단계를 거치지 않으면, 50번의 세션 후에 어떤 테스트가 무엇을 커버하는지 더 이상 알 수 없게 된다. 에이전트도 마찬가지다. 파일은 프로젝트 테스트 커버리지에 대한 단일 진실 원천이 된다.&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;전통적인 테스트가 없는 프로젝트 (예:`magic-stick`bash 스크립트를 테스트하는), 5단계는 대체됩니다`SCRIPT_VERIFICATION.adoc`. 메커니즘은 같습니다 : 스크립트 유효성 검사 상태를 추적하는 파일입니다. 프로젝트에 맞게 5단계를 조정하되 절대 건너뛰지 마세요. 그것은 침묵적인 회귀를 방지하는 안전망입니다.&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;만약 귀하의 프로젝트에 &amp;lt;strong&amp;gt;테스트가 전혀 없다&amp;lt;/strong&amp;gt; — 단위 테스트도, 기능 테스트도, 스크립트도 없이 — 빈 파일을 만들어서 « 할 일: 테스트 전략 정의하기. » 라는 섹션을 추가하세요. 이는 미래의 자신에게 이 주제가 아직 다루어지지 않았음을 상기시켜 줄 책갈피입니다.&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;위쪽 다이어그램은 세션의 전체 생명 주기를 보여줍니다. 핵심 포인트는 EAGER 로드 후의 분기입니다: 미션이 충분히 명확해서 직접 실행할 수 있는 경우(80%의 경우)와, 애매함을 해결하기 위해 LAZY를 로드하는 경우(20%의 경우)입니다. 이러한 차별화가 토큰을 절약합니다.&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;=== 왜 그녀는 필수적인가?&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;이 절차가 없으면 Eager/Lazy 전략은 무용지물이다. 이 절차가 세션의 작업을 지속 가능한 정보로 변환한다. 이 절차가 실행된다.&amp;lt;strong&amp;gt;사용자의 명시적 요청에 따라&amp;lt;/strong&amp;gt;(키워드 : &amp;quot;세션 종료&amp;quot;, &amp;quot;나는 떠나&amp;quot;, 등등), 그리고 그녀는&amp;lt;strong&amp;gt;필수&amp;lt;/strong&amp;gt;-- 예외도 없고, 누락도 없습니다.&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;파일`SESSION_CHECKLIST.adoc`이상적인 세션 메트릭스를 정의합니다 :&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;* 소요 시간 : 15-30분
* 수정된 파일 : 1-3 최대
* LLM 교환 : 5-10 메시지
* 컨텍스트 토큰 : &amp;amp;lt; 50k&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;세션을 변경해야 하는 징후는 다음과 같습니다 : _LLM이 이미 수정된 오류를 반복한다&amp;lt;/em&amp;gt;, &amp;lt;em&amp;gt;3개 이상의 파일이 동시에 수정됨&amp;lt;/em&amp;gt;, &amp;lt;em&amp;gt;대화가 50개 메시지 초과&amp;lt;/em&amp;gt;. 황금 규칙 :&amp;lt;strong&amp;gt;2시간 동안 디버깅이 혼란스러운 한 번의 세션보다 20분짜리 5번의 세션이 더 좋다.&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;=== 6단계 흐름 (침묵으로)&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;세션 종료&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;=== 150+ 세션의 결과&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;&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 세션&amp;lt;/strong&amp;gt;프로젝트 시작 이래
* &amp;lt;strong&amp;gt;240/240 테스트 PASS&amp;lt;/strong&amp;gt;(100% 커버리지)&amp;amp;#8201;&amp;amp;#8212;&amp;amp;#8201;EPICs 1-7 완료
* &amp;lt;strong&amp;gt;57개의 Cucumber BDD 시나리오&amp;lt;/strong&amp;gt;검증된
* 실제 오류에서 발생한 구성 파일 안전 규칙 (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;베이커리-플러그인&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 세션&amp;lt;/strong&amp;gt;두 주 안에
* Supabase → Firebase 마이그레이션 완료 (9개 테스트 수정됨)
* EPIC 6 (publishProfile)는 프로덕션에서 기능적으로 작동합니다
* 규칙 0이 생성되었습니다:`publishToMavenLocal`각 수정 후 필수&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;마법 스틱&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 세션&amp;lt;/strong&amp;gt;Xubuntu 라이브 시스템을 A/B 파티션으로 구성하려면
* 세션 10에서 생성된 첫 번째 ISO
* 공식화된 QEMU + VNC 부팅 테스트 (144줄의 LAZY 문서)
* CI/CD SourceForge 기능적인&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개의 공식 세션&amp;lt;/strong&amp;gt;+ 프리-시스템 세션 7개의 재구성
* Article 0101 (OpenCode PATH) 게시됨
* 제0108조 (이것)는 분석 후 재작성되었습니다
* 마크다운에서 애스시도크로 이전된 완전한 거버넌스&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;6단계를 침묵적으로 실행한 후, 에이전트는 확인 체크리스트를 표시해야 합니다 :&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;절대적인 규칙&amp;lt;/strong&amp;gt;: 어떤 단계도 표시할 수 없습니다`[✅]&amp;lt;code&amp;gt;파일이 실제로 수정되고 검증되지 않은 경우&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;== Bootstrap 가이드: 1일 차, 세션 0&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;나는 그 순간을 2026년 4월 28일에 경험했다. 나는 Opencode를 연다 위에`jhipster-gradle-plugins&amp;lt;/code&amp;gt;, 저의 JHipster Gradle 플러그인 두 개의 모노레포. 이미 존재하는 프로젝트입니다 — 코드가 거기에 있고, Gradle 작업들이 정상 동작합니다. 하지만 에이전트 거버넌스 ? 제로. 빈 페이지.처럼`plantuml-plugin`그의 세션 1에, 몇 달 전에.&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;[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 거버넌스 — 1일차, 세션 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;=== 단계 0 : &amp;lt;code&amp;gt;opencode.json&amp;lt;/code&amp;gt; 생성하기&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;이것은 첫 번째 파일입니다. 아니요`AGENT.adoc`, 안`INDEX.adoc`.&amp;lt;code&amp;gt;opencode.json`먼저, 간단한 이유 때문에: 당신이 만든다면`AGENT.adoc`먼저, 자동으로 충전되는 다리를 만드는 것을 잊게 될 것입니다. 저는 이것을 &amp;amp;#8230;&amp;amp;#8203;에서 했습니다.`bakery-plugin&amp;lt;/code&amp;gt;, 나는 내가 말하는 것이 무엇인지 안다.&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;=== 단계 1: 폴더 생성&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;빈 폴더 두 개`sessions/&amp;lt;code&amp;gt;각 세션의 아카이브를 받게 될 것입니다.`archives/`월별 COMPLETED_TASKS_ARCHIVE를 받게 됩니다. 이 폴더들은 에이전트가 거기에 쓰기를 필요로 하기 *전에&amp;lt;/strong&amp;gt; 존재해야 합니다. 폴더와 파일을 동시에 생성해야 하는 에이전트는 조용히 실패할 수 있는 에이전트입니다.&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;=== 단계 2: `AGENT.adoc&amp;lt;/code&amp;gt; 생성 — 마스터 파일&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;[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;의무 정지&amp;lt;/strong&amp;gt;rm 전, Write, 삭제:&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;1. 파일을 끝까지 읽어라
2. 확인 git ls-files
3. 확인 요청
4. 예&amp;quot;를 기다리다&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;&amp;lt;strong&amp;gt;이름&amp;lt;/strong&amp;gt;&amp;amp;#8230;&amp;amp;#8203; &amp;lt;strong&amp;gt;스택&amp;lt;/strong&amp;gt;: &amp;amp;#8230;&amp;amp;#8203; &amp;lt;strong&amp;gt;문서&amp;lt;/strong&amp;gt;: AsciiDoc&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;=== 0. 개발 환경&amp;lt;/p&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;p class=&amp;quot;tableblock&amp;quot;&amp;gt;=== 1. COMMITS/GIT&amp;lt;/p&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;p class=&amp;quot;tableblock&amp;quot;&amp;gt;=== 1b. 구성 파일 — 절대 안전 규칙&amp;lt;/p&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;p class=&amp;quot;tableblock&amp;quot;&amp;gt;=== 2. 세션 종료 테스트&amp;lt;/p&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;p class=&amp;quot;tableblock&amp;quot;&amp;gt;=== 3. 세션 종료 절차&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;필수 6단계&amp;amp;#8230;&amp;amp;#8203;&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;== 컨텍스트 관리 — LAZY/EAGER&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;EAGER 파일 / LAZY 파일&amp;amp;#8230;&amp;amp;#8203;&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;이 최소 템플릿은 에이전트가 시작할 수 있게 해줍니다. 풍부한 버전 — 프로젝트 구조, 핵심 구성 요소, EPIC, 백로그 포함 — 은 에이전트가 이미 기본 규칙을 손에 쥐고 있을 때 세션 1에 제공될 것이며, 문서를 풍부하게 하는 데 도움을 줄 것입니다.&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;=== 3단계: 만들기 &amp;lt;code&amp;gt;PROMPT_REPRISE.adoc&amp;lt;/code&amp;gt; — 세션 1 미션&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;다음과 같이 명시된 파일: « 이것이 세션 1, 정의해야 할 미션입니다. » 최대 70줄, 세션 0(부트스트랩 요약) 섹션과 세션 1(사용자와 정의할 우선순위) 섹션이 포함됩니다.&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;=== 4단계: 생성 &amp;lt;code&amp;gt;.agents/INDEX.adoc&amp;lt;/code&amp;gt; — 진입점&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;절대 규칙(실행 버전)과 세션 테이블을 포함할 파일입니다. 부트스트래핑을 위해, 규칙 0부터 3까지를 간결한 형식으로 나열하고, 프로젝트 포트폴리오(새로운 프로젝트와 함께 이모지 🆕 포함)와 빈 로드맵(채워질 준비가 된)을 나열합니다.&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;=== 5단계 : LAZY 구조화 파일 생성&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;1. &amp;lt;code&amp;gt;.agents/SESSIONS_HISTORY.adoc&amp;lt;/code&amp;gt;— 세션 0만 있는 테이블
2. &amp;lt;code&amp;gt;.agents/SESSION_CHECKLIST.adoc&amp;lt;/code&amp;gt;— 템플릿 « 세션 변경 시기
3. &amp;lt;code&amp;gt;.agents/PROCEDURES.adoc&amp;lt;/code&amp;gt;— 6단계 전체 절차 + EAGER/LAZY
4. &amp;lt;code&amp;gt;.agents/AGENT_SESSION_MANAGER.adoc&amp;lt;/code&amp;gt;— 아카이브 템플릿
5. &amp;lt;code&amp;gt;.agents/TEST_COVERAGE_ANALYSIS.adoc&amp;lt;/code&amp;gt;— 테스트 추적 테이블, 처음에 비어 있음&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;만약 당신의 프로젝트가 복잡한 비즈니스 도메인을 가지고 있다면 (예를 들어`jhipster-gradle-plugins`persistence/assistant 단일 레포와 함께, 지금 바로 전문 에이전트를 생성하세요 :&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;— 코드 컨벤션과 의존성 경계 (플러그인인 경우)
2. &amp;lt;code&amp;gt;.agents/BACKLOG_MANAGER.adoc&amp;lt;/code&amp;gt;— 계획 구조와 식별된 위험&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;사용하지 않을 에이전트는 만들지 마세요. 인코딩할 구체적인 규칙이 없는 에이전트는 오염을 일으키는 죽은 파일이다.&amp;lt;code&amp;gt;.agents/&amp;lt;/code&amp;gt;.&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;=== 단계 6: 모든 프로젝트의 포트폴리오에 프로젝트 추가&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;그것은 우리가 항상 잊어버리는 단계입니다. 각`INDEX.adoc`각 프로젝트는 동일한 방법론으로 모든 프로젝트를 나열하는 &amp;#39;프로젝트 포트폴리오&amp;#39; 테이블을 포함합니다. 새 프로젝트를 생성할 때 반드시 해야 할 일:&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;1. 새 프로젝트 포트폴리오에 라인 추가 (논리)
2. 기존 모든 프로젝트의 포트폴리오에 한 줄 추가하기 — 예, 모두&amp;lt;/p&amp;gt;
&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;내 네 개(지금은 다섯 개)의 프로젝트에 대해, 이는 여는 것을 의미한다.&amp;lt;code&amp;gt;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`그리고 거기에 줄을 추가`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;이건 지루합니다. 이건 수동적입니다. 이것도 작업 중인 프로젝트가 무엇이든 에이전트가 다른 프로젝트가 무엇인지와 그 상태를 알고 있도록 보장하는 유일한 방법입니다. 세션 012에서`magic-stick&amp;lt;/code&amp;gt;, 에이전트는 포트폴리오에서 두 가지 불일치를 감지했습니다 —`bakery-gradle`그의 COMPLETED_TASKS_ARCHIVE가 지연되어 있었고,`plantuml-gradle`_그것은 절차 문서(5단계)와 그 INDEX(6단계)에 차이가 있었다. 이 교차 표가 없었다면, 이러한 불일치는 눈에 띄지 않았을 것이다.&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 프로젝트 포트폴리오 — 교차 참조 INDEX.adoc
node &amp;quot;&amp;lt;strong&amp;gt;magic-stick&amp;lt;/strong&amp;gt;
Session 037
SCRIPT_VERIFICATION&amp;quot; as MS #E1BEE7
node &amp;quot;&amp;lt;strong&amp;gt;bakery-gradle&amp;lt;/strong&amp;gt;
세션 11
테스트 커버리지&amp;quot; as BG #FFE0B2
node &amp;quot;&amp;lt;strong&amp;gt;cheroliv.com&amp;lt;/strong&amp;gt;
세션 10
TEST_COVERAGE&amp;quot; as CH #C8E6C9
node &amp;quot;&amp;lt;strong&amp;gt;plantuml-gradle&amp;lt;/strong&amp;gt;
세션 133
테스트 커버리지&amp;quot; as PG #BBDEFB
node &amp;quot;&amp;lt;strong&amp;gt;jhipster-gradle&amp;lt;/strong&amp;gt;
세션 1 🆕
테스트_커버리지&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>왜 `serve`가 못생긴 사이트를 표시했는지, `publishSite`는 완벽했을까?</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;#_장면_네_개의_스크린샷_두_개의_렌더링&amp;quot;&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;#_로컬_렌더링&amp;quot;&amp;gt;로컬 렌더링&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_온라인_렌더링&amp;quot;&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;#_진단_serve가_올바른_폴더를_가리키고_있지_않았습니다&amp;quot;&amp;gt;진단 : `serve`가 올바른 폴더를 가리키고 있지 않았습니다.&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_근본_원인_jbake_cli는_이렇게_작동하지_않습니다&amp;quot;&amp;gt;근본 원인: JBake CLI는 이렇게 작동하지 않습니다&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_해결책_위치_인자_플래그가_아닌&amp;quot;&amp;gt;해결책: 위치 인자, 플래그가 아닌&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_curl을_사용한_빠른_검증&amp;quot;&amp;gt;cURL을 사용한 빠른 검증&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_왜_이_버그는_교활했는가&amp;quot;&amp;gt;왜 이 버그는 교활했는가?&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_결론&amp;quot;&amp;gt;결론&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;읽기 시간 : 10 minutes&amp;lt;/div&amp;gt;
&amp;lt;p&amp;gt;로컬에서 사이트가 아무 것도 아닌 것처럼 보였지만, 온라인에 배포된 후에는 모든 것이 깔끔해졌을 때를 아시나요? 이건 내 Gradle JBake 플러그인과 함께 일어났던 일입니다. 작업`serve`네모난 버튼을 표시하고, 검은 배경에 어두운 텍스트를 보여 주었으며, CSS가 이상하게도 빠져 있었습니다. однако`publishSite`같은 사이트를 생성했고, 그것이 완벽했다. 원인은 CSS도, 브라우저도, 캐시도 아니었다. 그것은 Kotlin 코드 한 줄이었고, 명령줄 인수와 관련된 나쁜 습관 때문이었다.&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;/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;_장면_네_개의_스크린샷_두_개의_렌더링&amp;quot;&amp;gt;장면: 네 개의 스크린샷, 두 개의 렌더링&amp;lt;/h2&amp;gt;
&amp;lt;div 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;./gradlew serve`위에`localhost:8820&amp;lt;/code&amp;gt;) 온라인 렌더링과 함께 (`publishSite`GitHub Pages에서). 페이지당 두 개의 스크린샷 : 위와 아래.&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;_로컬_렌더링&amp;quot;&amp;gt;로컬 렌더링&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;페이지 상단&amp;lt;/strong&amp;gt;— 버튼 &amp;lt;strong&amp;gt;Project&amp;lt;/strong&amp;gt; 및 &amp;lt;strong&amp;gt;Template*는&amp;lt;strong&amp;gt;작은, 직사각형&amp;lt;/strong&amp;gt;, aux couleurs standards (bleu et vert). La carte *Start Writing&amp;lt;/strong&amp;gt; a un&amp;lt;strong&amp;gt;단색 검은 배경&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_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;바닥글&amp;lt;/strong&amp;gt;— 부제목 *최신 글과 자료*는&amp;lt;strong&amp;gt;거의 읽을 수 없는&amp;lt;/strong&amp;gt;, 검은 배경에 너무 어둡습니다. 블로그 이미지 아래의 날짜(&amp;#39;17 October 2013&amp;#39;, 등등)은&amp;lt;strong&amp;gt;결석한&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;_온라인_렌더링&amp;quot;&amp;gt;온라인 렌더링&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;페이지 상단&amp;lt;/strong&amp;gt;— 같은 버튼들은&amp;lt;strong&amp;gt;큰, 양의, 화려한 네온 초록&amp;lt;/strong&amp;gt;, 대문자 텍스트. 카드에는&amp;lt;strong&amp;gt;흰색 둥근 프레임에 그림자&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;하단&amp;lt;/strong&amp;gt;— 부제목은&amp;lt;strong&amp;gt;가독성이 좋습니다&amp;lt;/strong&amp;gt;. 날짜는&amp;lt;strong&amp;gt;보이는&amp;lt;/strong&amp;gt;. 기사 카드에는&amp;lt;strong&amp;gt;둥근 모서리&amp;lt;/strong&amp;gt;그리고 제목들은&amp;lt;strong&amp;gt;굵게&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;첫 번째 반사: 테마. 나는 (밝은, 어두운, 높은 대비) 기반의 동적 테마 시스템을 사용`localStorage`. 아마도 로컬 스토어가 고대비 테마를 트리거했을 수도 있지? 아니. 나는 이미 썼어.https://cheroliv.com/blog/2025/0098_preloading_css_variable_post.html[주제 사전 로드에 관한 전체 기사], 나는 어떻게 작동하는지 알아. 그리고 심지어 high-contrast 모드에서도 CSS 구조(타원형 버튼, 그림자)가 있어야 했어. 그런데 그게 없었어.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;두 번째 반응: 브라우저 캐시. 아니요, 깨끗한 프로필로 테스트를 했었습니다. diff가 지속되었습니다.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;세 번째 반사 :`serve`같은 파일을 제공하지 않았습니다`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;_진단_serve가_올바른_폴더를_가리키고_있지_않았습니다&amp;quot;&amp;gt;진단 : `serve`가 올바른 폴더를 가리키고 있지 않았습니다.&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;내 그레이들 플러그인`bakery`두 가지 주요 작업이 있습니다:&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;: 안에 정적 사이트를 생성합니다`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;: 로컬 웹 서버를 시작합니다&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;: 밀다`build/bake/`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;차이는 오직 &amp;amp;#8230;&amp;amp;#8203;에서만 나올 수 있었다.&amp;lt;code&amp;gt;serve&amp;lt;/code&amp;gt;. Si &amp;lt;code&amp;gt;publishSite`그는 올바른 콘텐츠를 게시하고 있었고, 그것이`build/bake/`좋았어요. 만약`serve`다른 것을 표시하고 있었는데, 그건 그것이役에 서지 못했습니다.`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;코드를 봅시다. 안에서`SiteManager.kt`, 작업`serve`다음과 같이 정의됩니다:&amp;lt;/p&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`JBake 2.7.0 CLI의 진입점입니다. 아이디어: 전달하다&amp;lt;/code&amp;gt;-b`소스 폴더와`-s`대상 폴더에 대해, 서버 모드를 시작합니다. 단, &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;_근본_원인_jbake_cli는_이렇게_작동하지_않습니다&amp;quot;&amp;gt;근본 원인: JBake CLI는 이렇게 작동하지 않습니다&amp;lt;/h2&amp;gt;
&amp;lt;div 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은(는) 기다리고 있는&amp;lt;strong&amp;gt;위치 인수&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-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;그러나 내 코드에서 쓰고 있었는데:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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는 이것을 이렇게 해석했습니다 :&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;→ &amp;quot;bake&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;/path/to/site&amp;lt;/code&amp;gt;→ 위치 기반 인수 1이 됩니다 (출처)&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;→ &amp;#39;serve&amp;#39; 플래그(불리언)를 파싱, 서버 시작됨&amp;lt;strong&amp;gt;즉시&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;code&amp;gt;/path/to/build/bake&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;결과 : JBake 가져갔다`/path/to/site`소스로서,&amp;lt;strong&amp;gt;목적지를 고려하지 않았습니다.&amp;lt;/strong&amp;gt;제공된 (&amp;lt;code&amp;gt;build/bake&amp;lt;/code&amp;gt;), 그리고는 기본 디렉터리를 사용하고 있었습니다 (자주`./output`또는 임시 복사본) 파일`css/styles.css`빌드는 덮어쓰여지거나 무시되었고, 그래서 기본 제공되는 오래된 CSS(커스텀 변수도 없고, 둥근 모서리도 없고, 그림자도 없이)가 제공되었습니다.&amp;lt;/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;bake&amp;lt;/code&amp;gt; et &amp;lt;code&amp;gt;publishSite`그들이 그것을&amp;lt;strong&amp;gt;공식 Gradle JBake 플러그인&amp;lt;/strong&amp;gt;(`jbake-gradle-plugin&amp;lt;/code&amp;gt;) 깔끔하게 쓰는 안에`build/bake/`Gradle API를 통해. 그들은 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;_해결책_위치_인자_플래그가_아닌&amp;quot;&amp;gt;해결책: 위치 인자, 플래그가 아닌&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;수정은 간단합니다 — 한번 알면 됩니다. 소스와 대상을 위치 인수로 전달하기만 하면 됩니다, 그다음`-s`마지막으로:&amp;lt;/p&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;그게 다예요. 세 줄이 변경되었고 버그가 해결되었습니다.&amp;lt;/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;publishToMavenLocal&amp;lt;/code&amp;gt;), un `./gradlew serve`JBake를 올바른 구문으로 실행합니다 :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;그리고 이번엔, JBake에 내장된 Jetty 서버가 콘텐츠를 잘 제공합니다`build/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&amp;gt;@startuml
!define RECTANGLE class

package &amp;quot;이전 (버그)&amp;quot; {
  RECTANGLE JBakeCLI1 {
    + args = [&amp;quot;-b&amp;quot;, &amp;quot;사이트&amp;quot;, &amp;quot;-s&amp;quot;, &amp;quot;빌드/베이크&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;후 (수정)&amp;quot; {
  RECTANGLE JBakeCLI2 {
    + args = [&amp;quot;사이트&amp;quot;, &amp;quot;빌드/베이크&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;_curl을_사용한_빠른_검증&amp;quot;&amp;gt;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;서버가 올바른 콘텐츠를 제공하고 있는지 확인하기 위해, 간단한`curl`확인한다`index.html`포함한다`data-bs-theme=&amp;quot;light&amp;quot;`그리고`build/bake/css/styles.css`정확히 31 402 바이트입니다 — 생성된 파일과 동일한`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;로컬 렌더링은 이제&amp;lt;strong&amp;gt;픽셀-동일&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;_왜_이_버그는_교활했는가&amp;quot;&amp;gt;왜 이 버그는 교활했는가?&amp;lt;/h2&amp;gt;
&amp;lt;div 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;/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;명시적 오류 없음&amp;lt;/strong&amp;gt;: JBake는 크래시되지 않았습니다. 그것은 단지 잘못된 폴더와 함께 &amp;#39;작동&amp;amp;#8217;하고 있었습니다.&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;빌드가 작동하고 있었습니다&amp;lt;/strong&amp;gt;:`./gradlew bake`파일을 올바르게 생성하고 있었습니다`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;배포가 작동하고 있었다&amp;lt;/strong&amp;gt;(Empty)`publishSite`좋은 콘텐츠를 온라인에 게시하고 있었습니다.&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;serve`만 고장 났습니다&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;-key value`의 습관&amp;lt;/strong&amp;gt;: 개발자로서, 우리는 수년간의 Unix CLI에 익숙해져 있다`-o output`, &amp;lt;code&amp;gt;-i input&amp;lt;/code&amp;gt;). JBake CLI는 예외입니다 — 소스와 목적은 위치 기반.&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;_결론&amp;quot;&amp;gt;결론&amp;lt;/h2&amp;gt;
&amp;lt;div 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를 래핑하는 Gradle 플러그인(또는 기타 CLI 도구 포함)을 유지 관리하는 경우,&amp;lt;strong&amp;gt;인수 문서를 읽으세요&amp;lt;/strong&amp;gt;당신이 그들을 알고 있다고 생각하더라도. 암묵적인 가설 (&amp;lt;code&amp;gt;-s&amp;lt;/code&amp;gt;= &amp;quot;set destination&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;여기서 수정은 말 그대로 변경하는 것이었습니다 :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;세 개의 토큰이 이동된 후, 내 로컬 사이트는 다시 프로덕션 사이트만큼 아름답게 되었다.&amp;lt;/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;/strong&amp;gt;: 로컬과 프로덕션 간 렌더링이 명백한 이유 없이 다를 때, 먼저 *pipeline*을 의심하세요 — CSS가 아니고, 브라우저가 아니고, 더더욱 프레임워크가 아닙니다. 종종 렌더링 직전의 단계가 속이는 경우가 많습니다.&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와 불완전한 PATH: 왜 도구들이 에이전트 셸에 없었나요?</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;#_장면_도구의_세계에서_눈먼_에이전트&amp;quot;&amp;gt;장면: 도구의 세계에서 눈먼 에이전트&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_버그의_해부_두_개의_쉘_두_개의_세계&amp;quot;&amp;gt;버그의 해부 : 두 개의 쉘, 두 개의 세계&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_범인들_zshrc_vs_zshenv&amp;quot;&amp;gt;범인들: &amp;lt;code&amp;gt;.zshrc&amp;lt;/code&amp;gt; vs `.zshenv&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_단계별_진단&amp;quot;&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;#_1_에이전트_path_확인&amp;quot;&amp;gt;1. 에이전트 PATH 확인&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_2_zshrc에_있지만_zshenv에_없는_것을_식별한다&amp;quot;&amp;gt;2. .zshrc에 있지만 .zshenv에 없는 것을 식별한다&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_3_zshenv_파일이_존재하는지_확인&amp;quot;&amp;gt;3. .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;#_해결책_zshenv_올바른_경로&amp;quot;&amp;gt;해결책 : .zshenv + 올바른 경로&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;#_필수적인_모든_경로를_포함한_zshenv_파일을_만드세요&amp;quot;&amp;gt;필수적인 모든 경로를 포함한 .zshenv 파일을 만드세요.&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_왜_zshenv에서_source_sdkman_init_sh를_사용하지_않을까&amp;quot;&amp;gt;왜 .zshenv에서 `source sdkman-init.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;#_nvm_케이스_버전_관리자가_흔적을_남기는_것을_잊을_때&amp;quot;&amp;gt;NVM 케이스: 버전 관리자가 흔적을 남기는 것을 잊을 때&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;#_nvm_문제&amp;quot;&amp;gt;NVM 문제&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_해결책_current_심볼릭_링크를_nvm용으로_생성&amp;quot;&amp;gt;해결책: &amp;lt;code&amp;gt;current&amp;lt;/code&amp;gt; 심볼릭 링크를 NVM용으로 생성&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_심볼릭_링크_업데이트_자동화&amp;quot;&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;#_경로_요약_표&amp;quot;&amp;gt;경로 요약 표&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_함정과_완화&amp;quot;&amp;gt;함정과 완화&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_배운_교훈&amp;quot;&amp;gt;배운 교훈&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_최종_검증&amp;quot;&amp;gt;최종 검증&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_링크&amp;quot;&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;#_공식_문서&amp;quot;&amp;gt;공식 문서&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_언급된_도구&amp;quot;&amp;gt;언급된 도구&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_더_깊이&amp;quot;&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;/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;읽기 시간 : 13 minutes&amp;lt;/div&amp;gt;
&amp;lt;p&amp;gt;당신은 터미널에서 OpenCode를 실행하고, 모든 것이 정상적으로 작동합니다. 에이전트는 시도합니다`gh`— 찾을 수 없음. 하나`java -version`— 부재한. 하나`node`— 어디에도 없습니다. 하지만,これらのツール들은 &amp;lt;em&amp;gt;votre&amp;lt;/em&amp;gt; shell 안에 분명히 있습니다. 문제는? OpenCode는 셸을 실행합니다.&amp;lt;strong&amp;gt;비상호적&amp;lt;/strong&amp;gt;당신의 것을 절대 읽지 않는`.zshrc`. 여기서 진단하고 깔끔하게 수정하는 방법입니다.&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;/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;_장면_도구의_세계에서_눈먼_에이전트&amp;quot;&amp;gt;장면: 도구의 세계에서 눈먼 에이전트&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;그것은 화요일 저녁이었고, 나는 방금 설치하고 있었다.https://opencode.ai[OpenCode], 코딩 방식을 바꿔줄 것을 약속했던 AI 에이전트. 첫 번째 테스트 : 그것을 이용해 나의 GitHub 저장소 목록을 요청하기.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;이상하다`gh`내 터미널에서 완벽하게 작동했습니다. 다른 것을 시도해 보고 있습니다 :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;그리고:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;— 모든 것이 요원에게 보이지 않았다. 내 터미널은 모든 것을 볼 수 있었다. 요원과 내가 두 개의 평행 세계에 살고 있는 것처럼.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;이해하는 데 몇 시간이 걸렸습니다. 몇 시간의`echo $PATH`, de &amp;lt;code&amp;gt;which java&amp;lt;/code&amp;gt;, 점점 커지는 불만. 나는 결국 이해했다 — 문제는 도구가 아니었고, 그것이&amp;lt;strong&amp;gt;쉘&amp;lt;/strong&amp;gt;. OpenCode은 모든 하위 프로세스를 실행하는 에이전트처럼, 쉘에서 작업합니다.&amp;lt;strong&amp;gt;비대화식&amp;lt;/strong&amp;gt;. 그리고 Zsh는 이러한 셸에서 단순히 당신의`.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;다음은 진단, 해결 방법, 그리고 제가 배운 교훈에 대한 전체적인 설명입니다. — OpenCode, Aider, Cursor, 또는 cron 스크립트와 같은 AI 에이전트를 사용한다면 언젠가 이 문제에 직면하게 될 것입니다. — 이 문제는 언젠가 당신에게 영향을 미칠 것입니다.&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;_버그의_해부_두_개의_쉘_두_개의_세계&amp;quot;&amp;gt;버그의 해부 : 두 개의 쉘, 두 개의 세계&amp;lt;/h2&amp;gt;
&amp;lt;div 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는 이를 쉘로 처리합니다.&amp;lt;strong&amp;gt;대화형&amp;lt;/strong&amp;gt;. 그의 충전한다`.zshrc`, 이 모든 것을 초기화합니다: SDKMAN, NVM, pnpm, 별칭, 멋진 프롬프트. 환경을 완성했습니다.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;하지만 OpenCode가 명령을 실행할 때, 대화형 터미널을 실행하지 않습니다. 셸을 실행합니다.&amp;lt;strong&amp;gt;비대화형&amp;lt;/strong&amp;gt;— 작업 셸, 화면 뒤에 인간이 없는. 그리고 Zsh는 이 상황에서 점프한다.&amp;lt;code&amp;gt;.zshrc&amp;lt;/code&amp;gt;. 그는 다만 읽는다.&amp;lt;code&amp;gt;.zshenv&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;왜 이 구분이 존재하는가? 왜냐하면 비대화형 셸은 스크립트를 실행하도록 설계되었고, 인간 사용자를 위해 설계된 것이 아니기 때문이다. 백그라운드에서 실행되는 스크립트에 별칭, 프롬프트, 자동완성을 로드하는 것은 낭비이다. 문제는, 실행 가능한 파일을 찾는 데 가장 중요한 정보인 사용자의 PATH가 자주 초기화되는 곳에`.zshrc`, 안에 있지 않아`.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;당신&amp;quot; as user
actor &amp;quot;OpenCode
(에이전트)&amp;quot; as agent
participant &amp;quot;셸
대화형
(.zshrc 소스된)&amp;quot; as ishell
participant &amp;quot;Shell
비대화형
(.zshrc 무시됨)&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;문제의 근원 :&amp;lt;strong&amp;gt;Zsh는 대화형 셸에서만 &amp;lt;code&amp;gt;.zshrc`를 소스합니다&amp;lt;/strong&amp;gt;. OpenCode, 모든 IA 에이전트가 서브프로세스를 시작할 때와 마찬가지로, 비대화형 셸을 사용합니다. 이 셸은 오직 읽습니다.&amp;lt;/code&amp;gt;.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;_범인들_zshrc_vs_zshenv&amp;quot;&amp;gt;범인들: &amp;lt;code&amp;gt;.zshrc&amp;lt;/code&amp;gt; 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는 가지고 있습니다&amp;lt;strong&amp;gt;네 개의 초기화 파일&amp;lt;/strong&amp;gt;, 각kak 특정 역활을 가진. 세련된 디자인이다 — 이것도 문제의 핵심:&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;파일&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;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;항상&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;필수 환경 변수, 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;✅ 예 — 이것이 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;로그인 셸만&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;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;대화형 쉘만&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;&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;로그인 셸(.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;환영 메시지, 완료&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;표는 힌트를 주지만, 깊이 이해해야 합니다. 집의 방처럼 생각해 보세요:&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`은/는&amp;lt;strong&amp;gt;로비&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;`.zshrc`은/는&amp;lt;strong&amp;gt;거실&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;code&amp;gt;.zprofile&amp;lt;/code&amp;gt; et `.zlogin`로그인 셸을 위한 특수 구성 요소이며 (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;비극은 우리 대부분이 PATH를 거실에 두는 것이라는 점이다. 그리고 AI 에이전트는 거실에 들어갈 수 없다.&amp;lt;/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&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;터미널을 열면, Zsh는 대화형입니다: 읽습니다`.zshrc`, 모든 것이 작동합니다. OpenCode가 셸을 실행하여 명령을 실행할 때, Zsh는 비대화형입니다:それは 건너뜁니다.&amp;lt;code&amp;gt;.zshrc&amp;lt;/code&amp;gt;, 읽기만`.zshenv`. Et si `.zshenv`존재하지 않거나 PATH를 포함하지 않습니다 — 이것이 황무지입니다.&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;왜 Zsh가 그렇게 하는가?&amp;lt;/strong&amp;gt;이것은 Unix에서 상속받은 설계 선택이다. 비대화형 쉘이어야 한다&amp;lt;strong&amp;gt;빠른&amp;lt;/strong&amp;gt; et &amp;lt;strong&amp;gt;재현 가능한&amp;lt;/strong&amp;gt;. 별칭, 색이 있는 프롬프트, SDKMAN의 무거운 초기화를 크론 스크립트나 AI 에이전트에 로드하면 느리고 취약할 것입니다. 따라서 분리는 논리적 :`.zshenv`principalement (변수, PATH),`.zshrc`편안함을 위해 (aliases, prompt, complétons). 문제는 핵심 을 편안함에 두면 발생한다.&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;_단계별_진단&amp;quot;&amp;gt;단계별 진단&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;수정하기 전에 정확히 무엇이 부족한지 이해해야 합니다. 다음은 재현 가능한 진단 방법입니다 — 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;_1_에이전트_path_확인&amp;quot;&amp;gt;1. 에이전트 PATH 확인&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;대화형 터미널이 보는 내용과 비대화형 셸이 보는 내용을 비교합니다 — 즉, 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;일반적인 결과 :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;이제 에이전트가 보는 것을 시뮬레이션해 봅시다 — 비대화형 셸 :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;결과 :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;여섯 길 중 다섯이 사라졌다.&amp;lt;/strong&amp;gt;에이전트는 환경의 83%를 잃었습니다. 마치 당신이 절반의 조리 도구 없이 요리하라고 요청받은 것과 같습니다 — 물을 끓일 수는 있지만, 그 외에는 거의 할 수 있는 게 없습니다.&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;명령`zsh -c &amp;#39;echo $PATH&amp;#39;`당신의&amp;lt;strong&amp;gt;1등 진단 도구&amp;lt;/strong&amp;gt;. 매일 사용하는 도구가 결과에 포함되지 않으면, AI 에이전트도 그것을 볼 수 없습니다. 수정 전과 후에 테스트해 보세요. de`.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_zshrc에_있지만_zshenv에_없는_것을_식별한다&amp;quot;&amp;gt;2. .zshrc에 있지만 .zshenv에 없는 것을 식별한다&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;이제, 우리는 범인을 찾고 있는 에서`.zshrc`. 우리는 우리 도구를 언급하는 줄을 필터링합니다 :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;일반적으로 발견됩니다:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;그 모든 것이&amp;lt;strong&amp;gt;보이지 않는&amp;lt;/strong&amp;gt;OpenCode를 위해. 그리고 그`PATH`119번째 줄조차도 아닌`export`é — 현재 셸을 절대 떠나지 않습니다. 이것은 중요한 기술적 세부 사항입니다: 변수 없이`export`이 변수는 해당 셸에 국소적이다. 서브셸 — OpenCode에 의해 시작된 것들처럼 — 은 이를 결코 상속받지 않는다.&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_zshenv_파일이_존재하는지_확인&amp;quot;&amp;gt;3. .zshenv 파일이 존재하는지 확인&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;답변이 « FICHIER ABSENT »이면, 여기서 모든 것이 결정된다.&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;_해결책_zshenv_올바른_경로&amp;quot;&amp;gt;해결책 : .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;전략은 간단하지만 정밀함이 필요합니다: 안에 넣기`.zshenv` &amp;lt;strong&amp;gt;오직&amp;lt;/strong&amp;gt;필수 경로는, 무거운 초기화 스크립트를 소싱하지 않고. 우리는 이동하지 않습니다`.zshrc`안에`.zshenv`— 핵심 내용을 추출합니다.&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;_필수적인_모든_경로를_포함한_zshenv_파일을_만드세요&amp;quot;&amp;gt;필수적인 모든 경로를 포함한 .zshenv 파일을 만드세요.&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;`.zshenv`은/는&amp;lt;strong&amp;gt;단일 파일&amp;lt;/strong&amp;gt;Zsh가 소스를 보장하는&amp;lt;strong&amp;gt;모두&amp;lt;/strong&amp;gt;문맥들. PATH가 가야 할 곳은 여기입니다.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;사용합니다`$HOME/.nvm/current/bin`그리고 안`$HOME/.nvm/versions/node/v22.19.0/bin`. 이유 : Node 버전이 변경됩니다. 고정된 경로가 다음에 잘못됩니다.&amp;lt;code&amp;gt;nvm install&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&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_왜_zshenv에서_source_sdkman_init_sh를_사용하지_않을까&amp;quot;&amp;gt;왜 .zshenv에서 `source sdkman-init.sh`를 사용하지 않을까?&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;가장 유혹적인 접근법은 단순히 그곳에 재현하는 것이다`.zshenv`우리가 하는 것 안에`.zshrc`— 초기화 스크립트를 소스로 실행하다. 우리는&amp;lt;strong&amp;gt;할 수 있을&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;# ❌ 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;문제 :&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;느린&amp;lt;/strong&amp;gt;:`sdkman-init.sh`각 셸마다 네트워크 해결 및 검사를 수행합니다. 비대화형 셸에서는 이는 불필요한 비용입니다.&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;취약한&amp;lt;/strong&amp;gt;: SDKMAN은 대화형 환경을 기대합니다. 초기화가 파이프 또는 서브셸에서 조용히 실패할 수 있습니다.&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;쓸모없는&amp;lt;/strong&amp;gt;: SDKMAN은 후보자들을`~/.sdkman/candidates/&amp;amp;lt;tool&amp;amp;gt;/current/bin`— 안정적인 심볼릭 링크가 활성 버전을 가리키고 있습니다. 이를 사용할 수 있습니다.&amp;lt;strong&amp;gt;직접&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;올바른 접근 방식: SDKMAN 초기화를 우회하고 심볼릭 링크를 직접 가리키기`current`. 이것은 전체 해결책의 열쇠 :&amp;lt;strong&amp;gt;파일 구조를 계약으로 사용하고, 초기화 코드 대신 사용하라&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;느린 접근
(source sdkman-init.sh)&amp;quot; as slow {
  card &amp;quot;.zshenv에서 sdkman-init.sh 소스
쉘당 2-3초
네트워크 점검
실패 위험&amp;quot; as s1 #FDEDEC
}

rectangle &amp;quot;빠른 접근
(직접 심볼릭 링크)&amp;quot; as fast {
  card &amp;quot;.zshenv export PATH=...current/bin\n0 ms\nPas de réseau\nPas d&amp;#39;initialisation&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;_nvm_케이스_버전_관리자가_흔적을_남기는_것을_잊을_때&amp;quot;&amp;gt;NVM 케이스: 버전 관리자가 흔적을 남기는 것을 잊을 때&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;_nvm_문제&amp;quot;&amp;gt;NVM 문제&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;이곳이 조사가 나를 가장 멀리 이끌어간 곳입니다. SDKMAN은 세련된 디자인을 가지고 있습니다: 당신이 할 때`sdk install java 25.0.2-tem`, 심볼릭 링크를 생성합니다 :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;이 심볼릭 링크는&amp;lt;strong&amp;gt;항상 최신&amp;lt;/strong&amp;gt;. 이 안에 가리키세요`.zshenv`그리고 당신은 보호받고, 어떤 셸이든 상관 없습니다. Beautiful.&amp;lt;/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;/strong&amp;gt;텔의. 심볼릭 링크 없음`current`. 안정된 앵커 포인트가 없습니다. 우리는 하드코딩된 버전된 경로를 가리켜야 하며, 그것은 다음과 같이 보입니다 :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;다음에`nvm install 24`, 이 길은 죽었습니다. 당신의`.zshenv`더 이상 활성 버전이 아닌 버전을 가리키게 됩니다. 이것은 시한 폭탄입니다.&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;NVM은 왜 그렇게 하는가?&amp;lt;/strong&amp;gt;NVM은 PATH를 동적으로 수정하여 매번`nvm use`. 이는 버전을 자주 바꾸는 개발자를 위해 설계되었으며, 대화형 셸에서 사용됩니다. 심볼릭 링크`current`처음 사양에 들어 있지 않았습니다 — 이것은 우리가 직접 수정할 설계상의 실수입니다.&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 ❌ (수정 전)&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 ✅ (수정 후)&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;_해결책_current_심볼릭_링크를_nvm용으로_생성&amp;quot;&amp;gt;해결책: &amp;lt;code&amp;gt;current&amp;lt;/code&amp;gt; 심볼릭 링크를 NVM용으로 생성&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;NVM이 그것을 하지 않기 때문에, 우리가 직접 합니다. 원리는 SDKMAN과 동일합니다 — 심볼릭 링크`current`항상 활성 버전을 가리키는. 한 번 생성한 후 자동화하여 스스로 업데이트되도록 합니다.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;지금, 안에서`.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 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;대신에:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;_심볼릭_링크_업데이트_자동화&amp;quot;&amp;gt;심볼릭 링크 업데이트 자동화&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;심볼릭 링크는 갱신되지 않으면 쓸모가 없다. 심볼릭 링크`current`업데이트해야 할 때`nvm use` ou &amp;lt;code&amp;gt;nvm install&amp;lt;/code&amp;gt;. 해결책 : 하나&amp;lt;strong&amp;gt;래퍼&amp;lt;/strong&amp;gt;— 진짜 명령을 감싸는 함수`nvm`그리고 각 호출 후에 심볼릭 링크를 업데이트합니다.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;작동 방식, 자세히 :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;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 래퍼\n(_nvm)&amp;quot; as wrapper
participant &amp;quot;nvm 실제
(command nvm)&amp;quot; as realnvm
participant &amp;quot;~/.nvm/current\n(심볼릭 링크)&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;래퍼`_nvm`실제 명령을 호출한다`nvm`, 그리고 심볼릭 링크를 업데이트합니다. 별칭`nvm=&amp;#39;_nvm&amp;#39;`입력할 때 그렇게 되도록`nvm`, 우리는 래퍼를 통해 진행합니다. 그리고 셸 초기화 시에도 동일한 작업을 수행합니다.&amp;lt;code&amp;gt;nvm use --lts&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;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`는 NVM의 내부 기능으로, 버전의 전체 경로를 해결합니다. 이로 인해 경로를 수동으로 다시 구성할 필요가 없습니다.&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;_경로_요약_표&amp;quot;&amp;gt;경로 요약 표&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;트랩으로 넘어가기 전에, 변환의 시각적 요약. 좌측에, 당신이 가졌던 (모든 것`.zshrc`, 에이전트에게 보이지 않음). 오른쪽에서, 당신이 지금 가지고 있는 것 (필수 경로 내`.zshenv`, 모든 곳에 보이는).&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;도구&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;이전 (.zshrc만)&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;이후에 (.zshenv + symlink)&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;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가 .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;&amp;lt;code&amp;gt;$HOME/apps&amp;lt;/code&amp;gt;.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 자바&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;sdkman-init.sh&amp;lt;/code&amp;gt;.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;&amp;lt;code&amp;gt;$HOME/.sdkman/candidates/java/current/bin&amp;lt;/code&amp;gt;.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 그레이들&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;sdkman-init.sh&amp;lt;/code&amp;gt;.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;&amp;lt;code&amp;gt;$HOME/.sdkman/candidates/gradle/current/bin&amp;lt;/code&amp;gt;.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 노드&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;nvm use --lts&amp;lt;/code&amp;gt;.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;&amp;lt;code&amp;gt;$HOME/.nvm/current/bin&amp;lt;/code&amp;gt;.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;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;&amp;lt;code&amp;gt;$PNPM_HOME&amp;lt;/code&amp;gt;.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;&amp;lt;code&amp;gt;$PNPM_HOME&amp;lt;/code&amp;gt;.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;Toolbox에 의해 `.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;&amp;lt;code&amp;gt;$HOME/.local/share/JetBrains/Toolbox/scripts&amp;lt;/code&amp;gt;.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;파이썬 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`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;.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;_함정과_완화&amp;quot;&amp;gt;함정과 완화&amp;lt;/h2&amp;gt;
&amp;lt;div 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;/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;함정&amp;lt;/th&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;완화&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&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;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과 .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;`.zshenv`소싱되었습니다&amp;lt;strong&amp;gt;이전&amp;lt;/strong&amp;gt;.zshrc. 두 파일이 대화형 셸에서 읽힙니다. PATH가 중복될 수 있습니다. 이것은 미적인 문제일 뿐 기능적인 문제는 아닙니다. 이를 방지하려면 .zshenv에 기본 PATH에 없는 &amp;lt;em&amp;gt;absents&amp;lt;/em&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: .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;놓다`~/.nvm/versions/node/v22.19.0/bin`콘크리트에서 다음에 거짓이 된다`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;심볼릭 링크 사용`$HOME/.nvm/current/bin`+ 래퍼`_nvm`안 .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 : .zshenv에서 sdkman-init.sh 소스하기&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;심볼릭 링크 사용`candidates/&amp;amp;lt;tool&amp;amp;gt;/current/bin`직접적으로&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;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;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=&amp;#39;_nvm&amp;#39;`.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;쉘을 다시 시작하거나`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는 변경사항을 보지 않는다.&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;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을 사용해 하위 셸을 실행했습니다.&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;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 수정한 .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이 과도하게 로드됨&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;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에 넣으세요.&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;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 = 환경 변수 + PATH만. 없습니다`source`, 무거운 함수 없음, 프롬프트 없음.&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;_배운_교훈&amp;quot;&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;strong&amp;gt;.zshrc`는 대화형입니다, &amp;lt;code&amp;gt;.zshenv`은 범용입니다&amp;lt;/strong&amp;gt;— 변수가 &amp;lt;em&amp;gt;모든&amp;lt;/em&amp;gt; 셸(에이전트 AI, cron, 스크립트, IDE)에 존재해야 한다면, 그것은&amp;lt;/code&amp;gt;.zshenv`. 거실은 편안하지만, 입구 홀은 모두가 지나가는 유일한 장소입니다.&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;버전 관리자들은 동일하지 않다.&amp;lt;/strong&amp;gt;— SDKMAN은 심볼릭 링크를 생성합니다.`current`설계상. NVM 아니오. 이 부족함을 수동으로 채워야 합니다. 이것은 중요한 교훈입니다: PATH를 설정하기 전에, 관리자가 안정적인 고정점을 제공하는지 확인하세요.&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;.zshenv에서 init 스크립트를 소스하지 마세요&amp;lt;/strong&amp;gt; — &amp;lt;code&amp;gt;sdkman-init.sh&amp;lt;/code&amp;gt; et `nvm.sh`대화형 셸을 위해 설계되었습니다. 비대화형 컨텍스트에서는 느리고 취약합니다. 심볼릭 링크`current`그들은 충분하고 즉각적이다.&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;비대화형 쉘에서 항상 테스트&amp;lt;/strong&amp;gt;—`zsh -c &amp;#39;echo $PATH&amp;#39;`에이전트가 보는 것을 정확히 시뮬레이션합니다. 이것은 검증 테스트입니다. 이 테스트가 없으면, 에이전트에 대해 구성이 작동하는지 알 수 없습니다.&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;래퍼 패턴은 재사용 가능합니다.&amp;lt;/strong&amp;gt;— 같은 패턴`_nvm`+`alias nvm=&amp;#39;_nvm&amp;#39;`모든 PATH를 동적으로 수정하지만 흔적을 남기지 않는 도구에 적용됩니다. 이것은 당신의 아이디어 상자에 하나 더 추가되는 도구입니다.&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;전에&amp;quot; as avant {
  card &amp;quot;대화형 셸 : ✅
비대화형 셸 : ❌
OpenCode : ❌
Cron : ❌
스크립트 : ❌&amp;quot; as av1 #FDEDEC
}

rectangle &amp;quot;후에&amp;quot; as apres {
  card &amp;quot;대화형 셸 : ✅\n비대화형 셸 : ✅\nOpenCode : ✅\nCron : ✅\nScripts : ✅&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;_최종_검증&amp;quot;&amp;gt;최종 검증&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;생성한 후`.zshenv`그리고 NVM 심볼릭 링크, 모든 것이 작동하는지 확인하세요 — 두 가지 컨텍스트 모두에서 :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;둘 다 성공해야 합니다. 그렇게 된다면, 귀하의 IA 에이전트가 귀하와 동일한 도구를 볼 것입니다. 그렇지 않다면, 단계별 진단으로 돌아가세요 — 아마도 경로를 잊었거나 NVM 심링크가 최신이 아닐 것입니다.&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;이 테스트를 자동화하세요.&amp;lt;/strong&amp;gt;이 검사를 도구 업데이트 후에 실행하는 healthcheck 스크립트에 추가하세요. 하나`zsh -c &amp;#39;which java &amp;amp;amp;&amp;amp;amp; which node &amp;amp;amp;&amp;amp;amp; which gh&amp;#39;`CI에서는 예상치 못한 상황에 대비하는 보험입니다.&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;_ 대화형이 아닌 셸은 침묵하는 손님과 같다: 그것은 입구에 표시된 것만 읽는다. PATH가 거실에 있으면, 그것을 결코 볼 수 없다. &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;_링크&amp;quot;&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;_공식_문서&amp;quot;&amp;gt;공식 문서&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 문서 : 시작 파일&amp;lt;/a&amp;gt;— 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://opencode.ai/docs/config/&amp;quot;&amp;gt;OpenCode : 구성&amp;lt;/a&amp;gt;— OpenCode 및 실행 환경 설정 방법.&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;_언급된_도구&amp;quot;&amp;gt;언급된 도구&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;GitHub의 NVM&amp;lt;/a&amp;gt;— Node Version Manager. 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 — 공식 사이트&amp;lt;/a&amp;gt;— SDKMAN! JVM(Java, Kotlin, Gradle 등)의 버전 관리자&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: 설치&amp;lt;/a&amp;gt;— 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&amp;lt;/a&amp;gt;— GitHub 커맨드라인 도구, 모든 개발자에게 필수적인&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 — 공식 사이트&amp;lt;/a&amp;gt;— 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 — 공식 사이트&amp;lt;/a&amp;gt;— 빠르고 공간을 절약하는 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://www.jetbrains.com/toolbox-app/&amp;quot;&amp;gt;JetBrains 툴박스&amp;lt;/a&amp;gt;— JetBrains IDE 관리자, 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;_더_깊이&amp;quot;&amp;gt;더 깊이&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 : 완전한 가이드&amp;lt;/a&amp;gt;Zsh dotfiles 관리에 대한 심도 있는 기사&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;ArchWiki에서 Zsh&amp;lt;/a&amp;gt;— 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;NVM 이슈 GitHub&amp;lt;/a&amp;gt;— 심볼릭 링크 주변의 토론을 보려면`current`그리고 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>Gradle 워크플로에 Graphify 통합: Knowledge Graph에서 PlantUML 다이어그램까지 한 명령어로</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;#_문제_항상_코드보다_뒤처진_다이어그램&amp;quot;&amp;gt;문제: 항상 코드보다 뒤처진 다이어그램&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_해결책_결정적_파이프라인_knowledge_graph_plantuml&amp;quot;&amp;gt;해결책: 결정적 파이프라인 Knowledge Graph → PlantUML&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_단계_1_graphify_설치_및_knowledge_graph_추출&amp;quot;&amp;gt;단계 1 : Graphify 설치 및 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;#_설치&amp;quot;&amp;gt;설치&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_제외_항목_구성&amp;quot;&amp;gt;제외 항목 구성&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_지식_그래프_추출&amp;quot;&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;#_단계_2_gradle_플러그인이_knowledge_graph를_plantuml로_변환합니다&amp;quot;&amp;gt;단계 2 : Gradle 플러그인이 Knowledge Graph를 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;#_파이프라인_아키텍처&amp;quot;&amp;gt;파이프라인 아키텍처&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_내부_구성_요소&amp;quot;&amp;gt;내부 구성 요소&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_렌더링_규칙&amp;quot;&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;#_3단계_일상_사용&amp;quot;&amp;gt;3단계: 일상 사용&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;#_완전한_다이어그램&amp;quot;&amp;gt;완전한 다이어그램&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_커뮤니티별_필터&amp;quot;&amp;gt;커뮤니티별 필터&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_엣지_유형별로_필터링&amp;quot;&amp;gt;엣지 유형별로 필터링&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_노드_유형_및_신뢰도로_필터링&amp;quot;&amp;gt;노드 유형 및 신뢰도로 필터링&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_사용자_지정_출력_디렉터리&amp;quot;&amp;gt;사용자 지정 출력 디렉터리&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_속성_전체_참조&amp;quot;&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;#_gradle_워크플로우의_전체_파이프라인&amp;quot;&amp;gt;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;#_워크플로_유형&amp;quot;&amp;gt;워크플로 유형&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_개발_사이클에_통합&amp;quot;&amp;gt;개발 사이클에 통합&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_증분_업데이트&amp;quot;&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;#_도그푸딩_플러그인은_스스로를_문서화한다&amp;quot;&amp;gt;도그푸딩: 플러그인은 스스로를 문서화한다.&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_왜_그것이_결정적이며_그리고_왜_그것이_중요한가&amp;quot;&amp;gt;왜 그것이 결정적이며 (그리고 왜 그것이 중요한가)&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_파이프라인_자체_다이어그램&amp;quot;&amp;gt;파이프라인 자체 다이어그램&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_프로젝트_거버넌스_내_통합&amp;quot;&amp;gt;프로젝트 거버넌스 내 통합&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_준비_5분_체크리스트&amp;quot;&amp;gt;준비 : 5분 체크리스트&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_함정_및_완화_방안&amp;quot;&amp;gt;함정 및 완화 방안&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_최종적으로_얻는_것&amp;quot;&amp;gt;최종적으로 얻는 것&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_링크&amp;quot;&amp;gt;링크&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;읽기 시간 : 12 minutes&amp;lt;/div&amp;gt;
&amp;lt;p&amp;gt;당신의 코드베이스가 커지고 있습니다. 모듈 간의 의존성이 늘어납니다. 아키텍처 문서는 작성되기도 전에 구식이 됩니다. 만약 Gradle 빌드가 &amp;lt;em&amp;gt;자동으로&amp;lt;/em&amp;gt; 실제 코드 구조로부터 최신 다이어그램을 생성할 수 있다면? 이는 정확히 &amp;lt;code&amp;gt;pipeline Graphify + PlantUML Gradle Plugin&amp;lt;/code&amp;gt; :`graphify . --no-viz`Knowledge Graph을 추출합니다,`./gradlew generateKnowledgeGraphDiagram`이를 PlantUML 다이어그램으로 변환합니다. LLM 없음, 매뉴얼 없음, 100% 결정적.&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;/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;_문제_항상_코드보다_뒤처진_다이어그램&amp;quot;&amp;gt;문제: 항상 코드보다 뒤처진 다이어그램&amp;lt;/h2&amp;gt;
&amp;lt;div 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;/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;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;코드가 진화하고, 의존성이 바뀝니다.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;도표는 장식적인 거짓이 된다.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;아무도 이것을 업데이트하지 않아요, 왜냐하면 귀찮기 때문이에요&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&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;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;질문은 &amp;lt;em&amp;gt;도표가 필요한가요?&amp;lt;/em&amp;gt; — 모두가 알다시피 그렇습니다. 질문은 :&amp;lt;strong&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;답변 : 아무도 없습니다. 자동인 경우를 제외하고&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;_해결책_결정적_파이프라인_knowledge_graph_plantuml&amp;quot;&amp;gt;해결책: 결정적 파이프라인 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;원칙은 간단합니다: 손으로 다이어그램을 그리는 대신에, 우리는 그들을,&amp;lt;strong&amp;gt;실제 코드 구조에서 생성&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
(pip install graphifyy)&amp;quot; as Graphify
collections &amp;quot;graphify-out/graph.json
(지식 그래프)&amp;quot; as KGJSON
component &amp;quot;PlantUML Gradle Plugin\n(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
(검증 + 렌더링 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;두 개의 명령어. 그게 전부입니다.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;결과? 파일들`.puml` et &amp;lt;code&amp;gt;.png`안에`diagrams/knowledge-graph/&amp;lt;/code&amp;gt;, Git에서 버전 관리된, 코드와 항상 최신 상태인&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_graphify_설치_및_knowledge_graph_추출&amp;quot;&amp;gt;단계 1 : Graphify 설치 및 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;_설치&amp;quot;&amp;gt;설치&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Graphify는 코드베이스를 분석하고 구조화된 지식 그래프를 구축하는 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-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;_제외_항목_구성&amp;quot;&amp;gt;제외 항목 구성&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;파일 생성`.graphifyignore`프로젝트 루트에 비즈니스 로직과 관련 없는 파일을 제외하기 위해:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;_지식_그래프_추출&amp;quot;&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-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;플래그`--no-viz`HTML 생성을 건너뛰습니다 (Gradle 파이프라인에서는 불필요함). 결과는 파일입니다.`graphify-out/graph.json`포함 :&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;매듭들&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;모서리&amp;lt;/strong&amp;gt;:노드간 관계(EXTRACTED 코드에서, INFERRED 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;커뮤니티&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;title&amp;quot;&amp;gt;예시 구조`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;소스 코드 (&amp;lt;code&amp;gt;.kt&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;.java&amp;lt;/code&amp;gt;)는 tree-sitter에 의해 로컬에서 분석됩니다,&amp;lt;strong&amp;gt;항소할 수 없는 LLM&amp;lt;/strong&amp;gt;. 문서 파일들만 (&amp;lt;code&amp;gt;.adoc&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;.md&amp;lt;/code&amp;gt;) 의미 추출을 위해서는 LLM 호출이 필요합니다. 따라서`--update`Kotlin 코드를 컴파일하는 것은 거의 순간적입니다.&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;_단계_2_gradle_플러그인이_knowledge_graph를_plantuml로_변환합니다&amp;quot;&amp;gt;단계 2 : Gradle 플러그인이 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;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_파이프라인_아키텍처&amp;quot;&amp;gt;파이프라인 아키텍처&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;플러그인`com.cheroliv.plantuml`작업을 통합한다`generateKnowledgeGraphDiagram`이 변환하는`graph.json`en PlantUML 다이어그램 방식으로&amp;lt;strong&amp;gt;완전하게 결정적&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;지식 그래프 다이어그램 생성 작업&amp;quot; as Task
participant &amp;quot;지식 그래프 파서&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;_내부_구성_요소&amp;quot;&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;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;구성 요소&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;역할&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;파싱`graph.json`— 3가지 형식 지원:네이티브 graphify (&amp;lt;code&amp;gt;nodes&amp;lt;/code&amp;gt;+&amp;lt;code&amp;gt;links&amp;lt;/code&amp;gt;), 유산 (&amp;quot;)&amp;lt;code&amp;gt;communities&amp;lt;/code&amp;gt;), 평평한. 숫자 ID를 레이블로 해결합니다.&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;결정론적으로 변환해`KnowledgeGraph`PlantUML 코드에서. 유형별 그룹, 패키지 내 커뮤니티, 자동 범례.&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;Gradle 작업이 오케스트레이션: parse → render → validate → PNG. 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;데이터 모델:`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;문법 검증 + PNG 렌더링 (플러그인의 모든 작업에서 재사용됨).&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;_렌더링_규칙&amp;quot;&amp;gt;렌더링 규칙&amp;lt;/h3&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;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;표기법 PlantUML&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;의미&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;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;(실선, 검정)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;추론된&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;(점선)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;LLM에 의해 추론된 관계 (신뢰 점수)&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;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;(빨간 점선)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;커뮤니티는 자동 색상 팔레트가 적용된 PlantUML 패키지처럼 렌더링됩니다.&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단계_일상_사용&amp;quot;&amp;gt;3단계: 일상 사용&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;_완전한_다이어그램&amp;quot;&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-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;출력 :`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;_커뮤니티별_필터&amp;quot;&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-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;_엣지_유형별로_필터링&amp;quot;&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-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;_노드_유형_및_신뢰도로_필터링&amp;quot;&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-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;_사용자_지정_출력_디렉터리&amp;quot;&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-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;_속성_전체_참조&amp;quot;&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;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;결함&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;설명&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;(모두)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;&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;(모두)&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;콤마로 구분된 모서리 유형:`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;엣지에 대한 최소 신뢰도 임계값&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;(무한한)&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;표시할 최대 노드 수&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;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;콤마로 구분된 노드 유형 (예`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;파일을 위한 출력 디렉터리`.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;_gradle_워크플로우의_전체_파이프라인&amp;quot;&amp;gt;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;_워크플로_유형&amp;quot;&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&amp;gt;@startuml
skinparam backgroundColor #FEFEFE
skinparam ActivityBackgroundColor #E8F4FD
skinparam ActivityDiamondBackgroundColor #FFF3CD
skinparam ActivityBorderColor #2C3E50

start

partition &amp;quot;추출&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;세대&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;통합&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;_개발_사이클에_통합&amp;quot;&amp;gt;개발 사이클에 통합&amp;lt;/h3&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&amp;gt;@startuml
skinparam backgroundColor #FEFEFE

state &amp;quot;개발&amp;quot; as dev
state &amp;quot;추출
graphify . --no-viz&amp;quot; as extract
state &amp;quot;생성
./gradlew generateKnowledgeGraphDiagram&amp;quot; as generate
state &amp;quot;커밋
버전 관리된 다이어그램&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;_증분_업데이트&amp;quot;&amp;gt;증분 업데이트&amp;lt;/h3&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-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`수정된 파일만 다시 추출합니다 (SHA256로 감지된`graphify-out/cache/&amp;lt;/code&amp;gt;). Kotlin 코드에서는 tree-sitter가 로컬에서 작동하여 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;_도그푸딩_플러그인은_스스로를_문서화한다&amp;quot;&amp;gt;도그푸딩: 플러그인은 스스로를 문서화한다.&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;PlantUML 플러그인은 프롬프트를 다이어그램으로 변환하기 위해 존재합니다. 또한 자체 &amp;lt;em&amp;gt;propre&amp;lt;/em&amp;gt; 코드베이스의 Knowledge Graph를 문서화 다이어그램으로 변환할 수도 있습니다. 이것은 _dogfooding_입니다: 플러그인은 자신의 서비스를 소비합니다.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;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;정상 파이프라인
(사용자 → 다이어그램)&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;파이프라인 지식 그래프
(결정적, 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 → 플러그인 문서)&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;LlmService, ApiKeyPool도 동일하고,
PlantumlService도 동일합니다
— 중복 없음&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;연관된 Gradle 작업 :&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;작업&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;설명&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;아니요&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;변환해`graph.json`PlantUML에서 (결정적)&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;예&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;생성해`.prompt`그래프에서부터, 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 class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;_왜_그것이_결정적이며_그리고_왜_그것이_중요한가&amp;quot;&amp;gt;왜 그것이 결정적이며 (그리고 왜 그것이 중요한가)&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;파이프라인의 핵심 지점`generateKnowledgeGraphDiagram`:&amp;lt;strong&amp;gt;그는 어떤 LLM도 부르지 않는다.&amp;lt;/strong&amp;gt;. 변환`graph.json`→ PlantUML은 순수 함수이다.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;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;결정적 파이프라인\n`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;파이프라인 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;구체적인 이점:&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;장점&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;영향&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;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;같은`graph.json`→ 같은 다이어그램입니다. 정확히. 매번.&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;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;LLM 호출 없음 = 토큰 없음 = 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;지연 없음&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;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은 약 100ms가 소요되며, 1-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;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;API 키가 필요하지 않습니다. 변동적인 LLM 응답으로 인한 불안정한 테스트가 없습니다.&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;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;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`생성된 텍스트는 텍스트입니다. 우리는 그것을`diff&amp;lt;/code&amp;gt;, PR의 리뷰어, 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;_파이프라인_자체_다이어그램&amp;quot;&amp;gt;파이프라인 자체 다이어그램&amp;lt;/h2&amp;gt;
&amp;lt;div 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;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;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;개발자&amp;quot; as Dev
participant &amp;quot;그레이들&amp;quot; as G
participant &amp;quot;지식 그래프 다이어그램 생성 태스크&amp;quot; as Task
participant &amp;quot;지식 그래프 파서&amp;quot; as Parser
participant &amp;quot;지식 그래프 렌더러&amp;quot; as Renderer
participant &amp;quot;PlantUML서비스&amp;quot; as PS
collections &amp;quot;graphify-out/graph.json&amp;quot; as JSON
collections &amp;quot;다이어그램/지식-그래프/&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;_프로젝트_거버넌스_내_통합&amp;quot;&amp;gt;프로젝트 거버넌스 내 통합&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;우리 프로젝트에서 이 파이프라인은 AI 에이전트를 위한 EAGER/LAZY 컨텍스트 관리 전략에 통합됩니다. Knowledge Graph는 수동 아키텍처 문서를 구조화되고 질의 가능하며 자동으로 업데이트되는 그래프로 대체합니다.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;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(항상 충전된)&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 — 전략
(요청 시)&amp;quot; as lazy_strat {
    [Méthodologies] as meth
    [Archives sessions] as sessions
}

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

package &amp;quot;Pipeline PlantUML\n(자동 생성된)&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;두 시스템의 결합은 보완적이다 :&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;전략은 언제와 어떻게를 관리합니다&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;Graphify는 무엇과 어디를 관리합니다.&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;em&amp;gt;__&amp;lt;/em&amp;gt; 세션 전략이 관리하는&amp;lt;strong&amp;gt;언제&amp;lt;/strong&amp;gt; et le &amp;lt;strong&amp;gt;어떻게&amp;lt;/strong&amp;gt;(거버넌스, 워크플로우, 임계값), Graphify가 관리하는&amp;lt;strong&amp;gt;뭐&amp;lt;/strong&amp;gt; et le &amp;lt;strong&amp;gt;어디&amp;lt;/strong&amp;gt;(코드 구조, 관계, 대상 쿼리). 이 PlantUML 파이프라인이 그것을 관리한다.&amp;lt;strong&amp;gt;무엇으로&amp;lt;/strong&amp;gt;(결정적, 버전 관리된, 항상 최신 상태인 다이어그램). &amp;amp;lt;/think&amp;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;_준비_5분_체크리스트&amp;quot;&amp;gt;준비 : 5분 체크리스트&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;단계&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;명령&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;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;제외 사항 구성&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;만들다`.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;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;다이어그램 생성&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;결과를 커밋하다&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;_함정_및_완화_방안&amp;quot;&amp;gt;함정 및 완화 방안&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;설명&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;완화&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;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;사용하다`-Pplantuml.kg.maxNodes=30`그리고 커뮤니티별로 필터링&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;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;안에 파일이 너무 많음`.graphifyignore`그래프의 값을 줄입니다&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;먼저 credentials와 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;화살표의 방향&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;INFERRED 엣지는 방향이 모호할 수 있다.&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;필터 기준`-Pplantuml.kg.edgeTypes=EXTRACTED`확실한 관계에만&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;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;사용하다`graphify . --update`정기적으로 또는 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;업데이트된 비용&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;증분 업데이트는 거의 무료입니다 (tree-sitter 로컬)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;.adoc&amp;lt;/code&amp;gt;) 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;_최종적으로_얻는_것&amp;quot;&amp;gt;최종적으로 얻는 것&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;이전&amp;quot; as avant {
    card &amp;quot;수작업으로 그린 다이어그램
항상 구식
아무도 업데이트하지 않는다&amp;quot; as av1 #FDEDEC
}

rectangle &amp;quot;후&amp;quot; as apres {
    card &amp;quot;자동 생성된 다이어그램\n항상 코드와 최신 상태\nGit에서 버전 관리됨\n결정적이고 재현 가능&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;구체적인 혜택 :&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;수익&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;세부&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;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;유지보수 노력이 제로&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;기술 부채 감소&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;새로운 것을 위한 시각적 컨텍스트&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;파인튜닝 잠재력&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;서브그래프 → 다이어그램 쌍은 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;자동 검증&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;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()`생성된 각 다이어그램을 확인합니다&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;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;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;/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;_ _pourquoi_를 가진 사람은 모든 _comment_을 견딜 수 있다. &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;&amp;lt;em&amp;gt;왜&amp;lt;/em&amp;gt; : 항상 최신인 다이어그램. &amp;lt;em&amp;gt;어떻게&amp;lt;/em&amp;gt; : 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;_링크&amp;quot;&amp;gt;링크&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;GitHub의 Graphify&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 플러그인&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;fg 명령의 슈퍼 팁&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>Gradle 워크플로우에 Graphify 통합하기 : Knowledge Graph에서 PlantUML 다이어그램까지 한 명령으로</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;#_문제_다이어그램이_언제나_코드보다_뒤처진다&amp;quot;&amp;gt;문제: 다이어그램이 언제나 코드보다 뒤처진다&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_솔루션_결정적_knowledge_graph_plantuml_파이프라인&amp;quot;&amp;gt;솔루션 : 결정적 Knowledge Graph → PlantUML 파이프라인&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_단계_1_graphify_설치_및_knowledge_graph_추출&amp;quot;&amp;gt;단계 1: Graphify 설치 및 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;#_설치&amp;quot;&amp;gt;설치&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_제외_사항_구성&amp;quot;&amp;gt;제외 사항 구성&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_지식_그래프_추출&amp;quot;&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;#_2단계_gradle_플러그인이_knowledge_graph를_plantuml로_변환합니다&amp;quot;&amp;gt;2단계: Gradle 플러그인이 Knowledge Graph를 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;#_파이프라인_아키텍처&amp;quot;&amp;gt;파이프라인 아키텍처&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_내부_구성_요소&amp;quot;&amp;gt;내부 구성 요소&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_렌더링_규칙&amp;quot;&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;#_단계_3_일상_사용&amp;quot;&amp;gt;단계 3: 일상 사용&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;#_완전한_다이어그램&amp;quot;&amp;gt;완전한 다이어그램&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_커뮤니티별_필터&amp;quot;&amp;gt;커뮤니티별 필터&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_엣지_유형별로_필터&amp;quot;&amp;gt;엣지 유형별로 필터&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_노드_유형과_신뢰도로_필터링&amp;quot;&amp;gt;노드 유형과 신뢰도로 필터링&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_사용자_지정_출력_디렉터리&amp;quot;&amp;gt;사용자 지정 출력 디렉터리&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_속성에_대한_완전한_참조&amp;quot;&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;#_gradle_워크플로우의_전체_파이프라인&amp;quot;&amp;gt;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;#_워크플로_유형&amp;quot;&amp;gt;워크플로 유형&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_개발_주기_통합&amp;quot;&amp;gt;개발 주기 통합&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_증분_업데이트&amp;quot;&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;#_도그푸딩_플러그인이_스스로_문서화한다&amp;quot;&amp;gt;도그푸딩: 플러그인이 스스로 문서화한다&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_왜_그것이_결정적인가_그리고_왜_중요한가&amp;quot;&amp;gt;왜 그것이 결정적인가 (그리고 왜 중요한가)&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_파이프라인_자체의_다이어그램&amp;quot;&amp;gt;파이프라인 자체의 다이어그램&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_프로젝트_거버넌스_내_통합&amp;quot;&amp;gt;프로젝트 거버넌스 내 통합&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_준비_5분_체크리스트&amp;quot;&amp;gt;준비: 5분 체크리스트&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_함정과_완화&amp;quot;&amp;gt;함정과 완화&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_결과적으로_얻는_것&amp;quot;&amp;gt;결과적으로 얻는 것&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_링크&amp;quot;&amp;gt;링크&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;읽기 시간 : 12 minutes&amp;lt;/div&amp;gt;
&amp;lt;p&amp;gt;코드베이스가 커지고 있습니다. 모듈 간의 의존성이 점점 늘어나고 있습니다. 아키텍처 문서는 작성되기도 전에 이미 오래됩니다. 그리고 Gradle 빌드가 &amp;lt;em&amp;gt;자동으로&amp;lt;/em&amp;gt; 실제 코드 구조에서 최신 다이어그램을 생성할 수 있다면? 이게 바로 Graphify + PlantUML Gradle Plugin 파이프라인이 하는 일입니다:`graphify . --no-viz`Knowledge Graph를 추출,`./gradlew generateKnowledgeGraphDiagram`PlantUML 다이어그램으로 변환합니다. 제로 LLM, 제로 매뉴얼, 100% 결정적.&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;/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;_문제_다이어그램이_언제나_코드보다_뒤처진다&amp;quot;&amp;gt;문제: 다이어그램이 언제나 코드보다 뒤처진다&amp;lt;/h2&amp;gt;
&amp;lt;div 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;/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;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;코드가 진화하고, 의존성이 변경됩니다.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;다이어그램은 장식적인 거짓말이 된다&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;아무도 업데이트하지 않아요, 왜냐하면 그게 귀찮기 때문이에요&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&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;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;질문은 &amp;lt;em&amp;gt;faut-il des diagrammes ?&amp;lt;/em&amp;gt; 가 아니다 — 모두가 그렇다고 알고 있다. 질문은 :&amp;lt;strong&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;답변: 아무도 없습니다. 단, 자동인 경우에는 예외입니다.&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;_솔루션_결정적_knowledge_graph_plantuml_파이프라인&amp;quot;&amp;gt;솔루션 : 결정적 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;원칙은 간단합니다 : 손으로 다이어그램을 그리는 대신, 우리는 그들을&amp;lt;strong&amp;gt;실제 코드 구조에서 생성한다&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
(pip install graphifyy)&amp;quot; as Graphify
collections &amp;quot;graphify-out/graph.json
(지식 그래프)&amp;quot; as KGJSON
component &amp;quot;PlantUML Gradle 플러그인
(지식 그래프 다이어그램 생성)&amp;quot; as Plugin
component &amp;quot;KnowledgeGraphParser&amp;quot; as Parser
component &amp;quot;지식 그래프 렌더러&amp;quot; as Renderer
component &amp;quot;PlantumlService
(검증 + PNG 렌더링)&amp;quot; as PS
collections &amp;quot;diagrams/knowledge-graph/\n(.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;두 개의 명령어. 그게 전부야.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;결과 ? 파일들`.puml` et &amp;lt;code&amp;gt;.png`안에`diagrams/knowledge-graph/&amp;lt;/code&amp;gt;, Git에서 버전 관리되어 코드와 항상 최신 상태를 유지합니다.&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_graphify_설치_및_knowledge_graph_추출&amp;quot;&amp;gt;단계 1: Graphify 설치 및 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;_설치&amp;quot;&amp;gt;설치&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Graphify는 코드베이스를 분석하고 구조화된 지식 그래프를 구축하는 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-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;_제외_사항_구성&amp;quot;&amp;gt;제외 사항 구성&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;파일 만들기`.graphifyignore`프로젝트 루트에서 비즈니스 로직에 속하지 않는 파일을 제외하기 위해:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;_지식_그래프_추출&amp;quot;&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-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;플래그`--no-viz`HTML 생성을 건너뛰세요 (Gradle 파이프라인에서는 불필요함). 결과는 파일입니다.`graphify-out/graph.json`포함 :&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;매듭&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;가시&amp;lt;/strong&amp;gt;: 노드 간 관계 (EXTRACTED 코드에서, INFERRED 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;커뮤니티&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;title&amp;quot;&amp;gt;구조 예시`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;소스 코드 (&amp;lt;code&amp;gt;.kt&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;.java&amp;lt;/code&amp;gt;) 로컬에서 tree-sitter에 의해 분석된다,&amp;lt;strong&amp;gt;이의를 제기할 수 없는 LLM&amp;lt;/strong&amp;gt;. 문서 파일들만 (&amp;lt;code&amp;gt;.adoc&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;.md&amp;lt;/code&amp;gt;) 그들이 의미 추출을 위해 LLM 호출이 필요합니다. 따라서`--update`Kotlin 코드 실행은 거의 즉시입니다.&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;_2단계_gradle_플러그인이_knowledge_graph를_plantuml로_변환합니다&amp;quot;&amp;gt;2단계: Gradle 플러그인이 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;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_파이프라인_아키텍처&amp;quot;&amp;gt;파이프라인 아키텍처&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;플러그인`com.cheroliv.plantuml`작업을 포함합니다`generateKnowledgeGraphDiagram`변환하는`graph.json`PlantUML 다이어그램 방식으로&amp;lt;strong&amp;gt;완전히 결정론적인&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;생성 지식 그래프 다이어그램 작업&amp;quot; as Task
participant &amp;quot;KnowledgeGraphParser&amp;quot; as Parser
participant &amp;quot;지식 그래프 렌더러&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;_내부_구성_요소&amp;quot;&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;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;컴포넌트&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;역할&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;파싱하다`graph.json`— 3가지 형식을 지원합니다 : 네이티브 graphify (&amp;lt;code&amp;gt;nodes&amp;lt;/code&amp;gt;+&amp;lt;code&amp;gt;links&amp;lt;/code&amp;gt;), 레거시 (&amp;lt;code&amp;gt;communities&amp;lt;/code&amp;gt;), flat. 숫자 ID를 라벨로 해결합니다.&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;결정적으로 변환한다`KnowledgeGraph`PlantUML 코드. 유형별 그룹, 패키지 내 커뮤니티, 자동 범례.&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;Gradle 작업: 파싱 → 렌더링 → 검증 → PNG를 오케스트레이션합니다. 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;데이터 모델:`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;구문 검증 + PNG 렌더링 (플러그인의 모든 작업에서 재사용됨).&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;_렌더링_규칙&amp;quot;&amp;gt;렌더링 규칙&amp;lt;/h3&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;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;PlantUML 표기법&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;의미&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;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;(실선, 검정)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;추론된&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;(점선)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;LLM이 추론한 관계 (신뢰도 점수)&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;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;(빨간 점선)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;커뮤니티는 자동 색상 팔레트를 가진 PlantUML 패키지처럼 렌더링됩니다.&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_일상_사용&amp;quot;&amp;gt;단계 3: 일상 사용&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;_완전한_다이어그램&amp;quot;&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-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;출력 :`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;_커뮤니티별_필터&amp;quot;&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-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;_엣지_유형별로_필터&amp;quot;&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-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;_노드_유형과_신뢰도로_필터링&amp;quot;&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-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;_사용자_지정_출력_디렉터리&amp;quot;&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-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;_속성에_대한_완전한_참조&amp;quot;&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;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;결함&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;설명&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;(모두)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;&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;(모든)&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;쉼표로 구분된 엣지 유형 :`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;엣지에 대한 최소 신뢰도 임계값&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;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;&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;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;콤마로 구분된 노드 유형 (ex.&amp;lt;code&amp;gt;class&amp;lt;/code&amp;gt;, &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;파일용 출력 디렉터리`.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;_gradle_워크플로우의_전체_파이프라인&amp;quot;&amp;gt;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;_워크플로_유형&amp;quot;&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&amp;gt;@startuml
skinparam backgroundColor #FEFEFE
skinparam ActivityBackgroundColor #E8F4FD
skinparam ActivityDiamondBackgroundColor #FFF3CD
skinparam ActivityBorderColor #2C3E50

start

partition &amp;quot;추출&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;세대&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;통합&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;_개발_주기_통합&amp;quot;&amp;gt;개발 주기 통합&amp;lt;/h3&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&amp;gt;@startuml
skinparam backgroundColor #FEFEFE

state &amp;quot;개발&amp;quot; as dev
state &amp;quot;Extraction\ngraphify . --no-viz&amp;quot; as extract
state &amp;quot;생성
./gradlew generateKnowledgeGraphDiagram&amp;quot; as generate
state &amp;quot;커밋
버전 관리된 다이어그램&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;_증분_업데이트&amp;quot;&amp;gt;증분 업데이트&amp;lt;/h3&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-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`수정된 파일만 다시 추출 (SHA256로 감지된`graphify-out/cache/&amp;lt;/code&amp;gt;). Kotlin 코드에서는 tree-sitter가 로컬에서 작동하고 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;_도그푸딩_플러그인이_스스로_문서화한다&amp;quot;&amp;gt;도그푸딩: 플러그인이 스스로 문서화한다&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;PlantUML 플러그인은 프롬프트를 다이어그램으로 변환하기 위해 존재합니다. 또한 자체 &amp;lt;em&amp;gt;propre&amp;lt;/em&amp;gt; 코드베이스의 Knowledge Graph를 문서 다이어그램으로 변환할 수 있습니다. 이것은 _dogfooding_입니다: 플러그인은 자체 서비스를 소비합니다.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;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;정상 파이프라인
(사용자 → 다이어그램)&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;파이프라인 지식 그래프
(결정론적, 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 → 플러그인 문서)&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;같은 LlmService, 같은 ApiKeyPool,
같은 PlantumlService
 — 중복 없음&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;연관된 Gradle 작업:&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;작업&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;설명&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;아니요&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;변환해`graph.json`PlantUML (확정적)에서&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;예&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;생성해`.prompt`그래프에서부터, 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 class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;_왜_그것이_결정적인가_그리고_왜_중요한가&amp;quot;&amp;gt;왜 그것이 결정적인가 (그리고 왜 중요한가)&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;파이프라인의 핵심 포인트`generateKnowledgeGraphDiagram` : &amp;lt;strong&amp;gt;그는 어떤 LLM도 부르지 않는다.&amp;lt;/strong&amp;gt;. 변환`graph.json`PlantUML은 순수 함수이다.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;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;결정적 파이프라인
(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;파이프라인 LLM\n(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;구체적인 장점 :&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;장점&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;영향&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;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;같은`graph.json`→ 동일한 다이어그램. 정확히. 매번.&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;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;LLM 호출이 없으면 토큰도 없고 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;제로 지연&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;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는 약 100ms가 소요되며, 1-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;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;API 키가 필요하지 않습니다. 변동하는 LLM 응답으로 인한 불안정한 테스트가 없습니다.&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;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;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`생성된 것은 텍스트입니다. 그것을 할 수 있습니다.`diff&amp;lt;/code&amp;gt;, PR의 리뷰어, 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;_파이프라인_자체의_다이어그램&amp;quot;&amp;gt;파이프라인 자체의 다이어그램&amp;lt;/h2&amp;gt;
&amp;lt;div 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;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;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;개발자&amp;quot; as Dev
participant &amp;quot;그래들&amp;quot; as G
participant &amp;quot;지식 그래프 다이어그램 생성 태스크&amp;quot; as Task
participant &amp;quot;KnowledgeGraphParser&amp;quot; as Parser
participant &amp;quot;KnowledgeGraphRenderer&amp;quot; as Renderer
participant &amp;quot;PlantUML 서비스&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;_프로젝트_거버넌스_내_통합&amp;quot;&amp;gt;프로젝트 거버넌스 내 통합&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;우리 프로젝트에서 이 파이프라인은 AI 에이전트를 위한 EAGER/LAZY 컨텍스트 관리 전략에 통합됩니다. Knowledge Graph는 수동 아키텍처 문서를 구조화되고 쿼리 가능하며 자동으로 업데이트되는 그래프로 대체합니다.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;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
(항상 충전된)&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 — 전략\n(요청 시)&amp;quot; as lazy_strat {
    [Méthodologies] as meth
    [Archives sessions] as sessions
}

package &amp;quot;LAZY — Graphify
(목표된 쿼리)&amp;quot; as lazy_graph {
    [graph.json] as gj
    [Queries\n(query/path/explain)] as queries
}

package &amp;quot;Pipeline PlantUML
(자동 생성된)&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;두 시스템의 결합은 보완적입니다 :&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;전략은 QUAND와 COMMENT를 관리합니다&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;Graphify는 WHAT과 WHERE를 관리합니다.&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;em&amp;gt;__&amp;lt;/em&amp;gt; 세션 전략은 를 관리합니다.&amp;lt;strong&amp;gt;언제&amp;lt;/strong&amp;gt; et le &amp;lt;strong&amp;gt;어떻게&amp;lt;/strong&amp;gt;(거버넌스, 워크플로우, 임계치), Graphify는それを 관리한다&amp;lt;strong&amp;gt;뭐&amp;lt;/strong&amp;gt; et le &amp;lt;strong&amp;gt;어디&amp;lt;/strong&amp;gt;(코드 구조, 관계, 대상 쿼리). PlantUML 파이프라인이 관리&amp;lt;strong&amp;gt;무엇으로&amp;lt;/strong&amp;gt;(결정론적 다이어그램, 버전 관리된, 항상 최신). &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;_준비_5분_체크리스트&amp;quot;&amp;gt;준비: 5분 체크리스트&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;단계&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;주문&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;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;제외 항목 구성&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;생성하다`.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;지식 그래프 추출&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;다이어그램 생성&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;결과를 커밋하다&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;_함정과_완화&amp;quot;&amp;gt;함정과 완화&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;설명&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;완화&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;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;사용하다`-Pplantuml.kg.maxNodes=30`그리고 커뮤니티별로 필터링&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;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;너무 많은 파일이`.graphifyignore`그래프의 값을 줄입니다&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;먼저 credentials와 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;화살 방향&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;INFERRED 엣지들은 방향이 모호할 수 있습니다.&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;필터링 기준`-Pplantuml.kg.edgeTypes=EXTRACTED`확실한 관계에 대해서만&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;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;사용하다`graphify . --update`정기적으로 또는 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;업데이트된 비용&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;증분 업데이트는 거의 무료입니다 (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;문서만 (&amp;lt;code&amp;gt;.adoc&amp;lt;/code&amp;gt;) 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;_결과적으로_얻는_것&amp;quot;&amp;gt;결과적으로 얻는 것&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;전에&amp;quot; as avant {
    card &amp;quot;수작업으로 그린 다이어그램
항상 구식
아무도 업데이트하지 않는다&amp;quot; as av1 #FDEDEC
}

rectangle &amp;quot;이후&amp;quot; as apres {
    card &amp;quot;자동 생성된 다이어그램
항상 코드와 최신 상태
Git에서 버전 관리됨
결정적이고 재현 가능&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;실질적인 혜택 :&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;이익&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;세부&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;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;유지 보수 노력 없음&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;기술 부채 감소&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;신규를 위한 시각적 맥락&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;가능한 파인 튜닝&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;(서브그래프 → 다이어그램) 쌍은 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;자동 검증&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;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()`생성된 각 다이어그램을 확인합니다&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;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;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;/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;_ 누가 _pourquoi_를 가지고 있으면 모든 _comment_을 감당할 수 있다. &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;왜냐하면 (&amp;lt;em&amp;gt;pourquoi&amp;lt;/em&amp;gt;): 항상 최신 상태인 다이어그램. 어떻게 (&amp;lt;em&amp;gt;comment&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;_링크&amp;quot;&amp;gt;링크&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;GitHub에서 Graphify&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 플러그인&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;명령어 fg의 슈퍼 팁&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>fg 명령어의 슈퍼 팁 : Ctrl+Z가 터미널 세션을 저장할 때</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;#_문제_바쁜_터미널&amp;quot;&amp;gt;문제 : 바쁜 터미널&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_세_가지_필수_명령어&amp;quot;&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;#_ctrlz_일시_중지_sigtstp&amp;quot;&amp;gt;Ctrl+Z — 일시 중지 (SIGTSTP)&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_fg_앞으로_가져오기&amp;quot;&amp;gt;fg — 앞으로 가져오기&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_bg_백그라운드에서_재개&amp;quot;&amp;gt;bg — 백그라운드에서 재개&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_jobs_셸의_작업_목록_보기&amp;quot;&amp;gt;jobs — 셸의 작업 목록 보기&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;#_시나리오_1_긴_빌드&amp;quot;&amp;gt;시나리오 1 : 긴 빌드&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;#_한_걸음씩&amp;quot;&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;#_시나리오_2_잊어버린_편집자&amp;quot;&amp;gt;시나리오 2 : 잊어버린 편집자&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_시나리오_3_opencode_프로세스가_일시_중지됨&amp;quot;&amp;gt;시나리오 3: opencode 프로세스가 일시 중지됨&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_시나리오_4_여러_작업을_동시에_관리하기&amp;quot;&amp;gt;시나리오 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;#_작업_명령어_요약&amp;quot;&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;#_fg_bg_ctrlz_ctrlc_및_의_차이&amp;quot;&amp;gt;fg, bg, Ctrl+Z, Ctrl+C 및 &amp;amp;amp;의 차이&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_작업의_전체_수명_주기&amp;quot;&amp;gt;작업의 전체 수명 주기&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_고급_팁&amp;quot;&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;#_fg와_가장_최근의_직업_및_교대&amp;quot;&amp;gt;fg와 가장 최근의 직업 및 교대&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_작업을_깔끔하게_종료하다&amp;quot;&amp;gt;작업을 깔끔하게 종료하다&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_disown_쉘에서_분리&amp;quot;&amp;gt;disown : 쉘에서 분리&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_nohup과_결합하여_긴_프로세스를_실행&amp;quot;&amp;gt;nohup과 결합하여 긴 프로세스를 실행&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;#_함정과_일반적인_오류&amp;quot;&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;#_ctrlz와_ctrlc를_혼동하다&amp;quot;&amp;gt;Ctrl+Z와 Ctrl+C를 혼동하다&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_작업은_셸에_국한됩니다&amp;quot;&amp;gt;작업은 셸에 국한됩니다.&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_작업은_셸이_닫히면_생존하지_못합니다&amp;quot;&amp;gt;작업은 셸이 닫히면 생존하지 못합니다.&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_대화형_프로세스와_fg&amp;quot;&amp;gt;대화형 프로세스와 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;#_요약_다이어그램&amp;quot;&amp;gt;요약 다이어그램&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_왜_fg가_과소평가되는가&amp;quot;&amp;gt;왜 fg가 과소평가되는가?&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_기술_메모리_신호&amp;quot;&amp;gt;기술 메모리 : 신호&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_결론&amp;quot;&amp;gt;결론&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_참고문헌&amp;quot;&amp;gt;참고문헌&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;읽기 시간 : 10 minutes&amp;lt;/div&amp;gt;
&amp;lt;p&amp;gt;당신은 던집니다`./gradlew build`, 터미널이 3분 동안 차단되며, 다른 일을 해야 &amp;lt;em&amp;gt;대기 중&amp;lt;/em&amp;gt; 인가요? 새 탭을 열 필요가 없습니다.`Ctrl+Z`프로세스를 일시 중지하고,`fg`그곳으로 정확히 데려다 놓습니다. 이는 개발자의 일상에서 가장 과소평가되는 유닉스 팁입니다.&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;/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;_문제_바쁜_터미널&amp;quot;&amp;gt;문제 : 바쁜 터미널&amp;lt;/h2&amp;gt;
&amp;lt;div 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;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;터미널이 잠겼습니다. 더 이상 명령을 입력할 수 없습니다. 파일을 확인하고 실행해야 할`git 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&amp;gt;@startuml
skinparam backgroundColor #FEFEFE

actor Développeur as dev
participant &amp;quot;터미널&amp;quot; as term
participant &amp;quot;Gradle
(프로세스)&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;대부분의 개발자들은 새로운 터미널을 엽니다. 하지만 더 우아하고, 더 빠르고, 네이티브한 해결책이 있습니다: 작업 관리&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;_세_가지_필수_명령어&amp;quot;&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;_ctrlz_일시_중지_sigtstp&amp;quot;&amp;gt;Ctrl+Z — 일시 중지 (SIGTSTP)&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;`Ctrl+Z`신호를 보냅니다`SIGTSTP`포그라운드 프로세스에. 프로세스는&amp;lt;strong&amp;gt;중단된&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;$ ./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;대괄호 안의 숫자`[1]`(two spaces)&amp;lt;strong&amp;gt;잡 번호&amp;lt;/strong&amp;gt;. Le `+`기본 작업을 나타냅니다.&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_앞으로_가져오기&amp;quot;&amp;gt;fg — 앞으로 가져오기&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;`fg`일시 정지된 작업을 포그라운드로 가져옵니다. 프로세스는 정확히 중지된 지점에서 다시 시작됩니다.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;특정 작업 번호:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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_백그라운드에서_재개&amp;quot;&amp;gt;bg — 백그라운드에서 재개&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;`bg`프로세스를 &amp;lt;em&amp;gt;sans&amp;lt;/em&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;$ 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;`마지막에 그것은 프로세스가 백그라운드에서 실행 중임을 의미합니다.&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_셸의_작업_목록_보기&amp;quot;&amp;gt;jobs — 셸의 작업 목록 보기&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;전경\n(차단된 터미널)&amp;quot; as fg #LightCoral
state &amp;quot;일시 중지됨\n(Ctrl+Z)&amp;quot; as stopped #LightYellow
state &amp;quot;배경\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;_시나리오_1_긴_빌드&amp;quot;&amp;gt;시나리오 1 : 긴 빌드&amp;lt;/h2&amp;gt;
&amp;lt;div 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 빌드를 실행하면 터미널이 막히고 다른 작업을 해야 합니다.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;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;터미널&amp;quot; as term
participant &amp;quot;Gradle&amp;quot; as gradle
database &amp;quot;상태\n프로세스&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;_한_걸음씩&amp;quot;&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-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;_시나리오_2_잊어버린_편집자&amp;quot;&amp;gt;시나리오 2 : 잊어버린 편집자&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;당신은 안에 있습니다`vim`, 다른 파일을 &amp;lt;em&amp;gt;sans&amp;lt;/em&amp;gt; 떠나지 않고 확인해야 합니다`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;하지만 그것은 무겁다. 그리고`Ctrl+Z`, 즉시적입니다 :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;vim&amp;quot; as vim
participant &amp;quot;껍질&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;참고:`vim`완벽하게 그 상태를 복구한 후`fg`: 커서 위치, 버퍼 내용, 실행 취소 기록 — 전부 보존됩니다.&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_opencode_프로세스가_일시_중지됨&amp;quot;&amp;gt;시나리오 3: opencode 프로세스가 일시 중지됨&amp;lt;/h2&amp;gt;
&amp;lt;div 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;opencode&amp;lt;/code&amp;gt;(코드에서 LLM을 실행하기 위한 CLI 툴), 그리고 프로세스가 중지됩니다.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;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;opencode
(LLM 에이전트)&amp;quot; as agent
participant &amp;quot;껍질&amp;quot; as shell
participant &amp;quot;깃&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;장점은 두 배입니다:&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;컨텍스트 손실 없음&amp;lt;/strong&amp;gt;: 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;재시작 없음&amp;lt;/strong&amp;gt;opencode를 재시작할 필요 없고, 컨텍스트를 재로드할 필요 없고, 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;_시나리오_4_여러_작업을_동시에_관리하기&amp;quot;&amp;gt;시나리오 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;&amp;lt;code&amp;gt;fg&amp;lt;/code&amp;gt; et `bg`여러 개가 일시 중지된 상태에서 특정 프로세스를 대상으로 작업 번호를 받아들입니다.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;중지된 작업&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;행동&amp;quot; as actions #LightGreen {
  card &amp;quot;fg %2
→ 전경&amp;quot; as a1
  card &amp;quot;bg %3\n→ 배경&amp;quot; as a2
  card &amp;quot;fg %1
→ 전경&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;_작업_명령어_요약&amp;quot;&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;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;주문&amp;lt;/th&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;예시&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;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;전송`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;기본 작업을 포그라운드에서 재개합니다.&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;작업 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;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;백그라운드에서 기본 작업 재개&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;백그라운드에서 작업 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;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;현재 쉘의 작업 목록을 나열&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;(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;작업 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;_fg_bg_ctrlz_ctrlc_및_의_차이&amp;quot;&amp;gt;fg, bg, Ctrl+Z, Ctrl+C 및 &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;많은 개발자들이 이러한 메커니즘을 혼동합니다. 이것이 결정적인 명확화입니다.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;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;프로세스를 일시 중지
(메모리에 여전히 존재)
fg/bg와 함께 사용 가능&amp;quot; as c1
}

rectangle &amp;quot;**Ctrl+C**\nSIGINT&amp;quot; as ctrlc #LightCoral {
  card &amp;quot;프로세스를 죽여
(돌아갈 수 없습니다)
상태 전체 손실&amp;quot; as c2
}

rectangle &amp;quot;**&amp;amp;amp; **\n(배경)&amp;quot; as ampersand #LightGreen {
  card &amp;quot;직접 실행
백그라운드에서
터미널을 즉시 자유롭게&amp;quot; as c3
}

rectangle &amp;quot;**fg**
(전경)&amp;quot; as fg_ #SkyBlue {
  card &amp;quot;일시 정지된 작업을 전경으로 가져와
터미널이 사용 중&amp;quot; as c4
}

rectangle &amp;quot;**bg**\n(배경)&amp;quot; as bg_ #Lavender {
  card &amp;quot;작업을 재개합니다\n백그라운드에서 일시 중지된\n터미널 사용 가능&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;단축키/명령&amp;lt;/th&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;효과&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;뒤로 가시겠어요?&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;프로세스를 일시 중지 (메모리 내에 고정된, 동결된)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;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` 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;프로세스를 죽이세요 (완료됨)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;&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;프로세스를 죽이고 + 코어 덤프&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;&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;백그라운드에서 바로 실행&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;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`앞으로 가져오기 위해&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`이다&amp;lt;strong&amp;gt;비파괴적&amp;lt;/strong&amp;gt;. 프로세스는 그대로 메모리에서 정지됩니다. 당신은 즉시 이어서 할 수 있습니다.`fg&amp;lt;/code&amp;gt;. 이것은 _pause_일 뿐, _stop_이 아닙니다.
&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;_작업의_전체_수명_주기&amp;quot;&amp;gt;작업의 전체 수명 주기&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 셸 작업의 전체 생명 주기
state &amp;quot;존재하지 않는&amp;quot; as none
state &amp;quot;전경
(터미널 점유 중)&amp;quot; as foreground #LightCoral
state &amp;quot;일시 정지됨
(Ctrl+Z / SIGTSTP)&amp;quot; as suspended #LightYellow
state &amp;quot;배경\n(bg ou &amp;amp;amp;)&amp;quot; as background #LightGreen
state &amp;quot;완료
(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;_고급_팁&amp;quot;&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;_fg와_가장_최근의_직업_및_교대&amp;quot;&amp;gt;fg와 가장 최근의 직업 및 교대&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`인수 없이 표시된 작업을 되돌린다.&amp;lt;/code&amp;gt;+&amp;lt;code&amp;gt;(가장 최신). 하지만 또한 사용할 수 있습니다&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;$ 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;_작업을_깔끔하게_종료하다&amp;quot;&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-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_쉘에서_분리&amp;quot;&amp;gt;disown : 쉘에서 분리&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`쉘의 작업 테이블에서 작업을 제거합니다. 프로세스는 계속 실행되지만 더 이상 불러올 수 없습니다.`fg&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;$ ./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;_nohup과_결합하여_긴_프로세스를_실행&amp;quot;&amp;gt;nohup과 결합하여 긴 프로세스를 실행&amp;lt;/h3&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-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;일을 가져와라\n가장 최근의 (+)&amp;quot; as f1
  card &amp;quot;특정 직업 대상
특정 : fg %n&amp;quot; as f2
  card &amp;quot;교대로 : fg %-&amp;quot; as f3
}

rectangle &amp;quot;kill %n&amp;quot; as kill_ #LightCoral {
  card &amp;quot;SIGTERM (정상)&amp;quot; as k1
  card &amp;quot;SIGKILL -9 (강제)&amp;quot; as k2
}

rectangle &amp;quot;거부하다&amp;quot; as disown #Lavender {
  card &amp;quot;쉘에서 분리
(연결 해제 후에도 살아남음)&amp;quot; as d1
}

rectangle &amp;quot;nohup + &amp;amp;amp;&amp;quot; as nohup_ #LightGreen {
  card &amp;quot;면역된 프로세스
SIGHUP에 대한
(터미널 종료 시 살아남음
터미널)&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;_함정과_일반적인_오류&amp;quot;&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;_ctrlz와_ctrlc를_혼동하다&amp;quot;&amp;gt;Ctrl+Z와 Ctrl+C를 혼동하다&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;가장 흔한 실수입니다.`Ctrl+C`프로세스를 죽입니다.`Ctrl+Z`그를 중단합니다. 만약 당신이 누르면`Ctrl+C`반사적으로, 전체 상태가 손실됩니다 — 열린 파일, LLM 세션, 진행 중인 빌드.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;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;메모리에서 고정된 프로세스&amp;quot; as z1
  card &amp;quot;fg 그를 무사히 데려온다&amp;quot; as z2
  card &amp;quot;손실 없음&amp;quot; as z3
}

rectangle &amp;quot;Ctrl+C (SIGINT)&amp;quot; as c #LightCoral {
  card &amp;quot;프로세스 완료&amp;quot; as c1
  card &amp;quot;영구적으로 손실된 상태&amp;quot; as c2
  card &amp;quot;돌이킬 수 없습니다&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;_작업은_셸에_국한됩니다&amp;quot;&amp;gt;작업은 셸에 국한됩니다.&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`다른 터미널을 열면 다른 터미널의 작업을 볼 수 없습니다.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;시스템 전체 프로세스를 보려면`ps` ou &amp;lt;code&amp;gt;htop&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;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;_작업은_셸이_닫히면_생존하지_못합니다&amp;quot;&amp;gt;작업은 셸이 닫히면 생존하지 못합니다.&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;터미널을 닫으면 일시 중지된 작업 또는 백그라운드 작업이 모두 삭제됩니다. 프로세스가 살아남으려면 사용`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;_대화형_프로세스와_fg&amp;quot;&amp;gt;대화형 프로세스와 fg&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;일부 대화형 프로그램은 한 이후에 잘 작동하지 않는다`Ctrl+Z`+&amp;lt;code&amp;gt;fg&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;신호를 사용하는 일부 프로그램(vim, htop, less)은 잘 처리합니다.&amp;lt;code&amp;gt;Ctrl+Z&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;장시간 실행되는 네트워크 프로그램(ssh, opencode)은 일반적으로 정상적으로 복구됩니다.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;터미널을 수정하는 프로그램(ncurses)은 때때로 터미널을 예기치 않은 상태로 남겨둘 수 있다&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;터미널이 손상된 후`fg`, 입력 :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;_요약_다이어그램&amp;quot;&amp;gt;요약 다이어그램&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 — 모든 것을 바꾸는 팁&amp;quot;

rectangle &amp;quot;문제&amp;quot; as problem #LightCoral {
  card &amp;quot;터미널 차단됨\n프로세스에 의해&amp;quot; as p1
  card &amp;quot;새로운\n터미널 열기 = 무거운 해결책&amp;quot; as p2
}

rectangle &amp;quot;**해결책**&amp;quot; as solution #LightGreen {
  card &amp;quot;Ctrl+Z → 일시 정지&amp;quot; as s1
  card &amp;quot;fg → 다시 이어간다&amp;quot; as s2
  card &amp;quot;배경에서&amp;quot; as s3
  card &amp;quot;jobs → 정지된 것 보기&amp;quot; as s4
}

rectangle &amp;quot;장점&amp;quot; as advantages #SkyBlue {
  card &amp;quot;맥락 손실 없음&amp;quot; as a1
  card &amp;quot;재시작하지 마세요&amp;quot; as a2
  card &amp;quot;터미널 하나면 충분합니다.&amp;quot; as a3
}

problem -right-&amp;amp;gt; solution : &amp;quot;네이티브 솔루션
40년 된 유닉스&amp;quot;
solution -right-&amp;amp;gt; advantages : &amp;quot;결과&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;_왜_fg가_과소평가되는가&amp;quot;&amp;gt;왜 fg가 과소평가되는가?&amp;lt;/h2&amp;gt;
&amp;lt;div 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`늦거나, 전혀 이루어지지 않을 수도 있습니다. 몇 가지 이유 :&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는 터미널을 숨깁니다.&amp;lt;/strong&amp;gt;: VS Code는 내장 터미널을 제공하지만, 여러 탭이`fg`겉으로는 덜 필요함&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;그래픽 도구가 CLI를 대체합니다&amp;lt;/strong&amp;gt;: 파일 관리자, 시스템 모니터, 그래픽 Git 클라이언트&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와 스크린&amp;lt;/strong&amp;gt;: 이 멀티플렉서는 같은 문제를 다르게 해결하지만`fg`더 간단하고 항상 사용 가능합니다&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;그런데,`fg`는 :&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;보편적인&amp;lt;/strong&amp;gt;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;의존 없이&amp;lt;/strong&amp;gt;tmux 또는 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;스냅샷&amp;lt;/strong&amp;gt;: 두 번의 타격 (&amp;lt;code&amp;gt;Ctrl+Z`그리고`fg&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;컨텍스트 보존자&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;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 why #LightCoral {
  card &amp;quot;IDE와 터미널
다중 탭&amp;quot; as w1
  card &amp;quot;그래픽 도구\nCLI를 대체하는&amp;quot; as w2
  card &amp;quot;tmux/screen
문제를 해결합니다
다른 방식으로&amp;quot; as w3
}

rectangle &amp;quot;왜 fg 이다\n더 우월한 ?&amp;quot; as superior #LightGreen {
  card &amp;quot;범용: bash,
zsh, fish, sh...&amp;quot; as s1
  card &amp;quot;의존성 없음:
설치 필요 없음&amp;quot; as s2
  card &amp;quot;두 번의 타격:\nCtrl+Z 그리고 fg&amp;quot; as s3
  card &amp;quot;보존된 컨텍스트 :\n상태 손실 없음&amp;quot; as s4
}

why -right-&amp;amp;gt; superior : &amp;quot;간단함
항상 이긴다&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;_기술_메모리_신호&amp;quot;&amp;gt;기술 메모리 : 신호&amp;lt;/h2&amp;gt;
&amp;lt;div 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;/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;신호&amp;lt;/th&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;보낸 사람&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;효과&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;중단 — 프로세스를 종료합니다&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;코어 덤프와 함께 종료&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;대화형 정지 — 프로세스 일시 정지&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;계속 — 일시 중지된 프로세스를 재개한다&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;깨끗한 종료&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;강제 종료 (차단할 수 없음)&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;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;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;사용자&amp;quot; as user
participant &amp;quot;쉘&amp;quot; as shell
participant &amp;quot;프로세스&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;_결론&amp;quot;&amp;gt;결론&amp;lt;/h2&amp;gt;
&amp;lt;div 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`이것은 obscure한 팁이 아니라, 1970년대부터 사용 가능한 Unix 셸의 기본 메커니즘입니다. 대부분의 개발자들이 이를 모른다는 사실은 우리의 시대를 보여주는 증상입니다: IDE와 멀티플렉서가 우리에게 기본으로부터 멀어지게 만들었습니다.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;하지만 SSH 서버에 접속해 있거나, 최소한의 터미널을 사용 중이거나, 단순히 사용 중인 경우`opencode`그리고 LLM은 한창 작업 중입니다 —&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`votre 가장 친한 친구들이에요.&amp;lt;/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 일시정지, fg 재개. 터미널의 일시정지-재생입니다. &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;_참고문헌&amp;quot;&amp;gt;참고문헌&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;— 섹션 JOB CONTROL&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;— 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;Bash 참조 매뉴얼 — 작업 제어&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 — 리눅스 신호&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;고급 Bash 스크립팅 가이드 — 작업 제어&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: 로컬 설치 및 폰트 커스터마이징 (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;#_과정_개요&amp;quot;&amp;gt;과정 개요&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_왜냐하면_홈에서_vs_code&amp;quot;&amp;gt;왜냐하면: 홈에서 VS Code&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;#_시스템_설치의_문제&amp;quot;&amp;gt;시스템 설치의 문제&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_해결책_로컬_설치&amp;quot;&amp;gt;해결책: 로컬 설치&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_실질적인_이점&amp;quot;&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;#_메인_메뉴_글꼴_사용자_정의&amp;quot;&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;#_확인&amp;quot;&amp;gt;확인&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_기법_직접_css_패치&amp;quot;&amp;gt;기법: 직접 CSS 패치&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_무결성_경고_삭제&amp;quot;&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;#_탐색기_창의_글꼴_사용자_지정_전투&amp;quot;&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;#_시도_1_존재하지_않는_매개변수&amp;quot;&amp;gt;시도 1: 존재하지 않는 매개변수&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_시도_2_custom_css_and_js_loader_확장_프로그램&amp;quot;&amp;gt;시도 2: 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;#_함정_확장_기능을_매번_활성화해야_합니다&amp;quot;&amp;gt;함정 : 확장 기능을 매번 활성화해야 합니다.&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_시도_3_선들이_겹칩니다&amp;quot;&amp;gt;시도 3 : 선들이 겹칩니다.&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_해결_방법_zoom_상위_컨테이너에&amp;quot;&amp;gt;해결 방법: &amp;lt;code&amp;gt;zoom&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;#_선택기의_요약_및_작동하는_것&amp;quot;&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;#_자동_업데이트_스크립트&amp;quot;&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;#_문제&amp;quot;&amp;gt;문제&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_스크립트_vscode_update_sh&amp;quot;&amp;gt;스크립트 `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;#_관련_파일_개요&amp;quot;&amp;gt;관련 파일 — 개요&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_배운_교훈&amp;quot;&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;#_홈에_설치하면_모든_것이_해제됩니다&amp;quot;&amp;gt;홈에 설치하면 모든 것이 해제됩니다&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_zoom_font_size_목록_컴포넌트에_대해&amp;quot;&amp;gt;zoom` &amp;amp;gt; &amp;lt;code&amp;gt;font-size&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;#_custom_css_확장은_명시적_활성화가_필요합니다&amp;quot;&amp;gt;Custom CSS 확장은 명시적 활성화가 필요합니다.&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_업데이트_스크립트는_필수적입니다&amp;quot;&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;#_빠른_참조&amp;quot;&amp;gt;빠른 참조&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;읽는 시간 : 8 minutes&amp;lt;/div&amp;gt;
&amp;lt;p&amp;gt;왜 VS Code를 설치하나요?&amp;lt;code&amp;gt;/opt`또는 통해`apt`당신이 그것을 홈에 놓을 수 있고 IDE를 놀이터로 바꾼 뒤에도 절대 타이핑하지 않을 때`sudo&amp;lt;/code&amp;gt;?여기서는 로컬 설치의 이야기와 메뉴와 파일 탐색기의 글꼴을 확대하기 위한 치열한 싸움이 이어집니다.&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;/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;_과정_개요&amp;quot;&amp;gt;과정 개요&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;python
print(&amp;quot;opencode\n+ LLM&amp;quot;)&amp;quot; as agent

package &amp;quot;홈 사용자(~)&amp;quot; #LightGreen {
    package &amp;quot;~/apps/&amp;quot; {
        component &amp;quot;VS Code\n(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;_왜냐하면_홈에서_vs_code&amp;quot;&amp;gt;왜냐하면: 홈에서 VS Code&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;_시스템_설치의_문제&amp;quot;&amp;gt;시스템 설치의 문제&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;VS Code를 통해 설치된`apt`, &amp;lt;code&amp;gt;dnf`또는 안에서&amp;lt;/code&amp;gt;/opt`앱 파일을 루트 권한 뒤에 잠급니다. 네이티브 CSS의 모든 수정, de`product.json`또는 설치 폴더가 필요합니다`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;그런데 사용할 때&amp;lt;strong&amp;gt;오픈코드&amp;lt;/strong&amp;gt;(tool CLI를 사용하여 자신의 코드에서 LLM을 조종), 에이전트는 할 수 있어야 :&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;설정 파일에 쓰기`~/.config/Code/User/settings.json`) — 이건 이미 홈에 있습니다&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;IDE 에셋을 수정하세요 (워크벤치 CSS,&amp;lt;code&amp;gt;product.json&amp;lt;/code&amp;gt;) 고급 커스터마이징을 위해 — 그건 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;_해결책_로컬_설치&amp;quot;&amp;gt;해결책: 로컬 설치&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;공식 tarball 아카이브를 다운로드하고 바로 거기에 압축을 풉니다.&amp;lt;code&amp;gt;~/apps/&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;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;그렇다면 이진 파일은`~/apps/VSCode-linux-x64/bin/code`. 아무것도`sudo`아래의 어떤 파일을 읽거나 수정하거나 교체하는 데 필요하지 않습니다`~/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;_실질적인_이점&amp;quot;&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&amp;gt;@startuml
skinparam backgroundColor #FEFEFE

rectangle &amp;quot;시스템 설치
(/opt, apt, dnf)&amp;quot; #LightCoral {
    card &amp;quot;VS Code의 루트 속성&amp;quot; {
        [CSS du workbench : sudo requis]
        [product.json : sudo requis]
        [Dossier d&amp;#39;install : non modifiable]
    }
}

rectangle &amp;quot;설치 홈\n(~/apps)&amp;quot; #LightGreen {
    card &amp;quot;VS Code 사용자 속성&amp;quot; {
        [CSS du workbench : libre]
        [product.json : libre]
        [Dossier d&amp;#39;install : libre]
        [Script update maison]
    }
}

&amp;quot;시스템 설치
(/opt, apt, dnf)&amp;quot; -right-&amp;amp;gt; &amp;quot;설치 홈\n(~/apps)&amp;quot; : **Non, merci**\nJe prends le home

note bottom of &amp;quot;설치 홈\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;장점&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;세부&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;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;IDE의 모든 파일은 사용자에게 속합니다&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;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;패치할 수 있다.`workbench.desktop.main.css`특권 상승 없이&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;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;고립&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_메인_메뉴_글꼴_사용자_정의&amp;quot;&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;_확인&amp;quot;&amp;gt;확인&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;메인 메뉴(File, Edit, View&amp;amp;#8230;&amp;amp;#8203;)와 드롭다운의 기본 폰트가 너무 작습니다. VS Code에는 이를 변경할 수 있는 설정 옵션이 없습니다.&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;_기법_직접_css_패치&amp;quot;&amp;gt;기법: 직접 CSS 패치&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;파일 끝에 CSS를 추가합니다.&amp;lt;code&amp;gt;workbench.desktop.main.css&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&amp;gt;@startuml
skinparam backgroundColor #FEFEFE
autonumber

actor Développeur as dev
participant &amp;quot;workbench.desktop\n.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;당신의 설치는 손상되었습니다.&amp;quot;
dev -&amp;amp;gt; product : Supprimer clé &amp;quot;체크섬&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;_무결성_경고_삭제&amp;quot;&amp;gt;무결성 경고 삭제&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;VS Code는 자신의 파일 변경을 감지하고 &amp;quot;Your installation is corrupt&amp;quot; 배너를 표시합니다. 이를 방지하기 위해 체크섬을 삭제합니다.&amp;lt;code&amp;gt;product.json&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-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;재시작 후, 경고가 더 이상 없고 메뉴가 finally 읽을 수 있습니다.&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;_탐색기_창의_글꼴_사용자_지정_전투&amp;quot;&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;_시도_1_존재하지_않는_매개변수&amp;quot;&amp;gt;시도 1: 존재하지 않는 매개변수&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;workbench.tree.fontSize`안에`settings.json&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-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;결과 :&amp;lt;strong&amp;gt;효과 없음&amp;lt;/strong&amp;gt;. 이 설정은 VS Code에 존재하지 않습니다. 인식되지 않는 설정은 조용히 무시된다는 증거.&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_custom_css_and_js_loader_확장_프로그램&amp;quot;&amp;gt;시도 2: 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;확장을 설치합니다`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;CSS 파일을 만든다`~/.vscode-custom-css/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-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;그리고 우리는 이 파일을 설정에서 참조합니다:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;결과 :&amp;lt;strong&amp;gt;변경 사항이 적용되지 않습니다.&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;_함정_확장_기능을_매번_활성화해야_합니다&amp;quot;&amp;gt;함정 : 확장 기능을 매번 활성화해야 합니다.&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;확장 문서는 이 중요한 점에 대해 언급하지 않습니다: 커스텀 CSS를 수정할 때마다&amp;lt;strong&amp;gt;반드시&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;&amp;lt;code&amp;gt;Ctrl+Shift+P&amp;lt;/code&amp;gt;→ 타자치다&amp;lt;strong&amp;gt;사용자 정의 CSS 및 JS 활성화&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가 재시작을 요청합니다 → 수락&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;이 단계가 없으면 CSS는 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;_시도_3_선들이_겹칩니다&amp;quot;&amp;gt;시도 3 : 선들이 겹칩니다.&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;그리고도`font-size: 22px` et &amp;lt;code&amp;gt;line-height: 36px&amp;lt;/code&amp;gt;, 내려간 글자들(g, p, f, b)이 인접한 줄을 넘어갑니다. 추가를 시도하고 있습니다`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;결과 :&amp;lt;strong&amp;gt;변경 없음&amp;lt;/strong&amp;gt;. VS Code는 JavaScript에서 줄 높이를 계산하고 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;_해결_방법_zoom_상위_컨테이너에&amp;quot;&amp;gt;해결 방법: &amp;lt;code&amp;gt;zoom&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&amp;gt;@startuml
skinparam backgroundColor #FEFEFE

rectangle &amp;quot;시도 1
존재하지 않는 매개변수&amp;quot; #LightCoral {
    card &amp;quot;workbench.tree.fontSize&amp;quot; as t1 {
        [VS Code ignore le paramètre]
    }
}

rectangle &amp;quot;시도 2
사용자 정의 CSS + 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;시도 3
font-size + line-height + height&amp;quot; #LightYellow {
    card &amp;quot;높이: 36px !important&amp;quot; as t3 {
        [VS Code override\npar JavaScript]
        [Aucun effet]
    }
}

rectangle &amp;quot;솔루션
상위 컨테이너 줌&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;자바스크립트 레이아웃과 싸우는 대신, 속성을 사용합니다.`zoom`줄 컨테이너에서:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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`컨테이너 렌더링 — 텍스트와 간격 모두 — 을 균일하게 확대합니다. VS Code의 JavaScript 엔진이 계산한 레이아웃을 깨뜨리지 않으며, 비율 1.25는 대략 13px에서 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;결과 :&amp;lt;strong&amp;gt;파일 이름이 이제 가독성이 있으며, 줄이 적절히 간격되어 있습니다.&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;_선택기의_요약_및_작동하는_것&amp;quot;&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;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;접근&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;결과&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`위&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;더 큰 텍스트지만 겹친 줄&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`위에&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;효과 없음 (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`위에&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;성공 — 텍스트 + 비례 간격&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;_자동_업데이트_스크립트&amp;quot;&amp;gt;자동 업데이트 스크립트&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;개발자
또는 cron&amp;quot; as dev
participant &amp;quot;vscode-update.sh&amp;quot; as script
participant &amp;quot;vscode 타볼\n(code.visualstudio.com)&amp;quot; as remote
participant &amp;quot;~/apps/\nVSCode-linux-x64/&amp;quot; as local
participant &amp;quot;~/apps/\nvscode-backup&amp;quot; as backup
participant &amp;quot;작업대\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;이미 최신 상태입니다!&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;업데이트 완료
1.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;_문제&amp;quot;&amp;gt;문제&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;VS Code가 업데이트될 때, 설치 폴더를 교체하고&amp;lt;strong&amp;gt;모든 CSS 패치를 삭제하세요&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;_스크립트_vscode_update_sh&amp;quot;&amp;gt;스크립트 `vscode-update.sh&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;만든다`~/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;스크립트:&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;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;현재 버전과 다른지 확인해&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;현재 설치(타임스탬프된)를`~/apps/vscode-backup/`&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&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;재 적용&amp;lt;/strong&amp;gt;메인 메뉴 CSS 패치&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;체크섬을 삭제하세요&amp;lt;/strong&amp;gt; de `product.json`무결성 경고를 피하기 위해&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;참고: 맞춤 CSS 확장 및 파일을 통한 Explorer 창 맞춤 설정`~/.vscode-custom-css/custom.css`설치 폴더 밖에 있기 때문에 업데이트를 살아남는다.&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;_관련_파일_개요&amp;quot;&amp;gt;관련 파일 — 개요&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;설치 VS Code
(~/apps/VSCode-linux-x64/)&amp;quot; #LightCoral {
    component &amp;quot;workbench.desktop\n.main.css&amp;quot; as css #LightCoral
    component &amp;quot;product.json&amp;quot; as product #LightCoral
    component &amp;quot;바이너리
bin/code&amp;quot; as binary #LightCoral
}

rectangle &amp;quot;사용자 설정\n(~/.config/Code/)&amp;quot; #LightGreen {
    component &amp;quot;settings.json&amp;quot; as settings #LightGreen
    component &amp;quot;확장/&amp;quot; as extensions #LightGreen
}

rectangle &amp;quot;지속적인 커스터마이징\n(~/.vscode-custom-css/)&amp;quot; #SkyBlue {
    component &amp;quot;custom.css
(확대: 1.25)&amp;quot; as customcss #SkyBlue
}

rectangle &amp;quot;자동화
(~/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;길&amp;lt;/th&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;업데이트를 살아남나요?&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;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;아니요 (대체됨)&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 (메뉴, 드롭다운)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;&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;무결성 체크섬&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;&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;자동 업데이트 스크립트&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;&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;사용자 설정 (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;예&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;Explorer용 커스텀 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;예&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;_배운_교훈&amp;quot;&amp;gt;배운 교훈&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;**로컬 설치**\n 홈에&amp;quot; as install #LightGreen {
    card &amp;quot;sudo 없이
 CSS/config을 수정하기 위해&amp;quot; as i1
    card &amp;quot;opencode+LLM은
 모든 것을 수정할 수 있다&amp;quot; as i2
    card &amp;quot;롤백 = 복원
 타임스탬프가 적용된 백업&amp;quot; as i3
}

rectangle &amp;quot;CSS 커스터마이징
줌 vs 글꼴 크기&amp;quot; as css #LightYellow {
    card &amp;quot;줌 &amp;amp;gt; 폰트 크기
 컴포넌트 목록을 위한&amp;quot; as c1
    card &amp;quot;JS 레이아웃 오버라이드
높이/줄간격&amp;quot; as c2
    card &amp;quot;확장 Custom CSS
활성화가 필요합니다
수동 (Ctrl+Shift+P)&amp;quot; as c3
}

rectangle &amp;quot;**자동화**
 업데이트 스크립트&amp;quot; as auto #LightCoral {
    card &amp;quot;재패치 CSS 후
 각 MAJ&amp;quot; as a1
    card &amp;quot;체크섬 삭제\n product.json&amp;quot; as a2
    card &amp;quot;타임스탬프가 있는 백업\n 교체 전&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;_홈에_설치하면_모든_것이_해제됩니다&amp;quot;&amp;gt;홈에 설치하면 모든 것이 해제됩니다&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;로컬 설치에서`~/apps`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;없`sudo`CSS 또는 구성을 수정하려면&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;opencode+LLM 에이전트는 IDE의 구성 파일과 에셋에 쓸 수 있습니다.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&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 class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_zoom_font_size_목록_컴포넌트에_대해&amp;quot;&amp;gt;zoom` &amp;amp;gt; &amp;lt;code&amp;gt;font-size&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;VS Code는 JavaScript를 통해 줄 높이를 제어합니다. 수정`font-size`레이아웃 엔진이 할당한 높이를 만질 수 없으면 텍스트가 넘쳐 흐릅니다. 속성`zoom`부모 컨테이너를 비율대로 확대하면 이 문제를 피할 수 있다.&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;_custom_css_확장은_명시적_활성화가_필요합니다&amp;quot;&amp;gt;Custom CSS 확장은 명시적 활성화가 필요합니다.&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;이것은 &amp;#39;set and forget&amp;#39; 설정이 아닙니다. 맞춤 CSS 파일의 모든 수정은 명령어 팔레트에서 &amp;#39;Enable Custom CSS and JS&amp;amp;#8217;를 다시 실행해야 합니다 (&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;_업데이트_스크립트는_필수적입니다&amp;quot;&amp;gt;업데이트 스크립트는 필수적입니다.&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;스크립트 없이 VS Code 업데이트마다 워크벤치의 CSS 패치가 삭제됩니다. 스크립트`vscode-update.sh`다운로드, 교체, 재패치 및 체크섬 정리를 자동화합니다.&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;_빠른_참조&amp;quot;&amp;gt;빠른 참조&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;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;확장 프로그램 Custom CSS 및 JS 로더 :`be5invis.vscode-custom-css`&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;VS Code 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>Asciidoctor RevealJS 플러그인을 buildSrc Kotlin으로 마이그레이션: Groovy API 역 엔지니어링</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;#_배경_및_목적&amp;quot;&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;#_시작점&amp;quot;&amp;gt;시작점&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_목표&amp;quot;&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;#_첫_번째_장애물_ruby_gems&amp;quot;&amp;gt;첫 번째 장애물 : `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;#_그루비_구문_설탕이_숨기는_것&amp;quot;&amp;gt;그루비 구문 설탕이 숨기는 것&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_메커니즘의_분해&amp;quot;&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;#_세_부분으로_구성된_해결책&amp;quot;&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;#_1부_rubygems를_위한_ivy_리포지터리&amp;quot;&amp;gt;1부: rubygems를 위한 Ivy 리포지터리&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_제2부_asciidoctorgems_종속성&amp;quot;&amp;gt;제2부: asciidoctorGems 종속성&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_3부_settings_gradle_kts&amp;quot;&amp;gt;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;#_api의_바이트코드를_통한_내부_검사&amp;quot;&amp;gt;API의 바이트코드를 통한 내부 검사&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;#_왜_javap&amp;quot;&amp;gt;왜 &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;#_작업의_계층&amp;quot;&amp;gt;작업의 계층&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_발견_의_forkoptions&amp;quot;&amp;gt;발견 의 `forkOptions&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_javaforkoptions의_api_grolifant&amp;quot;&amp;gt;JavaForkOptions의 API (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;#_자바_툴체인_구성&amp;quot;&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;#_javatoolchainservice의_문제&amp;quot;&amp;gt;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;#_자동_docker_감지&amp;quot;&amp;gt;자동 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;#_컨텍스트&amp;quot;&amp;gt;컨텍스트&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_전략&amp;quot;&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;#_최종_결과&amp;quot;&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;#_소비자용_buildscript&amp;quot;&amp;gt;소비자용 buildscript&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;#_함정과_해결책_요약&amp;quot;&amp;gt;함정과 해결책 요약&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_조사_방법_javap을_사용해_알_수_없는_api_읽기&amp;quot;&amp;gt;조사 방법: javap을 사용해 알 수 없는 API 읽기&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;#_원칙&amp;quot;&amp;gt;원칙&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_1단계_gradle_캐시에서_jar_파일_찾기&amp;quot;&amp;gt;1단계: Gradle 캐시에서 JAR 파일 찾기&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_2단계_jar의_클래스_나열&amp;quot;&amp;gt;2단계: jar의 클래스 나열&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_단계_3_클래스_검사&amp;quot;&amp;gt;단계 3: 클래스 검사&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_단계_4_계층_구조_위로_이동&amp;quot;&amp;gt;단계 4: 계층 구조 위로 이동&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_단계_5_알_수_없는_유형_추적&amp;quot;&amp;gt;단계 5 : 알 수 없는 유형 추적&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_단계_6_프로젝트_확장자_확인&amp;quot;&amp;gt;단계 6 : 프로젝트 확장자 확인&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_방법_요약&amp;quot;&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;#_다음_단계&amp;quot;&amp;gt;다음 단계&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;_배경_및_목적&amp;quot;&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;_시작점&amp;quot;&amp;gt;시작점&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;프로젝트`slider-gradle`AsciiDoc 파일로부터 Gradle 플러그인을 통해 Reveal.js 발표를 생성합니다`org.asciidoctor.jvm.revealjs`. 주요 작업 설정`asciidoctorRevealJs`루트 빌드스크립트에 직접 있었다`build.gradle.kts`[No 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;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;_목표&amp;quot;&amp;gt;목표&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;이 전체 구성을`buildSrc/src/main/kotlin/slides/SlidesPlugin.kt`소비자 buildscript가 다음과 같이 축소되도록 :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;플러그인은 완전히 자율적이어야 함: 자체적으로 플러그인 종속성을 적용하고, 레포지토리를 구성하며, 루비 젬을 관리해야 합니다.&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;_첫_번째_장애물_ruby_gems&amp;quot;&amp;gt;첫 번째 장애물 : `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;_그루비_구문_설탕이_숨기는_것&amp;quot;&amp;gt;그루비 구문 설탕이 숨기는 것&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;라인`repositories { ruby { gems() } }`이는 빌드스크립트 실행 컨텍스트에서만 사용할 수 있는 Groovy DSL 확장입니다. 이는 buildSrc에서 접근 가능한 정적 Kotlin API가 아닙니다.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;그에게 전화를 걸려고 시도하면서`SlidesPlugin.kt`, 컴파일이 즉시 실패합니다.&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;_메커니즘의_분해&amp;quot;&amp;gt;메커니즘의 분해&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;~/.gradle/caches/modules-2/files-2.1/rubygems/&amp;lt;/code&amp;gt;), 파일에서 발견합니다`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() }`실제로 수행하고 있었다&amp;lt;strong&amp;gt;세 개의 별도 작업&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;Ivy 레포를 가리키는 저장`https://rubygems.org/gems/`&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;그룹 제외`rubygems`충돌을 피하기 위한 Maven 저장소&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;구성에 gem을 저장`asciidoctorGems`JRuby가 런타임에 이를 로드하도록&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;이 세 가지 책임은 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;_세_부분으로_구성된_해결책&amp;quot;&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;_1부_rubygems를_위한_ivy_리포지터리&amp;quot;&amp;gt;1부: rubygems를 위한 Ivy 리포지터리&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;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;확장`.gem`하드코딩되어야 합니다.&amp;lt;code&amp;gt;[ext]`기본적으로 해결하는&amp;lt;/code&amp;gt;.jar`, 이는 오류를 일으킵니다`Resource missing`.&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 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;
Ivy 레포는 바로 위에 선언됩니다.`project.repositories`그리고 블록 안에 있지 않음`repositories { }`왜냐하면 이 블록의 수신기가 아니기 때문에`RepositoryHandler`Gradle 표준이지만 확장과 호환되지 않는 grolifant API`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;_제2부_asciidoctorgems_종속성&amp;quot;&amp;gt;제2부: asciidoctorGems 종속성&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;플러그인`org.asciidoctor.jvm.gems`먼저 적용해야 합니다 — 구성을 만듭니다.&amp;lt;code&amp;gt;asciidoctorGems`그리고 작업`asciidoctorGemsPrepare&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;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;적용 순서는 중요합니다 :`gems`이전`revealjs`. &amp;amp;lt;2&amp;amp;gt; 수식어`@gem`의존성에 올바른 확장을 강제합니다.&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부_settings_gradle_kts&amp;quot;&amp;gt;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`안에`settings.gradle.kts`프로젝트가 자체 저장소를 선언하도록 허용해야 합니다 :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;이 라인이 없으면 Gradle은 플러그인에 선언된 저장소를 무시하고, gem 의존성 해결이 실패합니다.&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;_api의_바이트코드를_통한_내부_검사&amp;quot;&amp;gt;API의 바이트코드를 통한 내부 검사&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;_왜_javap&amp;quot;&amp;gt;왜 &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;플러그인`asciidoctor-gradle-jvm-slides`버전에 있습니다`4.0.0-alpha.1`. 문서는 존재하지 않거나 불완전합니다. 신뢰할 수 있는 유일한 소스는 컴파일된 클래스를 직접 검사하는 것이다.&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;_작업의_계층&amp;quot;&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-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;결과 :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;_발견_의_forkoptions&amp;quot;&amp;gt;발견 의 `forkOptions&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;검사하면서`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;다음과 같습니다:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;_javaforkoptions의_api_grolifant&amp;quot;&amp;gt;JavaForkOptions의 API (grolifant)&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;`javaLauncher`이 작업에는 존재하지 않습니다. 실제 API의`org.ysb33r.grolifant.api.v4.JavaForkOptions`노출 :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;방법`executable(Object)`할당을 대체한다`executable = &amp;amp;#8230;&amp;amp;#8203;`컴파일되지 않는다 (`val`재할당할 수 없습니다).&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 { }`작업의 방법이 아닌&amp;lt;strong&amp;gt;프로젝트 연장&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;접근 방법 :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;_자바_툴체인_구성&amp;quot;&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;_javatoolchainservice의_문제&amp;quot;&amp;gt;JavaToolchainService의 문제&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;`JavaToolchainService`프로젝트 확장이 아닙니다. 다음 호출이 실패합니다:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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는`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;_자동_docker_감지&amp;quot;&amp;gt;자동 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;_컨텍스트&amp;quot;&amp;gt;컨텍스트&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Asciidoctor/JRuby 플러그인은 Java 17이 필요합니다. buildSrc의 Kotlin 2.0.x는 Java 25를 지원하지 않습니다(버전 파서가에서 크래시됩니다`&amp;quot;25.0.2&amp;quot;`). Gradle 데몬은 따라서 Java 17에서 실행되거나 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;_전략&amp;quot;&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;Docker 사용 가능 → 컨테이너를 통한 실행`eclipse-temurin:17`(기본 동작)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Docker이 없음 + Java 17 → 로컬 실행&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Docker 없음 + Java &amp;amp;gt; 17 → 명시적 오류&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;_최종_결과&amp;quot;&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;_소비자용_buildscript&amp;quot;&amp;gt;소비자용 buildscript&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;그게 전부예요. 플러그인이 모든 책임을 집니다.&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;_함정과_해결책_요약&amp;quot;&amp;gt;함정과 해결책 요약&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;문제&amp;lt;/th&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;해결책&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() }`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;Groovy DSL 확장만&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;세 가지 별도의 메커니즘 : Ivy 저장소 + 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가 찾는`.jar`대신에`.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]`해결하는`jar`기본적으로&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;하드코더`.gem`Ivy 패턴 + 한정자 안에`@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 { }`컴파일이 되지 않음`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;수신자 grolifant는 Kotlin DSL과 호환되지 않습니다.&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;직접 통화`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`해결되지 않은&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;존재하지 않는 속성`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;`컴파일되지 않습니다&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;속성`val`안에`JavaForkOptions`그롤리팡&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;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(Object)`대신에&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`를 통해 찾을 수 없음`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;이것은 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;&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 { }`작업에서 해결되지 않음&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;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;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는 두 자리 숫자 버전의 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;자동 Docker 감지 + 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;_조사_방법_javap을_사용해_알_수_없는_api_읽기&amp;quot;&amp;gt;조사 방법: javap을 사용해 알 수 없는 API 읽기&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;_원칙&amp;quot;&amp;gt;원칙&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;javap`는 JDK의 표준 도구로 파일을 디컴파일하는 도구입니다.&amp;lt;/code&amp;gt;.class`읽을 수 있는 Java 서명, 소스 코드가 필요 없이.&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;_1단계_gradle_캐시에서_jar_파일_찾기&amp;quot;&amp;gt;1단계: Gradle 캐시에서 JAR 파일 찾기&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Gradle은 모든 의존성을 다운로드합니다`~/.gradle/caches/modules-2/files-2.1/`. 첫 번째 단계는 검사할 클래스를 포함하는 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;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;결과 :&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;_2단계_jar의_클래스_나열&amp;quot;&amp;gt;2단계: jar의 클래스 나열&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;클래스를 검사하기 전에, 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;_단계_3_클래스_검사&amp;quot;&amp;gt;단계 3: 클래스 검사&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;이것은 사용 가능한 모든 클래스를 보여줍니다 : &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;, 등&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;옵션`-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-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;_단계_4_계층_구조_위로_이동&amp;quot;&amp;gt;단계 4: 계층 구조 위로 이동&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;AbstractAsciidoctorTask&amp;lt;/code&amp;gt;. 그 차례에 검사한다 먼저 그의 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;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;거기서 우리가 발견한다`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; 유형`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;_단계_5_알_수_없는_유형_추적&amp;quot;&amp;gt;단계 5 : 알 수 없는 유형 추적&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;`JavaForkOptions`는 알려지지 않은 grolifant 클래스입니다. 우리는 그 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;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;그 후에 그것을 검사합니다 :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;거기에서 찾을 수 있습니다`executable(java.lang.Object)&amp;lt;code&amp;gt;— 호출되어야 할 올바른 메서드, 대조적으로`executable = &amp;amp;#8230;&amp;amp;#8203;`속성이라서 컴파일이 되지 않습니다`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;_단계_6_프로젝트_확장자_확인&amp;quot;&amp;gt;단계 6 : 프로젝트 확장자 확인&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;를 위해`revealjs { }`, 질문은 이것이 작업의 방법이었는가? 또는 프로젝트 확장 ? 검사`AsciidoctorJRevealJSTask` 어떤 방법도 보여주지 않는다.&amp;lt;code&amp;gt;revealjs&amp;lt;/code&amp;gt;. 그때 점검합니다`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;존재하는`NAME`확인합니다. 이는 프로젝트에 등록된 확장 기능입니다, 접근 가능한`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;_방법_요약&amp;quot;&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;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;단계&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;행동&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;— 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;— 클래스가 존재하는지 확인&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;— 클래스를 점검하다&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;식별자`extends`그리고 계층을 올라가다&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;자신들의 jars에서 알려지지 않은 유형을 추적하다&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;검색하다`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;이 방법은 문서화되지 않은 API를 가진 모든 Gradle 플러그인에 적용됩니다. 또는 알파 버전이 기존 문서와 더 이상 일치하지 않는.&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;_다음_단계&amp;quot;&amp;gt;다음 단계&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;이 buildSrc 플러그인은 Gradle Plugin Portal 또는 Maven Local에 게시된 독립된 프로젝트로 추출됩니다. 소비자 buildscript는 다음과 같이 될 것입니다:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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`최소한으로 축소될 것이며 참조를 갖지 않을`foojay-resolver-convention&amp;lt;/code&amp;gt;, 플러그인 자체에서 관리되거나 사전 요구 사항으로 문서화된 JDK 프로비저닝.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>아키텍처 포트폴리오 JBake - 비전 및 사용 사례</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;#_건축_비전&amp;quot;&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;#_주도_원칙&amp;quot;&amp;gt;주도 원칙&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_전략적_선택_포트폴리오를_위한_asciidoc&amp;quot;&amp;gt;전략적 선택 : 포트폴리오를 위한 AsciiDoc&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_데이터_모델&amp;quot;&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;#_상세한_사용_사례&amp;quot;&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;#_uc1_포트폴리오에_항목_추가&amp;quot;&amp;gt;UC1 : 포트폴리오에 항목 추가&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;#_정상_흐름&amp;quot;&amp;gt;정상 흐름&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_생성할_파일의_템플릿&amp;quot;&amp;gt;생성할 파일의 템플릿&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_검증_포인트&amp;quot;&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;#_uc2_포트폴리오에서_항목_삭제&amp;quot;&amp;gt;UC2 : 포트폴리오에서 항목 삭제&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;#_명목_플럭스&amp;quot;&amp;gt;명목 플럭스&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_대체_전략_아카이빙&amp;quot;&amp;gt;대체 전략: 아카이빙&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_리소스_정리&amp;quot;&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;#_uc3_비공개초안로_설정&amp;quot;&amp;gt;UC3 : 비공개(초안)로 설정&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;#_명목_흐름&amp;quot;&amp;gt;명목 흐름&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_가능한_상태&amp;quot;&amp;gt;가능한 상태&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_일반적인_사용_사례&amp;quot;&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;li&amp;gt;&amp;lt;a href=&amp;quot;#_템플릿_아키텍처&amp;quot;&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;#_재사용_가능한_템플릿_전략&amp;quot;&amp;gt;재사용 가능한 템플릿 전략&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_데이터_추출_패턴&amp;quot;&amp;gt;데이터 추출 패턴&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_명명_규칙&amp;quot;&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;#_출판_워크플로우&amp;quot;&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;#_개발_파이프라인&amp;quot;&amp;gt;개발 파이프라인&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_환경&amp;quot;&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;#_확장성&amp;quot;&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;#_새로운_속성_추가&amp;quot;&amp;gt;새로운 속성 추가&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_고급_분류&amp;quot;&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;#_모범_사례&amp;quot;&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;#_파일_구성&amp;quot;&amp;gt;파일 구성&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_콘텐츠_관리&amp;quot;&amp;gt;콘텐츠 관리&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_성능&amp;quot;&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;#_결론&amp;quot;&amp;gt;결론&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;_건축_비전&amp;quot;&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;_주도_원칙&amp;quot;&amp;gt;주도 원칙&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;관심사의 분리&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;Contenu&amp;lt;/strong&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;strong&amp;gt;소개&amp;lt;/strong&amp;gt; : 재사용 가능한 Thymeleaf 템플릿&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;데이터&amp;lt;/strong&amp;gt; : JBake에 의해 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;스타일&amp;lt;/strong&amp;gt; : Bootstrap 5를 시각적 일관성을 위해&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;_전략적_선택_포트폴리오를_위한_asciidoc&amp;quot;&amp;gt;전략적 선택 : 포트폴리오를 위한 AsciiDoc&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. 왜 아스키독?&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;기준&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;장점&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;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;메타데이터&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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-*&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;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;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 버전 관리 가능&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;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;템플릿화&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;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은 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;_데이터_모델&amp;quot;&amp;gt;데이터 모델&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;각 포트폴리오 프로젝트는 다음과 같은 AsciiDoc 문서입니다:&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;헤더 메타데이터&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;문서 본문&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;s용자 정의 속성&amp;lt;/strong&amp;gt; : 접두사가 된`project-*`자동 추출을 위해&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;프로젝트&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;_상세한_사용_사례&amp;quot;&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;_uc1_포트폴리오에_항목_추가&amp;quot;&amp;gt;UC1 : 포트폴리오에 항목 추가&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;sect3&amp;quot;&amp;gt;
&amp;lt;h4 id=&amp;quot;_정상_흐름&amp;quot;&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&amp;gt;@startuml
skinparam backgroundColor #FEFEFE
actor Développeur as dev
participant &amp;quot;편집기\n텍스트&amp;quot; as editor
participant &amp;quot;파일
AsciiDoc&amp;quot; as file
participant &amp;quot;JBake
엔진&amp;quot; as jbake
participant &amp;quot;사이트
정적&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;_생성할_파일의_템플릿&amp;quot;&amp;gt;생성할 파일의 템플릿&amp;lt;/h4&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;개발자는 만든다`content/portfolio/nom-projet.adoc`표준화된 구조와 함께 :&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;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;표준화된 섹션 (컨텍스트, 도전, 솔루션, 결과)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&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 class=&amp;quot;sect3&amp;quot;&amp;gt;
&amp;lt;h4 id=&amp;quot;_검증_포인트&amp;quot;&amp;gt;검증 포인트&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;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;참조된 이미지는 에 존재합니다.&amp;lt;code&amp;gt;assets/img/portfolio/&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;JBake 빌드가 오류 없이 성공합니다&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;포트폴리오 페이지에 프로젝트가 나타납니다&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&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;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_uc2_포트폴리오에서_항목_삭제&amp;quot;&amp;gt;UC2 : 포트폴리오에서 항목 삭제&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;sect3&amp;quot;&amp;gt;
&amp;lt;h4 id=&amp;quot;_명목_플럭스&amp;quot;&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&amp;gt;@startuml
skinparam backgroundColor #FEFEFE
actor Développeur as dev
participant &amp;quot;시스템
파일&amp;quot; as fs
participant &amp;quot;JBake
엔진&amp;quot; as jbake
participant &amp;quot;사이트
정적&amp;quot; as site
database &amp;quot;캐시
JBake&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;_대체_전략_아카이빙&amp;quot;&amp;gt;대체 전략: 아카이빙&amp;lt;/h4&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;영구적으로 삭제하는 대신, 폴더를 만들 수 있는 가능성`content/portfolio/archive/`:&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;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;쉽게 복원할 수 있습니다&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Git 기록을 더 명확하게 유지합니다&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;_리소스_정리&amp;quot;&amp;gt;리소스 정리&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;고아 이미지 확인`assets/img/portfolio/`&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;다른 프로젝트에 참조되지 않은 이미지 삭제&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;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&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_uc3_비공개초안로_설정&amp;quot;&amp;gt;UC3 : 비공개(초안)로 설정&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;sect3&amp;quot;&amp;gt;
&amp;lt;h4 id=&amp;quot;_명목_흐름&amp;quot;&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&amp;gt;@startuml
skinparam backgroundColor #FEFEFE
actor Développeur as dev
participant &amp;quot;파일\nAsciiDoc&amp;quot; as file
participant &amp;quot;JBake\n엔진&amp;quot; as jbake
participant &amp;quot;정적
사이트&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;_가능한_상태&amp;quot;&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&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;_일반적인_사용_사례&amp;quot;&amp;gt;일반적인 사용 사례&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;작성 중인 프로젝트&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;임시 기밀 프로젝트&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;주요 업데이트&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;A/B testing&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&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_템플릿_아키텍처&amp;quot;&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;_재사용_가능한_템플릿_전략&amp;quot;&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&amp;gt;@startuml
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle

package &amp;quot;Thymeleaf 템플릿&amp;quot; {
  component [index.html] as index
  component [portfolio.html] as portfolio
  component [project.html] as project

  package &amp;quot;부분들&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;데이터 JBake&amp;quot; {
  database &amp;quot;게시된 프로젝트&amp;quot; as data
  database &amp;quot;내용 (프로젝트)&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;_데이터_추출_패턴&amp;quot;&amp;gt;데이터 추출 패턴&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;JBake은 AsciiDoc 속성을 자동으로 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;문서 AsciiDoc&amp;quot; {
  (&amp;quot;:project-client: 애크미 코퍼레이션&amp;quot;) as attr1
  (&amp;quot;:project-duration: 6개월&amp;quot;) as attr2
  (&amp;quot;:project-tags: java, react&amp;quot;) as attr3
}

rectangle &amp;quot;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;템플릿 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;태그 : ${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;_명명_규칙&amp;quot;&amp;gt;명명 규칙&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;프로젝트 속성&amp;lt;/strong&amp;gt; : 접두사`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;파일&amp;lt;/strong&amp;gt; : kebab-case (예:`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; : 프로젝트 이름 접두사 (예:`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; : 기능적 이름 (예:`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;_출판_워크플로우&amp;quot;&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;_개발_파이프라인&amp;quot;&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&amp;gt;@startuml
skinparam backgroundColor #FEFEFE
skinparam handwritten false

class ProjetAsciiDoc {
  +titre: String
  +jbake-type: &amp;quot;프로젝트&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;_환경&amp;quot;&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;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;환경&amp;lt;/th&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;수락된 상태&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;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;스테이징&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;생산&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;/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;_확장성&amp;quot;&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;_새로운_속성_추가&amp;quot;&amp;gt;새로운 속성 추가&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;project-*&amp;lt;/code&amp;gt;[No 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;&amp;lt;code&amp;gt;project-awards&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;`project-testimonial`고객 인용&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;`project-team-size`팀 규모&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;: 예산 범위&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;이러한 속성은 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;_고급_분류&amp;quot;&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&amp;gt;@startuml
skinparam backgroundColor #FEFEFE

object Projet {
  project-category = &amp;quot;웹&amp;quot;
  project-subcategory = &amp;quot;전자상거래&amp;quot;
  project-industry = &amp;quot;소매&amp;quot;
  project-complexity = &amp;quot;높은&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;_모범_사례&amp;quot;&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;_파일_구성&amp;quot;&amp;gt;파일 구성&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;파일 하나 = 프로젝트 하나&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;전용 폴더의 이미지&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;일관된 명명법&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;Versioning Git&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 class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_콘텐츠_관리&amp;quot;&amp;gt;콘텐츠 관리&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;기본 초안 상태&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;출판 전 검토&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;완전한 메타데이터&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;풍부한 서사 내용&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 class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_성능&amp;quot;&amp;gt;성능&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;이미지 최적화&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;필요한 경우 페이지네이션&amp;lt;/strong&amp;gt; : 프로젝트가 20개 이상이면&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; : 갤러리 이미지 필요 시 로드&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;브라우저 캐시&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;_결론&amp;quot;&amp;gt;결론&amp;lt;/h2&amp;gt;
&amp;lt;div 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;/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;간편함&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;유연성&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;유지보수성&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;추적성&amp;lt;/strong&amp;gt; : 완전한 Git 버전 관리&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;Automatisation&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;AsciiDoc 선택은 사이트의 나머지와 일관성을 유지하면서 전문 포트폴리오에 필요한 풍부한 메타데이터를 제공한다.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>하이브리드 아키텍처 : Supabase와 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;읽기 시간 : 12 minutes&amp;lt;/div&amp;gt;
&amp;lt;p&amp;gt;Supabase의 간결함과 Spring Boot의 강력함 사이에서 선택하기보다 왜 두 가지를 결합하지 않을까요? 각 기술의 장점을 활용하는 하이브리드 아키텍처를 살펴보세요.&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;/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;_현대_건축의_가짜_딜레마&amp;quot;&amp;gt;현대 건축의 가짜 딜레마&amp;lt;/h2&amp;gt;
&amp;lt;div 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;/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;백엔드-어즈-어-서비스 솔루션에 모든 것을 걸다&amp;lt;/strong&amp;gt;(Supabase, 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;전체 백엔드 구축하기&amp;lt;/strong&amp;gt;(Spring Boot, Node.js) - 강력하지만 복잡한&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;각 기술을 그들이 가장 잘하는 곳에 사용하는 것&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;_건축_비전&amp;quot;&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;_전통적인_건축_일체형&amp;quot;&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&amp;gt;@startuml
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle

package &amp;quot;전통적인 건축&amp;quot; {
  actor Utilisateur as user
  component &amp;quot;프론트엔드&amp;quot; as frontend #LightBlue
  component &amp;quot;모놀리식 백엔드&amp;quot; as backend #LightCoral {
    [Authentification]
    [CRUD]
    [Logique Métier]
    [Jobs]
    [APIs Tierces]
  }
  database &amp;quot;데이터베이스&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;이 접근 방식에서는 가장 간단한 작업(글 읽기, 댓글 작성)이라 할지라도 백엔드를 경유해야 하며, 첫날부터 복잡성의 비용을 지불하게 됩니다.&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;_baas_아키텍처_모든_대리자&amp;quot;&amp;gt;BaaS 아키텍처: 모든 대리자&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;순수 BaaS 아키텍처&amp;quot; {
  actor Utilisateur as user
  component &amp;quot;프론트엔드&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;반면에, BaaS에 모든 것을 위임하는 것은 처음에는 매력적이지만, 비즈니스 로직이 복잡해지면 곧 한계가 드러납니다.&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;_하이브리드_아키텍처_책임_분리&amp;quot;&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&amp;gt;@startuml
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle

actor Utilisateur as user #LightBlue

package &amp;quot;프론트엔드 레이어&amp;quot; {
  component &amp;quot;정적 사이트 JBake&amp;quot; as frontend #SkyBlue
}

package &amp;quot;단순 레이어 (Supabase)&amp;quot; #LightGreen {
  component &amp;quot;인증&amp;quot; as auth
  component &amp;quot;CRUD 기본&amp;quot; as crud
  component &amp;quot;파일 저장&amp;quot; as storage
  component &amp;quot;실시간&amp;quot; as realtime
}

package &amp;quot;비즈니스 계층 (Spring Boot)&amp;quot; #Coral {
  component &amp;quot;복잡한 논리&amp;quot; as logic
  component &amp;quot;편곡&amp;quot; as orchestration
  component &amp;quot;예약된 작업&amp;quot; as jobs
  component &amp;quot;통합&amp;quot; as integrations
}

database &amp;quot;PostgreSQL 공유&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;하이브리드 아키텍처는 복잡성과 운영의 특성에 따라 책임을 명확히 분리한다.&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;_디자인_원칙&amp;quot;&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;_원칙_1_간단히_시작하고_지능적으로_발전하기&amp;quot;&amp;gt;원칙 1: 간단히 시작하고, 지능적으로 발전하기&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;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;**2단계 : 성장**&amp;quot; #LightYellow {
  component &amp;quot;JBake + Supabase
+ Spring Boot (옵션)&amp;quot; as growth
  note right
    ✓ Logique métier émerge
    ✓ Premiers jobs planifiés
    ✓ Intégrations tierces
  end note
}

rectangle &amp;quot;**3단계 : 확대**&amp;quot; #LightCoral {
  component &amp;quot;완전한 아키텍처&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;필요하지 않다면 Spring Boot을 구축하지 마세요.&amp;lt;/strong&amp;gt;Supabase로 시작하고, 복잡성이 이를 정당화할 때 Spring Boot를 추가하세요.&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_작업의_성격에_따른_분리&amp;quot;&amp;gt;원칙 2: 작업의 성격에 따른 분리&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;_원칙_3_단_하나의_진실의_원천&amp;quot;&amp;gt;원칙 3 : 단 하나의 진실의 원천&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;스키마: auth (Supabase)&amp;quot; #LightGreen {
    [users]
    [sessions]
    [identities]
  }

  package &amp;quot;스키마: 공개 (공유)&amp;quot; #SkyBlue {
    [articles]
    [comments]
    [profiles]
  }

  package &amp;quot;스키마: 비즈니스 (Spring Boot)&amp;quot; #Coral {
    [orders]
    [payments]
    [reports]
  }
}

component &amp;quot;수퍼베이스&amp;quot; as supabase #LightGreen
component &amp;quot;스프링 부트&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;PostgreSQL 동일한 인스턴스를 공유함으로써, Supabase와 Spring Boot는 복잡한 동기화 없이 동일한 데이터에서 작업합니다.&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;_흐름_오케스트레이션&amp;quot;&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;_플럭스_1_중앙_집중식_인증&amp;quot;&amp;gt;플럭스 1 : 중앙 집중식 인증&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;사용자&amp;quot; as user
participant &amp;quot;JBake&amp;quot; as jbake
participant &amp;quot;Supabase Auth&amp;quot; as auth
participant &amp;quot;스프링 부트&amp;quot; as spring
database &amp;quot;PostgreSQL&amp;quot; as db

== Authentification ==
user -&amp;amp;gt; jbake : Clique &amp;quot;로그인&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;핵심 포인트&amp;lt;/strong&amp;gt;: 하나의 인증 프로세스, 하나의 JWT, 어디에서나 사용 가능.&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_복잡한_비즈니스_프로세스의_오케스트레이션&amp;quot;&amp;gt;플럭스 2 : 복잡한 비즈니스 프로세스의 오케스트레이션&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;클라이언트&amp;quot; as user
participant &amp;quot;JBake&amp;quot; as jbake
participant &amp;quot;스프링 부트&amp;quot; as spring
participant &amp;quot;Supabase DB&amp;quot; as supadb
participant &amp;quot;스트라이프 API&amp;quot; as stripe
participant &amp;quot;재고 서비스&amp;quot; as inventory
participant &amp;quot;이메일 서비스&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;Spring Boot가 제공하는 것&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;_flux_3_예약된_작업_및_동기화&amp;quot;&amp;gt;Flux 3 : 예약된 작업 및 동기화&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;스케줄러\n(Spring Boot)&amp;quot; as scheduler
participant &amp;quot;리포트 서비스&amp;quot; as report
participant &amp;quot;PostgreSQL&amp;quot; as db
participant &amp;quot;외부 API&amp;quot; as api
participant &amp;quot;이메일 서비스&amp;quot; as email
participant &amp;quot;Supabase 스토리지&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;Spring Boot가 제공하는 것&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;_의사결정_매트릭스&amp;quot;&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;_supabase를_언제_사용해야_하나요&amp;quot;&amp;gt;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;_spring_boot은_언제_사용해야_하나요&amp;quot;&amp;gt;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;_프로젝트_유형별_건축_예시&amp;quot;&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;_블로그_포트폴리오&amp;quot;&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&amp;gt;@startuml
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle

package &amp;quot;개인 블로그 - 95% Supabase&amp;quot; {
  component &amp;quot;JBake&amp;quot; as jbake #SkyBlue

  package &amp;quot;Supabase (무료)&amp;quot; #LightGreen {
    [Auth: OAuth2 Google]
    [Articles (CRUD)]
    [Commentaires]
    [Images (Storage)]
    [Notifications Realtime]
  }

  package &amp;quot;Spring Boot (선택 사항)&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 (선택 사항)&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;판결&amp;lt;/strong&amp;gt;: 100% Supabase면 충분합니다.&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;_saas_애플리케이션&amp;quot;&amp;gt;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;SaaS 애플리케이션 - 하이브리드 아키텍처&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;스프링 부트&amp;quot; #Coral {
    [Facturation (Stripe)]
    [Analytics Avancées]
    [Rapports Mensuels]
    [Quotas &amp;amp;amp; Limites]
    [Webhooks Stripe]
  }

  cloud &amp;quot;외부 서비스&amp;quot; {
    [Stripe API]
    [SendGrid Email]
  }

  database &amp;quot;공유된 PostgreSQL&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;스프링 부트&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;평결&amp;lt;/strong&amp;gt;: 필요한 하이브리드 아키텍처. Supabase는 주로 사용하고, Spring Boot는 중요한 부분(결제, 청구)에 사용합니다.&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;_전자상거래&amp;quot;&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&amp;gt;@startuml
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle

package &amp;quot;전자상거래 - 70% Spring Boot&amp;quot; {
  component &amp;quot;스토어프론트 (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 (핵심 비즈니스)&amp;quot; #Coral {
    [Gestion Commandes]
    [Paiements Stripe]
    [Gestion Stock]
    [Calcul Frais Port]
    [Génération Factures]
    [Synchronisation ERP]
    [Emails Transactionnels]
  }

  cloud &amp;quot;통합&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 (핵심 비즈니스)&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;판결&amp;lt;/strong&amp;gt;Spring Boot 필수적입니다. Supabase는 인증 및 카탈로그용으로만 사용합니다.&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;_점진적_마이그레이션_전략&amp;quot;&amp;gt;점진적 마이그레이션 전략&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;_비용_및_운영_고려_사항&amp;quot;&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;_비용_구조&amp;quot;&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&amp;gt;@startuml
skinparam backgroundColor #FEFEFE

package &amp;quot;예상 월간 비용&amp;quot; {

  rectangle &amp;quot;MVP 단계
(0-1000 사용자)&amp;quot; #LightGreen {
    [JBake (GitHub Pages): 0€]
    [Supabase: 0€]
    [Spring Boot: N/A]
    note bottom: **Total: 0€/mois**
  }

  rectangle &amp;quot;성장 단계
(1k-10k 사용자)&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;단계 규모
(10k-100k 사용자)&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;단계 규모
(10k-100k 사용자)&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;_운영_책임&amp;quot;&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&amp;gt;@startuml
skinparam backgroundColor #FEFEFE

package &amp;quot;누가 무엇을 관리하나요?&amp;quot; {

  rectangle &amp;quot;Supabase에서 관리 (0 노력)&amp;quot; #LightGreen {
    [Authentification]
    [Base de données]
    [Backups automatiques]
    [Scaling automatique]
    [Monitoring DB]
    [Sécurité DB]
  }

  rectangle &amp;quot;GitHub에서 관리 (노력 없음)&amp;quot; #SkyBlue {
    [Hébergement site statique]
    [HTTPS/SSL]
    [CDN global]
    [Déploiement automatique]
  }

  rectangle &amp;quot;관리 (Spring Boot)&amp;quot; #LightCoral {
    [Déploiement application]
    [Monitoring APM]
    [Logs centralisés]
    [Scaling horizontal]
    [Mises à jour sécurité]
  }
}

note bottom of &amp;quot;관리 (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;_결론_완벽한_균형&amp;quot;&amp;gt;결론 : 완벽한 균형&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Supabase + Spring Boot 하이브리드 아키텍처는 타협이 아니라, 이것은&amp;lt;strong&amp;gt;시너지&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;_기억해야_할_원칙&amp;quot;&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&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;_이_아키텍처를_언제_선택해야_할까요&amp;quot;&amp;gt;이 아키텍처를 언제 선택해야 할까요?&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;✅ 이 아키텍처는 다음 경우에 이상적입니다 :&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;당신은 빠르게 시작하고 싶으신가요 (몇 달이 아닌 며칠 안에 MVP를 구현하고 싶으신가요)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;당신은 비즈니스 복잡성의 성장을 예상합니다.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;당신은 초기 비용을 최소화하고 싶습니다.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;PostgreSQL을 좋아하고 단일한 진실의 출처를 원합니다.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;당신은 비즈니스 코드를 위해 Kotlin과 코루틴을 좋아합니다&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&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;❌ 이 아키텍처는 적합하지 않으면:&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;당신의 프로젝트는 간단하고 그대로 유지될 것입니다 (개인 블로그 → 100% Supabase면 충분합니다)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;당신은 이미 확립된 백엔드 스택을 잘 알고 있습니다&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;전통적인 모노리스를 선호합니다.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;.NET, Python 또는 백엔드 측 언어가 필요합니다&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;_최종_전체_비전&amp;quot;&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&amp;gt;@startuml
skinparam backgroundColor #FEFEFE

package &amp;quot;완전한 하이브리드 아키텍처&amp;quot; {

  actor &amp;quot;사용자&amp;quot; as users

  rectangle &amp;quot;프론트엔드 (정적)&amp;quot; #SkyBlue {
    [JBake / GitHub Pages]
    note right: Gratuit, CDN global
  }

  rectangle &amp;quot;백엔드 간단 (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;비즈니스 백엔드 (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
단일 진실의 출처&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;_양쪽의_최고&amp;quot;&amp;gt;양쪽의 최고&amp;lt;/h3&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;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;시작은 시간 단위로, 주 단위가 아니다.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;몇 클릭만으로 OAuth2 인증&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;자동으로 생성된 REST API&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&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;💪 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과 Coroutines&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;복잡한 비즈니스 프로세스 오케스트레이션&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;신뢰할 수 있는 예약된 작업&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;보장된 ACID 트랜잭션&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;완전한 Spring 생태계&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;💰 진보적인 경제&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€로 시작하고 확인&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;성장에 따르는 비용&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&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;🎯 건축적 유연성&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;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;필요한 경우에만 Spring Boot 추가&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;완전한 벤더 락인 없음&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&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;_더_나아가기&amp;quot;&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;_기술_자원&amp;quot;&amp;gt;기술 자원&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;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;인증 가이드&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;행 수준 보안&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&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;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와 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;코루틴과 스프링&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 리소스 서버&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;부칙:&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;JWT를 사용한 API 보안&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;PostgreSQL 성능 최적화&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;점진적인 마이그레이션 패턴&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&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 class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_실제_사용_사례&amp;quot;&amp;gt;실제 사용 사례&amp;lt;/h3&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;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;B2B SaaS&amp;lt;/strong&amp;gt;: Supabase 인증, 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;마켓플레이스&amp;lt;/strong&amp;gt;Supabase 카탈로그, 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;콘텐츠 플랫폼&amp;lt;/strong&amp;gt;: Supabase 문서, 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;내부 도구&amp;lt;/strong&amp;gt;: CRUD Supabase, 워크플로우 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;_시작_체크리스트&amp;quot;&amp;gt;시작 체크리스트&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 - 기초 (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; Supabase 계정 만들기 - [ ] PostgreSQL 프로젝트 구성 - [ ] 인증 활성화 (Google, GitHub) - [ ] 초기 스키마 정의 - [ ] 구성 Row Level Security - [ ] JBake에서 API 테스트&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;2단계 - MVP (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; 메인 페이지 구현 - [ ] OAuth2 인증 통합 - [ ] CRUD 폼 생성 - [ ] Storage를 파일용으로 구성 - [ ] 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;&amp;lt;strong&amp;gt;Phase 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; 복잡한 논리 요구 사항 식별 - [ ] 필요하면 Spring Boot 프로젝트를 생성 - [ ] Supabase JWT 검증 구성 - [ ] PostgreSQL Supabase에 연결 - [ ] 복잡한 기능 마이그레이션 - [ ] 예정된 작업 구현 - [ ] Spring Boot 배포 (Cloud Run 등)&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;_피해야_할_안티패턴&amp;quot;&amp;gt;피해야 할 안티패턴&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;❌ 하지 말 것 :&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;Supabase와 Spring Boot 사이의 데이터 복제&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;두 개의 별도 PostgreSQL 데이터베이스 생성&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;인증을 직접 코딩하세요&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Spring Boot를 이용해 간단한 CRUD&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;초기부터 과도하게 설계하다&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Supabase의 Row Level Security 무시하기&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;✅ 더 하다:&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;단 하나의 PostgreSQL 데이터베이스 공유&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Supabase가 인증을 관리하도록 두세요&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;복잡함을 위해서만 Spring Boot을 사용&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;간단히 시작하고 점차 발전하다&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;각 기술의 강점을 활용&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;데이터베이스 수준에서 RLS로 보안 강화하기&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;_발전_전망&amp;quot;&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;_새로운_기능_추가&amp;quot;&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&amp;gt;@startuml
skinparam backgroundColor #FEFEFE

rectangle &amp;quot;현재 아키텍처&amp;quot; #SkyBlue {
  [JBake]
  [Supabase]
  [Spring Boot]
}

rectangle &amp;quot;가능한 변화&amp;quot; #LightYellow {

  package &amp;quot;성능&amp;quot; {
    [Redis Cache]
    [CDN pour Assets]
    [PostgreSQL Replicas]
  }

  package &amp;quot;모니터링&amp;quot; {
    [Grafana]
    [Prometheus]
    [Sentry]
  }

  package &amp;quot;지속적 통합/지속적 배포&amp;quot; {
    [GitHub Actions]
    [Tests Automatisés]
    [Déploiement Continu]
  }

  package &amp;quot;기능&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;_수평_확장&amp;quot;&amp;gt;수평 확장&amp;lt;/h3&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;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;자동으로 수백만 건의 작업까지 확장&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;읽기 성능을 위한 읽기 복제본&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&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;스프링 부트 :&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;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Stateless 설계는 수평 확장을 용이하게 합니다.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&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;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;대용량 테이블 파티셔닝&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;연결 풀링 (PgBouncer)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&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;_건축적_증언&amp;quot;&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;_스타트업_saas_50k_사용자&amp;quot;&amp;gt;스타트업 SaaS (50k 사용자)&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;_ 우리는 Supabase로 100% 시작했습니다. 5k 사용자 수준에서 우리는 Stripe 결제 청구와 월간 보고서를 위해 Spring Boot만 추가했습니다. 1년 후, 우리의 운영 중 80%가 여전히 Supabase를 통해 이루어집니다. Spring Boot는 비판적인 부분만 처리합니다. 이러한 분리를 통해 리팩터링 없이 확장이 가능했습니다. &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;_e_러닝_플랫폼_10k_사용자&amp;quot;&amp;gt;E-러닝 플랫폼 (10k 사용자)&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;_ Supabase의 OAuth2 인증 덕분에 3주 개발 시간을 절약할 수 있었습니다. 자동 생성된 API가 전체 강의 카탈로그를 처리합니다. Spring Boot는 PDF 인증서와 진행 이메일에만 사용됩니다. 간단하고 유지보수 가능하며 확장 가능한 아키텍처. &amp;amp;lt;/think&amp;amp;gt; &amp;lt;/em&amp;gt;&amp;lt;em&amp;gt;&amp;lt;/em&amp;gt;&amp;lt;/em&amp;gt;&amp;lt;em&amp;gt;&amp;lt;em&amp;gt;&amp;lt;/em&amp;gt;&amp;lt;em&amp;gt;&amp;lt;/em&amp;gt;&amp;lt;/em&amp;gt;&amp;lt;em&amp;gt;&amp;lt;em&amp;gt;&amp;lt;/em&amp;gt;&amp;lt;em&amp;gt;&amp;lt;/em&amp;gt;&amp;lt;/em&amp;gt;&amp;lt;em&amp;gt;&amp;lt;em&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&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_b2b_마켓플레이스_3k_사용자&amp;quot;&amp;gt;B2B 마켓플레이스 (3k 사용자)&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은 구매자와 판매자 간 거래를 관리합니다(중요). Supabase는 나머지 모든 것을 관리합니다: 프로필, 실시간 메시징, 문서. 그들이 동일한 PostgreSQL 데이터베이스를 공유한다는 사실 덕분에 복잡한 동기화를 피할 수 있습니다. 우리가 한 최고의 아키텍처 선택입니다. &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;_요약_현대_건축_패러다임&amp;quot;&amp;gt;요약 : 현대 건축 패러다임&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Supabase + Spring Boot 하이브리드 아키텍처는 새로운 패러다임을 나타냅니다 :&amp;lt;strong&amp;gt;건축 전문화&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;구식 패러다임&amp;quot; #LightCoral {
  card &amp;quot;백엔드 안의 모든 것&amp;quot; {
    [Auth]
    [CRUD]
    [Logique Métier]
    [Jobs]
    [Storage]
    [Realtime]
  }
  note bottom
    Complexité maximale
    dès le jour 1
  end note
}

rectangle &amp;quot;새로운 패러다임&amp;quot; #LightGreen {
  card &amp;quot;Supabase
(편리함)&amp;quot; {
    [Auth]
    [CRUD Simple]
    [Storage]
    [Realtime]
  }

  card &amp;quot;Spring Boot
(차별화)&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;기본 원칙 :&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;_8020_법칙&amp;quot;&amp;gt;80/20 법칙&amp;lt;/h3&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;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%의 작업&amp;lt;/strong&amp;gt;기본 CRUD → 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%의 운영&amp;lt;/strong&amp;gt;비즈니스 로직이 필요 → 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;이 자연스러운 규칙은 하이브리드 아키텍처를 정당화한다.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;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;운영 분배&amp;quot; {

  rectangle &amp;quot;80% - 표준 운영&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% - 비즈니스 로직&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;_최종_결론&amp;quot;&amp;gt;최종 결론&amp;lt;/h2&amp;gt;
&amp;lt;div 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;strong&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;&amp;lt;strong&amp;gt;할 수 있습니다:&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;✅ 기능적인 MVP로 빠르게 시작하기&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;✅ 무거운 투자 없이 시장을 검증하세요&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;복잡도가 요구될 때 점진적으로 발전시키다&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;✅ 각 단계에서 비용을 관리하세요&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;✅ 각 기술의 최고를 활용하다&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;✅ 조기 과도한 엔지니어링 피하기&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&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;그녀는 나타냅니다:&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;실용주의&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;속도&amp;lt;/strong&amp;gt;: Time-to-market 최소한의&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;경제&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;확장성&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;견고성&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;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_당신의_다음_단계&amp;quot;&amp;gt;당신의 다음 단계&amp;lt;/h3&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;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;Supabase 계정을 만드세요&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;최소한의 데이터 스키마를 정의하세요&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;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;당신의 첫 번째 JBake 페이지를 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;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;작동 가능한 MVP를 갖게 될 것입니다&amp;lt;strong&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;Spring Boot은 필요할 때 자연스럽게 따라올 것입니다. 그 이전에는 아닙니다.&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;하이브리드 아키텍처는 필요한 것을 필요한 때에 정확히 만드는 예술이다.&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;/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;/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>하이브리드 아키텍처: Supabase와 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;읽기 시간 : 12 minutes&amp;lt;/div&amp;gt;
&amp;lt;p&amp;gt;Supabase의 단순함과 Spring Boot의 강력함 사이에서 선택하는 대신, 왜 두 가지를 결합하지 않을까요? 각 기술의 장점을 활용하는 하이브리드 아키텍처를 살펴보세요.&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;/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;_현대_건축의_거짓_딜레마&amp;quot;&amp;gt;현대 건축의 거짓 딜레마&amp;lt;/h2&amp;gt;
&amp;lt;div 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;/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;백엔드-아즈-어-서비스 솔루션에 모든 것을 걸다&amp;lt;/strong&amp;gt;(Supabase, 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;완전한 백엔드 구축&amp;lt;/strong&amp;gt;(Spring Boot, Node.js) - 강력하지만 복잡한&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;각 기술이 뛰어난 곳에서 그 기술을 사용하라&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;_건축적_비전&amp;quot;&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;_전통적인_아키텍처_모놀리식&amp;quot;&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&amp;gt;@startuml
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle

package &amp;quot;전통 건축&amp;quot; {
  actor Utilisateur as user
  component &amp;quot;프론트엔드&amp;quot; as frontend #LightBlue
  component &amp;quot;단일 백엔드&amp;quot; as backend #LightCoral {
    [Authentification]
    [CRUD]
    [Logique Métier]
    [Jobs]
    [APIs Tierces]
  }
  database &amp;quot;데이터베이스&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;이 접근 방식에서는 가장 단순한 작업(게시글 읽기, 댓글 작성)도 백엔드를 거쳐야 합니다. 첫날부터 복잡성의 비용을 지불하게 됩니다.&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;_baas_아키텍처_모든_대표&amp;quot;&amp;gt;BaaS 아키텍처: 모든 대표&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;순수 BaaS 아키텍처&amp;quot; {
  actor Utilisateur as user
  component &amp;quot;프론트엔드&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;반면에, BaaS에 모든 것을 위임하는 것은 처음에는 매력적이지만, 비즈니스 로직이 복잡해지면 바로 그 한계를 드러냅니다.&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;_하이브리드_아키텍처_책임_분리&amp;quot;&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&amp;gt;@startuml
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle

actor Utilisateur as user #LightBlue

package &amp;quot;프론트엔드 레이어&amp;quot; {
  component &amp;quot;정적 사이트 JBake&amp;quot; as frontend #SkyBlue
}

package &amp;quot;단순한 레이어 (Supabase)&amp;quot; #LightGreen {
  component &amp;quot;인증&amp;quot; as auth
  component &amp;quot;기본 CRUD&amp;quot; as crud
  component &amp;quot;파일 저장&amp;quot; as storage
  component &amp;quot;실시간&amp;quot; as realtime
}

package &amp;quot;비즈니스 레이어(Spring Boot)&amp;quot; #Coral {
  component &amp;quot;복잡한 논리&amp;quot; as logic
  component &amp;quot;오케스트레이션&amp;quot; as orchestration
  component &amp;quot;예정된 작업&amp;quot; as jobs
  component &amp;quot;통합&amp;quot; as integrations
}

database &amp;quot;공유 PostgreSQL&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;하이브리드 아키텍처는 복잡성과 운영의 성격에 따라 책임을 명확히 구분한다.&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;_디자인_원칙&amp;quot;&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;_원칙_1_간단하게_시작하고_지혜롭게_발전시키기&amp;quot;&amp;gt;원칙 1: 간단하게 시작하고, 지혜롭게 발전시키기&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;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;**단계 2 : 성장**&amp;quot; #LightYellow {
  component &amp;quot;JBake + Supabase
+ Spring Boot (선택 사항)&amp;quot; as growth
  note right
    ✓ Logique métier émerge
    ✓ Premiers jobs planifiés
    ✓ Intégrations tierces
  end note
}

rectangle &amp;quot;**단계 3 : 확장**&amp;quot; #LightCoral {
  component &amp;quot;완전한 아키텍처&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;필요하지 않다면 Spring Boot을 만들지 마세요.&amp;lt;/strong&amp;gt;Supabase로 시작하고, 복잡성이 이를 정당화할 때 Spring Boot을 추가하세요.&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_운영의_성질에_따른_분리&amp;quot;&amp;gt;원칙 2: 운영의 성질에 따른 분리&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;_원칙_3_하나의_진실한_출처&amp;quot;&amp;gt;원칙 3: 하나의 진실한 출처&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;스키마: auth (Supabase)&amp;quot; #LightGreen {
    [users]
    [sessions]
    [identities]
  }

  package &amp;quot;스키마: 공개 (공유)&amp;quot; #SkyBlue {
    [articles]
    [comments]
    [profiles]
  }

  package &amp;quot;스키마: 비즈니스 (스프링 부트)&amp;quot; #Coral {
    [orders]
    [payments]
    [reports]
  }
}

component &amp;quot;수퍼베이스&amp;quot; as supabase #LightGreen
component &amp;quot;스프링 부트&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;같은 PostgreSQL 인스턴스를 공유함으로써 Supabase와 Spring Boot는 복잡한 동기화 없이 동일한 데이터에서 작업합니다.&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;_플로우_오케스트레이션&amp;quot;&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;_플럭스_1_중앙_집중식_인증&amp;quot;&amp;gt;플럭스 1: 중앙 집중식 인증&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;사용자&amp;quot; as user
participant &amp;quot;JBake&amp;quot; as jbake
participant &amp;quot;Supabase 인증&amp;quot; as auth
participant &amp;quot;스프링 부트&amp;quot; as spring
database &amp;quot;PostgreSQL&amp;quot; as db

== Authentification ==
user -&amp;amp;gt; jbake : Clique &amp;quot;로그인&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;핵심 포인트&amp;lt;/strong&amp;gt;하나의 인증 프로세스, 하나의 JWT, 어디서든 사용 가능&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_2_복잡한_비즈니스_프로세스의_오케스트레이션&amp;quot;&amp;gt;Flux 2: 복잡한 비즈니스 프로세스의 오케스트레이션&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;클라이언트&amp;quot; as user
participant &amp;quot;제이베이크&amp;quot; as jbake
participant &amp;quot;스프링 부트&amp;quot; as spring
participant &amp;quot;Supabase 데이터베이스&amp;quot; as supadb
participant &amp;quot;스트라이프 API&amp;quot; as stripe
participant &amp;quot;인벤토리 서비스&amp;quot; as inventory
participant &amp;quot;이메일 서비스&amp;quot; as email
database &amp;quot;포스트그레스큐엘&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;Spring Boot가 제공하는 것&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;_플럭스_3_예정된_작업_및_동기화&amp;quot;&amp;gt;플럭스 3: 예정된 작업 및 동기화&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;스케줄러
(스프링 부트)&amp;quot; as scheduler
participant &amp;quot;리포트 서비스&amp;quot; as report
participant &amp;quot;포스트그레스큐엘&amp;quot; as db
participant &amp;quot;외부 API&amp;quot; as api
participant &amp;quot;이메일 서비스&amp;quot; as email
participant &amp;quot;Supabase 저장소&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;Spring Boot가 제공하는 것&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;_의사결정_매트릭스&amp;quot;&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;_supabase를_언제_사용해야_하나요&amp;quot;&amp;gt;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;_spring_boot을_언제_사용해야_하나&amp;quot;&amp;gt;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;_프로젝트_유형별_아키텍처_예시&amp;quot;&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;_블로그_포트폴리오&amp;quot;&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&amp;gt;@startuml
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle

package &amp;quot;개인 블로그 - 95% Supabase&amp;quot; {
  component &amp;quot;JBake&amp;quot; as jbake #SkyBlue

  package &amp;quot;Supabase (무료)&amp;quot; #LightGreen {
    [Auth: OAuth2 Google]
    [Articles (CRUD)]
    [Commentaires]
    [Images (Storage)]
    [Notifications Realtime]
  }

  package &amp;quot;Spring Boot (선택적)&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 (선택적)&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;판결&amp;lt;/strong&amp;gt;: 100% Supabase면 충분합니다.&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;_saas_애플리케이션&amp;quot;&amp;gt;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;SaaS 애플리케이션 - 하이브리드 아키텍처&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;스프링 부트&amp;quot; #Coral {
    [Facturation (Stripe)]
    [Analytics Avancées]
    [Rapports Mensuels]
    [Quotas &amp;amp;amp; Limites]
    [Webhooks Stripe]
  }

  cloud &amp;quot;외부 서비스&amp;quot; {
    [Stripe API]
    [SendGrid Email]
  }

  database &amp;quot;공유된 PostgreSQL&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;스프링 부트&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;판결&amp;lt;/strong&amp;gt;필요한 하이브리드 아키텍처. 핵심 부분은 Supabase를 사용하고, 중요한 부분(결제, 청구)에는 Spring Boot를 사용합니다.&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;_이커머스&amp;quot;&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&amp;gt;@startuml
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle

package &amp;quot;E-커머스 - 70% Spring Boot&amp;quot; {
  component &amp;quot;매장 (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 (핵심 비즈니스)&amp;quot; #Coral {
    [Gestion Commandes]
    [Paiements Stripe]
    [Gestion Stock]
    [Calcul Frais Port]
    [Génération Factures]
    [Synchronisation ERP]
    [Emails Transactionnels]
  }

  cloud &amp;quot;통합&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 (핵심 비즈니스)&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;판결&amp;lt;/strong&amp;gt;: Spring Boot 필수적입니다. Supabase은 인증 및 카탈로그에만 사용.&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;_점진적_마이그레이션_전략&amp;quot;&amp;gt;점진적 마이그레이션 전략&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;_비용_및_운영_고려_사항&amp;quot;&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;_비용_구조&amp;quot;&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&amp;gt;@startuml
skinparam backgroundColor #FEFEFE

package &amp;quot;예상 월간 비용&amp;quot; {

  rectangle &amp;quot;MVP 단계
(0-1000 사용자)&amp;quot; #LightGreen {
    [JBake (GitHub Pages): 0€]
    [Supabase: 0€]
    [Spring Boot: N/A]
    note bottom: **Total: 0€/mois**
  }

  rectangle &amp;quot;성장 단계
(1k-10k 사용자)&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;단계 규모\n(10k-100k 사용자)&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;단계 규모\n(10k-100k 사용자)&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;_운영_책임&amp;quot;&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&amp;gt;@startuml
skinparam backgroundColor #FEFEFE

package &amp;quot;누가 무엇을 관리하나요?&amp;quot; {

  rectangle &amp;quot;Supabase에서 관리 (0 노력)&amp;quot; #LightGreen {
    [Authentification]
    [Base de données]
    [Backups automatiques]
    [Scaling automatique]
    [Monitoring DB]
    [Sécurité DB]
  }

  rectangle &amp;quot;GitHub에 의해 관리됨 (노력 0)&amp;quot; #SkyBlue {
    [Hébergement site statique]
    [HTTPS/SSL]
    [CDN global]
    [Déploiement automatique]
  }

  rectangle &amp;quot;관리해야 할 (Spring Boot)&amp;quot; #LightCoral {
    [Déploiement application]
    [Monitoring APM]
    [Logs centralisés]
    [Scaling horizontal]
    [Mises à jour sécurité]
  }
}

note bottom of &amp;quot;관리해야 할 (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;_결론_완벽한_균형&amp;quot;&amp;gt;결론: 완벽한 균형&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Supabase + Spring Boot 하이브리드 아키텍처는 타협이 아니며, 그것은&amp;lt;strong&amp;gt;시너지&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;_기억해야_할_원칙들&amp;quot;&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&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;_이_아키텍처를_언제_선택해야_할까요&amp;quot;&amp;gt;이 아키텍처를 언제 선택해야 할까요?&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;✅ 이 아키텍처는 다음과 같은 경우에 이상적입니다 :&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;당신은 빠르게 시작하고 싶습니다 (MVP는 개월이 아닌 일 단위입니다)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;당신은 비즈니스 복잡성 성장을 예상합니다.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;초기 비용을 최소화하고 싶습니다.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;PostgreSQL을 좋아하고 단일 진실의 원천을 원합니다.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;당신은 비즈니스 로직을 위해 Kotlin과 코루틴을 좋아합니다.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&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;❌ 다음 조건이면 이 아키텍처는 적합하지 않습니다:&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;귀하의 프로젝트는 간단하며 그대로 유지됩니다 (개인 블로그 → 100% Supabase이면 충분합니다)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;당신은 이미 확립된 백엔드 스택을 숙지하고 있습니다.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;당신은 전통적인 모놀리스를 선호합니다&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;.NET, Python, 또는 다른 백엔드 언어가 필요합니다.&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;_최종_전체_비전&amp;quot;&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&amp;gt;@startuml
skinparam backgroundColor #FEFEFE

package &amp;quot;완전한 하이브리드 아키텍처&amp;quot; {

  actor &amp;quot;사용자&amp;quot; as users

  rectangle &amp;quot;프론트엔드 (정적)&amp;quot; #SkyBlue {
    [JBake / GitHub Pages]
    note right: Gratuit, CDN global
  }

  rectangle &amp;quot;간단한 백엔드 (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;비즈니스 백엔드 (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
단일 진실의 원천&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;_두_세계의_최고&amp;quot;&amp;gt;두 세계의 최고&amp;lt;/h3&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;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;🚀 서프베이스의 속도&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;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;몇 번의 클릭으로 간편한 OAuth2 인증&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;자동으로 생성된 REST API&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&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;💪 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;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;복잡한 업무 프로세스 오케스트레이션&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;신뢰할 수 있는 예약된 작업&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;ACID 트랜잭션 보장&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&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;💰 진보적인 경제&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€로 시작하고 확인하기&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;성장과 함께 증가하는 비용&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&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;🎯 건축적 유연성&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;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;필요한 경우에만 Spring Boot 추가&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;전체적인 벤더 락인이 없습니다.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&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;_더_깊이_가기_위해&amp;quot;&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;_기술_자원&amp;quot;&amp;gt;기술 자원&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;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;인증 가이드&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;행 수준 보안&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&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;스프링 부트 문서 :&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;Kotlin과 함께 Spring Boot&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;코루틴과 스프링&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 리소스 서버&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;추가 조항 :&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;JWT를 사용한 API 보안&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;PostgreSQL 성능 최적화&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;점진적 마이그레이션 패턴&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&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 class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_실제_사용_사례&amp;quot;&amp;gt;실제 사용 사례&amp;lt;/h3&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;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;: Supabase 인증, 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;마켓플레이스&amp;lt;/strong&amp;gt;: 카탈로그 Supabase, 트랜잭션 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;콘텐츠 플랫폼&amp;lt;/strong&amp;gt;Supabase 글, 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;내부 도구&amp;lt;/strong&amp;gt;CRUD Supabase, 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;_시작_체크리스트&amp;quot;&amp;gt;시작 체크리스트&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 - 기초 (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; Supabase 계정 만들기 - [ ] PostgreSQL 프로젝트 구성하기 - [ ] 인증 활성화 (Google, GitHub) - [ ] 초기 스키마 정의 - [ ] Row Level Security 설정 - [ ] JBake에서 API 테스트&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 (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; 주요 페이지 구현 - [ ] OAuth2 인증 통합 - [ ] CRUD 폼 생성 - [ ] 파일용 Storage 설정 - [ ] 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;&amp;lt;strong&amp;gt;Phase 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; 복잡한 논리 필요 사항 식별 - [ ] 필요 시 Spring Boot 프로젝트 생성 - [ ] Supabase JWT 검증 구성 - [ ] Supabase PostgreSQL에 연결하기 - [ ] 복잡한 기능 마이그레이션 - [ ] 예약된 작업 구현 - [ ] Spring Boot 배포하기 (Cloud Run 등)&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;_피해야_할_반패턴&amp;quot;&amp;gt;피해야 할 반패턴&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;❌ 하지 말아야 할 것 :&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;Supabase와 Spring Boot 사이에 데이터를 복제&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;두 개의 별도 PostgreSQL 데이터베이스를 생성&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;인증 직접 코딩하기&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Spring Boot를 사용하여 간단한 CRUD를 구현하다&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;처음부터 과도하게 설계하다&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Supabase의 Row Level Security 무시하기&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;✅ 더 나은 행동 :&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;하나의 PostgreSQL 데이터베이스 공유&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Supabase가 인증을 관리하도록 두다&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;복잡성을 위해 Spring Boot만 사용&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;간단히 시작하고, 점차적으로 발전시키다&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;각 기술의 강점을 활용&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;데이터베이스 수준에서 RLS를 적용하여 보안하기&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;_진화_전망&amp;quot;&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;_새로운_기능_추가&amp;quot;&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&amp;gt;@startuml
skinparam backgroundColor #FEFEFE

rectangle &amp;quot;현재 아키텍처&amp;quot; #SkyBlue {
  [JBake]
  [Supabase]
  [Spring Boot]
}

rectangle &amp;quot;가능한 발전&amp;quot; #LightYellow {

  package &amp;quot;성능&amp;quot; {
    [Redis Cache]
    [CDN pour Assets]
    [PostgreSQL Replicas]
  }

  package &amp;quot;모니터링&amp;quot; {
    [Grafana]
    [Prometheus]
    [Sentry]
  }

  package &amp;quot;지속적 통합/지속적 배포&amp;quot; {
    [GitHub Actions]
    [Tests Automatisés]
    [Déploiement Continu]
  }

  package &amp;quot;기능&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;_수평_스케일링&amp;quot;&amp;gt;수평 스케일링&amp;lt;/h3&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;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;자동으로 수백만 건의 작업까지 확장&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;읽기 전용 복제본을 위한 읽기 성능&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&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;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;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;무상태 설계는 수평 스케일링을 용이하게 한다.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;필요에 따라 오케스트레이션을 위한 Kubernetes&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;대용량 테이블 파티셔닝&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;연결 풀링 (PgBouncer)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&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;_건축학적_증언&amp;quot;&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;_startup_saas_50k_사용자&amp;quot;&amp;gt;Startup SaaS (50k 사용자)&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;_ 우리는 100% Supabase로 시작했습니다. 5k 사용자 때, 우리는 Stripe 결제 처리와 월간 보고서를 위해서만 Spring Boot를 추가했습니다. 1년 후, 우리 운영의 80%는 여전히 Supabase를 통해 처리됩니다. Spring Boot는 오직 핵심 부분만 처리합니다. 이 분리 덕분에 우리는 재설계 없이 확장할 수 있었습니다. &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;_e_러닝_플랫폼_10k_사용자&amp;quot;&amp;gt;E-러닝 플랫폼 (10k 사용자)&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;_ Supabase의 OAuth2 인증은 3주 개발 시간을 절약해 주었습니다. 자동 생성된 API는 우리 강의 카탈로그 전체를 처리합니다. Spring Boot는 PDF 인증서와 진행 상황 이메일에만 사용됩니다. 간단하고 유지보수 가능하며 확장 가능한 아키텍처. &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;_b2b_마켓플레이스_3k_사용자&amp;quot;&amp;gt;B2B 마켓플레이스 (3k 사용자)&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은 구매자와 판매자 간의 거래를 처리합니다(중요). Supabase는 나머지 모든 것을 처리합니다: 프로필, 실시간 메시징, 문서. 그들이 동일한 PostgreSQL 데이터베이스를 공유한다는 사실은 복잡한 동기화를 피하게 해줍니다. 우리가 내린 최고의 아키텍처 선택입니다. &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;_요약_현대적인_건축_패러다임&amp;quot;&amp;gt;요약: 현대적인 건축 패러다임&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Supabase + Spring Boot 하이브리드 아키텍처는 새로운 패러다임을 나타냅니다 :&amp;lt;strong&amp;gt;건축 전문 분야&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;구식 패러다임&amp;quot; #LightCoral {
  card &amp;quot;백엔드 안의 모든 것&amp;quot; {
    [Auth]
    [CRUD]
    [Logique Métier]
    [Jobs]
    [Storage]
    [Realtime]
  }
  note bottom
    Complexité maximale
    dès le jour 1
  end note
}

rectangle &amp;quot;새로운 패러다임&amp;quot; #LightGreen {
  card &amp;quot;Supabase
(편의)&amp;quot; {
    [Auth]
    [CRUD Simple]
    [Storage]
    [Realtime]
  }

  card &amp;quot;Spring Boot
(차별화)&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;기본적인 원칙 :&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;_8020_법칙&amp;quot;&amp;gt;80/20 법칙&amp;lt;/h3&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;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%의 운영&amp;lt;/strong&amp;gt;표준 CRUD → 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%의 운영&amp;lt;/strong&amp;gt;비즈니스 로직이 필요함 → 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;이 자연스러운 규칙은 하이브리드 아키텍처를 정당화합니다.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;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;작업 분배&amp;quot; {

  rectangle &amp;quot;80% - 표준 운영&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% - 비즈니스 로직&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;_최종_결론&amp;quot;&amp;gt;최종 결론&amp;lt;/h2&amp;gt;
&amp;lt;div 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;strong&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;&amp;lt;strong&amp;gt;다음과 같이 할 수 있습니다:&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;✅ 기능적인 MVP로 빠르게 시작하기&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;✅ 큰 투자 없이 시장 검증&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;✅ 복잡함이 요구될 때 점진적으로 발전하다&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;✅ 각 단계에서 비용을 관리하세요&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;✅ 각 기술의 장점 활용&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;조기 과설계 방지&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&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;그녀는:&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;실용주의&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;속도&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;경제&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;확장성&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;견고성&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;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_다음_단계&amp;quot;&amp;gt;다음 단계&amp;lt;/h3&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;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;Supabase 계정을 생성하세요&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;최소 데이터 스키마를 정의하세요&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;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;당신의 첫 JBake 페이지를 만들어 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;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;작동 가능한 MVP를 갖게 될&amp;lt;strong&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;Spring Boot은 필요할 때 자연스럽게 올 것입니다. 그 이전에는 오지 않을 것입니다.&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;하이브리드 아키텍처는 필요한 것을 필요한 때에 정확히 구축하는 예술이다.&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;/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;&amp;quot;이 기사를 공유하면 다른 개발자들이 올바른 아키텍처 선택을 하는 데 도움이 될 거라고 생각한다면 공유해 주세요.&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&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>프리로딩의 예술: JavaScript와 CSS로 테마 &quot;Flash&quot; 제거하기</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;#_요약&amp;quot;&amp;gt;요약&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_문제_이_저주받은_테마_깜박임&amp;quot;&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;#_사용자_경험의_악몽&amp;quot;&amp;gt;사용자 경험의 악몽&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_fouc_오래된_웹_문제&amp;quot;&amp;gt;FOUC: 오래된 웹 문제&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_품질_인식에_대한_영향&amp;quot;&amp;gt;품질 인식에 대한 영향&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_문제의_기술적_분석&amp;quot;&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;#_왜_클래식_스크립트가_충분하지_않을까요&amp;quot;&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;#_직관적이지만_비효율적인_접근&amp;quot;&amp;gt;직관적이지만 비효율적인 접근&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_domcontentloaded_이벤트_이해&amp;quot;&amp;gt;DOMContentLoaded 이벤트 이해&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_렌더_블로킹_문제&amp;quot;&amp;gt;렌더 블로킹 문제&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_문제_시각화&amp;quot;&amp;gt;문제 시각화&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_효과_없는_해결_시도들&amp;quot;&amp;gt;효과 없는 해결 시도들&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_진정한_해결책_더_빨리_행동하기&amp;quot;&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;#_해결책_사전_로딩_early_loading&amp;quot;&amp;gt;해결책 : 사전 로딩 (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;#_기본_원칙&amp;quot;&amp;gt;기본 원칙&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_왜_head가_이상적인_장소인가&amp;quot;&amp;gt;왜 `&amp;amp;lt;head&amp;amp;gt;`가 이상적인 장소인가?&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_3층_솔루션_아키텍처&amp;quot;&amp;gt;3층 솔루션 아키텍처&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;#_단계_1_사용자_선택_저장&amp;quot;&amp;gt;단계 1 : 사용자 선택 저장&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_당신의_지속적인_메모리&amp;quot;&amp;gt;localStorage : 당신의 지속적인 메모리&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_테마_백업_구현&amp;quot;&amp;gt;테마 백업 구현&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_오류_케이스_관리&amp;quot;&amp;gt;오류 케이스 관리&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_고급_지속성_전략&amp;quot;&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;#_단계_2_head_내의_프리로드_스크립트&amp;quot;&amp;gt;단계 2: &amp;lt;code&amp;gt;&amp;amp;lt;head&amp;amp;gt;&amp;lt;/code&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;#_해결책의_핵심&amp;quot;&amp;gt;해결책의 핵심&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_스크립트_해부_모든_라인이_중요&amp;quot;&amp;gt;스크립트 해부: 모든 라인이 중요&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_시스템_선호도_감지_보너스&amp;quot;&amp;gt;시스템 선호도 감지 (보너스)&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_성능_왜_이_스크립트는_빠른가&amp;quot;&amp;gt;성능 : 왜 이 스크립트는 빠른가&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_head_안에_최적의_배치&amp;quot;&amp;gt;&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;#_단계_3_css_속성_선택자의_힘&amp;quot;&amp;gt;단계 3 : 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;#_bootstrap_5_테마_시스템&amp;quot;&amp;gt;Bootstrap 5 테마 시스템&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_테마_시스템을_위한_css_구조&amp;quot;&amp;gt;테마 시스템을 위한 CSS 구조&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;_요약&amp;quot;&amp;gt;요약&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;이 기술적인 심도 있는 글에서, 우리는 페이지 로드 중에 발생하는 불쾌한 깜박임 효과 없이 라이트/다크 테마 선택기를 구현하는 방법을 탐구합니다. 우리는 브라우저 렌더링 주기를, FOUC(플래시 언스타일드 콘텐츠)의 근본 원인을 파악한 후, 동기식 사전 로드를 기반으로 한 견고한 솔루션을 제안합니다. 안의`&amp;amp;lt;head&amp;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;_문제_이_저주받은_테마_깜박임&amp;quot;&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;_사용자_경험의_악몽&amp;quot;&amp;gt;사용자 경험의 악몽&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;상상을 해보세요: 웹 애플리케이션용 아름다운 다크 테마를 디자인하는 데 몇 시간을 보냈습니다. 색상이 완벽하게 균형을 이루고, 대비가 최적이며, 사용자들이 이 옵션을 좋아합니다. 하지만 부끄러운 문제가 있습니다: 페이지를 새로 고칠 때마다, 몇 분의 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;#39;플래시&amp;amp;#8217;는 짧을 때도 (가끔 100ms 미만이지만) 인간의 눈에는 즉시 느껴지며 불쾌한 경험을 만든다. 어두운 테마를 시각적 편안함이나 접근성을 위해 선택한 사용자에게는 이 깜박임이 심지어 고통스러울 수 있다. 특히 조명이 어두운 환경에서는 더욱 그렇다.&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_오래된_웹_문제&amp;quot;&amp;gt;FOUC: 오래된 웹 문제&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;이 현상은 &amp;quot;Flash of Unstyled Content&amp;quot; (FOUC)라고 부르는 것의 변형이며, 이는 CSS 초기 시절부터 이어져 온 웹 개발의 고전적인 문제입니다. FOUC는 브라우저가 CSS 스타일이 적용되지 않은 HTML 콘텐츠를 일시적으로 표시할 때 발생하며, 이로 인해 스타일이 적용되지 않은 콘텐츠의 플래시가 발생합니다.&amp;lt;/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;/strong&amp;gt;(FOWT) - 내용이 스타일링되지만 잘못된 테마가 적용됩니다. 이는 특히 짜증나는 데, 애플리케이션이 페이지 로드마다 사용자 선호를 &amp;quot;잊어버린다&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;_품질_인식에_대한_영향&amp;quot;&amp;gt;품질 인식에 대한 영향&amp;lt;/h3&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;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&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;&amp;lt;strong&amp;gt;일관성 붕괴&amp;lt;/strong&amp;gt;: 응용 프로그램은 사용자의 선호를 &amp;quot;잊어버린&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;&amp;lt;strong&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;&amp;lt;strong&amp;gt;인식된 성능&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;_문제의_기술적_분석&amp;quot;&amp;gt;문제의 기술적 분석&amp;lt;/h3&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;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;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;CSS 스타일의 적용&amp;lt;/strong&amp;gt;: 브라우저가 CSS 규칙을 적용하고 시각적 렌더링을 계산&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;JavaScript 실행&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;문제는 이벤트 3번(자바스크립트 실행)이 브라우저가 이미 시작했거나 이벤트 2번(스타일 적용)을 마친 후에 발생할 때 나타납니다. 이 시점에서 브라우저는 이미 어떤 테마를 표시할지 결정했으며, 당신의 코드는 첫 번째 렌더링 이전에 영향을 미치기에 너무 늦게 도착합니다.&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;_왜_클래식_스크립트가_충분하지_않을까요&amp;quot;&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;_직관적이지만_비효율적인_접근&amp;quot;&amp;gt;직관적이지만 비효율적인 접근&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;개발자에게 가장 자연스러운 접근법은 스크립트를 우리의 끝에 배치하는 것이다.`&amp;amp;lt;body&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-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;이 접근 방식은 처음 보면 논리적으로 보입니다. 우리는 DOM이 준비될 때까지 기다렸다가 테마를 적용합니다. 간단하죠? 불행히도, 이 간결함은 브라우저 렌더링 사이클의 타이밍과 관련된 근본적인 결함을 숨깁니다.&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;_domcontentloaded_이벤트_이해&amp;quot;&amp;gt;DOMContentLoaded 이벤트 이해&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;이벤트`DOMContentLoaded`초기 HTML 문서가 브라우저에 의해 완전히 로드되고 분석될 때 트리거됩니다,&amp;lt;strong&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;다음은 전형적인 사건의 순서 :&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;브라우저가 HTML 다운로드를 시작합니다.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;수신되는 대로 HTML을 분석합니다.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;그는 태그를 발견한다.`&amp;amp;lt;link&amp;amp;gt;`CSS용이고 다운로드를 시작합니다&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;그는 태그를 발견한다`&amp;amp;lt;script&amp;amp;gt;`그리고 그것들을 실행합니다 (그들의 유형과 속성에 따라)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;그는 DOM(문서 개체 모델)을 구축합니다.&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;이벤트 `DOMContentLoaded`가 발생합니다&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;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;첫 번째 페인트(표시) 발생&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;이벤트`load`모두 로드될 때 트리거됩니다&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;문제? DOMContentLoaded(6단계)와 첫 페인트(8단계) 사이에 브라우저는 페이지를 어떻게 표시할지 이미 결정을 내렸습니다. 테마 변경 스크립트가 6단계에서 실행되면, 기본 스타일로 첫 번째 표시를 피하기엔 이미 너무 늦습니다.&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;_렌더_블로킹_문제&amp;quot;&amp;gt;렌더 블로킹 문제&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;사실, 타이밍은 더욱 복잡합니다. 최신 브라우저는 인식된 성능을 개선하기 위해 정교한 최적화 기법을 사용합니다. 사용자가 화면에 무언가를 볼 수 있도록 첫 번째 페인트(First Contentful Paint)를 가능한 한 빠르게 수행하려고 합니다.&amp;lt;/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는 기본적으로 &amp;quot;render-blocking&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;그런데 여기서 함정이 있습니다 : 브라우저가 이 CSS 스타일을 처음 적용할 때, 현재 DOM 상태를 기준으로 그렇게 합니다. 만약 속성`data-bs-theme`태그에 아직 정의되지 않았습니다.&amp;lt;code&amp;gt;&amp;amp;lt;html&amp;amp;gt;&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;그다음에, 스크립트가 실행되어 이 속성을 변경하면, 브라우저는 :&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;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;필요하다면 레이아웃을 다시 구성하세요.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&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;/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;_문제_시각화&amp;quot;&amp;gt;문제 시각화&amp;lt;/h3&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 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;HTML 파서&amp;quot; as Parser
participant &amp;quot;CSSOM&amp;quot; as CSS
participant &amp;quot;Script.js (body 끝)&amp;quot; as Script
participant &amp;quot;렌더링 엔진&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: 기본 테마 (light)
Render -&amp;amp;gt; Navigateur: 첫 번째 페인트 (라이트 테마)
note right: 사용자는 라이트 테마를 본다
deactivate Render

Navigateur -&amp;amp;gt; Script: 스크립트를 실행한다
activate Script
Script -&amp;amp;gt; Script: 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: 스타일을 재계산하고 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;이 다이어그램은 문제를 명확히 보여줍니다: 첫 번째 페인트는 우리 스크립트가 올바른 테마를 정의할 기회가 있기 전에 발생합니다. 이어지는 리페인트가 보이는 깜박임을 유발합니다.&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;_효과_없는_해결_시도들&amp;quot;&amp;gt;효과 없는 해결 시도들&amp;lt;/h3&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;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;방법 1 : 로드될 때까지 콘텐츠 숨기기&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;이 접근 방식은 JavaScript가 올바른 테마를 설정할 때까지 모든 콘텐츠를 숨깁니다. 문제점? 이는 콘텐츠 표시를 인위적으로 지연시켜 사이트가 더 느리게 느껴지게 만듭니다. 게다가 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;&amp;lt;strong&amp;gt;방법 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;Approach 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;방법 3: 어두운 테마로 기본 설정&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;일부 개발자는 CSS에서 다크 테마를 기본값으로 설정합니다. 이로 인해 다크 테마 사용자의 깜빡임을 방지하지만, 라이트 테마 사용자에게는 반대로 문제를 일으킵니다.&amp;lt;/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&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_진정한_해결책_더_빨리_행동하기&amp;quot;&amp;gt;진정한 해결책 : 더 빨리 행동하기&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;이 문제를 해결하는 열쇠는 해당 속성을 정의해야 함을 깨닫는 것`data-bs-theme` &amp;lt;strong&amp;gt;전에&amp;lt;/strong&amp;gt;브라우저가 CSS 스타일을 적용하기 시작하지 않습니다. 이는 우리 스크립트가 페이지 수명 주기에서 더 일찍 실행되어야 함을 의미하며, 이것이 바로 다음 섹션에서 탐구할 내용입니다.&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;_해결책_사전_로딩_early_loading&amp;quot;&amp;gt;해결책 : 사전 로딩 (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;_기본_원칙&amp;quot;&amp;gt;기본 원칙&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;애플리케이션의 상태를 브라우저의 렌더링 프로세스와 동기화하다&amp;lt;/strong&amp;gt;. 페이지가 로드될 때까지 기다리는 대신 테마를 설정해야 하며, 이를 설정해야 합니다.&amp;lt;strong&amp;gt;동안&amp;lt;/strong&amp;gt;로드 중, 심지어 CSS 스타일이 적용되기도 전에.&amp;lt;/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;#39;Early Loading&amp;#39; 또는 &amp;#39;Synchronous Preloading&amp;amp;#8217;이라고 부릅니다. 아이디어는 페이지 생명주기에서 가능한 가장早く 우리 테마 감지 로직을 실행하는 것인데, 이상적으로는 태그에서`&amp;amp;lt;head&amp;amp;gt;`, 브라우저가 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;_왜_head가_이상적인_장소인가&amp;quot;&amp;gt;왜 `&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;Le `&amp;amp;lt;head&amp;amp;gt;`HTML 문서는 브라우저에 의해 위에서 아래로 순차적으로 처리됩니다. 각 요소는 나타나는 순서대로 처리됩니다. 이 특성은 우리의 해결책에 매우 중요합니다.&amp;lt;/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;amp;lt;script&amp;amp;gt;`안에`&amp;amp;lt;head&amp;amp;gt;`속성 없이`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;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;스크립트를 다운로드해&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;즉시 스크립트를 실행해&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;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;이 행동은 일반적으로 성능 문제로 간주됨(따라서 스크립트를 페이지 끝에 배치하는 것이 일반적인 권장 사항임)이며, 이 특정 경우에 우리의 아군이 됩니다. 우리 테마 감지 스크립트를 페이지 시작에 배치함으로써`&amp;amp;lt;head&amp;amp;gt;`, 우리는それが 브라우저가 태그를 만나기 전에 실행된다고 보장합니다.`&amp;amp;lt;link&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;_3층_솔루션_아키텍처&amp;quot;&amp;gt;3층 솔루션 아키텍처&amp;lt;/h3&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;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;레이어 1 : 지속성 (localStorage)&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;2 계층: 조기 동기화 (인라인 스크립트 &amp;lt;code&amp;gt;&amp;amp;lt;head&amp;amp;gt;&amp;lt;/code&amp;gt; 내)&amp;lt;/strong&amp;gt; 이 레이어는 초기 렌더링 전에 애플리케이션의 상태를 DOM과 동기화합니다.&amp;lt;/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;레이어 3 : 반응형 스타일 (CSS 속성 선택자)&amp;lt;/strong&amp;gt; 이 레이어는 레이어 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;이제 각 층을 자세히 탐구해 봅시다.&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;_단계_1_사용자_선택_저장&amp;quot;&amp;gt;단계 1 : 사용자 선택 저장&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_당신의_지속적인_메모리&amp;quot;&amp;gt;localStorage : 당신의 지속적인 메모리&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`는 브라우저에 키-값 쌍을 지속적으로 저장할 수 있는 웹 스토리지 API입니다. 쿠키와 달리, 데이터의`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;절대 자동으로 서버에 전송되지 않습니다.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;더 큰 저장 용량을 가지고 있습니다(일반적으로 5-10MB)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;만료 날짜가 없습니다(명시적 삭제까지 지속됨)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;프로토콜과 도메인에 제한됩니다 (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;우리의 사용 사례에 대해`localStorage`완벽합니다. 왜냐하면:&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;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;우리는 선호가 무기한 지속되기를 원합니다.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&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 class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_테마_백업_구현&amp;quot;&amp;gt;테마 백업 구현&amp;lt;/h3&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-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;_오류_케이스_관리&amp;quot;&amp;gt;오류 케이스 관리&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;필수적으로 관리해야 하는 경우에 대한`localStorage`는 사용할 수 없거나 접근할 수 없습니다. 여러 시나리오가 접근을 차단할 수 있습니다.&amp;lt;code&amp;gt;localStorage&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;&amp;lt;strong&amp;gt;엄격한 개인 정보 보호 브라우징&amp;lt;/strong&amp;gt;: Safari가 비공개 모드에서 예외를 발생시킵니다`QuotaExceededError`쓰는 시도 중`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;프라이버시 설정&amp;lt;/strong&amp;gt;: 일부 브라우저 또는 개인정보 보호 확장 프로그램은 액세스를 차단할 수 있습니다.&amp;lt;code&amp;gt;localStorage&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;&amp;lt;strong&amp;gt;도메인 제한&amp;lt;/strong&amp;gt; : Le `localStorage`프로토콜에서 접근할 수 없습니다`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;&amp;lt;strong&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;그래서 우리 코드는 블록을 사용합니다.`try&amp;amp;#8230;&amp;amp;#8203;catch`이러한 경우를 우아하게 처리하기 위해, 지속성이 제공되지 않더라도 테마 변경 기능을 계속 제공합니다.&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;_고급_지속성_전략&amp;quot;&amp;gt;고급 지속성 전략&amp;lt;/h3&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;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;서버 동기화 (선택 사항)&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;이 접근 방식은 로그인된 사용자를 위해 기기 간 선호도를 동기화하면서도 즉각적인 로컬 응답성을 유지합니다.&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;_단계_2_head_내의_프리로드_스크립트&amp;quot;&amp;gt;단계 2: &amp;lt;code&amp;gt;&amp;amp;lt;head&amp;amp;gt;&amp;lt;/code&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;_해결책의_핵심&amp;quot;&amp;gt;해결책의 핵심&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;인라인&amp;lt;/strong&amp;gt;우리 안에 직접적으로`&amp;amp;lt;head&amp;amp;gt;`, 모든 우리 태그 앞에`&amp;amp;lt;link&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-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;_스크립트_해부_모든_라인이_중요&amp;quot;&amp;gt;스크립트 해부: 모든 라인이 중요&amp;lt;/h3&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;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;즉시 실행 함수 표현(IIFE)&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;이 구조는 즉시 실행되는 함수를 만든다. 왜? 로컬 스코프에서 변수를 격리하여 전역 스코프를 오염시키지 않기 위해서다. 우리가 단지 사용하고 있을 뿐이라도`const`(블록 스코프를 가지는), IIFE는 우리의 의도를 명확히 하고 이름 충돌을 방지하는 좋은 관행입니다.&amp;lt;/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;/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;이 지시문은 JavaScript의 엄격 모드를 활성화합니다, 이는 : 미선언된 변수 사용을 금지합니다. - 위험한 작업에 대한 오류를 발생시킵니다 - 특정 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;중요한 스크립트인 경우에는 최대 보안을 원합니다.&amp;lt;/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;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;이 블록은 절대적으로 중요합니다. 이는 어떤 문제가 발생하더라도 (localStorage 차단, improb한 문법 오류, 등), 우리의 스크립트가 전체 페이지 로드를 차단하지 않음을 보장합니다. 사용법은`console.warn`보다는`console.error`이는 비중요한 문제임을 나타냅니다.&amp;lt;/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;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;이 줄은 일부 컨텍스트(사파리의 엄격한 사적 브라우징)에서 예외를 발생시킬 수 있습니다. 이것이 바로 이 줄이 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;조건부 적용&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;주제를 저장된 것을 찾았을 경우에만 적용하고, 그렇지 않으면 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;_시스템_선호도_감지_보너스&amp;quot;&amp;gt;시스템 선호도 감지 (보너스)&amp;lt;/h3&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-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;이 기능은 미디어 쿼리를 사용합니다.`prefers-color-scheme`시스템을 쿼리하기 위해. macOS, Windows 10+, iOS, 그리고 최신 Android에서 이 쿼리는 사용자의 시스템 기본 설정을 반환합니다.&amp;lt;/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;/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;고려 사항:&amp;lt;/strong&amp;gt; - 모든 브라우저가 이 기능을 지원하지는 않습니다(하지만 2020년부터 지원이 뛰어납니다) - 검증`window.matchMedia`호환성을 보장합니다 - 사용자는 언제든지 이 선택을 재정의할 수 있습니다.&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;_성능_왜_이_스크립트는_빠른가&amp;quot;&amp;gt;성능 : 왜 이 스크립트는 빠른가&amp;lt;/h3&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;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;최소 크기&amp;lt;/strong&amp;gt;: 약 300바이트(비압축), 200바이트(압축). 이미지나 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;&amp;lt;strong&amp;gt;인라인&amp;lt;/strong&amp;gt;추가 HTTP 요청이 없습니다. 스크립트는 HTML 내에 있으므로 즉시 사용 가능합니다.&amp;lt;/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;/strong&amp;gt;: 로컬스토리지의 키 읽기 (초고속 작업) 및 DOM 속성 수정 (브라우저 네이티브 작업).&amp;lt;/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;/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;단일 실행&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;실제로 현대 하드웨어에서는 이 스크립트가 1밀리초 미만으로 실행되며, 이는 페이지 로드 성능에 전혀 영향을 주지 않는 거의 느낄 수 없는 시간이다.&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;_head_안에_최적의_배치&amp;quot;&amp;gt;&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;요소들의 순서가`&amp;amp;lt;head&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-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;이 주문은 다음을 보장합니다 :&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;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;우리 스크립트는 CSS가 로드되기 전에 실행됩니다.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;CSS는 나중에 로드되어 올바른 테마를 직접 적용합니다.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&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&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_단계_3_css_속성_선택자의_힘&amp;quot;&amp;gt;단계 3 : 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;_bootstrap_5_테마_시스템&amp;quot;&amp;gt;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는 CSS 커스텀 속성(변수)과 속성 선택자를 기반으로 하는 우아한 테마 관리 시스템을 도입했습니다. 이 시스템은 속성을`data-bs-theme`요소 위`&amp;amp;lt;html&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;이 시스템의 아름다움은 그 단순함에 있다: 수천 개의 요소에 대한 다양한 스타일시트를 로드하거나 클래스를 토글하는 대신, 단일 요소의 속성만 바꾸면 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;_테마_시스템을_위한_css_구조&amp;quot;&amp;gt;테마 시스템을 위한 CSS 구조&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;다음은 견고한 테마 시스템을 구현하기 위한 완전한 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-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>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;#_소개&amp;quot;&amp;gt;소개&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_문제_gradlerunner_의존성&amp;quot;&amp;gt;문제: 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;#_플러그인_개발의_맥락&amp;quot;&amp;gt;플러그인 개발의 맥락&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_로깅_딜레마&amp;quot;&amp;gt;로깅 딜레마&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_잘못된_좋은_생각들&amp;quot;&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;#_해결책_독립_로거&amp;quot;&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;#_추천_아키텍처&amp;quot;&amp;gt;추천 아키텍처&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_실행_흐름&amp;quot;&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;#_완전_구현&amp;quot;&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;#_gradle_의존성&amp;quot;&amp;gt;Gradle 의존성&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_logback_구성&amp;quot;&amp;gt;Logback 구성&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_완전한_테스트_클래스&amp;quot;&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;#_다단계_로깅_스키마&amp;quot;&amp;gt;다단계 로깅 스키마&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_모범_사례&amp;quot;&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;#_권장_로깅_구조&amp;quot;&amp;gt;권장 로깅 구조&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_가독성을_위한_시각적_상징&amp;quot;&amp;gt;가독성을 위한 시각적 상징&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_출력에서_정보_추출&amp;quot;&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;#_전후_비교&amp;quot;&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;#_이전_문제적_접근&amp;quot;&amp;gt;이전 : 문제적 접근&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_후_강건한_접근법&amp;quot;&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;#_요약도&amp;quot;&amp;gt;요약도&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_피해야_할_함정&amp;quot;&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;#_함정_1번_잊기_forwardoutput&amp;quot;&amp;gt;❌ 함정 1번 : 잊기 `.forwardOutput()&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_함정_2번_초기화되지_않은_로거&amp;quot;&amp;gt;❌ 함정 2번 : 초기화되지 않은 로거&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_함정_3번_과도한_로깅&amp;quot;&amp;gt;❌ 함정 3번 : 과도한 로깅&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;#_결과_및_지표&amp;quot;&amp;gt;결과 및 지표&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_결론&amp;quot;&amp;gt;결론&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_자원&amp;quot;&amp;gt;자원&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_더_멀리_가다&amp;quot;&amp;gt;더 멀리 가다&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;_소개&amp;quot;&amp;gt;소개&amp;lt;/h2&amp;gt;
&amp;lt;div 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 플러그인을 개발하는 동안, 기능 테스트는 실제 Gradle 환경에서 플러그인의 동작을 검증하는 데 필수적입니다. 그러나 중요한 질문이 빠르게 제기됩니다:&amp;lt;strong&amp;gt;어떻게 `GradleRunner`와 문제의존성을 만들지 않고 효과적인 로깅 시스템을 설정할 수 있나요?&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;/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 기능 테스트에서의 로깅 문제&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;SLF4J과 Logback을 사용한 견고한 솔루션의 아키텍처&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;완전한 구현과 구체적인 예시&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&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;_문제_gradlerunner_의존성&amp;quot;&amp;gt;문제: 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;_플러그인_개발의_맥락&amp;quot;&amp;gt;플러그인 개발의 맥락&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;그레이들 플러그인을 개발할 때 일반적으로 를 사용합니다`GradleRunner`&amp;lt;em&amp;gt;Gradle TestKit&amp;lt;/em&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;@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;_로깅_딜레마&amp;quot;&amp;gt;로깅 딜레마&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;로깅은 초기화 여부가 확실하지 않은 객체에 의존해서는 안 된다&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;문제적인 아키텍처&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;_잘못된_좋은_생각들&amp;quot;&amp;gt;잘못된 좋은 생각들&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;하지 마세요 :&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;_해결책_독립_로거&amp;quot;&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;_추천_아키텍처&amp;quot;&amp;gt;추천 아키텍처&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;완전히 독립적인&amp;lt;/strong&amp;gt; du &amp;lt;code&amp;gt;GradleRunner&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&amp;gt;@startuml
!define RECTANGLE class

skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle

package &amp;quot;올바른 아키텍처&amp;quot; {
    RECTANGLE TestClass {
        + {static} logger: Logger ✓
        + runTest()
    }

    RECTANGLE SLF4J {
        + LoggerFactory
    }

    RECTANGLE GradleRunner {
        + build()
        + forwardOutput()
    }

    RECTANGLE &amp;quot;콘솔 출력&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;_실행_흐름&amp;quot;&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&amp;gt;@startuml
!theme plain
skinparam backgroundColor #FEFEFE

participant &amp;quot;JUnit&amp;quot; as junit
participant &amp;quot;테스트클래스&amp;quot; as test
participant &amp;quot;로거 (SLF4J)&amp;quot; as logger
participant &amp;quot;GradleRunner&amp;quot; as runner
participant &amp;quot;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;세트업 테스트...&amp;quot;)

junit -&amp;amp;gt; test : @Test
test -&amp;amp;gt; logger : info(&amp;quot;시작 중...&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;출력 분석 중...&amp;quot;)
test -&amp;amp;gt; test : analyze output
test -&amp;amp;gt; logger : info(&amp;quot;✓ 테스트 통과&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;_완전_구현&amp;quot;&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;_gradle_의존성&amp;quot;&amp;gt;Gradle 의존성&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;필요한 종속성을 추가하세요`build.gradle.kts`:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;_logback_구성&amp;quot;&amp;gt;Logback 구성&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;파일을 만드세요`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;_완전한_테스트_클래스&amp;quot;&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;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;_다단계_로깅_스키마&amp;quot;&amp;gt;다단계 로깅 스키마&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;로깅 레벨&amp;quot; {

    rectangle &amp;quot;흔적&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;디버그&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;정보&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;경고&amp;quot; #FFFFC8 {
        note
            Avertissements
            • Comportements inattendus
            • Outcomes inhabituels
        end note
    }

    rectangle &amp;quot;오류&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;_모범_사례&amp;quot;&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;_권장_로깅_구조&amp;quot;&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;@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;_가독성을_위한_시각적_상징&amp;quot;&amp;gt;가독성을 위한 시각적 상징&amp;lt;/h3&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-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;_출력에서_정보_추출&amp;quot;&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;// 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;_전후_비교&amp;quot;&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;_이전_문제적_접근&amp;quot;&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;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;_후_강건한_접근법&amp;quot;&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;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;_요약도&amp;quot;&amp;gt;요약도&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 기능 테스트에서의 완전한 로깅 흐름
actor &amp;quot;개발자&amp;quot; as dev
participant &amp;quot;JUnit&amp;quot; as junit
participant &amp;quot;테스트클래스&amp;quot; as test
participant &amp;quot;Logger\n(SLF4J)&amp;quot; as logger
participant &amp;quot;로그백&amp;quot; as logback
participant &amp;quot;GradleRunner&amp;quot; as runner
participant &amp;quot;콘솔/파일&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;초기화&amp;quot;)
logger -&amp;amp;gt; output : [INFO] Initialisation

junit -&amp;amp;gt; test : @BeforeEach
test -&amp;amp;gt; logger : debug(&amp;quot;설정 테스트&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;시작&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;출력 분석 중&amp;quot;)
logger -&amp;amp;gt; output : [DEBUG] Analyzing output

test -&amp;amp;gt; test : assertions
test -&amp;amp;gt; logger : info(&amp;quot;✓ 테스트 통과&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;정리&amp;quot;)
logger -&amp;amp;gt; output : [DEBUG] Cleanup

junit -&amp;amp;gt; test : @AfterAll
test -&amp;amp;gt; logger : info(&amp;quot;테스트 종료&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;_피해야_할_함정&amp;quot;&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;_함정_1번_잊기_forwardoutput&amp;quot;&amp;gt;❌ 함정 1번 : 잊기 `.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;_함정_2번_초기화되지_않은_로거&amp;quot;&amp;gt;❌ 함정 2번 : 초기화되지 않은 로거&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;_함정_3번_과도한_로깅&amp;quot;&amp;gt;❌ 함정 3번 : 과도한 로깅&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;_결과_및_지표&amp;quot;&amp;gt;결과 및 지표&amp;lt;/h2&amp;gt;
&amp;lt;div 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;/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;메트릭&amp;lt;/th&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;이후&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;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;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줄&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;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 라인 (+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;디버그 시간&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;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;~5 min (-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;감지된 오류&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;유지보수&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;독립&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;/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;_결론&amp;quot;&amp;gt;결론&amp;lt;/h2&amp;gt;
&amp;lt;div 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 플러그인의 기능 테스트에서 안정적인 로깅 시스템을 구축하는 것은 간단한 원칙에 기반합니다 :&amp;lt;strong&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;주요 포인트:&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;독립형 로거&amp;lt;/strong&amp;gt;: 에서 초기화된`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&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;: 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;구조화된 로깅&amp;lt;/strong&amp;gt;: INFO 단계용, DEBUG 세부정보용&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;출력 분석&amp;lt;/strong&amp;gt;:부터 정보 추출`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;이 접근 방식은 테스트를 보장합니다.&amp;lt;strong&amp;gt;유지 가능한&amp;lt;/strong&amp;gt;, &amp;lt;strong&amp;gt;디버그 가능한&amp;lt;/strong&amp;gt; et &amp;lt;strong&amp;gt;튼튼한&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;_자원&amp;quot;&amp;gt;자원&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 문서&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;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;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;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;_더_멀리_가다&amp;quot;&amp;gt;더 멀리 가다&amp;lt;/h2&amp;gt;
&amp;lt;div 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;/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;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;로그가 포함된 HTML 테스트 보고서&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;JVM 연결 고급 디버깅&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&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;이 글은 내 기술 블로그의 &amp;quot;Gradle 플러그인 개발&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&amp;gt;</description>
        </item><item>
            <title>AsciiDoc에서 색이 있는 사각형 표시하기: 실용 가이드</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;#_소개&amp;quot;&amp;gt;소개&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_문제&amp;quot;&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;#_사용_컨텍스트&amp;quot;&amp;gt;사용 컨텍스트&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_가능한_접근_방법&amp;quot;&amp;gt;가능한 접근 방법&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_유니코드_문자&amp;quot;&amp;gt;옵션 1: 유니코드 문자&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_옵션_2_외부_이미지&amp;quot;&amp;gt;옵션 2 : 외부 이미지&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_옵션_3_사용자_정의_css_역할&amp;quot;&amp;gt;옵션 3: 사용자 정의 CSS 역할&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_옵션_4_html_주입_선택된_솔루션&amp;quot;&amp;gt;옵션 4 : HTML 주입 (선택된 솔루션)&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;#_해결책_를_사용한_html_인젝션&amp;quot;&amp;gt;해결책: 를 사용한 HTML 인젝션&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;#_작동_원리&amp;quot;&amp;gt;작동 원리&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_구현&amp;quot;&amp;gt;구현&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;#_css_속성_설명&amp;quot;&amp;gt;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;#_완전한_예시&amp;quot;&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;#_최적화_및_모범_사례&amp;quot;&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;#_밝은_색_관리&amp;quot;&amp;gt;밝은 색 관리&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_정사각형_크기_조정&amp;quot;&amp;gt;정사각형 크기 조정&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_변형_만들기&amp;quot;&amp;gt;변형 만들기&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;#_색_원&amp;quot;&amp;gt;색 원&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_색_테두리가_있는_정사각형&amp;quot;&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;li&amp;gt;&amp;lt;a href=&amp;quot;#_솔루션_아키텍처&amp;quot;&amp;gt;솔루션 아키텍처&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_장점과_한계&amp;quot;&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;#_장점&amp;quot;&amp;gt;장점&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_제한&amp;quot;&amp;gt;제한&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_접근성_개선&amp;quot;&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;#_구체적인_예시_주제_문서&amp;quot;&amp;gt;구체적인 예시: 주제 문서&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_결론&amp;quot;&amp;gt;결론&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_자원&amp;quot;&amp;gt;자원&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;_소개&amp;quot;&amp;gt;소개&amp;lt;/h2&amp;gt;
&amp;lt;div 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은 문서 작성에 매우 강력하지만, 색상 샘플을 표시하기 위한 네이티브 구문을 제공하지 않습니다. 이 글에서는 이 문제를 살펴보고 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;_문제&amp;quot;&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;_사용_컨텍스트&amp;quot;&amp;gt;사용 컨텍스트&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;상상해 보세요. 웹 애플리케이션 테마의 CSS 변수를 문서화하고 있습니다. 다음이 필요합니다:&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;16진수 색상 코드 나열&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;각 색상의 시각적 미리보기 표시&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;소스 문서의 가독성 유지&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;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;기술 작가&amp;quot; as writer
actor &amp;quot;독자&amp;quot; as reader

rectangle &amp;quot;문서 AsciiDoc&amp;quot; {
  usecase &amp;quot;색상 팔레트 문서화&amp;quot; as UC1
  usecase &amp;quot;16진수 코드 표시&amp;quot; as UC2
  usecase &amp;quot;색상 시각화&amp;quot; as UC3
  usecase &amp;quot;정적 사이트 생성&amp;quot; as UC4
  usecase &amp;quot;문서 참조&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;_가능한_접근_방법&amp;quot;&amp;gt;가능한 접근 방법&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;여러 가지 해결책을 고려하여 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;가능한 해결책&amp;quot; {
  component &amp;quot;유니코드 문자&amp;quot; as unicode
  component &amp;quot;외부 이미지&amp;quot; as images
  component &amp;quot;사용자 정의 CSS 역할&amp;quot; as css
  component &amp;quot;HTML 삽입&amp;quot; as html
}

package &amp;quot;평가 기준&amp;quot; {
  component &amp;quot;간결함&amp;quot; as simple
  component &amp;quot;휴대성&amp;quot; as portable
  component &amp;quot;맞춤 설정&amp;quot; as custom
  component &amp;quot;유지 보수&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;_옵션_1_유니코드_문자&amp;quot;&amp;gt;옵션 1: 유니코드 문자&amp;lt;/h4&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;유니코드 문자를 사용하는`&amp;amp;#9632;`(■)은 간단하지만 제한적입니다:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;제한 사항&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;sect3&amp;quot;&amp;gt;
&amp;lt;h4 id=&amp;quot;_옵션_2_외부_이미지&amp;quot;&amp;gt;옵션 2 : 외부 이미지&amp;lt;/h4&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;각 색상에 대해 PNG 또는 SVG 이미지를 생성합니다 :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;제한점&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;sect3&amp;quot;&amp;gt;
&amp;lt;h4 id=&amp;quot;_옵션_3_사용자_정의_css_역할&amp;quot;&amp;gt;옵션 3: 사용자 정의 CSS 역할&amp;lt;/h4&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;CSS 클래스를 정의하고 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;제한 사항&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;sect3&amp;quot;&amp;gt;
&amp;lt;h4 id=&amp;quot;_옵션_4_html_주입_선택된_솔루션&amp;quot;&amp;gt;옵션 4 : HTML 주입 (선택된 솔루션)&amp;lt;/h4&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;매크로 사용``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-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;_해결책_를_사용한_html_인젝션&amp;quot;&amp;gt;해결책: 를 사용한 HTML 인젝션&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;_작동_원리&amp;quot;&amp;gt;작동 원리&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;매크로``AsciiDoc는 해석되지 않은 원시 콘텐츠를 삽입할 수 있게 해줍니다. 이를 통해 인라인 CSS 스타일이 적용된 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&amp;gt;@startuml
participant &amp;quot;문서
아스키독&amp;quot; as doc
participant &amp;quot;프로세서\nAsciiDoc&amp;quot; as processor
participant &amp;quot;pass:[] 매크로&amp;quot; as pass
participant &amp;quot;HTML
최종&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;_구현&amp;quot;&amp;gt;구현&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;다음은 주입할 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;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;_css_속성_설명&amp;quot;&amp;gt;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;속성&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;기능&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;width/height를 설정할 수 있으며 인라인 흐름을 유지합니다&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;글꼴 크기 대비 정사각형 크기 (반응형)&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;표시할 색상 (필요에 따라 변수)&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;인접한 텍스트와 사각형을 정렬하세요&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;정사각형과 텍스트 사이의 간격&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;_완전한_예시&amp;quot;&amp;gt;완전한 예시&amp;lt;/h3&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-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;다음 렌더링을 생성하는 :&amp;lt;/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;/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;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`(아이보리)&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`(매우 진한 검정)&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`(보라색 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`(더 어두운 보라색)&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`(흰)&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;_최적화_및_모범_사례&amp;quot;&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;_밝은_색_관리&amp;quot;&amp;gt;밝은 색 관리&amp;lt;/h3&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-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;회색 테두리 (&amp;lt;code&amp;gt;border:1px solid #ccc&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;_정사각형_크기_조정&amp;quot;&amp;gt;정사각형 크기 조정&amp;lt;/h3&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-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;단위 사용`em`사각형이 글꼴 크기에 맞게 조정됨을 보장합니다.&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;_변형_만들기&amp;quot;&amp;gt;변형 만들기&amp;lt;/h3&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;sect3&amp;quot;&amp;gt;
&amp;lt;h4 id=&amp;quot;_색_원&amp;quot;&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-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;_색_테두리가_있는_정사각형&amp;quot;&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-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;_솔루션_아키텍처&amp;quot;&amp;gt;솔루션 아키텍처&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;문서 워크플로우&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;_장점과_한계&amp;quot;&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;_장점&amp;quot;&amp;gt;장점&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;자율성&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;이식성&amp;lt;/strong&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;strong&amp;gt;동기화&amp;lt;/strong&amp;gt; : 색상 코드는 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;유연성&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;Maintenance&amp;lt;/strong&amp;gt; : 간단한 16진수 코드 수정&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; : 정적 사이트 생성기와 완벽하게 호환됩니다&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;_제한&amp;quot;&amp;gt;제한&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;장황함&amp;lt;/strong&amp;gt; : 반복적인 HTML 코드가 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;소스 가독성&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;접근성&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 class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_접근성_개선&amp;quot;&amp;gt;접근성 개선&amp;lt;/h3&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-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;속성들`role=&amp;quot;img&amp;quot;` et `aria-label`이들은 보조 기술이 시각적 콘텐츠를 이해하고 설명하도록 허용한다.&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;_구체적인_예시_주제_문서&amp;quot;&amp;gt;구체적인 예시: 주제 문서&amp;lt;/h2&amp;gt;
&amp;lt;div 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;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;_결론&amp;quot;&amp;gt;결론&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;매크로를 통한 HTML 삽입``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;이 기술은 외부 구성이 필요 없으며, 추가 스타일시트도 필요하지 않으며, JBake 및 모든 표준 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;첫 번째 색이 있는 사각형을 만든 뒤에는, HTML 코드를 복사해서 붙여넣고 16진수 코드를 변경하면 빠르게 전체 팔레트를 만들 수 있습니다.&amp;lt;/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&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_자원&amp;quot;&amp;gt;자원&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;문서 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;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 : 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;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>타임리프를 사용한 JBake 정적 사이트의 국제화</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;#_소개&amp;quot;&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;#_사용_사례_다이어그램_use_case&amp;quot;&amp;gt;사용 사례 다이어그램 (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;#_국제화_아키텍처&amp;quot;&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;#_구조도_파일_조직&amp;quot;&amp;gt;구조도 (파일 조직)&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_왜_이_접근_방식인가요&amp;quot;&amp;gt;왜 이 접근 방식인가요?&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_컴포넌트_다이어그램&amp;quot;&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;#_thymeleaf을_사용한_템플릿의_국제화&amp;quot;&amp;gt;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;#_메시지_파일의_구조&amp;quot;&amp;gt;메시지 파일의 구조&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_메시지_파일의_내용&amp;quot;&amp;gt;메시지 파일의 내용&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_템플릿에서의_사용&amp;quot;&amp;gt;템플릿에서의 사용&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_시퀀스_다이어그램_i18n_해상도&amp;quot;&amp;gt;시퀀스 다이어그램 - i18n 해상도&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_jbake의_로케일_구성&amp;quot;&amp;gt;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;#_문서의_i18n_폴더별_구성&amp;quot;&amp;gt;문서의 I18n : 폴더별 구성&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;#_흐름도flow_생성&amp;quot;&amp;gt;흐름도(Flow) - 생성&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_폴더_구조&amp;quot;&amp;gt;폴더 구조&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_이_접근_방식의_장점&amp;quot;&amp;gt;이 접근 방식의 장점&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_기사의_메타데이터&amp;quot;&amp;gt;기사의 메타데이터&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_url_설정&amp;quot;&amp;gt;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;#_멀티언어_표시용_템플릿&amp;quot;&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;#_언어_선택기가_있는_기사_템플릿&amp;quot;&amp;gt;언어 선택기가 있는 기사 템플릿&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_언어별_필터링된_인덱스&amp;quot;&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;#_언어_간_내비게이션&amp;quot;&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;#_글로벌_언어_선택기&amp;quot;&amp;gt;글로벌 언어 선택기&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_흐름도_사용자_보기&amp;quot;&amp;gt;흐름도 - 사용자 보기&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_선택자에_대한_css_스타일&amp;quot;&amp;gt;선택자에 대한 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;#_언어별_rss_피드&amp;quot;&amp;gt;언어별 RSS 피드&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_좋은_관행과_팁&amp;quot;&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;#_1_기사_식별자의_일관성&amp;quot;&amp;gt;1. 기사 식별자의 일관성&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_2_일관된_날짜&amp;quot;&amp;gt;2. 일관된 날짜&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_3_다국어_태그&amp;quot;&amp;gt;3. 다국어 태그&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_4_미번역된_기사_관리&amp;quot;&amp;gt;4. 미번역된 기사 관리&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_5_다국어_사이트맵&amp;quot;&amp;gt;5. 다국어 사이트맵&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;#_결론&amp;quot;&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;#_배포도&amp;quot;&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;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_소개&amp;quot;&amp;gt;소개&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;정적 웹사이트의 국제화(i18n)는 처음엔 복잡해 보일 수 있지만, JBake와 Thymeleaf를 결합하면 다국어 사이트를 구축하기 위한 우아한 솔루션을 제공합니다. 이 글에서는 블로그에 i18n을 어떻게 구현했는지 보여드리겠습니다. 이때 템플릿과 다국어 게시글 관리를 모두 다룰 것입니다.&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;_사용_사례_다이어그램_use_case&amp;quot;&amp;gt;사용 사례 다이어그램 (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;작가&amp;quot; as author
actor &amp;quot;프랑스어 리더&amp;quot; as reader_fr
actor &amp;quot;플레이어 EN&amp;quot; as reader_en

rectangle &amp;quot;다언어 JBake 사이트&amp;quot; {
  usecase &amp;quot;FR 기사 작성&amp;quot; as UC1
  usecase &amp;quot;기사 쓰기 EN&amp;quot; as UC2
  usecase &amp;quot;i18n 메시지 구성&amp;quot; as UC3
  usecase &amp;quot;사이트 생성&amp;quot; as UC4
  usecase &amp;quot;FR 기사 읽기&amp;quot; as UC5
  usecase &amp;quot;기사 읽기 EN&amp;quot; as UC6
  usecase &amp;quot;언어 변경&amp;quot; as UC7
  usecase &amp;quot;사용 가능한 번역 보기&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;_국제화_아키텍처&amp;quot;&amp;gt;국제화 아키텍처&amp;lt;/h2&amp;gt;
&amp;lt;div 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;/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;템플릿의 i18n&amp;lt;/strong&amp;gt;: 인터페이스 요소에 대한 Thymeleaf 메시지 파일 사용&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;콘텐츠의 국제화&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;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_구조도_파일_조직&amp;quot;&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&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;_왜_이_접근_방식인가요&amp;quot;&amp;gt;왜 이 접근 방식인가요?&amp;lt;/h3&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;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;어떤 언어든 인터페이스의 일관성을 유지하라&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;컨텐츠와 기사 번역을 독립적으로 관리&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;주요 리팩터링 없이 새 언어 추가를 용이하게 하다&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&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 class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_컴포넌트_다이어그램&amp;quot;&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&amp;gt;@startuml
skinparam componentStyle rectangle

package &amp;quot;소스&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;영어/&amp;quot; as content_en {
      [article1.adoc] as article1_en
      [article2.adoc] as article2_en
    }
  }

  folder &amp;quot;템플릿/&amp;quot; {
    [post.html]
    [index.html]
    [index_en.html]
  }

  folder &amp;quot;(empty)&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;생성된 사이트&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;영어/&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;_thymeleaf을_사용한_템플릿의_국제화&amp;quot;&amp;gt;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;_메시지_파일의_구조&amp;quot;&amp;gt;메시지 파일의 구조&amp;lt;/h3&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-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;_메시지_파일의_내용&amp;quot;&amp;gt;메시지 파일의 내용&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;messages_fr.properties&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;# 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;그리고 그 영어 판`messages_en.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=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;_템플릿에서의_사용&amp;quot;&amp;gt;템플릿에서의 사용&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;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 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;_시퀀스_다이어그램_i18n_해상도&amp;quot;&amp;gt;시퀀스 다이어그램 - 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
파서&amp;quot; as parser
participant &amp;quot;타임리프
엔진&amp;quot; as thymeleaf
participant &amp;quot;I18n
해결사&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;홈&amp;quot;
i18n --&amp;amp;gt; thymeleaf: &amp;quot;홈&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;집&amp;quot;
i18n --&amp;amp;gt; thymeleaf: &amp;quot;집&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;_jbake의_로케일_구성&amp;quot;&amp;gt;JBake의 로케일 구성&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;당신의 파일에서`jbake.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;# 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;_문서의_i18n_폴더별_구성&amp;quot;&amp;gt;문서의 I18n : 폴더별 구성&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;_흐름도flow_생성&amp;quot;&amp;gt;흐름도(Flow) - 생성&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;각 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;각 템플릿마다&amp;quot; {
  :Charger template Thymeleaf;
  :Identifier langue cible;
  :Charger messages_{lang}.properties;

  partition &amp;quot;콘텐츠 필터링&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;_폴더_구조&amp;quot;&amp;gt;폴더 구조&amp;lt;/h3&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-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;_이_접근_방식의_장점&amp;quot;&amp;gt;이 접근 방식의 장점&amp;lt;/h3&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;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;명확한 분리&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;유연한 이름 지정&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;확장성&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;자연 조직&amp;lt;/strong&amp;gt;: 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;_기사의_메타데이터&amp;quot;&amp;gt;기사의 메타데이터&amp;lt;/h3&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;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;프랑스어 버전&amp;lt;/strong&amp;gt;&amp;amp;lt;/think&amp;amp;gt; (No content to translate)&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;영어 버전&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;속성`:jbake-article-id:`중요합니다 : 같은 기사의 다양한 번역들을 연결할 수 있게 해줍니다.&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;_url_설정&amp;quot;&amp;gt;URL 설정&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;에`jbake.properties`, URL 패턴을 언어를 포함하도록 구성하세요 :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;이는 다음과 같은 URL을 생성합니다:&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;_멀티언어_표시용_템플릿&amp;quot;&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;_언어_선택기가_있는_기사_템플릿&amp;quot;&amp;gt;언어 선택기가 있는 기사 템플릿&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;템플릿을 만드세요`post.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 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;_언어별_필터링된_인덱스&amp;quot;&amp;gt;언어별 필터링된 인덱스&amp;lt;/h3&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;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;index.html&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-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;(영어 색인) :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;_언어_간_내비게이션&amp;quot;&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;_글로벌_언어_선택기&amp;quot;&amp;gt;글로벌 언어 선택기&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;_흐름도_사용자_보기&amp;quot;&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&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;메인 템플릿에 언어 선택기를 추가하세요:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;_선택자에_대한_css_스타일&amp;quot;&amp;gt;선택자에 대한 CSS 스타일&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;_언어별_rss_피드&amp;quot;&amp;gt;언어별 RSS 피드&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;언어별로 분리된 RSS 피드를 만들려면 별도의 템플릿을 만들어야 합니다 :&amp;lt;/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;(프랑스어 흐름) :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;_좋은_관행과_팁&amp;quot;&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;_1_기사_식별자의_일관성&amp;quot;&amp;gt;1. 기사 식별자의 일관성&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;확인해 주세요`:jbake-article-id:`는 같은 기사의 모든 번역에 대해 동일합니다. 일관된 형식을 사용하세요:&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;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;단어를 구분하기 위해 하이픈을 사용하세요.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&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 class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_2_일관된_날짜&amp;quot;&amp;gt;2. 일관된 날짜&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;:jbake-date:&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;_3_다국어_태그&amp;quot;&amp;gt;3. 다국어 태그&amp;lt;/h3&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;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;옵션 1: 영어 보편적 태그&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;옵션 2: 매핑을 사용한 번역된 태그&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_미번역된_기사_관리&amp;quot;&amp;gt;4. 미번역된 기사 관리&amp;lt;/h3&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&amp;gt;
&amp;lt;div class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_5_다국어_사이트맵&amp;quot;&amp;gt;5. 다국어 사이트맵&amp;lt;/h3&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-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;_결론&amp;quot;&amp;gt;결론&amp;lt;/h2&amp;gt;
&amp;lt;div 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 사이트를 Thymeleaf과 함께 국제화하는 것은 강력한 유지 관리 가능한 접근 방식입니다. 메시지 파일을 통해 템플릿의 i18n을 분리하고 폴더 조직을 통해 콘텐츠의 i18n을 분리함으로써, 쉽게 발전할 수 있는 유연한 시스템을 얻을 수 있습니다.&amp;lt;/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;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;Thymeleaf 메시지 파일&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;폴더별 조직&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;아이템 식별자&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;전용 템플릿&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;명시적 URL&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;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_배포도&amp;quot;&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&amp;gt;@startuml
node &amp;quot;개발 기계&amp;quot; {
  artifact &amp;quot;출처&amp;quot; {
    folder &amp;quot;만족한&amp;quot;
    folder &amp;quot;템플릿/&amp;quot;
    folder &amp;quot;자산/&amp;quot;
  }

  component &amp;quot;JBake CLI&amp;quot; as jbake

  Sources --&amp;amp;gt; jbake : jbake -b
}

node &amp;quot;빌드 서버
(CI/CD)&amp;quot; {
  component &amp;quot;GitHub Actions
또는 GitLab CI&amp;quot; as ci

  jbake --&amp;amp;gt; ci : push
}

cloud &amp;quot;CDN / 호스팅&amp;quot; {
  node &amp;quot;정적 웹 서버&amp;quot; {
    artifact &amp;quot;생성된 사이트&amp;quot; {
      folder &amp;quot;/ko/blog/&amp;quot;
      folder &amp;quot;/en/blog/&amp;quot;
      folder &amp;quot;/assets/&amp;quot;
    }
  }
}

ci --&amp;amp;gt; &amp;quot;생성된 사이트&amp;quot; : déploiement

actor &amp;quot;독자들&amp;quot; as users

users --&amp;amp;gt; &amp;quot;정적 웹 서버&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;이 아키텍처를 사용하면 두 언어로 시작하기가 간편하고 추가 언어를 추가할 수 있으며 큰 리팩터링이 필요하지 않습니다. 모든 것이 완전히 정적이며 고성능을 유지하며 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;다국어 개발 잘하세요! 🌍&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>우분투에서 Tor 브라우저를 설치하고 확인하기</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;#_소개&amp;quot;&amp;gt;소개&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_tor_및_해당_서명_다운로드&amp;quot;&amp;gt;Tor 및 해당 서명 다운로드&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_서명의_암호학적_검증&amp;quot;&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;#_1_tor_project의_공개_키_가져오기&amp;quot;&amp;gt;1. Tor Project의 공개 키 가져오기&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_2_서명_확인&amp;quot;&amp;gt;2. 서명 확인&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_실제_사례_유효한_서명이지만_인증되지_않은_키&amp;quot;&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;#_왜_서명을_검증해야_하나요&amp;quot;&amp;gt;왜 서명을 검증해야 하나요?&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_토르_브라우저_추출_및_실행&amp;quot;&amp;gt;토르 브라우저 추출 및 실행&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_우분투_데스크톱_통합&amp;quot;&amp;gt;우분투 데스크톱 통합&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_tor를_이용해_교육_자료_탐색하기&amp;quot;&amp;gt;Tor를 이용해 교육 자료 탐색하기&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_tor_browser_업데이트&amp;quot;&amp;gt;Tor Browser 업데이트&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_결론&amp;quot;&amp;gt;결론&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_참고문헌&amp;quot;&amp;gt;참고문헌&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;_소개&amp;quot;&amp;gt;소개&amp;lt;/h2&amp;gt;
&amp;lt;div 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 Browser*는 익명성을 유지하고 정보의 자유를 보호하는 데 필수적인 도구로 남아 있다. 그러나 다운로드한 소프트웨어를 실행하지 않지 않도록 하는 것이 중요하다.&amp;lt;strong&amp;gt;그의 진위를 확인했다&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;다운로드 &amp;lt;strong&amp;gt;Tor Browser&amp;lt;/strong&amp;gt; 및 그 서명`.asc`&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;다운로드된 파일의 유효성을 확인하십시오&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Ubuntu에서 &amp;lt;strong&amp;gt;Tor Browser&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;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Tor를 통해 교육 자료와 윤리적 OSINT를 탐색하다&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;_tor_및_해당_서명_다운로드&amp;quot;&amp;gt;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 브라우저 전용 폴더를 만드세요:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;다음으로 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;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;_서명의_암호학적_검증&amp;quot;&amp;gt;서명의 암호학적 검증&amp;lt;/h2&amp;gt;
&amp;lt;div 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;strong&amp;gt;필수적인&amp;lt;/strong&amp;gt;다운로드한 파일이 실제로 Tor Project에서 왔는지, 그리고 수정되지 않았는지 확인하기 위해.&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_tor_project의_공개_키_가져오기&amp;quot;&amp;gt;1. Tor Project의 공개 키 가져오기&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;키가 제대로 가져와졌는지 확인하세요 :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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_서명_확인&amp;quot;&amp;gt;2. 서명 확인&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;모두 올바르면 다음 메시지가 표시됩니다:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;인증되지 않은 키 경고가 발생한 경우, 이는 단순히 아직 Tor Project의 신뢰 키에 서명하지 않았음을 의미하지만, 서명은 여전히 유효합니다.&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;_실제_사례_유효한_서명이지만_인증되지_않은_키&amp;quot;&amp;gt;실제 사례 : 유효한 서명이지만 인증되지 않은 키&amp;lt;/h3&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-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;이 메시지는 서명이 기술적으로 &amp;#39;좋음&amp;#39;(파일이 변경되지 않았음)을 나타내지만, GPG 시스템은 키 자체에 대한 *신뢰를 인증*할 수 없습니다. 이 의심을 해소하려면 주 키의 지문을 비교하는 것이 필수적입니다. (&amp;lt;code&amp;gt;EF6E 286D DA85 EA2A 4BA7 DE68 4E2C 6E87 9329 8290&amp;lt;/code&amp;gt;) 공식 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;공식 사이트에 따르면, 예상되는 흔적은 :`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;왜냐하면 표시된 발자국`gpg`&amp;lt;strong&amp;gt;정확히&amp;lt;/strong&amp;gt; 공식 사이트와 일치한다면, 키가 합법적이며 파일이 안전하다는 것을 확신할 수 있습니다. &amp;quot;인증되지 않은 키&amp;quot; 경고는 그러면 GPG 로컬 설정에 대한 정보일 뿐, 서명의 유효성이나 파일의 진위와는 관련이 없습니다.&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;_왜_서명을_검증해야_하나요&amp;quot;&amp;gt;왜 서명을 검증해야 하나요?&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;GPG 서명의 검증은 디지털 보안의 기본 행위입니다. 다음과 같이 할 수 있습니다:&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;진정성&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;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;참여하는&amp;lt;strong&amp;gt;디지털 주권&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;/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;사용자&amp;quot; as U
actor &amp;quot;TorProject 서버&amp;quot; as S

usecase &amp;quot;Tor와 그 서명을 다운로드&amp;quot; as D
usecase &amp;quot;공개 키 가져오기&amp;quot; as K
usecase &amp;quot;GPG 서명 확인&amp;quot; as V
usecase &amp;quot;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;_토르_브라우저_추출_및_실행&amp;quot;&amp;gt;토르 브라우저 추출 및 실행&amp;lt;/h2&amp;gt;
&amp;lt;div 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;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;그런 다음 브라우저를 실행하세요:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;첫 실행 시, 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;_우분투_데스크톱_통합&amp;quot;&amp;gt;우분투 데스크톱 통합&amp;lt;/h2&amp;gt;
&amp;lt;div 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;strong&amp;gt;Tor Browser&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;./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;다음에 할 수 있습니다:&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;Tor Browser&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;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;원하는 경우 키보드 단축키리를 만d세요&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;_tor를_이용해_교육_자료_탐색하기&amp;quot;&amp;gt;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은 익명 도구 그 이상이다. 이는 교육 및 연구 자원에 접근할 수 있는 포털이며, 특히 다음 분야에서 그러하다:&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 (공개 소스 정보)&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;교육 공유 및 오픈 소스&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;(토어를 통해 더 많은 프라이버시를 위해)&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;(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;(무료 과학 문헌 접근)&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;지식의 전달과 디지털 사회에서의 인간의 이해&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;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&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;_tor_browser_업데이트&amp;quot;&amp;gt;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;최적의 보안을 보장하기 위해 &amp;lt;strong&amp;gt;Tor Browser&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;Tor Browser 설치 디렉토리에서 시작 스크립트를 다시 실행하기만 하면 됩니다.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;시작할 때, *Tor Browser*는 새 버전이 있는지 자동으로 확인합니다. 만약 새 버전이 존재한다면, 그것을 다운로드하고 설치해 드립니다.&amp;lt;/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&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_결론&amp;quot;&amp;gt;결론&amp;lt;/h2&amp;gt;
&amp;lt;div 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를 설치하는 것은&amp;lt;strong&amp;gt;디지털 무결성 보호&amp;lt;/strong&amp;gt;, &amp;lt;strong&amp;gt;무료 소프트웨어에 대한 신뢰를 유지&amp;lt;/strong&amp;gt;, et &amp;lt;strong&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;디지털 보안은 편집증이 아니라 양심의 행위이다.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;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 Tor 사용 개요
actor Utilisateur
rectangle &amp;quot;토르 브라우저&amp;quot; {
rectangle &amp;quot;익명&amp;quot; as A
rectangle &amp;quot;OSINT 검색&amp;quot; as O
rectangle &amp;quot;교육적 공유&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;_참고문헌&amp;quot;&amp;gt;참고문헌&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;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;서명 검증 문서: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;우분투 문서: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;교육 아카이브 :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;이 기사는 &amp;lt;strong&amp;gt;디지털 보안 및 인식&amp;lt;/strong&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&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>제5조: 공유된 libs.versions.toml의 모험</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;#_소개&amp;quot;&amp;gt;소개&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_1_버전_카탈로그_libs_versions_toml_한_가지_알림&amp;quot;&amp;gt;1. 버전 카탈로그 (&amp;lt;code&amp;gt;libs.versions.toml&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;#_2_문제_컴포지트_빌드와_두_개의_별개의_세계&amp;quot;&amp;gt;2. 문제: 컴포지트 빌드와 두 개의 별개의 세계&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_3_솔루션_의존성_해결_공유&amp;quot;&amp;gt;3. 솔루션 : 의존성 해결 공유&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_4_결론_잘_관리된_컴포지트_빌드의_힘&amp;quot;&amp;gt;4. 결론: 잘 관리된 컴포지트 빌드의 힘&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;_소개&amp;quot;&amp;gt;소개&amp;lt;/h2&amp;gt;
&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;대상 독자 : 멀티빌드 프로젝트에서 의존성을 관리하고 있는 Gradle 개발자&amp;lt;/div&amp;gt;
&amp;lt;p&amp;gt;우리의 플러그인을 만들기 위한 탐색에서`site-baker`우리는 깨끗하고 중앙집중된 코드베이스를 유지하려고 노력했습니다. 현대 Gradle 생태계에서 최고의 실천 방법 중 하나는 이를 사용하는 것입니다.&amp;lt;strong&amp;gt;버전 카탈로그&amp;lt;/strong&amp;gt;(&amp;lt;code&amp;gt;libs.versions.toml&amp;lt;/code&amp;gt;) 로 의존성을 관리합니다. 하지만 프로젝트가 더 복잡해질 때 무슨 일이 일어나나요, 예를 들어 하나의&amp;lt;strong&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;여기서 우리 모험은 예상치 못한 방향으로 바뀌었다. 우리는 우리 플러그인이`site-baker`, 그것은 자체적으로 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;_1_버전_카탈로그_libs_versions_toml_한_가지_알림&amp;quot;&amp;gt;1. 버전 카탈로그 (&amp;lt;code&amp;gt;libs.versions.toml&amp;lt;/code&amp;gt;) : 한 가지 알림&amp;lt;/h2&amp;gt;
&amp;lt;div 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/libs.versions.toml`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;&amp;lt;code&amp;gt;[versions]&amp;lt;/code&amp;gt;: 버전 번호에 대한 별칭을 정의합니다(예:`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;: 완전한 종속성에 대한 별칭을 정의하며, 위에 정의된 버전을 사용합니다(예:`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]&amp;lt;/code&amp;gt;: 여러 라이브러리를 하나의 별칭으로 묶습니다 (ex:`jackson = [&amp;quot;jackson-core&amp;quot;, &amp;quot;jackson-databind&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;[plugins]&amp;lt;/code&amp;gt;: 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;정의되면 Gradle은 자동으로 타입이 지정된 액세서를 생성하며, 이로 인해 귀하의`build.gradle.kts`훨씬 더 읽기 쉽습니다:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;버전 카탈로그의 데이터 흐름&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;읽히기&amp;quot;
G --&amp;amp;gt; BuildScript : &amp;quot;접근자를 생성합니다 `libs&amp;quot;
BuildScript -&amp;amp;gt; G : &amp;quot;사용해 `libs` 로 종속성을 선언해&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_문제_컴포지트_빌드와_두_개의_별개의_세계&amp;quot;&amp;gt;2. 문제: 컴포지트 빌드와 두 개의 별개의 세계&amp;lt;/h2&amp;gt;
&amp;lt;div 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;strong&amp;gt;복합체 구축&amp;lt;/strong&amp;gt;. 주요 프로젝트 (&amp;lt;code&amp;gt;thymeleaf.cheroliv.com&amp;lt;/code&amp;gt;) 플러그인 프로젝트를 포함합니다 (&amp;lt;code&amp;gt;site-baker&amp;lt;/code&amp;gt;) 명령을 통해`includeBuild(&amp;quot;site-baker&amp;quot;)&amp;lt;code&amp;gt;파일에서`settings.gradle.kts&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;title&amp;quot;&amp;gt;복합 빌드 구조&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;주요 프로젝트&amp;quot; {
  file &amp;quot;settings.gradle.kts&amp;quot; as MainSettings
  folder &amp;quot;그레이들&amp;quot; {
    file &amp;quot;libs.versions.toml&amp;quot; as MainToml
  }
}

package &amp;quot;플러그인 `site-baker` (포함된 빌드)&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;사이트 베이커&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;문제는 기본적으로 포함된 빌드가 하나이다&amp;lt;strong&amp;gt;별개의 세계&amp;lt;/strong&amp;gt;. 그의 고유한 구성과 고유한 생명 주기를 가지고 있으며, 파일을 보지 못한다`libs.versions.toml`주요 빌드. 우리`plugin/build.gradle.kts`사용하려고 노력하고 있었다`libs.plugins.kotlin.jvm`, 그러나 그 객체`libs`올바른 TOML 파일에서 생성되지 않아서 빌드 오류가 발생했습니다.&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_솔루션_의존성_해결_공유&amp;quot;&amp;gt;3. 솔루션 : 의존성 해결 공유&amp;lt;/h2&amp;gt;
&amp;lt;div 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 기능임이 밝혀졌습니다:`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;파일에`settings.gradle.kts` du &amp;lt;strong&amp;gt;포괄적인 빌드&amp;lt;/strong&amp;gt; (&amp;lt;code&amp;gt;site-baker/settings.gradle.kts&amp;lt;/code&amp;gt;), 우리는 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;다음은 우리가 추가한 구성`site-baker/settings.gradle.kts`:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;핵심 부분을 분석해 봅시다:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;)`우리는 별칭을 통해 접근 가능한 버전 카탈로그를 선언합니다`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;: 이것은 마법입니다. 우리는 Gradle에게 이 카탈로그의 소스가 디렉터리에 위치한 TOML 파일이라고 나타냅니다.&amp;lt;code&amp;gt;gradle&amp;lt;/code&amp;gt; du &amp;lt;strong&amp;gt;상위 프로젝트&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;(Note: The output includes a leading space and an opening parenthesis as in the source, with no additional text.)&amp;lt;code&amp;gt;../&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;이 구성으로 빌드`site-baker`이제 그는 메인 프로젝트의 버전 카탈로그를 사용해야 한다는 것을 알고 있습니다. 객체`libs`정상적으로 생성되며, 종속성이 예상대로 해결됩니다.&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_결론_잘_관리된_컴포지트_빌드의_힘&amp;quot;&amp;gt;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;이 경험은 많은 교훈을 제공했습니다. 버전 카탈로그는 유지보수성을 위한 훌륭한 도구이지만, 복합 빌드 시나리오에서의 동작은 항상 직관적이지 않습니다.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;핵심은 포함된 빌드가 여전히 독립된 빌드라는 사실을 기억하는 것입니다. 버전 카탈로그와 같은 구성 설정을 공유하려면 Gradle이 제공하는 명시적인 메커니즘, 예를 들어 블록을 사용해야 합니다.&amp;lt;code&amp;gt;dependencyResolutionManagement`안에`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;이 문제를 해결함으로써 우리는 단순히 빌드를 더 깨끗하게 만들었을 뿐만 아니라, 플러그인과 메인 프로젝트가 의존성에 대한 단일 진실의 출처를 공유하도록 함으로써 프로젝트의 일관성도 강화했습니다.&amp;lt;/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&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>제4조: 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;#_서론&amp;quot;&amp;gt;서론&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_증상_유령의_빌드&amp;quot;&amp;gt;증상: 유령의 빌드&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_조사_구성_캐시는_무엇입니까&amp;quot;&amp;gt;조사: 구성 캐시는 무엇입니까?&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_문제의_원인_비준수_플러그인&amp;quot;&amp;gt;문제의 원인: 비준수 플러그인&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_임시_솔루션_캐시_비활성화&amp;quot;&amp;gt;임시 솔루션: 캐시 비활성화&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_진정한_해결책_플러그인_호환성_확보&amp;quot;&amp;gt;진정한 해결책: 플러그인 호환성 확보&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;_서론&amp;quot;&amp;gt;서론&amp;lt;/h2&amp;gt;
&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;대상 : Gradle 개발자 중에서 이미 &amp;#39;매직&amp;amp;#8217;하거나 예상치 못한 빌드 동작을 경험한 사람들&amp;lt;/div&amp;gt;
&amp;lt;p&amp;gt;플러그인을 만드는 우리의 모험`site-baker`, 우리는 엄격한 TDD 접근 방식을 따랐습니다. 각 기능은 테스트되고, 검증되었으며, 우리는 확신을 가지고 진행했습니다. 그리고ある日, 예기치 못한 일이 발생했습니다. 빌드가 예측할 수 없이 동작하기 시작했습니다. 플러그인 로직이나 구성 파일의 수정이 무시되는 것처럼 보였고, 이전에 신뢰할 수 있었던 기능 테스트가 명백한 이유 없이 실패하기 시작했습니다.&amp;lt;/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;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;_증상_유령의_빌드&amp;quot;&amp;gt;증상: 유령의 빌드&amp;lt;/h2&amp;gt;
&amp;lt;div 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;/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;println&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;managed-jbake-context.yml&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;/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;모든 것이 마치 Gradle가 우리 빌드의 &amp;#39;유령 버전&amp;amp;#8217;을 실행하는 것처럼 진행되고 있었고, 우리의 가장 최근 변경 사항을 무시하고 있었다.&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;_조사_구성_캐시는_무엇입니까&amp;quot;&amp;gt;조사: 구성 캐시는 무엇입니까?&amp;lt;/h2&amp;gt;
&amp;lt;div 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의 기능이며, 최신 버전에서는 기본적으로 활성화됩니다. 그 목적은 빌드를 더 빠르게 만드는 것입니다.&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;원칙은 간단합니다 :&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;첫 번째 실행 시, Gradle은 단계를 실행합니다.&amp;lt;strong&amp;gt;구성&amp;lt;/strong&amp;gt;(읽기의`build.gradle.kts`, 작업 생성, 의존성 해결) 그리고 작업 그래프를 구성한다&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;이 단계가 끝날 때, Gradle&amp;lt;strong&amp;gt;이 작업 그래프 직렬화&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;code&amp;gt;gradle.properties&amp;lt;/code&amp;gt;, etc.), Gradle&amp;lt;strong&amp;gt;설정 단계를 완전히 건너뛴다&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;시간 절약이 큰 프로젝트에서 놀라울 정도입니다. 그러나 이 성능에는 대가가 따릅니다: 플러그인이 어떻게 작성되어야 하는지에 대해 엄격한 규칙을 적용합니다.&amp;lt;/p&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;구성 캐시의 수명 주기&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;_문제의_원인_비준수_플러그인&amp;quot;&amp;gt;문제의 원인: 비준수 플러그인&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;우리 플러그인`site-baker`그는 알지 못한 채 구성 캐시의 여러 규칙을 위반하고 있었다. 작업 그래프가 직렬화 가능하려면, 작업은 복합 객체와 같은 참조를 포함해서는 안 된다.`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;주요 오류는 확장 프로그램에 저장된 경로 참조를 사용하여 작업 실행 로직 내에서 YAML 파일의 내용을 직접 읽는 것이었습니다. 이 방식은 Gradle이 파일 내용이 변경되었는지 알 수 없도록 이 읽기를 모델링하지 않으면 캐시와 호환되지 않습니다.&amp;lt;strong&amp;gt;작업 항목&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;_임시_솔루션_캐시_비활성화&amp;quot;&amp;gt;임시 솔루션: 캐시 비활성화&amp;lt;/h2&amp;gt;
&amp;lt;div 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;gradle.properties`플러그인을 사용하는 프로젝트 (또는 우리 경우, 테스트 프로젝트)`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;즉시, 빌드가 정상적인 동작을 되찾았습니다. 각 실행이 구성 단계를 다시 실행했고, 우리의 변경 사항이 반영되었습니다.&amp;lt;/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&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_진정한_해결책_플러그인_호환성_확보&amp;quot;&amp;gt;진정한 해결책: 플러그인 호환성 확보&amp;lt;/h2&amp;gt;
&amp;lt;div 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 생태계의 좋은 시민이 되려면 구성 캐시와 호환되어야 합니다. 이는 우리 작업으로 데이터가 흐르는 방식을 재고해야 함을 의미합니다.&amp;lt;/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;제공자 API&amp;lt;/strong&amp;gt;Gradle의. 직접적인 값을 전달하는 대신 (예를 들어`String` ou un &amp;lt;code&amp;gt;File&amp;lt;/code&amp;gt;) 우리의 작업에 대해, 우리는 통과해야 합니다`Property&amp;amp;lt;T&amp;amp;gt;`또는 일부의`Provider&amp;amp;lt;T&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;다음은 리팩터링 계획입니다 :&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;작업 항목 선언 :&amp;lt;/strong&amp;gt;YAML 파일을 파싱하는 작업은 이 파일을 입력으로 선언해야 합니다. 이를 위해 어노테이션을 사용합니다.&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 ----&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;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>제3항: Gradle 플러그인에서 TDD를 사용한 YAML 파싱을 위한 Jackson 통합</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;#_소개&amp;quot;&amp;gt;소개&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_컨텍스트_우리_플러그인_site_baker&amp;quot;&amp;gt;컨텍스트 : 우리 플러그인 `site-baker&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_2_코틀린에서의_구성_모델링&amp;quot;&amp;gt;2. 코틀린에서의 구성 모델링&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_3_jackson을_build_gradle_kts에_통합&amp;quot;&amp;gt;3. Jackson을 `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_tdd_접근_방식_yaml_파싱_테스트&amp;quot;&amp;gt;4. TDD 접근 방식 : 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_단위_테스트_yaml을_kotlin_객체에_매핑하기&amp;quot;&amp;gt;4.1. 단위 테스트 : YAML을 Kotlin 객체에 매핑하기&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_4_2_기능_테스트_구성_파일_읽기_검증&amp;quot;&amp;gt;4.2. 기능 테스트 : 구성 파일 읽기 검증&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_파싱_흐름_시각화&amp;quot;&amp;gt;5. 파싱 흐름 시각화&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_결론&amp;quot;&amp;gt;결론&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;_소개&amp;quot;&amp;gt;소개&amp;lt;/h2&amp;gt;
&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;대상 : 복잡한 구성을 관리하려는 중급 Gradle 개발자&amp;lt;/div&amp;gt;
&amp;lt;p&amp;gt;Gradle 플러그인 개발에서 복잡한 구성 관리는 일반적인 작업입니다. 백 개 이상의 속성으로 DSL을 과부하 시키는 대신, 외부 파일(YAML과 같은)에 구성을 정의하는 것이 종종 더 깔끔하고 유지 관리하기 쉽습니다. 이 글에서는 강력한 Jackson 라이브러리를 통합하여 YAML 파일을 Kotlin 객체로 파싱하는 과정을 안내하며, 플러그인의 견고함을 보장하기 위해 엄격한 TDD 접근 방식을 채택합니다.&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;_컨텍스트_우리_플러그인_site_baker&amp;quot;&amp;gt;컨텍스트 : 우리 플러그인 `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;우리 플러그인`site-baker`정적 사이트의 생성과 배포를 자동화하도록 설계되었습니다. YAML 설정 파일을 읽어야 합니다 (&amp;lt;code&amp;gt;managed-jbake-context.yml&amp;lt;/code&amp;gt;) 소스 경로, 배포 대상, 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;파싱하고자 하는 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-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_코틀린에서의_구성_모델링&amp;quot;&amp;gt;2. 코틀린에서의 구성 모델링&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;파싱하기 전에 Kotlin에서 데이터 구조를 정의해야 합니다. Jackson은 이러한 클래스를 사용하여 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;기능`parseSiteConfiguration`는 역직렬화를 위한 진입점입니다. 그녀는 사용합니다`ObjectMapper`Jackson의, 구성된`YAMLFactory`YAML 형식과`registerKotlinModule()`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;_3_jackson을_build_gradle_kts에_통합&amp;quot;&amp;gt;3. Jackson을 `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;Jackson을 사용하려면, 필요한 종속성을 추가해야 합니다. 안에`build.gradle.kts`우리 모듈의`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;// 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;우리는 사용합니다`jackson-module-kotlin`Kotlin 지원과`jackson-dataformat-yaml`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_tdd_접근_방식_yaml_파싱_테스트&amp;quot;&amp;gt;4. TDD 접근 방식 : 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;이제 TDD를 적용하여 파싱 논리를 검증해 봅시다.&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_단위_테스트_yaml을_kotlin_객체에_매핑하기&amp;quot;&amp;gt;4.1. 단위 테스트 : YAML을 Kotlin 객체에 매핑하기&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;우리는 단위 테스트로 시작합니다`SiteBakerPluginTest.kt`함수가 올바르게 작동하는지 확인하기 위해`parseSiteConfiguration`YAML 문자열을 개체로 올바르게 변환할 수 있습니다.&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;이 테스트는 파일의 내용을 읽는다`managed-jbake-context.yml`(테스트가 유효하려면 존재해야 하는) 그리고 객체에 대해 어서션을 수행합니다`SiteConfiguration`결과적으로. YAML의 키 값이 올바르게 매핑되었는지 검증합니다.&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_기능_테스트_구성_파일_읽기_검증&amp;quot;&amp;gt;4.2. 기능 테스트 : 구성 파일 읽기 검증&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;플러그인이 DSL을 통해 구성 파일을 읽을 수 있도록 보장하고 실제 Gradle 환경에서 파싱이 동작하도록 하기 위해 기능 테스트를 추가합니다.&amp;lt;code&amp;gt;SiteBakerPluginFunctionalTest.kt&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;이 테스트는 실제 프로젝트에서 플러그인 실행을 시뮬레이션하기 때문에 중요합니다. 이것은&amp;lt;strong&amp;gt;밀폐된&amp;lt;/strong&amp;gt;, 즉, 그가 직접 파일을 만들어야 한다는 뜻이다.`managed-jbake-context.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 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;이 기능 테스트는 테스트의 임시 디렉터리에 복사된 구성 파일이 예상된 데이터를 포함하고 있는지 확인합니다. 이 테스트는 YAML을 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;_5_파싱_흐름_시각화&amp;quot;&amp;gt;5. 파싱 흐름 시각화&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;다음 다이어그램은 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;사용자 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;사이트 확장&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 (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;_결론&amp;quot;&amp;gt;결론&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;TDD 방식을 따르면서 Jackson 라이브러리를 성공적으로 통합하여 Gradle 플러그인 내에서 YAML 구성 파일을 Kotlin 객체로 파싱했습니다. 이를 통해 우리는 다음과 같이 할 수 있게 되었습니다 :&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;명확히 모델링&amp;lt;/strong&amp;gt;우리의 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;파싱 로직 검증&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;통합을 보장하다&amp;lt;/strong&amp;gt;실제 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;이 견고한 기반은 이 구성을 기반으로 하는 기능을 갖춘 우리 플러그인을 확장할 수 있는 큰 확신을 제공하며, 동시에 코드의 유지 관리 가능성과 안정성을 보장합니다. YAML 파싱은 이제 우리 플러그인의 신뢰할 수 있고 테스트된 기능이 되었습니다.&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>제2조 : TDD 접근 방식으로 Gradle 플러그인 개발</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;#_1_프로젝트_초기화&amp;quot;&amp;gt;1. 프로젝트 초기화&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_2_우리_첫_번째_tdd_사이클_작업_기록하기&amp;quot;&amp;gt;2. 우리 첫 번째 TDD 사이클: 작업 기록하기&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_먼저_테스트_실패하는_테스트&amp;quot;&amp;gt;2.1. 먼저 테스트 (실패하는 테스트)&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_2_2_코드_다음_테스트_통과하기&amp;quot;&amp;gt;2.2. 코드 다음 (테스트 통과하기)&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_두_번째_tdd_사이클_dsl_확장_추가&amp;quot;&amp;gt;3. 두 번째 TDD 사이클: 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_먼저_테스트&amp;quot;&amp;gt;3.1. 먼저 테스트&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_3_2_다음_코드&amp;quot;&amp;gt;3.2. 다음 코드&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_기능_테스트_통합_검증&amp;quot;&amp;gt;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;#_4_1_통합_테스트_통제된_환경_만들기&amp;quot;&amp;gt;4.1. 통합 테스트: 통제된 환경 만들기&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_4_2_논리_최종화&amp;quot;&amp;gt;4.2. 논리 최종화&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;#_결론&amp;quot;&amp;gt;결론&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;이 글에서는 테스트 주도 개발(TDD) 접근법을 사용해 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;_1_프로젝트_초기화&amp;quot;&amp;gt;1. 프로젝트 초기화&amp;lt;/h2&amp;gt;
&amp;lt;div 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은 명령을 통해 새로운 플러그인 생성을 매우 쉽게 합니다`gradle init`. Kotlin을 사용하여 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;&amp;lt;strong&amp;gt;단위 테스트 :&amp;lt;/strong&amp;gt;에 위치한`src/test`, 그들은 플러그인의 격리된 구성 요소, 예를 들어 작업의 내부 로직이나 확장 구성을 검증할 수 있도록 허용합니다. 그들은 빠르며 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;기능 테스트 :&amp;lt;/strong&amp;gt;에 위치한`src/functionalTest`, 그들은 사용합니다`GradleRunner`임시 테스트 프로젝트에서 완전한 Gradle 빌드를 실행하기 위해. 이를 통해 통제된 환경에서 플러그인의 실제 동작을 확인할 수 있다.&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_우리_첫_번째_tdd_사이클_작업_기록하기&amp;quot;&amp;gt;2. 우리 첫 번째 TDD 사이클: 작업 기록하기&amp;lt;/h2&amp;gt;
&amp;lt;div 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;printSiteConfig&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;_2_1_먼저_테스트_실패하는_테스트&amp;quot;&amp;gt;2.1. 먼저 테스트 (실패하는 테스트)&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;TDD에 따라, 우리는 먼저 이 작업의 존재를 확인하는 테스트를 작성합니다. 안에`SiteBakerPluginTest.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;@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;이 테스트가 실패하는 이유는, 아직 플러그인에 코드를 전혀 작성하지 않았기 때문입니다.&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_코드_다음_테스트_통과하기&amp;quot;&amp;gt;2.2. 코드 다음 (테스트 통과하기)&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;이제 우리는 안에 필요한 최소한의 코드를 작성합니다`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;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;우리는 테스트를 다시 실행하고, 그들은 통과합니다. 우리의 첫 번째 기능이 검증되었습니다.&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_두_번째_tdd_사이클_dsl_확장_추가&amp;quot;&amp;gt;3. 두 번째 TDD 사이클: 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;다음 요구사항은 사용자가 DSL 블록을 통해 우리 플러그인을 구성할 수 있도록 하는 것, 그들의`build.gradle.kts`. 우리는 하나의 블록을 원합니다.`site { &amp;amp;#8230;&amp;amp;#8203; }`구성 경로를 지정할 수 있는.&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_먼저_테스트&amp;quot;&amp;gt;3.1. 먼저 테스트&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;우리는 확장이 정상인지 확인하기 위해 테스트를 추가합니다.`site`잘 기록되어 있고 거기에 값을 할당할 수 있습니다.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;이 테스트는 실패했는데 클래스가 도이고`SiteExtension`확장 프로그램 등록도 존재하지 않습니다.&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_다음_코드&amp;quot;&amp;gt;3.2. 다음 코드&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;우리는 클래스를 만듭니다`SiteBakerExtension.kt`그리고 업데이트합니다`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;테스트가 다시 통과됩니다.&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_기능_테스트_통합_검증&amp;quot;&amp;gt;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;이제 단위가 테스트되었으므로, 실제 빌드에서 모든 것이 함께 작동하는지 확인해야 합니다. 이는 기능 테스트의 역할이다`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_통합_테스트_통제된_환경_만들기&amp;quot;&amp;gt;4.1. 통합 테스트: 통제된 환경 만들기&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;기밀한&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;/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;생성하는`build.gradle.kts`우리 플러그인 및 DSL을 사용하는 테스트&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;구성 파일 만들기&amp;lt;/strong&amp;gt;(&amp;lt;code&amp;gt;managed-jbake-context.yml&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;작업을 실행하다`printSiteConfig`경유하여`GradleRunner`.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&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;strong&amp;gt;재현 가능한&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;@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;이 테스트는 작업이 끝날 때까지 실패할 것입니다.`printSiteConfig`확장 기능의 값을 실제로 사용하지 않을 것입니다.&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_논리_최종화&amp;quot;&amp;gt;4.2. 논리 최종화&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;우리는 작업을 업데이트하고 있습니다`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;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;모든 테스트, 단위 및 기능 테스트, 이제는 통과합니다.&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;_결론&amp;quot;&amp;gt;결론&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;TDD 방식을 따라 점진적이고 안전하게 플러그인을 구축했습니다. 작은 기능 하나하나를 테스트로 즉시 검증하며, 빠른 단위 테스트부터 완전한 통합을 검증하는 기능 테스트까지 수행합니다.&amp;lt;/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;/strong&amp;gt;— 필요에 따라 프로그래밍 방식으로 필요한 구성 파일을 생성함으로써 — 우리는 매우 견고한 안전망을 구축합니다. 이 엄격함은 회귀에 효과적으로 대처하고 향후 더 복잡한 기능을 추가할 수 있는 큰 자신감을 줍니다. 예를 들어 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;이 강력한 테스트베이스는 플러그인의 유지보수와 미래 발전을 위한 가장 귀중한 자산입니다.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>문서 1: gradle init 명령으로 A부터 Z까지 Gradle 플러그인 만들기</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;#_소개&amp;quot;&amp;gt;소개&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_1단계_프로젝트_어시스턴트_실행&amp;quot;&amp;gt;1단계: 프로젝트 어시스턴트 실행&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_2단계_생성된_구조_이해하기&amp;quot;&amp;gt;2단계: 생성된 구조 이해하기&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;#_주요_파일_및_폴더&amp;quot;&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;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_소개&amp;quot;&amp;gt;소개&amp;lt;/h2&amp;gt;
&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;l대상 : 초중급 Gradle 개발자.&amp;lt;/div&amp;gt;
&amp;lt;p&amp;gt;Gradle 플러그인 새 프로젝트를 시작하는 것은 위협적으로 느껴질 수 있습니다. 다행히도, Gradle은 강력한 대화형 어시스턴트를 제공합니다, 작업`init`, 완전하고 깔끔하며 즉시 사용 가능한 프로젝트 구조를 생성하는. 이 가이드는 이 도구를 사용하여 건강한 플러그인 베이스를 만드는 방법을 보여주고, 구조를 설명하며, 처음 빌드 작업을 시작하는 방법을 보여줄 것입니다.&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단계_프로젝트_어시스턴트_실행&amp;quot;&amp;gt;1단계: 프로젝트 어시스턴트 실행&amp;lt;/h2&amp;gt;
&amp;lt;div 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 init`은 시작점입니다. 그녀는 명령줄 어시스턴트를 실행하여 프로젝트를 구성하기 위한 일련의 질문을 던집니다.&amp;lt;/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-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;어시스턴트가 안내해 드릴 것입니다. 다음은 Kotlin으로 작성된 Gradle 플러그인에 대한 선택 사항입니다:&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;생성할 프로젝트 유형을 선택하세요:&amp;lt;/strong&amp;gt;선택하세요`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;구현 언어를 선택하세요:&amp;lt;/strong&amp;gt;선택하세요`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;빌드 스크립트 DSL 선택:&amp;lt;/strong&amp;gt;선택하세요`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;프로젝트 이름:&amp;lt;/strong&amp;gt;프로젝트에 이름을 입력하세요 (예:`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:&amp;lt;/strong&amp;gt;플러그인에 고유한 식별자를 지정하세요 (예:`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;플러그인 클래스:&amp;lt;/strong&amp;gt;구현 클래스의 이름을 지정하세요 (예:`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;완료 후, 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;title&amp;quot;&amp;gt;초기화 프로세스 다이어그램&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 플러그인&amp;quot;;
:Choisir &amp;quot;코틀린&amp;quot; (langage);
:Choisir &amp;quot;코틀린&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;_2단계_생성된_구조_이해하기&amp;quot;&amp;gt;2단계: 생성된 구조 이해하기&amp;lt;/h2&amp;gt;
&amp;lt;div 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;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;_주요_파일_및_폴더&amp;quot;&amp;gt;주요 파일 및 폴더&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`이 파일은 루트에 위치하며 빌드에 포함된 모듈을 정의합니다. 여기서, 이는 하위 프로젝트를 포함합니다.`plugin&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;[source,kotlin] ---- rootProject.name = &amp;quot;site-baker 포함(&amp;quot;플러그인&amp;quot;) (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&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>제0조 : 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;#_소개&amp;quot;&amp;gt;소개&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_1_jdk_gradle_엔진&amp;quot;&amp;gt;1. JDK: 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;#_어떤_버전을_선택해야_할까요&amp;quot;&amp;gt;어떤 버전을 선택해야 할까요?&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_sdkman을_통한_설치_권장&amp;quot;&amp;gt;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;#_sdkman_설치&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;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;#_jdk_버전_설치&amp;quot;&amp;gt;JDK 버전 설치&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_설치_확인&amp;quot;&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;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_빌드_도구&amp;quot;&amp;gt;3. 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;#_sdkman을_통한_설치&amp;quot;&amp;gt;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;#_gradle_설치&amp;quot;&amp;gt;Gradle 설치&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_설치_확인_2&amp;quot;&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;li&amp;gt;&amp;lt;a href=&amp;quot;#_4_jbake_정적_사이트_생성기&amp;quot;&amp;gt;4. JBake : 정적 사이트 생성기&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;#_sdkman을_통한_설치_2&amp;quot;&amp;gt;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;#_jbake_설치&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;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;#_설치_확인_3&amp;quot;&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;li&amp;gt;&amp;lt;a href=&amp;quot;#_5_gradle_wrapper_절대적인_모범_사례&amp;quot;&amp;gt;5. Gradle Wrapper: 절대적인 모범 사례&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_6_docker와_portainer_재현_가능한_빌드_선택_사항&amp;quot;&amp;gt;6. Docker와 Portainer : 재현 가능한 빌드 (선택 사항)&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;#_docker_engine_설치&amp;quot;&amp;gt;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;#_docker을_공식_스크립트로_설치하기&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;Docker을 공식 스크립트로 설치하기 :&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;#_docker_관리_sudo_없이_권장&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;Docker 관리 &amp;lt;code&amp;gt;sudo&amp;lt;/code&amp;gt; 없이 (권장) :&amp;lt;/strong&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;#_portainer_gui로_docker_관리하기&amp;quot;&amp;gt;Portainer (GUI)로 Docker 관리하기&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;#_portainer_데이터용_볼륨을_생성하세요&amp;quot;&amp;gt;Portainer 데이터용 볼륨을 생성하세요:&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_portainer_컨테이너_실행&amp;quot;&amp;gt;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;#_결론&amp;quot;&amp;gt;결론&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;_소개&amp;quot;&amp;gt;소개&amp;lt;/h2&amp;gt;
&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;대상 고객 : Gradle 플러그인 개발에 도전하고자 하는 개발자.&amp;lt;/div&amp;gt;
&amp;lt;p&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;이 가이드는 네 가지 필수 기둥에 초점을 맞춥니다 :&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), Gradle과 JBake가 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;통합 개발 환경&amp;lt;/strong&amp;gt;현대적인 (IntelliJ IDEA) Kotlin과 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;그레이들&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;제이베이크&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;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;개발 환경 구성 요소&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;개발자 기계&amp;quot; {
  component &amp;quot;JDK (JVM)&amp;quot; as JDK
  component &amp;quot;IntelliJ IDEA&amp;quot; as IDE
  component &amp;quot;Gradle (전역)&amp;quot; as Gradle
  component &amp;quot;JBake (글로벌)&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_gradle_엔진&amp;quot;&amp;gt;1. JDK: 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는 Java Virtual Machine (JVM)에서 실행되는 애플리케이션입니다. 따라서 JDK 설치는 절대적인 필수 조건입니다.&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;_어떤_버전을_선택해야_할까요&amp;quot;&amp;gt;어떤 버전을 선택해야 할까요?&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;자바의 Long-Term Support (LTS) 버전을 사용하는 것이 권장됩니다. 현재로서는,&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;훌륭한 선택입니다.&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;_sdkman을_통한_설치_권장&amp;quot;&amp;gt;SDKMAN!을 통한 설치 (권장)&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;SDKMAN! 은 Java와 Gradle을 포함한 많은 SDK의 버전을 관리하기에 훌륭한 도구입니다.&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;_sdkman_설치&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;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;_jdk_버전_설치&amp;quot;&amp;gt;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;_설치_확인&amp;quot;&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;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;방금 설치한 버전이 표시되는 것을 보셔야 합니다.&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;Gradle 플러그인 개발에 Kotlin DSL을 사용할 때,&amp;lt;strong&amp;gt;인텔리J IDEA&amp;lt;/strong&amp;gt;는 선호되는 도구입니다. 그의 Gradle 및 Kotlin과의 통합은 비교할 수 없습니다.&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;Community Edition :&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;얼티밋 에디션 :&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;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가 자동으로 파일을 감지합니다.&amp;lt;code&amp;gt;build.gradle.kts&amp;lt;/code&amp;gt; et &amp;lt;code&amp;gt;settings.gradle.kts&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;_3_gradle_빌드_도구&amp;quot;&amp;gt;3. 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 Wrapper를 통해 자체 Gradle 버전을 사용하더라도, 새 프로젝트 초기화와 같은 작업에는 전역 Gradle 설치를 해두는 것이 유용합니다 (&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;_sdkman을_통한_설치&amp;quot;&amp;gt;SDKMAN!을 통한 설치!&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;JDK와 마찬가지로, SDKMAN!은 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;_gradle_설치&amp;quot;&amp;gt;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;_설치_확인_2&amp;quot;&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;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;이 명령은 사용되는 Gradle, Kotlin, Groovy 및 JDK의 버전을 표시합니다.&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_정적_사이트_생성기&amp;quot;&amp;gt;4. JBake : 정적 사이트 생성기&amp;lt;/h2&amp;gt;
&amp;lt;div 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는 우리가 소스 파일(예: 이 파일)을 정적 웹사이트로 변환하는 데 사용할 도구입니다. 로컬 설치를 갖는 것은 변경 사항을 미리 보는 데 편리합니다.&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;_sdkman을_통한_설치_2&amp;quot;&amp;gt;SDKMAN!을 통한 설치&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;다른 도구들과 마찬가지로, SDKMAN!이 가장 간단한 방법입니다.&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;_jbake_설치&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;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;_설치_확인_3&amp;quot;&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;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;이 명령은 JBake 버전과 이를 사용하는 Java 환경 정보를 표시합니다.&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_절대적인_모범_사례&amp;quot;&amp;gt;5. Gradle Wrapper: 절대적인 모범 사례&amp;lt;/h2&amp;gt;
&amp;lt;div 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;gradle&amp;lt;/code&amp;gt;. 대신에, 당신은 그것을 사용할 것입니다.&amp;lt;strong&amp;gt;그레이들 래퍼&amp;lt;/strong&amp;gt; (&amp;lt;code&amp;gt;./gradlew&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;왜?&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;재현성:&amp;lt;/strong&amp;gt;Wrapper는 프로젝트에서 작업하는 모든 사람(CI/CD 서버 포함)이 정확히 라를 사용하도록 보장합니다.&amp;lt;strong&amp;gt;같은 버전의 Gradle&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;단순함 :&amp;lt;/strong&amp;gt;프로젝트에 기여하기 위해 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;저장소를 클론하고 실행하기만 하면 됩니다.&amp;lt;code&amp;gt;./gradlew&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;일반적인 명령은 다음과 같이 보일 것입니다 :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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와_portainer_재현_가능한_빌드_선택_사항&amp;quot;&amp;gt;6. Docker와 Portainer : 재현 가능한 빌드 (선택 사항)&amp;lt;/h2&amp;gt;
&amp;lt;div 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와 Portainer는 재현 가능한 빌드 생성을 크게 용이하게 하고 환경을 관리하는 데 도움을 주는 현대적인 DevOps 도구입니다.&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;_docker_engine_설치&amp;quot;&amp;gt;Docker Engine 설치&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Docker을 사용하면 애플리케이션과 그 종속성을 컨테이너에 패키징할 수 있습니다.&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;_docker을_공식_스크립트로_설치하기&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;Docker을 공식 스크립트로 설치하기 :&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;_docker_관리_sudo_없이_권장&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;Docker 관리 &amp;lt;code&amp;gt;sudo&amp;lt;/code&amp;gt; 없이 (권장) :&amp;lt;/strong&amp;gt;&amp;lt;/h4&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;사용자를 그룹에 추가하세요.&amp;lt;code&amp;gt;docker&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;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;_portainer_gui로_docker_관리하기&amp;quot;&amp;gt;Portainer (GUI)로 Docker 관리하기&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Portainer는 Docker 컨테이너를 관리하기 위한 가벼운 웹 인터페이스입니다.&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;_portainer_데이터용_볼륨을_생성하세요&amp;quot;&amp;gt;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;_portainer_컨테이너_실행&amp;quot;&amp;gt;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;이제 Portainer 인터페이스에 접근할 수 있습니다`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;_결론&amp;quot;&amp;gt;결론&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;당신의 환경이 이제 준비되었습니다! JDK, 가장 적합한 IDE, Gradle 및 JBake가 있습니다. 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;다음 기사에서는 명령을 사용할 것입니다.`gradle init`첫 번째 플러그인 프로젝트의 구조를 생성하기 위해&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 – 3부 : Poetry, Docker 및 고급 배포로 더 나아가기</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;#_poetry와의_통합&amp;quot;&amp;gt;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;#_poetry_설치&amp;quot;&amp;gt;Poetry 설치&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_프로젝트_초기화&amp;quot;&amp;gt;프로젝트 초기화&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_의존성_추가_및_설치&amp;quot;&amp;gt;의존성 추가 및 설치&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_poetry와_함께_게시&amp;quot;&amp;gt;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;#_도커를_사용한_재현_가능한_빌드&amp;quot;&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;#_dockerfile_예시&amp;quot;&amp;gt;Dockerfile 예시&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_github_actions_통합&amp;quot;&amp;gt;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;#_조건부_게시&amp;quot;&amp;gt;조건부 게시&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_renovate_및_dependabot을_사용한_의존성_자동화&amp;quot;&amp;gt;Renovate 및 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;#_리모델링하다&amp;quot;&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;#_plantuml_다이어그램&amp;quot;&amp;gt;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;#_사용_사례_고급_cicd&amp;quot;&amp;gt;사용 사례 – 고급 CI/CD&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_시퀀스_조건부_게시&amp;quot;&amp;gt;시퀀스 – 조건부 게시&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_상태_cicd_파이프라인&amp;quot;&amp;gt;상태 – CI/CD 파이프라인&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_배포_cicd_아키텍처&amp;quot;&amp;gt;배포 – 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;#_결론&amp;quot;&amp;gt;결론&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;이 시리즈의 처음 두 부분에서 우리는 : - 작동하는 CI/CD 파이프라인을 Python 애플리케이션용으로 GitHub Actions 및 PyPI를 이용해 구축했습니다. - 이 파이프라인을 다중 버전 테스트, Test 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;이 세 번째 부분에서 우리는 갈 것입니다.&amp;lt;strong&amp;gt;PyPI에 단순한 게시를 넘어&amp;lt;/strong&amp;gt;CI/CD 파이프라인을 완전히 강력하고 전문적으로 구축하기 위해 : - &amp;lt;strong&amp;gt;시&amp;lt;/strong&amp;gt;현대적인 패키징과 최적화된 의존성 관리를 위해 - &amp;lt;strong&amp;gt;도커&amp;lt;/strong&amp;gt;재현 가능하고 다중 플랫폼을 지원하는 빌드를 만들기 위해 - &amp;lt;strong&amp;gt;조건부 발행&amp;lt;/strong&amp;gt;릴리스 후보와 같은 시나리오를 관리하기 위해 - &amp;lt;strong&amp;gt;자동화 도구&amp;lt;/strong&amp;gt;(Renovate, Dependabot)를 사용해 파이프라인을 손쉽게 최신 상태로 유지하기&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;_poetry와의_통합&amp;quot;&amp;gt;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는 기존 패키징 도구(setup.py, requirements.txt)를 대체하여 의존성 및 빌드 관리를 중앙 집중화하여`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;_poetry_설치&amp;quot;&amp;gt;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;_프로젝트_초기화&amp;quot;&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-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;이는 파일을 생성합니다.&amp;lt;code&amp;gt;pyproject.toml&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-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;_의존성_추가_및_설치&amp;quot;&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-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;_poetry와_함께_게시&amp;quot;&amp;gt;Poetry와 함께 게시&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Poetry 네이티브하게 출판을 :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;이 명령은 자동으로 에 있는 정보를 사용합니다`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;_도커를_사용한_재현_가능한_빌드&amp;quot;&amp;gt;도커를 사용한 재현 가능한 빌드&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;개발, CI/CD, 프로덕션에서 동일한 실행 환경을 보장하기 위해 Docker는 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;_dockerfile_예시&amp;quot;&amp;gt;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;이것은 보장합니다: - 동결된 파이썬 환경. - 잠긴 의존성들 via`poetry.lock`. - 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;_github_actions_통합&amp;quot;&amp;gt;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;_조건부_게시&amp;quot;&amp;gt;조건부 게시&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;전문적인 파이프라인에서는 특정 경우에만 게시할 수 있어야 합니다 : - Release candidates Test PyPI сторону - 안정적인 버전을 PyPI로. - 도커 빌드만 트리거됨`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;이 접근법은 실수로 게시되는 것을 방지합니다.&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;_renovate_및_dependabot을_사용한_의존성_자동화&amp;quot;&amp;gt;Renovate 및 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;의존성이 낡아지지 않도록 자동 업데이트 도구를 통합하세요.&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는 매주 Python 및 GitHub Actions 의존성을 업데이트하기 위해 PR을 엽니다.&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;_리모델링하다&amp;quot;&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-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는 더 세밀한 제어를 제공합니다: 의존성 그룹화, 일정, 그리고 고급 규칙.&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;_plantuml_다이어그램&amp;quot;&amp;gt;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;_사용_사례_고급_cicd&amp;quot;&amp;gt;사용 사례 – 고급 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
actor Developer
actor GitHub as &amp;quot;GitHub Actions&amp;quot;
actor PyPI
actor DockerRegistry as &amp;quot;Docker 레지스트리&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;_시퀀스_조건부_게시&amp;quot;&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&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;_상태_cicd_파이프라인&amp;quot;&amp;gt;상태 – 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;_배포_cicd_아키텍처&amp;quot;&amp;gt;배포 – 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;개발자 머신&amp;quot; {
  component &amp;quot;Git 클라이언트&amp;quot;
}

node &amp;quot;GitHub 작업&amp;quot; {
  component &amp;quot;CI 워크플로우&amp;quot;
  component &amp;quot;CD 워크플로우&amp;quot;
}

node &amp;quot;패키지 저장소&amp;quot; {
  artifact &amp;quot;테스트 PyPI&amp;quot;
  artifact &amp;quot;PyPI&amp;quot;
  artifact &amp;quot;도커 레지스트리&amp;quot;
}

&amp;quot;개발자 머신&amp;quot; --&amp;amp;gt; &amp;quot;GitHub 작업&amp;quot;
&amp;quot;GitHub 작업&amp;quot; --&amp;amp;gt; &amp;quot;테스트 PyPI&amp;quot;
&amp;quot;GitHub 작업&amp;quot; --&amp;amp;gt; &amp;quot;PyPI&amp;quot;
&amp;quot;GitHub 작업&amp;quot; --&amp;amp;gt; &amp;quot;도커 레지스트리&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;_결론&amp;quot;&amp;gt;결론&amp;lt;/h2&amp;gt;
&amp;lt;div 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를 활용한 Python 패키징 현대화 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;이제 CI/CD 파이프라인을 보유하고 있습니다.&amp;lt;strong&amp;gt;완전한, 산업화된, 그리고 안전한&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>2부 : 고급 Python CI/CD 파이프라인 산업화 및 보안</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;#_소개&amp;quot;&amp;gt;소개&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_cicd_파이프라인_아키텍처&amp;quot;&amp;gt;CI/CD 파이프라인 아키텍처&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_프로젝트_구조&amp;quot;&amp;gt;프로젝트 구조&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_pyproject_toml을_사용한_패키지_구성&amp;quot;&amp;gt;pyproject.toml을 사용한 패키지 구성&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_cicd_워크플로_테스트_및_품질&amp;quot;&amp;gt;CI/CD 워크플로 - 테스트 및 품질&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_릴리스_및_배포_워크플로우&amp;quot;&amp;gt;릴리스 및 배포 워크플로우&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_아키텍처_다이어그램&amp;quot;&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;#_시퀀스_다이어그램_릴리스_프로세스&amp;quot;&amp;gt;시퀀스 다이어그램 - 릴리스 프로세스&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_상태_다이어그램_패키지_생명주기&amp;quot;&amp;gt;상태 다이어그램 - 패키지 생명주기&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_배포_다이어그램_cicd_인프라스트럭처&amp;quot;&amp;gt;배포 다이어그램 - 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;#_파이프라인_객체_및_모델&amp;quot;&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;#_클래스_다이어그램_cicd_모델&amp;quot;&amp;gt;클래스 다이어그램 - 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;#_시크릿_구성&amp;quot;&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;#_github_actions_시크릿&amp;quot;&amp;gt;GitHub Actions 시크릿&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_pypi_토큰_생성&amp;quot;&amp;gt;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;#_로컬_개발_스크립트&amp;quot;&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;#_메이크파일&amp;quot;&amp;gt;메이크파일&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_버전_스크립트&amp;quot;&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;#_모범_사례_및_권고안&amp;quot;&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;#_시맨틱_버저닝&amp;quot;&amp;gt;시맨틱 버저닝&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_전략_브랜칭&amp;quot;&amp;gt;전략 브랜칭&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_테스트_및_커버리지&amp;quot;&amp;gt;테스트 및 커버리지&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_안전&amp;quot;&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;#_모니터링_및_관찰_가능성&amp;quot;&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;#_파이프라인_메트릭스&amp;quot;&amp;gt;파이프라인 메트릭스&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_알림&amp;quot;&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;#_결론&amp;quot;&amp;gt;결론&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_보충_자료&amp;quot;&amp;gt;보충 자료&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;_소개&amp;quot;&amp;gt;소개&amp;lt;/h2&amp;gt;
&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;/div&amp;gt;
&amp;lt;p&amp;gt;이 시리즈의 첫 번째 부분에서 우리는 Python 패키지를 PyPI에 배포하고 테스트를 자동화하기 위해 간단하면서도 효과적인 CI/CD 파이프라인을 구축했습니다.&amp;lt;/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;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;현대 표준인 pyproject.toml로 마이그레이션,&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;품질 및 안전 도구 추가 (Black, Mypy, Bandit, Safety),&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;멀티버전 테스트를 설정,&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;Test PyPI를 통해 점진적 배포를 통합하는&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&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;기능적인 파이프라인에서 전문적인 CI/CD 인프라로 전환할 준비를 하세요.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;파이썬 애플리케이션을 PyPI에 배포하려면 테스트, 빌드, 패키지 출시를 자동화하는 강력한 CI/CD 파이프라인이 필요합니다. 이 문서는 Python CLI 애플리케이션을 위한 GitHub Actions를 사용한 완전한 파이프라인 구축을 자세히 설명하며, 현대 파이썬 생태계의 모범 사례를 기반으로 합니다.&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;_cicd_파이프라인_아키텍처&amp;quot;&amp;gt;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;우리가 구축할 파이프라인은 여러 단계로 구성된 접근 방식을 따릅니다 :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;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 파이썬 CI/CD 파이프라인 PyPI로
skinparam backgroundColor #f8f9fa
skinparam componentStyle rectangle

rectangle &amp;quot;개발자&amp;quot; as dev
rectangle &amp;quot;GitHub 저장소&amp;quot; as repo {
  rectangle &amp;quot;.github/workflows/&amp;quot; as workflows
  rectangle &amp;quot;시험/&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 액션&amp;quot; as actions {
  rectangle &amp;quot;테스트 러너&amp;quot; as test_runner
  rectangle &amp;quot;빌드&amp;quot; as build
  rectangle &amp;quot;보안 스캔&amp;quot; as security
  rectangle &amp;quot;품질 검사&amp;quot; as quality
}

rectangle &amp;quot;PyPI&amp;quot; as pypi {
  rectangle &amp;quot;테스트 PyPI&amp;quot; as test_pypi
  rectangle &amp;quot;프로덕션 PyPI&amp;quot; as prod_pypi
}

rectangle &amp;quot;사용자들&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;_프로젝트_구조&amp;quot;&amp;gt;프로젝트 구조&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;배포 준비가 된 Python CLI 애플리케이션은 표준화된 구조를 따라야 합니다 :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;_pyproject_toml을_사용한_패키지_구성&amp;quot;&amp;gt;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;파일`pyproject.toml`는 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;_cicd_워크플로_테스트_및_품질&amp;quot;&amp;gt;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;주요 워크플로 (&amp;lt;code&amp;gt;ci.yml&amp;lt;/code&amp;gt;) 테스트를 여러 버전에서 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;_릴리스_및_배포_워크플로우&amp;quot;&amp;gt;릴리스 및 배포 워크플로우&amp;lt;/h2&amp;gt;
&amp;lt;div 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;release.yml&amp;lt;/code&amp;gt;) 자동 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;_아키텍처_다이어그램&amp;quot;&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;_시퀀스_다이어그램_릴리스_프로세스&amp;quot;&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&amp;gt;@startuml
!theme plain
title 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;테스트 PyPI&amp;quot; as tpypi
participant &amp;quot;PyPI&amp;quot; as pypi
participant &amp;quot;사용자&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;_상태_다이어그램_패키지_생명주기&amp;quot;&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&amp;gt;@startuml
!theme plain
title 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;_배포_다이어그램_cicd_인프라스트럭처&amp;quot;&amp;gt;배포 다이어그램 - 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 배포 인프라스트럭처
node &amp;quot;깃허브&amp;quot; {
  component &amp;quot;저장소&amp;quot; as repo
  component &amp;quot;액션 러너&amp;quot; as runner
  component &amp;quot;비밀 저장소&amp;quot; as secrets
}

node &amp;quot;PyPI 인프라&amp;quot; {
  component &amp;quot;PyPI&amp;quot; as pypi
  component &amp;quot;테스트 PyPI&amp;quot; as testpypi
  database &amp;quot;패키지 인덱스&amp;quot; as index
}

node &amp;quot;개발자 머신&amp;quot; {
  component &amp;quot;Git 클라이언트&amp;quot; as git
  component &amp;quot;파이썬 환경&amp;quot; as python
  component &amp;quot;통합 개발 환경&amp;quot; as ide
}

node &amp;quot;사용자 환경&amp;quot; {
  component &amp;quot;피프&amp;quot; as pip_client
  component &amp;quot;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;_파이프라인_객체_및_모델&amp;quot;&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;_클래스_다이어그램_cicd_모델&amp;quot;&amp;gt;클래스 다이어그램 - 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 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;_시크릿_구성&amp;quot;&amp;gt;시크릿 구성&amp;lt;/h2&amp;gt;
&amp;lt;div 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에 다음 비밀을 구성해야 합니다:&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;_github_actions_시크릿&amp;quot;&amp;gt;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;_pypi_토큰_생성&amp;quot;&amp;gt;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;_로컬_개발_스크립트&amp;quot;&amp;gt;로컬 개발 스크립트&amp;lt;/h2&amp;gt;
&amp;lt;div 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;/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;_메이크파일&amp;quot;&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-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;_버전_스크립트&amp;quot;&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-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;_모범_사례_및_권고안&amp;quot;&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;_시맨틱_버저닝&amp;quot;&amp;gt;시맨틱 버저닝&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;의미론적 버전 관리(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;: 호환되지 않는 변경&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;: 새로운 호환 가능한 기능&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;: 호환 가능한 버그 수정&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;_전략_브랜칭&amp;quot;&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-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;_테스트_및_커버리지&amp;quot;&amp;gt;테스트 및 커버리지&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;최소 코드 커버리지: 85%&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;비즈니스 로직을 위한 단위 테스트&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;어댑터를 위한 통합 테스트&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;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;_안전&amp;quot;&amp;gt;안전&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;자동 의존성 스캔 (Safety)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;정적 코드 분석 (Bandit)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;비밀 assoluto 코드에&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;특정 PyPI 토큰 사용&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;_모니터링_및_관찰_가능성&amp;quot;&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;_파이프라인_메트릭스&amp;quot;&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-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;_알림&amp;quot;&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-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;_결론&amp;quot;&amp;gt;결론&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;이 완전한 Python용 CI/CD 파이프라인은 다음을 제공합니다:&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;완전 자동화&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;보안&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;품질&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;신뢰성&amp;lt;/strong&amp;gt;: 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;추적성&amp;lt;/strong&amp;gt;: 아티팩트, 보고서 및 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;이러한 관행을 채택하면 Python 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;_보충_자료&amp;quot;&amp;gt;보충 자료&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;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;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 도움말 문서&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;의미적 버전 관리 사양&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;변경 로그를 유지해&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>1부 : 파이썬과 PyPI를 위한 간단한 CI/CD 파이프라인 구축</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;#_소개&amp;quot;&amp;gt;소개&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_파이프라인_아키텍처&amp;quot;&amp;gt;파이프라인 아키텍처&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_연속_통합ci_파이프라인_구성&amp;quot;&amp;gt;연속 통합(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;#_ci_워크플로우_구조&amp;quot;&amp;gt;CI 워크플로우 구조&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_ci_단계_분석&amp;quot;&amp;gt;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_트리거_on&amp;quot;&amp;gt;1. 트리거 (&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;_소개&amp;quot;&amp;gt;소개&amp;lt;/h2&amp;gt;
&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;목표 : 독자가 최소한이지만 기능적인 파이썬 애플리케이션용 CI/CD 파이프라인을 설정하고 PyPI에 자동 배포하도록 안내합니다.&amp;lt;/div&amp;gt;
&amp;lt;p&amp;gt;개발 프로세스의 자동화는 현대 프로젝트에서 필수적이 되었습니다. 잘 설계된 CI/CD 파이프라인은 개발 주기 초기에 회귀를 감지하는 것뿐만 아니라 배포 프로세스를 완전히 자동화할 수 있습니다.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;이 문서에서는 GitHub Actions를 사용하여 Python 애플리케이션에 대한 완전한 CI/CD 파이프라인을 구축하는 방법을 살펴보겠습니다. 여기서 CI는 지속적 통합, CD는 지속적 배포를 의미하며, 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;_파이프라인_아키텍처&amp;quot;&amp;gt;파이프라인 아키텍처&amp;lt;/h2&amp;gt;
&amp;lt;div 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;/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;CI 파이프라인&amp;lt;/strong&amp;gt;각 푸시 및 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;CD 파이프라인&amp;lt;/strong&amp;gt;: 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 저장소&amp;quot; {
  [Source Code] as source
  [GitHub Actions] as actions
}

package &amp;quot;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 파이프라인&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;_연속_통합ci_파이프라인_구성&amp;quot;&amp;gt;연속 통합(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;_ci_워크플로우_구조&amp;quot;&amp;gt;CI 워크플로우 구조&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;CI 워크플로우는 코드의 각 기여를 검증하도록 설계되었습니다. 다음은 전체 구성 :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;_ci_단계_분석&amp;quot;&amp;gt;CI 단계 분석&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;sect3&amp;quot;&amp;gt;
&amp;lt;h4 id=&amp;quot;_1_트리거_on&amp;quot;&amp;gt;1. 트리거 (&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
----

파이프라인이 다음에 따라 트리거됩니다: - 브랜치에 푸시할 때마다`main` - 각 pull request에 대해`main`

이 접근 방식은 메인 코드가 안정적으로 유지되도록 하고 모든 기여가 통합되기 전에 검증되도록 보장합니다.

==== 2. 실행 환경

----

runs-on: ubuntu-latest

Ubuntu Latest는 대부분의 Python 프로젝트에 성능, 비용, 호환성 측면에서 좋은 균형을 제공합니다.

==== 3. 코드 체크아웃

```yaml
----
----
- name: Checkout code
  uses: actions/checkout@v4
----

행동`checkout@v4`저장소의 소스 코드를 가져옵니다. v4 버전은 성능과 보안 개선을 제공합니다.

==== 4. 파이썬 구성

----

- name: Set up Python uses: actions/setup-python@v5 with: python-version: &amp;#39;3.x&amp;#39;

사용`&amp;#39;3.x&amp;#39;`자동으로 최신 안정 버전의 Python 3을 사용하게 하여 유지 관리를 간소화합니다.

==== 5. 의존성 설치

```yaml
----
----
- name: Install dependencies
  run: |
    python -m pip install --upgrade pip
    pip install -r requirements.txt
    pip install ruff pytest pytest-mock
----

이 단계: - pip을 최신 버전으로 업데이트합니다 - 프로젝트 의존성을 설치합니다. - 개발 도구(린팅 및 테스트)를 추가합니다.

==== 6. Ruff로 린팅

----

- name: Run Linting (Ruff) run: ruff check .

**러프**은 초고속 파이썬 린터로, Rust로 작성되었습니다. 이는 여러 도구(Flake8, Black, isort)의 기능을 하나의 성능 좋은 도구로 결합합니다.

==== 7. 테스트 실행

```yaml
----
----
- name: Run Tests (Pytest)
  run: pytest
----

Pytest는 테스트 스위트 전체를 실행하여 변경 사항이 회귀를 일으키지 않도록 보장합니다.

== 배포 파이프라인 구성(CD)

=== CD 워크플로우 구조

CD 워크플로우는 GitHub 릴리스에서만 트리거되고 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/*
----

=== CD 단계 분석

==== 트리거 출시

----

on: release: types: - published

CD 파이프라인은 GitHub 릴리스가 게시될 때만 트리거됩니다. 이 접근 방식은 배포에 대한 정확한 제어를 보장합니다.

==== 2. 빌드 도구 설치

```yaml
----
----
pip install setuptools wheel twine
----

- **setuptools**: 파이썬 패키징 도구 - **바퀴**: 현대적인 Python 배포 형식 - **끈**PyPI에 업로드하는 보안 도구

==== 3. 인증 구성

----

env: TWINE_USERNAME: __token__ TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}

인증은 GitHub 비밀에 저장된 PyPI API 토큰을 사용하며, 이는 기존 자격 증명보다 더 안전합니다.

==== 4. 빌드 및 배포

```yaml
----
----
run: |
  python setup.py sdist bdist_wheel
  twine upload dist/*
----

- `sdist`: 소스 배포본을 생성합니다. - `bdist_wheel`: 휠(바이너리 배포)를 생성합니다 - `twine upload`PyPI에 배포를 게시합니다

== 파이썬 패키지 구성

=== setup.py의 구조

파이프라인이 작동하려면 프로젝트에 파일을 포함해야 합니다.`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;,
        ],
    },
)
----

=== setup.py의 핵심 포인트

1. **메타데이터**: 이름, 버전, 작가, 설명
2. **의존성**필요한 패키지 목록
3. **진입점**: 노출된 CLI 명령어
4. **분류기**: PyPI용 메타데이터

== GitHub Secrets를 사용한 보안

=== PyPI 토큰 설정

1. **PyPI에서 API 토큰 생성**:

- PyPI에 로그인하세요 - 계정 설정 &amp;amp;gt; API 토큰으로 이동하세요 - 적절한 권한으로 새 토큰을 생성하세요

1. **GitHub에 비밀을 추가**:

- 리포지토리 설정 &amp;amp;gt; 비밀 및 변수 &amp;amp;gt; 작업 - 새로운 비밀을 이름을 지정하여 생성하세요`PYPI_API_TOKEN` - PyPI 토큰을 붙여넣으세요

[plantuml, secrets-flow, svg]
----
@startuml
!theme plain

actor Developer as dev
participant &amp;quot;GitHub 저장소&amp;quot; as repo
participant &amp;quot;GitHub 액션&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
----

== 전체 배포 워크플로

=== 배포 시퀀스

[plantuml, deployment-sequence, svg]
----
@startuml
!theme plain

actor Developer as dev
participant &amp;quot;로컬 Git&amp;quot; as git
participant &amp;quot;GitHub&amp;quot; as github
participant &amp;quot;GitHub Actions&amp;quot; as actions
participant PyPI
participant &amp;quot;최종 사용자&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
----

=== 파이프라인 상태

[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
----

== 모범 사례 및 최적화

=== 1. 버전 관리

의미 있는 Git 태그를 사용하세요 :

----

git tag -a v1.2.3 -m &amp;quot;Release version 1.2.3&amp;quot; git push origin v1.2.3

=== 2. 매트릭스 테스트

여러 Python 버전에서 테스트하기 :

```yaml
----
----
strategy:
  matrix:
    python-version: [3.8, 3.9, &amp;quot;3.10&amp;quot;, &amp;quot;3.11&amp;quot;]
----

=== 3. 의존성 캐시

캐시로 빌드를 가속화하세요:

----

- name: Cache pip dependencies uses: actions/cache@v3 with: path: ~/.cache/pip key: ${{ runner.os }}-pip-${{ hashFiles(&amp;#39;**/requirements.txt&amp;#39;) }}

=== 4. 배포 환경

GitHub 환경을 사용하여 제어된 배포를 수행하세요 :

```yaml
----
----
jobs:
  deploy:
    environment: production
    runs-on: ubuntu-latest
----

== 유스케이스 및 아키텍처

=== 사용 사례 다이어그램

[plantuml, use-cases, svg]
----
@startuml
!theme plain

left to right direction

actor &amp;quot;개발자&amp;quot; as dev
actor &amp;quot;깃허브 액션&amp;quot; as ga
actor &amp;quot;최종 사용자&amp;quot; as user

package &amp;quot;CI/CD 시스템&amp;quot; {
  usecase &amp;quot;링팅 실행&amp;quot; as lint
  usecase &amp;quot;테스트 실행&amp;quot; as test
  usecase &amp;quot;빌드 패키지&amp;quot; as build
  usecase &amp;quot;PyPI에 게시&amp;quot; as publish
  usecase &amp;quot;릴리스 만들기&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
----

=== 배포 아키텍처

[plantuml, deployment-architecture, svg]
----
@startuml
!theme plain

cloud &amp;quot;깃헙&amp;quot; {
  [Source Repository]
  [GitHub Actions]
  [Secrets Store]
}

cloud &amp;quot;PyPI&amp;quot; {
  [Package Registry]
  [Distribution Files]
}

node &amp;quot;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
----

== 감시와 디버깅

=== 로그 및 모니터링

GitHub Actions은 각 단계에 대한 자세한 로그를 제공합니다. 디버그하려면 :

1. **로그를 조사해 주세요**각각의 step
2. **디버그를 활성화하세요**와 함께`ACTIONS_STEP_DEBUG`
3. **아티팩트를 사용하세요**빌드 파일을 저장하기 위해

----

- name: Upload build artifacts uses: actions/upload-artifact@v3 if: failure() with: name: build-logs path: build/

=== 알림

슬랙 또는 이메일 알림을 추가하세요 :

```yaml
----
----
- name: Notify on failure
  if: failure()
  uses: 8398a7/action-slack@v3
  with:
    status: ${{ job.status }}
    webhook_url: ${{ secrets.SLACK_WEBHOOK }}
----

== 결론

GitHub Actions를 사용해 견고한 CI/CD 파이프라인을 구축하면 개발 경험이 근본적으로 바뀝니다. 린팅, 테스트, 배포를 자동화하면 :

- **오류를 줄이세요**생산 중 - **주기를 가속화하세요**개발 - **신뢰를 개선하세요**당신의 릴리스에서 - **협업을 원활하게 하세요**팀으로

이 파이프라인은 linting 도구, 테스트 프레임워크, 또는 배포 대상을 조정하여 다양한 유형의 Python 프로젝트에 맞출 수 있습니다.

이러한 워크플로우의 초기 설정 투자는 배포 과정에서의 시간 절감과 수작업 오류 감소 덕분에 빠르게 회수됩니다.

== 보충 자료

- https://docs.github.com/en/actions[GitHub Actions 문서] - https://packaging.python.org/[Python 패키징 가이드] - https://docs.pytest.org/[문서 Pytest] - https://docs.astral.sh/ruff/[Ruff 문서] - https://twine.readthedocs.io/[Twine 문서]

✅ 기능적인 파이프라인 도달! 이제 간단한 CI/CD 파이프라인이 있어 GitHub Actions에서 직접 테스트를 자동화하고 Python 패키지를 PyPI에 게시할 수 있습니다.

그러나 이 파이프라인은 의도적으로 최소한으로 유지됩니다. 아직 전문적인 맥락에서 필수적인 일부 측면을 다루지 않습니다 :

파이썬 멀티버전 테스트,

자동 보안 분석,

Test PyPI를 통한 점진적 배포,

파이프라인 모니터링 및 메트릭,

버전 관리 자동화 및 현대적인 관례 통합 (pyproject.toml).

다음 섹션에서는 한 단계 더 나아갈 것입니다. 이 기본 파이프라인을 실제 산업용 배포 파이프라인, 즉 강력하고 안전한 형태로 변환하는 방법을 배우게 될 것입니다. 이렇게 변환된 파이프라인은 프로덕션 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>Discord 음악 봇 개발: 기능 아키텍처 및 로그 기반 개발</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;#_소개&amp;quot;&amp;gt;소개&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_현재_상황과_과제&amp;quot;&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;#_음악_api의_진화&amp;quot;&amp;gt;음악 API의 진화&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_로그_중심_개발_접근법&amp;quot;&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;#_개념_아키텍처&amp;quot;&amp;gt;개념 아키텍처&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_스택_기술과_함수형_패러다임&amp;quot;&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;#_기술적_선택&amp;quot;&amp;gt;기술적 선택&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_적용된_기능_원칙&amp;quot;&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;#_애자일_방법론과_백로그&amp;quot;&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;#_주요_에픽&amp;quot;&amp;gt;주요 에픽&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_인프라_봇_디스코드&amp;quot;&amp;gt;에픽 1: 인프라 봇 디스코드&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_에픽_2_spotify_통합&amp;quot;&amp;gt;에픽 2 : Spotify 통합&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_에픽_3_youtube_통합&amp;quot;&amp;gt;에픽 3 : YouTube 통합&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_에픽_4_음악_기능&amp;quot;&amp;gt;에픽 4 : 음악 기능&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;#_상세한_사용자_스토리&amp;quot;&amp;gt;상세한 사용자 스토리&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_봇_초기화&amp;quot;&amp;gt;US1.1 : 봇 초기화&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_us2_1_스마트_spotify_검색&amp;quot;&amp;gt;US2.1 : 스마트 Spotify 검색&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_us3_1_유튜브_복원력_있는_추출&amp;quot;&amp;gt;US3.1 : 유튜브 복원력 있는 추출&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;#_로그_기반_개발_접근&amp;quot;&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;#_로깅_전략&amp;quot;&amp;gt;로깅 전략&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_로그_구조&amp;quot;&amp;gt;로그 구조&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_로그_디자인_예제&amp;quot;&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;#_pydantic을_사용한_검증_아키텍처&amp;quot;&amp;gt;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;#_데이터_모델&amp;quot;&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;#_기능_오류_관리&amp;quot;&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;#_모나드와_오류_처리&amp;quot;&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;#_제한_회피_전략&amp;quot;&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;#_멀티소스_접근&amp;quot;&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;#_반복적_개발_계획&amp;quot;&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;#_스프린트_계획&amp;quot;&amp;gt;스프린트 계획&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_품질_메트릭스&amp;quot;&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;#_배포_및_모니터링&amp;quot;&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;#_생산_아키텍처&amp;quot;&amp;gt;생산 아키텍처&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_주동적_모니터링&amp;quot;&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;#_결론_및_전망&amp;quot;&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;#_다음_단계&amp;quot;&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;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_소개&amp;quot;&amp;gt;&amp;lt;a class=&amp;quot;anchor&amp;quot; href=&amp;quot;#_소개&amp;quot;&amp;gt;&amp;lt;/a&amp;gt;소개&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Discord 봇을 개발하여 Spotify 및 YouTube API를 통합하는 것은 현대적인 기술적 도전 과제를 제시하며, 특히 최근의 제한 및 플랫폼의 변화에 직면해 있습니다. 이 글은 다음과 같은 방법론적 접근을 기반으로 합니다&amp;lt;strong&amp;gt;로그 기반 개발&amp;lt;/strong&amp;gt;(LDD), 테스트 주도 개발의 확장이며, 파이썬을 사용한 함수형 프로그래밍 패러다임에 적용되었습니다.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;우리 목표 : 현재 음악 API의 제약을 탐색하면서도 디스코드에서 원활한 사용자 경험을 제공할 수 있는 강력하고 유지보수 가능하며 확장 가능한 봇을 만드는 것이다.&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;_현재_상황과_과제&amp;quot;&amp;gt;&amp;lt;a class=&amp;quot;anchor&amp;quot; href=&amp;quot;#_현재_상황과_과제&amp;quot;&amp;gt;&amp;lt;/a&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;_음악_api의_진화&amp;quot;&amp;gt;&amp;lt;a class=&amp;quot;anchor&amp;quot; href=&amp;quot;#_음악_api의_진화&amp;quot;&amp;gt;&amp;lt;/a&amp;gt;음악 API의 진화&amp;lt;/h3&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;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;스포티파이&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;유튜브&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;디스코드&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 class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_로그_중심_개발_접근법&amp;quot;&amp;gt;&amp;lt;a class=&amp;quot;anchor&amp;quot; href=&amp;quot;#_로그_중심_개발_접근법&amp;quot;&amp;gt;&amp;lt;/a&amp;gt;로그 중심 개발 접근법&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;LDD는 로그를 개발의 핵심에 배치함으로써 TDD를 확장한다 :&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;로그의 정의&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;관찰을 통한 검증&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;완전한 추적 가능성&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;프로액티브 디버깅&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&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_개념_아키텍처&amp;quot;&amp;gt;&amp;lt;a class=&amp;quot;anchor&amp;quot; href=&amp;quot;#_개념_아키텍처&amp;quot;&amp;gt;&amp;lt;/a&amp;gt;개념 아키텍처&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;디스코드 봇 코어&amp;quot; {
  [Command Handler] as CH
  [Event Listener] as EL
  [Log Manager] as LM
}

package &amp;quot;음악 통합 레이어&amp;quot; {
  [Spotify Client] as SC
  [YouTube Client] as YC
  [Audio Processor] as AP
}

package &amp;quot;기능적 핵심&amp;quot; {
  [Data Validation] as DV
  [Business Logic] as BL
  [Error Handling] as EH
}

package &amp;quot;외부 API&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;_스택_기술과_함수형_패러다임&amp;quot;&amp;gt;&amp;lt;a class=&amp;quot;anchor&amp;quot; href=&amp;quot;#_스택_기술과_함수형_패러다임&amp;quot;&amp;gt;&amp;lt;/a&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;_기술적_선택&amp;quot;&amp;gt;&amp;lt;a class=&amp;quot;anchor&amp;quot; href=&amp;quot;#_기술적_선택&amp;quot;&amp;gt;&amp;lt;/a&amp;gt;기술적 선택&amp;lt;/h3&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;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;부작용 관리 및 함수 합성&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;타입 안전한 데이터 검증 및 직렬화&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;비동기 입출력&amp;lt;/strong&amp;gt;&amp;lt;/dt&amp;gt;
&amp;lt;dd&amp;gt;
&amp;lt;p&amp;gt;API를 위한 비동기 프로그래밍&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;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;_적용된_기능_원칙&amp;quot;&amp;gt;&amp;lt;a class=&amp;quot;anchor&amp;quot; href=&amp;quot;#_적용된_기능_원칙&amp;quot;&amp;gt;&amp;lt;/a&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&amp;gt;@startuml
!theme plain

title 기능적 데이터 흐름
participant &amp;quot;디스코드 명령&amp;quot; as DC
participant &amp;quot;검증기&amp;quot; as V
participant &amp;quot;비즈니스 로직&amp;quot; as BL
participant &amp;quot;API 클라이언트&amp;quot; as AC
participant &amp;quot;로거&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;_애자일_방법론과_백로그&amp;quot;&amp;gt;&amp;lt;a class=&amp;quot;anchor&amp;quot; href=&amp;quot;#_애자일_방법론과_백로그&amp;quot;&amp;gt;&amp;lt;/a&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;_주요_에픽&amp;quot;&amp;gt;&amp;lt;a class=&amp;quot;anchor&amp;quot; href=&amp;quot;#_주요_에픽&amp;quot;&amp;gt;&amp;lt;/a&amp;gt;주요 에픽&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;우리 개발은 4개의 주요 에픽을 중심으로 구성됩니다:&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;_에픽_1_인프라_봇_디스코드&amp;quot;&amp;gt;&amp;lt;a class=&amp;quot;anchor&amp;quot; href=&amp;quot;#_에픽_1_인프라_봇_디스코드&amp;quot;&amp;gt;&amp;lt;/a&amp;gt;에픽 1: 인프라 봇 디스코드&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;Valeur métier&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;수용 기준&amp;lt;/strong&amp;gt; : - 안정적인 Discord 연결과 재접속 관리 - 모듈형 명령 시스템 통합된 구조화된 로깅 - 중앙 집중 오류 관리&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;_에픽_2_spotify_통합&amp;quot;&amp;gt;&amp;lt;a class=&amp;quot;anchor&amp;quot; href=&amp;quot;#_에픽_2_spotify_통합&amp;quot;&amp;gt;&amp;lt;/a&amp;gt;에픽 2 : 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;비즈니스 가치&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;수용 기준&amp;lt;/strong&amp;gt; : - 안전한 OAuth2 인증 - 스마트 캐시를 사용한 트랙 검색 - 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;_에픽_3_youtube_통합&amp;quot;&amp;gt;&amp;lt;a class=&amp;quot;anchor&amp;quot; href=&amp;quot;#_에픽_3_youtube_통합&amp;quot;&amp;gt;&amp;lt;/a&amp;gt;에픽 3 : 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;업무 가치&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;수용 기준&amp;lt;/strong&amp;gt; : - 제한 사항의 합법적인 회피 - 최적화된 오디오 추출 - 비공개/삭제된 비디오 관리 - 유튜브의 ToS 준수&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;_에픽_4_음악_기능&amp;quot;&amp;gt;&amp;lt;a class=&amp;quot;anchor&amp;quot; href=&amp;quot;#_에픽_4_음악_기능&amp;quot;&amp;gt;&amp;lt;/a&amp;gt;에픽 4 : 음악 기능&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;업무 가치&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;수락 기준&amp;lt;/strong&amp;gt; : - 고품질 오디오 강의 - 스마트 재생 큐 - 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;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_상세한_사용자_스토리&amp;quot;&amp;gt;&amp;lt;a class=&amp;quot;anchor&amp;quot; href=&amp;quot;#_상세한_사용자_스토리&amp;quot;&amp;gt;&amp;lt;/a&amp;gt;상세한 사용자 스토리&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;sect3&amp;quot;&amp;gt;
&amp;lt;h4 id=&amp;quot;_us1_1_봇_초기화&amp;quot;&amp;gt;&amp;lt;a class=&amp;quot;anchor&amp;quot; href=&amp;quot;#_us1_1_봇_초기화&amp;quot;&amp;gt;&amp;lt;/a&amp;gt;US1.1 : 봇 초기화&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;En tant que&amp;lt;/strong&amp;gt; 개발자 &amp;lt;strong&amp;gt;나는 원해&amp;lt;/strong&amp;gt; 신뢰성 있게 연결되는 디스코드 봇 &amp;lt;strong&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;&amp;lt;strong&amp;gt;DoD (완료의 정의)&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;sect3&amp;quot;&amp;gt;
&amp;lt;h4 id=&amp;quot;_us2_1_스마트_spotify_검색&amp;quot;&amp;gt;&amp;lt;a class=&amp;quot;anchor&amp;quot; href=&amp;quot;#_us2_1_스마트_spotify_검색&amp;quot;&amp;gt;&amp;lt;/a&amp;gt;US2.1 : 스마트 Spotify 검색&amp;lt;/h4&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Discord 사용자로서 저는 Spotify에서 곡을 검색하고 싶어요 &amp;lt;strong&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;&amp;lt;strong&amp;gt;DoD&amp;lt;/strong&amp;gt; : - [ ] 명령`/search`기능적인 - [ ] 관련 결과와 메타데이터 - [ ] 로컬 캐시로 쿼리 최적화 - [ ] 우아한 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_유튜브_복원력_있는_추출&amp;quot;&amp;gt;&amp;lt;a class=&amp;quot;anchor&amp;quot; href=&amp;quot;#_us3_1_유튜브_복원력_있는_추출&amp;quot;&amp;gt;&amp;lt;/a&amp;gt;US3.1 : 유튜브 복원력 있는 추출&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;나는&amp;lt;/strong&amp;gt; 유튜브 오디오를 신뢰할 수 있게 추출하고 싶다 &amp;lt;strong&amp;gt;Afin de&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;DoD&amp;lt;/strong&amp;gt; : - [ ] ToS 위반 없이 추출 - [ ] 최적의 오디오 품질 - [ ] 지리적 제한 관리 - [ ] 상세한 작업 로그&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;_로그_기반_개발_접근&amp;quot;&amp;gt;&amp;lt;a class=&amp;quot;anchor&amp;quot; href=&amp;quot;#_로그_기반_개발_접근&amp;quot;&amp;gt;&amp;lt;/a&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;_로깅_전략&amp;quot;&amp;gt;&amp;lt;a class=&amp;quot;anchor&amp;quot; href=&amp;quot;#_로깅_전략&amp;quot;&amp;gt;&amp;lt;/a&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&amp;gt;@startuml
!theme spacelab

title 로그 기반 개발 흐름
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;_로그_구조&amp;quot;&amp;gt;&amp;lt;a class=&amp;quot;anchor&amp;quot; href=&amp;quot;#_로그_구조&amp;quot;&amp;gt;&amp;lt;/a&amp;gt;로그 구조&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;우리의 LDD 접근 방식은 의미 수준을 갖는 구조화된 로그를 사용합니다:&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;흔적&amp;lt;/strong&amp;gt;&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;dt class=&amp;quot;hdlist1&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;디버그&amp;lt;/strong&amp;gt;&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;dt class=&amp;quot;hdlist1&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;정보&amp;lt;/strong&amp;gt;&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;dt class=&amp;quot;hdlist1&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;경고&amp;lt;/strong&amp;gt;&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;dt class=&amp;quot;hdlist1&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;오류&amp;lt;/strong&amp;gt;&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;dt class=&amp;quot;hdlist1&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;CRITICAL&amp;lt;/strong&amp;gt;&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 class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_로그_디자인_예제&amp;quot;&amp;gt;&amp;lt;a class=&amp;quot;anchor&amp;quot; href=&amp;quot;#_로그_디자인_예제&amp;quot;&amp;gt;&amp;lt;/a&amp;gt;로그 디자인 예제&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Spotify 검색 기능을 구현하기 전에 그 로그를 정의합니다:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;_pydantic을_사용한_검증_아키텍처&amp;quot;&amp;gt;&amp;lt;a class=&amp;quot;anchor&amp;quot; href=&amp;quot;#_pydantic을_사용한_검증_아키텍처&amp;quot;&amp;gt;&amp;lt;/a&amp;gt;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;_데이터_모델&amp;quot;&amp;gt;&amp;lt;a class=&amp;quot;anchor&amp;quot; href=&amp;quot;#_데이터_모델&amp;quot;&amp;gt;&amp;lt;/a&amp;gt;데이터 모델&amp;lt;/h3&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&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;_기능_오류_관리&amp;quot;&amp;gt;&amp;lt;a class=&amp;quot;anchor&amp;quot; href=&amp;quot;#_기능_오류_관리&amp;quot;&amp;gt;&amp;lt;/a&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;_모나드와_오류_처리&amp;quot;&amp;gt;&amp;lt;a class=&amp;quot;anchor&amp;quot; href=&amp;quot;#_모나드와_오류_처리&amp;quot;&amp;gt;&amp;lt;/a&amp;gt;모나드와 오류 처리&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;PyMonade를 사용하면 오류를 우아하게 관리할 수 있습니다 :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;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 오류 처리 흐름
participant &amp;quot;명령&amp;quot; as C
participant &amp;quot;아마도 모나드&amp;quot; as M
participant &amp;quot;Either 모나드&amp;quot; as E
participant &amp;quot;로거&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;_제한_회피_전략&amp;quot;&amp;gt;&amp;lt;a class=&amp;quot;anchor&amp;quot; href=&amp;quot;#_제한_회피_전략&amp;quot;&amp;gt;&amp;lt;/a&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;_멀티소스_접근&amp;quot;&amp;gt;&amp;lt;a class=&amp;quot;anchor&amp;quot; href=&amp;quot;#_멀티소스_접근&amp;quot;&amp;gt;&amp;lt;/a&amp;gt;멀티소스 접근&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;API 제한에 직면하여 우리는 다각화 전략을 채택하고 있습니다 :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;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 멀티 소스 전략
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;_반복적_개발_계획&amp;quot;&amp;gt;&amp;lt;a class=&amp;quot;anchor&amp;quot; href=&amp;quot;#_반복적_개발_계획&amp;quot;&amp;gt;&amp;lt;/a&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;_스프린트_계획&amp;quot;&amp;gt;&amp;lt;a class=&amp;quot;anchor&amp;quot; href=&amp;quot;#_스프린트_계획&amp;quot;&amp;gt;&amp;lt;/a&amp;gt;스프린트 계획&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;우리의 개발은 2주 스프린트 사이클을 따릅니다:&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;스프린트 1-2&amp;lt;/strong&amp;gt;&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;dt class=&amp;quot;hdlist1&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;스프린트 3-4&amp;lt;/strong&amp;gt;&amp;lt;/dt&amp;gt;
&amp;lt;dd&amp;gt;
&amp;lt;p&amp;gt;Spotify와 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;스프린트 5-6&amp;lt;/strong&amp;gt;&amp;lt;/dt&amp;gt;
&amp;lt;dd&amp;gt;
&amp;lt;p&amp;gt;YouTube 통합 및 우회&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;스프린트 7-8&amp;lt;/strong&amp;gt;&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;dt class=&amp;quot;hdlist1&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;스프린트 9-10&amp;lt;/strong&amp;gt;&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 class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_품질_메트릭스&amp;quot;&amp;gt;&amp;lt;a class=&amp;quot;anchor&amp;quot; href=&amp;quot;#_품질_메트릭스&amp;quot;&amp;gt;&amp;lt;/a&amp;gt;품질 메트릭스&amp;lt;/h3&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;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;로그 커버리지&amp;lt;/strong&amp;gt;중요한 경로의 90% 초과&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;API 신뢰성&amp;lt;/strong&amp;gt;: &amp;amp;lt;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;성능&amp;lt;/strong&amp;gt;: &amp;amp;lt;500ms 평균 응답 시간&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;유지성&amp;lt;/strong&amp;gt;: 순환 복잡도 &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;_배포_및_모니터링&amp;quot;&amp;gt;&amp;lt;a class=&amp;quot;anchor&amp;quot; href=&amp;quot;#_배포_및_모니터링&amp;quot;&amp;gt;&amp;lt;/a&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;_생산_아키텍처&amp;quot;&amp;gt;&amp;lt;a class=&amp;quot;anchor&amp;quot; href=&amp;quot;#_생산_아키텍처&amp;quot;&amp;gt;&amp;lt;/a&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&amp;gt;@startuml
!theme vibrant

cloud &amp;quot;디스코드 서버&amp;quot; {
  [User Commands]
}

node &amp;quot;프로덕션 환경&amp;quot; {
  [Discord Bot]
  [Log Aggregator]
  [Metrics Collector]
  [Health Monitor]
}

database &amp;quot;로그 저장소&amp;quot; {
  [Structured Logs]
  [Error Traces]
  [Performance Metrics]
}

cloud &amp;quot;외부 API&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;_주동적_모니터링&amp;quot;&amp;gt;&amp;lt;a class=&amp;quot;anchor&amp;quot; href=&amp;quot;#_주동적_모니터링&amp;quot;&amp;gt;&amp;lt;/a&amp;gt;주동적 모니터링&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;LDD는 스마트 모니터링을 용이하게 합니다 :&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;로그 패턴 기반 경고&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;행동 이상 탐지&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;실시간 비즈니스 메트릭스&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;로그 상관을 통한 디버깅 지원&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;_결론_및_전망&amp;quot;&amp;gt;&amp;lt;a class=&amp;quot;anchor&amp;quot; href=&amp;quot;#_결론_및_전망&amp;quot;&amp;gt;&amp;lt;/a&amp;gt;결론 및 전망&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;이 방법론적 접근은 함수형 프로그래밍 패러다임의 이점과 Log Driven Development의 견고함을 결합합니다. 이를 통해 우리는 :&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;문제를 예측하다&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;품질을 유지하다&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;빠르게 적응해&amp;lt;/strong&amp;gt;APIs의 변경 사항에&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;추적 가능성 보장&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;반복적 개발과 모듈러 아키텍처는 변화하는 음악 플랫폼의 제약에 대한 확장성을 보장한다.&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;_다음_단계&amp;quot;&amp;gt;&amp;lt;a class=&amp;quot;anchor&amp;quot; href=&amp;quot;#_다음_단계&amp;quot;&amp;gt;&amp;lt;/a&amp;gt;다음 단계&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;1단계&amp;lt;/strong&amp;gt;: 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;2단계&amp;lt;/strong&amp;gt;: 스마트 캐시가 적용된 Spotify 통합&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;3단계&amp;lt;/strong&amp;gt;: 탄력적인 YouTube 솔루션&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단계&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;/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;이 문서는 각 구성 요소의 구현을 상세히 설명하는 기술 시리즈로 이어지며, 코드 예시와 기능적 패턴을 포함합니다.&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>우분투에서 노트북 온도를 낮추고 모니터링하기</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;#_이_가이드의_목적&amp;quot;&amp;gt;이 가이드의 목적&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_전제_조건&amp;quot;&amp;gt;전제 조건&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_도구를_설치하고_구성하세요&amp;quot;&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;#_lm_sensors_센서를_감지하기_위해&amp;quot;&amp;gt;lm-sensors` 센서를 감지하기 위해&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_tlp_지능형_에너지_관리자&amp;quot;&amp;gt;tlp` : 지능형 에너지 관리자&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_cpufrequtils_cpu_주파수_제한&amp;quot;&amp;gt;cpufrequtils` : CPU 주파수 제한&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_auto_cpufreq_간소화된_동적_관리&amp;quot;&amp;gt;auto-cpufreq` : 간소화된 동적 관리&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_thermald_자동_열_보호&amp;quot;&amp;gt;thermald` : 자동 열 보호&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;#_실시간_모니터링&amp;quot;&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;#_sensors_명령줄에서&amp;quot;&amp;gt;sensors` 명령줄에서&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_gui_옵션_psensor&amp;quot;&amp;gt;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;#_추가_팁&amp;quot;&amp;gt;추가 팁&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_시스템을_청소하여_과부하를_제한&amp;quot;&amp;gt;시스템을 청소하여 과부하를 제한&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_더_깊이_가기&amp;quot;&amp;gt;더 깊이 가기&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_결론&amp;quot;&amp;gt;결론&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;Ubuntu에서 노트북이 비정상적으로 과열되고 있다고 느끼십니까? 이 글은 당신에게 한 가지 접근 방식을 제안합니다.&amp;lt;strong&amp;gt;소프트웨어&amp;lt;/strong&amp;gt;완전한 를 위한&amp;lt;strong&amp;gt;냉각하다&amp;lt;/strong&amp;gt;, &amp;lt;strong&amp;gt;감시하다&amp;lt;/strong&amp;gt; et &amp;lt;strong&amp;gt;온도 제어&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;_이_가이드의_목적&amp;quot;&amp;gt;이 가이드의 목적&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;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;에너지 관리 최적화&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;중요한 구성 요소 모니터링 (CPU, GPU, etc.)&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;열 스로틀링&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;_전제_조건&amp;quot;&amp;gt;전제 조건&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;Ubuntu 머신 (20.04 이상 권장)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;인터넷 연결&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&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;_도구를_설치하고_구성하세요&amp;quot;&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;_lm_sensors_센서를_감지하기_위해&amp;quot;&amp;gt;lm-sensors` 센서를 감지하기 위해&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;답변해`YES`모든 질문의`sensors-detect`모듈을 감지하기 위해.&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_지능형_에너지_관리자&amp;quot;&amp;gt;tlp` : 지능형 에너지 관리자&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이 활성화되었나요?&amp;quot;
if &amp;quot;예&amp;quot; then
  --&amp;amp;gt; [yes] &amp;quot;소비 감소&amp;quot;
  --&amp;amp;gt; &amp;quot;CPU 주파수 제한&amp;quot;
else
  --&amp;amp;gt; [no] &amp;quot;TLP 시작&amp;quot;
  --&amp;amp;gt; &amp;quot;자동 활성화&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_cpu_주파수_제한&amp;quot;&amp;gt;cpufrequtils` : 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;: 주파수 피크를 제한&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;: 온도보다 전력을 우선시한다&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_간소화된_동적_관리&amp;quot;&amp;gt;auto-cpufreq` : 간소화된 동적 관리&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_자동_열_보호&amp;quot;&amp;gt;thermald` : 자동 열 보호&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;_실시간_모니터링&amp;quot;&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;_sensors_명령줄에서&amp;quot;&amp;gt;sensors` 명령줄에서&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;_gui_옵션_psensor&amp;quot;&amp;gt;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;CPU, GPU, 팬을 위한 간단한 그래픽 인터페이스.&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;_추가_팁&amp;quot;&amp;gt;추가 팁&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;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;배터리에서 과도한 사용을 피하십시오&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;통해 프로세스를 모니터링`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;_시스템을_청소하여_과부하를_제한&amp;quot;&amp;gt;시스템을 청소하여 과부하를 제한&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;_더_깊이_가기&amp;quot;&amp;gt;더 깊이 가기&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;당신은 서비스를 생성할 수 있습니다.`systemd`시작 시 자동으로 일부 설정을 적용하도록 맞춤 설정된 다음 글에 예시를 원하시나요? 알려 주세요!&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;_결론&amp;quot;&amp;gt;결론&amp;lt;/h2&amp;gt;
&amp;lt;div 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;/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;제어된 CPU 온도&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;환기 소음 감소&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&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;/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;_ CPU를 녹이는 것보다 예방하는 것이 더 낫다. &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>디버깅 이야기: Bootstrap 5로 Firefox의 변덕을 다스리기</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;#_전장_모형_세_개의_브라우저_여러_렌더링&amp;quot;&amp;gt;전장: 모형, 세 개의 브라우저, 여러 렌더링&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_문제_1_hero_섹션의_날아가는_카드&amp;quot;&amp;gt;문제 1: &amp;#39;Hero&amp;#39; 섹션의 날아가는 카드&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_문제_2_식인_네비게이션_바&amp;quot;&amp;gt;문제 2: 식인 네비게이션 바&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_문제_3_무질서한_타이포그래피&amp;quot;&amp;gt;문제 3: 무질서한 타이포그래피&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_문제_4_넘치는_내비게이션_바&amp;quot;&amp;gt;문제 4: 넘치는 내비게이션 바&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_문제_5_보이지_않는_햄버거_메뉴&amp;quot;&amp;gt;문제 5 : 보이지 않는 햄버거 메뉴&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_결론_끈질긴_디버깅의_교훈&amp;quot;&amp;gt;결론 : 끈질긴 디버깅의 교훈&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;읽는 데 걸리는 시간:&amp;lt;/strong&amp;gt; 약 7분 &amp;lt;strong&amp;gt;레벨 :&amp;lt;/strong&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;이 상세한 사례 연구에서 우리는 Bootstrap 5.2를 사용해 UI 프로토타입을 만들 때 발생하는 가장 일반적인 브라우저 호환성 문제와 때때로 가장 성가신 문제들을 분석할 것입니다. Flexbox 관리부터 글꼴 렌더링의 미묘함까지, Firefox에서 어긋난 디자인을 모든 브라우저에서 픽셀 완벽한 경험으로 변환하는 디버깅 과정을 알아보세요.&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;_전장_모형_세_개의_브라우저_여러_렌더링&amp;quot;&amp;gt;전장: 모형, 세 개의 브라우저, 여러 렌더링&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;모든 프론트엔드 개발자는 이 감정을 잘 알죠. 몇 시간의 작업 끝에 드디어 디자인이 완벽해집니다. 정렬은 깔끔하고, 타이포그래피는 우아하며, 애니메이션은 부드러워요. 만족감에 한숨이 나오고, Chrome(또는 Brave, 혹은 Edge)에서 작업을 감상한 뒤, 혹시 모를 상황을 대비해 Firefox에서 프로젝트를 열어봅니다. 그리고 그곳에서 재앙이 일어납니다. 요소들이 넘쳐흐르고, 정렬이 깨지고, 글꼴 크기가 제멋대로라… 아름다운 조화가 사라져 버렸습니다.&amp;lt;/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;vanilla JavaScript*와 *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;이 기사는 기적 같은 솔루션 목록이 아닙니다. 이는 우리 디버깅 세션의 로그이며, Blink(Chromium, Brave) 엔진과 Gecko(Firefox) 엔진 간의 렌더링 차이의 &amp;#39;왜&amp;amp;#8217;에 대한 심층 분석입니다. 그리고 강력하고 현대적인 솔루션이 언제나 임시 방편보다 더 낫다는 것을 보여줍니다.&amp;lt;/p&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;사용 사례 다이어그램 : 디버깅 과정&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;프론트엔드 개발자&amp;quot; as Dev

rectangle &amp;quot;Bootstrap 5 UI 목업&amp;quot; {
  usecase &amp;quot;Flexbox 정렬 수정&amp;quot; as UC1
  usecase &amp;quot;패딩을 동적으로 조정 (JS)&amp;quot; as UC2
  usecase &amp;quot;타이포그래피 제어 (clamp())&amp;quot; as UC3
  usecase &amp;quot;메뉴 오버플로 해결&amp;quot; as UC4
  usecase &amp;quot;메뉴 아이콘을 보이게 하다&amp;quot; as UC5
}

actor &amp;quot;파이어폭스 브라우저&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;컴포넌트 다이어그램: 프론트엔드 솔루션 아키텍처&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;솔루션 프론트엔드&amp;quot; {
  component &amp;quot;HTML (템플릿 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;DOM 조작&amp;quot; as iDOM
  interface &amp;quot;CSS 수정&amp;quot; as iCSS
}

package &amp;quot;프레임워크&amp;quot; {
  component &amp;quot;Bootstrap 5.2&amp;quot; as BS
  interface &amp;quot;CSS 변수&amp;quot; as iVars
  interface &amp;quot;유틸리티 클래스&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;_문제_1_hero_섹션의_날아가는_카드&amp;quot;&amp;gt;문제 1: &amp;#39;Hero&amp;#39; 섹션의 날아가는 카드&amp;lt;/h2&amp;gt;
&amp;lt;div 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;quot;hero&amp;quot;)은 두 개의 열로 구성되어 있습니다: 왼쪽에는 메인 타이틀이 있고, 오른쪽에는 소개 카드가 있습니다.&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;증상:&amp;lt;/strong&amp;gt;Chrome와 Brave에서는 두 열이 수직으로 완벽히 중앙에 위치했습니다. 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;조사 :&amp;lt;/strong&amp;gt;하지만 HTML 코드는 간단하고 올바른 것처럼 보였으며, 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용`.hero-section`정의하고 있었다`display: flex` et &amp;lt;code&amp;gt;align-items: center&amp;lt;/code&amp;gt;, 그리고 수업`min-vh-100`줄에 최소 높이를 주었다 (&amp;lt;code&amp;gt;div.row&amp;lt;/code&amp;gt;). 그럼 왜 Firefox는 열을 가운데 맞히지 않았나요?&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;근본적인 원인 :&amp;lt;/strong&amp;gt;이것은 렌더링 엔진이 암시적 높이를 해석하는 방식에 대한 모범 사례입니다. 라`&amp;amp;lt;section&amp;amp;gt;`가지고 있었다`min-height`, 그러나`height`명시적. 적용하기 위해`align-items-center`내부에`div.row`, Firefox는 이 컨테이너의 기준 높이를 알아야 합니다. 부모 요소와 (&amp;lt;code&amp;gt;div.container&amp;lt;/code&amp;gt; et la `&amp;amp;lt;section&amp;amp;gt;`그녀 자신은 높이가 없었다&amp;lt;strong&amp;gt;엄격한&amp;lt;/strong&amp;gt;, Firefox가 길을 잃었습니다. Blink 엔진은 더 permissive하여 의도를 &amp;#39;추측&amp;amp;#8217;하고 요소를 가운데로 맞춥니다. Gecko는 사양에 더 충실하여 그렇게 하지 않습니다.&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;해결책:&amp;lt;/strong&amp;gt;높이를 명시적으로 만듭니다. 우리는 그것을 강제했습니다.&amp;lt;code&amp;gt;.container&amp;lt;/code&amp;gt; et le `.row`해당 부모의 높이 100%를 차지하기 위해 유틸리티 클래스를 사용`h-100`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;누가`align-items`Flexbox가 수직 축에서 예상대로 작동하지 않으면 컨테이너에 높이가 있는지 항상 확인하세요 (&amp;lt;code&amp;gt;height&amp;lt;/code&amp;gt; ou &amp;lt;code&amp;gt;min-height&amp;lt;/code&amp;gt;) 정의되고, 문제가 계속되면 (특히 Firefox에서), 중간 부모 컨테이너가 이 높이를 적절히 전달하도록 확인하세요.&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;_문제_2_식인_네비게이션_바&amp;quot;&amp;gt;문제 2: 식인 네비게이션 바&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;영웅 섹션이 정렬된 후, 다른 문제가 발생했습니다: 내비게이션 바, 그 클래스와 함께`fixed-top`, 제목 위에 겹쳐 있었다. 하나`padding-top: 80px`섹션 &amp;quot;Hero&amp;quot;에 해결책으로 적용되었습니다.&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;증상 :&amp;lt;/strong&amp;gt; Le `padding`Chrome에서는 충분했지만, Firefox에서는 아니었다. 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;근본적인 원인:&amp;lt;/strong&amp;gt;매직 넘버&amp;quot; 사용`80px`)는 매우 나쁜 관행입니다. 네비게이션 바와 같은 요소의 높이는 폰트 렌더링, 간격, 심지어 사용자 옵션의 미세한 차이로 인해 브라우저마다 몇 픽셀씩 달라질 수 있습니다. 고정된 값에 의존하는 것은 취약한 디자인을 만드는 방법입니다.&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;해결책 :&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;실제&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;이 측정 높이를 적용해`padding-top`Hero 섹션으로&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&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 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;시퀀스 다이어그램 : 패딩의 동적 조정&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
&amp;lt;pre&amp;gt;@startuml
title 칸니발 네비게이션 바 문제 해결
actor Utilisateur
participant &amp;quot;브라우저&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;동시에 우리는 물론 규칙을 삭제했습니다.&amp;lt;code&amp;gt;padding-top: 80px;`우리 파일의`styles.css&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;_문제_3_무질서한_타이포그래피&amp;quot;&amp;gt;문제 3: 무질서한 타이포그래피&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;다음 우려는 브랜드 이미지에 가장 해로운 것이었으니, 바로 메인 제목의 크기였다.&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;증상 :&amp;lt;/strong&amp;gt;파이어폭스에서는 제목 &amp;#39;개발자 강사&amp;amp;#8230;&amp;amp;#8203;&amp;#39;의 글꼴이 엄청나게 커서 거의 충격적이었으나, 다른 브라우저에서는 조화로웠습니다.&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;근본 원인 :&amp;lt;/strong&amp;gt;제목은 한 클래스를 사용했다.`display-4`Bootstrap의. 이러한 클래스는 반응형 글꼴 크기를 사용하며, 종종 단위를 기준으로 합니다.`rem`그리고 미디어 쿼리. 한 번 더, Firefox의 렌더링 알고리즘이 &amp;quot;Inter&amp;quot; 글꼴과 결합되면서 1920x1080 해상도에서 훨씬 더 큰 크기로 계산되었습니다.&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;해결책:&amp;lt;/strong&amp;gt;CSS 함수를 사용해 제어하기`clamp()&amp;lt;code&amp;gt;. 이 기능은 유동적인 타이포그래피에 혁명을 일으킵니다. 이는 세 가지 값으로 글꼴 크기를 정의할 수 있게 해줍니다: 최소 크기, &amp;#39;선호&amp;#39; 크기(뷰의 너비에 맞춰 조정됨),`vw&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;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;와`clamp()&amp;lt;code&amp;gt;, 우리는 브라우저에게 말할 수 있었습니다 : &amp;quot;화면에 맞춰 글씨 크기를 키워, 초과하지 마&amp;lt;strong&amp;gt;결코&amp;lt;/strong&amp;gt;의 제한`2.5rem&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;_문제_4_넘치는_내비게이션_바&amp;quot;&amp;gt;문제 4: 넘치는 내비게이션 바&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;이 문제는 우리에게 가장 많은 어려움을 주었고, 결국 렌더링의 미세한 차이의 본질을 드러냈다.&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;증상:&amp;lt;/strong&amp;gt;1920x1080 해상도의 화면에서 메뉴의 마지막 링크(&amp;#39;Blog&amp;#39;, &amp;#39;Contact&amp;#39;)가 화면 오른쪽 밖으로 밀려 났지만, 이것은 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;근본적인 원인:&amp;lt;/strong&amp;gt;몇 번의 시도 실패 후 (Bootstrap의 브레이크포인트를 변경하는)&amp;lt;code&amp;gt;lg&amp;lt;/code&amp;gt; à &amp;lt;code&amp;gt;xl`그리고`xxl&amp;lt;/code&amp;gt;), 우리는 이해했습니다. 문제는 Bootstrap의 로직이 아니라 단지 너비 계산이었습니다. 파이어폭스에서는 모든 링크의 너비 합계, 이들의`padding` et `margin`서브 픽셀 수준에서, 그것은 1920 픽셀보다 &amp;lt;strong&amp;gt;약간&amp;lt;/strong&amp;gt; 더 컸습니다. Chrome에서는 이 합이 &amp;lt;strong&amp;gt;약간&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;해결책:&amp;lt;/strong&amp;gt;수술적인 간격 축소. 구식 CSS 해크를 이용해 파이어폭스를 특정적으로 타겟팅하는 것은 논외였기 때문에, 깨끗한 유일한 해결책은 어디서든 작동하는 공통 스타일을 찾는 것이었다. 따라서 각 탐색 링크의 여백과 수평 패딩을 약간 줄였습니다.&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;이 감소는 개별 요소에서 거의 눈에 띄지 않지만, Firefox에서 모든 링크가 프레임 안에 들어갈 수 있도록 필요한 공간을 collectively 확보하게 해주었으며, 다른 브라우저에서는 거의 동일한 외관을 유지합니다.&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;_문제_5_보이지_않는_햄버거_메뉴&amp;quot;&amp;gt;문제 5 : 보이지 않는 햄버거 메뉴&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;마지막 문제였고, 작은 것이 아니었으며, 모바일 접근성과 관련이 있었습니다.&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;증상 :&amp;lt;/strong&amp;gt;응답형 메뉴(햄버거 메뉴) 모드에서는 &amp;#39;dark&amp;amp;#8217;와 &amp;#39;high-contrast&amp;#39; 테마에서 메뉴 아이콘이 보이지 않았습니다.&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;근본 원인 :&amp;lt;/strong&amp;gt;Bootstrap 5 메뉴의 아이콘은`background-image`(URL로 인코딩된 SVG). 기본적으로, 선 색은 어둡고 밝은 배경에 최적화되어 있습니다. 테마 변경에 자동으로 적응하지 않습니다.&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;해결책 :&amp;lt;/strong&amp;gt;Bootstrap CSS 변수를 사용하여 아이콘을 오버라이드합니다. 우리는 새로운 SVG 아이콘을 정의했지만, 이번엔 흰색 스트로크로 (&amp;lt;code&amp;gt;#ffffff&amp;lt;/code&amp;gt;), 그리고 우리는 그것을 주제에 대해 구체적으로 적용했다`dark` ou `high-contrast`활동 중입니다.&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;Bootstrap 구성 요소와 같은 것의 맞춤 설정은 CSS 변수 오버라이드를 통해 점점 더 많이 이루어지고 있습니다.&amp;lt;code&amp;gt;--bs-component-property&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&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_결론_끈질긴_디버깅의_교훈&amp;quot;&amp;gt;결론 : 끈질긴 디버깅의 교훈&amp;lt;/h2&amp;gt;
&amp;lt;div 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;strong&amp;gt;엄격한 다중 브라우저 테스트를 대체할 수 있는 것은 없습니다.&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 : 디버깅과 배운 교훈 요약&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;마법의 숫자&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;마법 숫자&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;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;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;Firefox는 협력자 :&amp;lt;/strong&amp;gt;CSS 표준에 대한 가장 큰 충실함은 우리를 더 견고하고 덜 모호한 코드를 작성하도록 강요한다.&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;마법 수를 피하라 :&amp;lt;/strong&amp;gt;고정된 값들 (예를 들어`padding-top: 80px`) 시한 폭탄입니다. 항상 동적인 솔루션(JavaScript) 또는 상대적인 솔루션(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;타이포그래피를 마스터하세요 :&amp;lt;/strong&amp;gt;사용하세요`clamp()`반응형 폰트 크기를 완전히 제어하기 위해.&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;구성 요소 단위로 생각하세요:&amp;lt;/strong&amp;gt;Bootstrap을 커스터마이징하려면 프레임워크를 덮어쓰는 규칙을 늘리기보다 CSS 변수를 오버라이드하세요.&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;브라우저를 대상으로 삼지 마세요:&amp;lt;/strong&amp;gt;해결책은 거의 특정 브라우저를 위한 \&amp;quot;해킹\&amp;quot;을 하는 것이 아니라, 어디서나 작동하는 공통된 스타일을 찾는 것입니다.&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;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>Gradle 단위 테스트 도전 과제 해결하기 `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;#_문제_gradle_플러그인의_단위_테스트_격리&amp;quot;&amp;gt;문제 : 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;#_문제_아키텍처&amp;quot;&amp;gt;문제 아키텍처&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_거짓_좋은_아이디어의_유혹&amp;quot;&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;#_해결책_extrapropertiesextension를_사용해_속성을_시뮬레이션하기&amp;quot;&amp;gt;해결책 : `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;#_1단계_extrapropertiesextension_이해하기&amp;quot;&amp;gt;1단계: ExtraPropertiesExtension 이해하기&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_2단계_테스트_설정_준비&amp;quot;&amp;gt;2단계: 테스트 설정 - 준비&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_단계_3_속성_주입&amp;quot;&amp;gt;단계 3: 속성 주입&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_단계_4_솔루션_흐름도&amp;quot;&amp;gt;단계 4: 솔루션 흐름도&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_단계_5_비교_전후&amp;quot;&amp;gt;단계 5: 비교 전/후&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_단계_6_다중_테스트_사례_관리&amp;quot;&amp;gt;단계 6 : 다중 테스트 사례 관리&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_솔루션의_최종_아키텍처&amp;quot;&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;#_이_접근_방식의_장점&amp;quot;&amp;gt;이 접근 방식의 장점&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_모범_사례와_조언&amp;quot;&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;#_유틸리티_메서드_생성&amp;quot;&amp;gt;유틸리티 메서드 생성&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_속성_검증&amp;quot;&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;#_결론&amp;quot;&amp;gt;결론&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;_문제_gradle_플러그인의_단위_테스트_격리&amp;quot;&amp;gt;문제 : 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 플러그인에 대한 단위 테스트를 작성할 때 일반적인 도전 과제는 프로젝트 구성에 대한 의존성을 관리하는 것, 즉 파일에 정의된 속성과 같은 것`gradle.properties`. 우리의 경우, 플러그인`jbake.ghpages`속성을 읽어야 했다`site_config_path`올바르게 작동하기 위해. 작업을 위한 단위 테스트`initialize`이 속성이 존재할 때 플러그인의 동작을 확인해야 했다.&amp;lt;/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;/strong&amp;gt; 상태여야 한다는 것입니다. 사용하는 것은`ProjectBuilder`Gradle이 인스턴스를 생성합니다.&amp;lt;code&amp;gt;Project`메모리에서, 파일 시스템에 있는 실제 프로젝트와 완전히 연결되지 않은 상태입니다. 따라서 이 테스트 인스턴스는 파일을 자동으로 읽지 않습니다.`gradle.properties`그리고 따라서 그 속성에 대해 알지 못합니다.`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;_문제_아키텍처&amp;quot;&amp;gt;문제 아키텍처&amp;lt;/h3&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&amp;gt;@startuml
!define RECTANGLE class

package &amp;quot;프로덕션 환경&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;테스트 환경&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;_거짓_좋은_아이디어의_유혹&amp;quot;&amp;gt;거짓 좋은 아이디어의 유혹&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;첫 번째 접근 방식은 파일을 생성하는 것일 수 있다.`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-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;그러나, 이 방법은 실패를 피할 수 없는 운명이기 때문에`ProjectBuilder`설계된 것이 파일 시스템을 스캔하여 구성 파일을 찾는 것이 아니므로, 테스트는 격리된 상태로 남아 이 파일을 무시합니다.&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;_해결책_extrapropertiesextension를_사용해_속성을_시뮬레이션하기&amp;quot;&amp;gt;해결책 : `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;이 문제의 우아한 솔루션은 파일을 &amp;lt;strong&amp;gt;읽는&amp;lt;/strong&amp;gt; 것이 아니라, 객체에 속성의 존재를 직접 &amp;lt;strong&amp;gt;시뮬레이션*하는 것이다.`Project`de 테스트. Gradle은 이를 위한 강력한 메커니즘을 제공합니다: *추가 속성&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;_1단계_extrapropertiesextension_이해하기&amp;quot;&amp;gt;1단계: ExtraPropertiesExtension 이해하기&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;`ExtraPropertiesExtension`이는 Gradle 모델의 각 객체에 연결된 키-값 컨테이너입니다. 이를 통해 프로젝트, 태스크 또는 기타 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&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;_2단계_테스트_설정_준비&amp;quot;&amp;gt;2단계: 테스트 설정 - 준비&amp;lt;/h3&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;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;_단계_3_속성_주입&amp;quot;&amp;gt;단계 3: 속성 주입&amp;lt;/h3&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;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;_단계_4_솔루션_흐름도&amp;quot;&amp;gt;단계 4: 솔루션 흐름도&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;검사&amp;quot; {
  :Vérifier project.hasProperty();
  :Appliquer le plugin;
  :Récupérer la tâche;
}

partition &amp;quot;실행&amp;quot; {
  :Exécuter les actions\nde la tâche;
  :Le plugin lit\nproject.properties;
}

partition &amp;quot;주장&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;_단계_5_비교_전후&amp;quot;&amp;gt;단계 5: 비교 전/후&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;이전 - 문제&amp;quot; {
  RECTANGLE TestProject1 {
    + ProjectBuilder.build()
    + Pas de propriétés
  }

  RECTANGLE Plugin1 {
    + initialize()
    + ❌ project.properties[&amp;quot;사이트_설정_경로&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;후 - 해결책&amp;quot; {
  RECTANGLE TestProject2 {
    + ProjectBuilder.build()
    + ExtraPropertiesExtension
  }

  RECTANGLE ExtraProps {
    + set(&amp;quot;사이트_설정_경로&amp;quot;, &amp;quot;...&amp;quot;)
  }

  RECTANGLE Plugin2 {
    + initialize()
    + ✅ project.properties[&amp;quot;사이트_설정_경로&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;_단계_6_다중_테스트_사례_관리&amp;quot;&amp;gt;단계 6 : 다중 테스트 사례 관리&amp;lt;/h3&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;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;_솔루션의_최종_아키텍처&amp;quot;&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&amp;gt;@startuml
!define RECTANGLE class

package &amp;quot;테스트 환경&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;_이_접근_방식의_장점&amp;quot;&amp;gt;이 접근 방식의 장점&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;완전 절연&amp;lt;/strong&amp;gt;테스트는 외부 파일에 의존하지 않습니다. 독립적이며, 로컬, CI/CD 등을 포함한 어떤 환경에서도 신뢰성 있게 실행할 수 있습니다.&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;명확함과 의도&amp;lt;/strong&amp;gt;테스트는 실행에 필요한 선행 조건을 명시적으로 선언합니다. 테스트를 읽는 사람은 누구든 플러그인이 해당 속성을 필요로 한다는 것을 즉시 알 수 있다.`site_config_path`작동하기 위해.&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;유지보수성&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;유연성&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;퍼포먼스&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;재현성&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 class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_모범_사례와_조언&amp;quot;&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;_유틸리티_메서드_생성&amp;quot;&amp;gt;유틸리티 메서드 생성&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;_속성_검증&amp;quot;&amp;gt;속성 검증&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;_결론&amp;quot;&amp;gt;결론&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;단위 테스트 환경이 구성 파일을 읽도록 애쓰는 대신, 필요한 상태를 시뮬레이션하는 것이 모범 사례입니다. 사용`ExtraPropertiesExtension`프로그래밍 방식으로 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;이 기술은 외부 의존성 문제를 간단한 의존성 주입으로 변환하여 테스트 주도 개발(TDD)의 철학 그 자체에 심어줍니다. 이 기술은 테스트 환경에 대한 완전한 제어를 제공하면서도 고품질 단위 테스트에 필요한 격리를 유지합니다.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;이 기사에 제시된 다이어그램과 예시는 이 접근 방식을 점진적이고 체계적으로 구현하는 방법을 보여주며, 이를 통해 Gradle 플러그인을 위한 견고하고 유지 관리 가능한 테스트 제품군을 만들 수 있습니다.&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 DSL과 buildSrc를 사용한 Gradle 작업 관리 최적화</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;#_allprojects와_subprojects를_사용하여_configurations을_중앙_집중화하세요&amp;quot;&amp;gt;allprojects`와 `subprojects`를 사용하여 Configurations을 중앙 집중화하세요&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;빌드 작업 자동화는 모든 소프트웨어 프로젝트에 매우 중요하며, Gradle의 Kotlin DSL을 사용하면 뛰어난 유연성을 제공합니다. 우리의 대화 중에, 우리는 빌드 로직을 중앙화하고 재사용하는 방법을 탐구했으며, 특히 테스트 보고서에 이를 활용하여&amp;lt;strong&amp;gt;모든 프로젝트&amp;lt;/strong&amp;gt;, &amp;lt;strong&amp;gt;buildSrc&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;_allprojects와_subprojects를_사용하여_configurations을_중앙_집중화하세요&amp;quot;&amp;gt;allprojects`와 `subprojects`를 사용하여 Configurations을 중앙 집중화하세요&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;블록들`allprojects` et `subprojects`당신의`build.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;*&amp;lt;code&amp;gt;allprojects { &amp;amp;#8230;&amp;amp;#8203; }&amp;lt;/code&amp;gt;: 구성을 적용&amp;lt;strong&amp;gt;루트 프로젝트 및 모든 하위 프로젝트&amp;lt;/strong&amp;gt;. 정의하기에 좋은`group`, 하나의`version`, 또는`repositories`공통의. *&amp;lt;code&amp;gt;subprojects { &amp;amp;#8230;&amp;amp;#8203; }`구성을 적용&amp;lt;strong&amp;gt;하위 프로젝트에만&amp;lt;/strong&amp;gt;, 루트 프로젝트를 제외하고. 모듈에 특정한 플러그인을 적용하기에 완벽함 (예`java&amp;lt;/code&amp;gt; ou &amp;lt;code&amp;gt;kotlin-jvm&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;다음은 예시입니다 :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;\==&amp;lt;code&amp;gt;buildSrc&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;작업의 로직이 복잡해지거나 재사용해야 할 때,&amp;lt;strong&amp;gt;buildSrc&amp;lt;/strong&amp;gt;선호되는 솔루션입니다. 이는 주요 빌드 스크립트보다 먼저 컴파일되는 특수 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;왜 사용하나요?`buildSrc`작업을 위해?&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;재사용성&amp;lt;/strong&amp;gt;정의된 작업 안에`buildSrc`해당 빌드의 모든 프로젝트에 적용할 수 있습니다.&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;조직&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;타입 안전성과 자동 완성&amp;lt;/strong&amp;gt;: Kotlin 코드 안의`buildSrc`컴파일되어 IDE의 오류 검사와 자동완성을 제공하여 개발 경험을 향상시킵니다.&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;흐름도`buildSrc`(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;이 다이어그램을 생성하려면 아래 코드를 복사하고 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 handwritten true 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;rectangle &amp;quot;Gradle 빌드 프로세스&amp;quot; { component &amp;quot;buildSrc&amp;quot; as BS { file &amp;quot;MyCustomTask.kt&amp;quot; as T file &amp;quot;MyConventionPlugin.kt&amp;quot; as P } 컴포넌트 &amp;quot;Root Project&amp;quot;으로서 RP 컴포넌트 &amp;quot;Subproject A&amp;quot;를 SA로 구성요소 &amp;quot;Subproject B&amp;quot;를 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;정의됨 P --\&amp;amp;gt; RP : &amp;quot;적용됨 P --\&amp;amp;gt; SA : &amp;quot;적용됨 P --\&amp;amp;gt; SB : &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;RP --|\&amp;amp;gt; SA : &amp;quot;contains RP --|\&amp;amp;gt; SB : &amp;quot;contains&amp;lt;/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;빌드 로직을 위해 의존함 SA -up&amp;amp;#8594; BS : &amp;quot;의존 (빌드 로직을 위한) SB -up-\&amp;amp;gt; BS : &amp;quot;depends on (for build logic)&amp;lt;/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 오른쪽에 메모 커스텀 작업 클래스 (예: OpenTestReportTask) 끝 노트&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;P 오른쪽의 메모 협약 플러그인 작업을 기록하는 미주&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;\== 추상 보고서 작업 생성&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;테스트 보고서를 관리하기 위해 우리는 모듈식 접근 방식을 설계했습니다 추상 작업 클래스를 사용하여 안에서`buildSrc`. 귀하의 필요에 따라 이 클래스는 상속받을 수 있습니다`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;\=== 추상적 작업`OpenTestReportTask`(상속받는`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;파일 시스템이나 다른 Gradle API와 상호작용하는 맞춤 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;_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;패키지 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 가져오기 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;추상 클래스 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;)
}
----

## }

구체적인 구현

이 클래스들은 추상 작업을 상속받고 보고서의 특정 경로를 정의합니다.

## [source,kotlin]

패키지 com.yourpackage

추상 클래스 ReportUnitTestsTask : OpenTestReportTask() init { Firefox에서 단위 테스트 보고서를 엽니다. reportPath = &amp;quot;build/reports/tests/test/index.html } }

패키지 com.yourpackage

## abstract class ReportFunctionalTestsTask : OpenTestReportTask() { init { description = &amp;quot;파이어폭스에서 기능 테스트 보고서를 엽니다.&amp;quot; reportPath = &amp;quot;build/reports/tests/functionalTest/index.html&amp;quot; } }

\=== 작업 기록

안에`build.gradle.kts`당신의 루트 프로젝트:

## [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;) {}

\=== 보고서 작업의 UML 다이어그램 (PlantUML 코드)

아래 코드를 복사하여 PlantUML을 지원하는 도구에 붙여넣으세요.

## [source,plantuml]

@startuml skinparam handwritten true skinparam 단색 true

추상 클래스 DefaultTask { }

추상 클래스 Exec { \+ commandLine(args: String...) \+ exec() }

추상 클래스 OpenTestReportTask 확장 DefaultTask { \+ group: String = &amp;quot;검증 \+ 설명: 문자열 \+ dependsOn(&amp;quot;check&amp;quot;) \+ abstract reportPath: String \+ openReport() : void }

추상 클래스 AbstractJbakeExecTask는 Exec를 확장합니다 { \+ group: String = &amp;quot;검증 \+ 설명: 문자열 \+ dependsOn(&amp;quot;check&amp;quot;) \+ 추상 보고상대경로: 문자열 \+ exec() : void }

클래스 ReportUnitTestsTask는 OpenTestReportTask를 확장합니다 { \+ reportPath: String = &amp;quot;build/reports/tests/test/index.html }

클래스 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 AbstractJbakeExecTask \&amp;amp;lt;-- ReportJbakeFunctionalTestsTask

DefaultTask \&amp;amp;lt;|-- OpenTestReportTask Exec \&amp;amp;lt;|-- AbstractJbakeExecTask DefaultTask \&amp;amp;lt;|-- Exec

## @enduml

\== 추상적인 작업`AbstractJbakeExecTask`(상속받는`Exec`)

주로 외부 명령에 변수 인자를 실행하는 작업이라면, 직접 상속받는**실행**더 직접적입니다.

## [source,kotlin]

패키지 com.yourpackage

import org.gradle.api.tasks.Exec import org.gradle.api.tasks.Input import java.io.File

추상 클래스 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 }

## }

\=== 구체적인 구현을 위한`Exec`

## [source,kotlin]

package com.yourpackage

추상 클래스 ReportJbakeTestsTask : AbstractJbakeExecTask() { init { description = &amp;quot;Firefox에서 Jbake 단위 테스트 보고서를 엽니다. reportRelativePath = &amp;quot;build/reports/tests/test/index.html } }

package com.yourpackage

abstract class ReportJbakeFunctionalTestsTask : AbstractJbakeExecTask() { init { description = &amp;quot;파이어폭스에서 기능 테스트 보고서를 엽니다.&amp;quot; reportRelativePath = &amp;quot;build/reports/tests/functionalTest/index.html&amp;quot; } }

\== 작업 가시성`buildSrc`

당신이 정의하는 작업 및 클래스는**buildSrc**이다 :

***보이고 사용 가능한**votre 메인 빌드의 모든 프로젝트(루트 및 하위 프로젝트)에서. 따라서 사용할 수 있습니다`com.yourpackage.ReportUnitTestsTask`안에`allprojects { ... }`. ***실행 불가능한**직접 buildSrc 작업으로서 (예를 들어,`gradle :buildSrc:reportTests`작업이 특정하게 기록되지 않으면 작동하지 않을 것입니다`buildSrc/build.gradle.kts`).`buildSrc`컴파일 모듈일 뿐, 이 메인 빌드 작업에 사용되는 실행 가능한 애플리케이션 모듈이 아닙니다.

테스트를 위한 보고서 실행`buildSrc`자기

Si `buildSrc`자신만의 테스트를 수행하고 보고서를 생성하면, 보고 작업을 직접 기록할 수 있습니다`buildSrc/build.gradle.kts`:

## [source,kotlin]

plugins { `kotlin-jvm` }

저장소 { mavenCentral() }

tasks.withType\&amp;amp;lt;Test\&amp;amp;gt; { useJUnitPlatform() 보고서.html.출력위치.set(레이아웃.빌드디렉터리.dir(&amp;quot;보고서/테스트&amp;quot;)) }

import com.yourpackage.ReportJbakeTestsTask // 작업 클래스를 가져옵니다

tasks.register\&amp;amp;lt;ReportJbakeTestsTask\&amp;amp;gt;(&amp;quot;reportBuildSrcTests&amp;quot;) { // Le chemin est déjà défini dans la classe, il pointera vers les rapports de buildSrc }

그 다음에 실행할 수 있습니다 :`./gradlew :buildSrc:test`다음에`./gradlew :buildSrc:reportBuildSrcTests`.

이러한 관행을 채택함으로써 Kotlin DSL을 사용한 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&amp;gt;</description>
        </item><item>
            <title>JBake와 JGit을 사용한 구성 가능한 Kotlin Gradle 플러그인 TDD</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;#_목표&amp;quot;&amp;gt;목표&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_선언적_구성&amp;quot;&amp;gt;선언적 구성&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_플러그인_아키텍처&amp;quot;&amp;gt;플러그인 아키텍처&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_dsl_확장_정의&amp;quot;&amp;gt;DSL 확장 정의&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_사용자_지정_작업의_정의&amp;quot;&amp;gt;사용자 지정 작업의 정의&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_인터페이스_gitadapter&amp;quot;&amp;gt;인터페이스 GitAdapter&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_jgit을_사용한_구체적인_구현&amp;quot;&amp;gt;JGit을 사용한 구체적인 구현&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_kotlin_dsl_구성&amp;quot;&amp;gt;Kotlin DSL 구성&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_mockito를_사용한_단위_테스트&amp;quot;&amp;gt;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;#_의존성&amp;quot;&amp;gt;의존성&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_git_동작_테스트&amp;quot;&amp;gt;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;#_결론&amp;quot;&amp;gt;결론&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_다음_단계&amp;quot;&amp;gt;다음 단계&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;이 글은 Kotlin DSL로 작성된 Gradle 플러그인의 설계 및 테스트(TDD) 전체 과정을 소개합니다. 이 플러그인은 *JBake*를 사용한 정적 사이트 생성과 *JGit*을 통한 *Git 배포*를 자동화하며, 모든 것이 구성 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;_목표&amp;quot;&amp;gt;목표&amp;lt;/h2&amp;gt;
&amp;lt;div 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 플러그인 만들기 :&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 DSL에서 설정 가능;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;할 수 있는 :&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;JBake에서 정적 사이트 생성 ;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;원격 Git 저장소에 JGit을 통해 배포 ;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;CI/CD 워크플로에 통합 가능한 ;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;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;_선언적_구성&amp;quot;&amp;gt;선언적 구성&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;다음은 우리 DSL에서 모델링할 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-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;이 구성은 Kotlin DSL에서 선언 가능한 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;_플러그인_아키텍처&amp;quot;&amp;gt;플러그인 아키텍처&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;엔진-플러그인&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;_dsl_확장_정의&amp;quot;&amp;gt;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;_사용자_지정_작업의_정의&amp;quot;&amp;gt;사용자 지정 작업의 정의&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;_인터페이스_gitadapter&amp;quot;&amp;gt;인터페이스 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;_jgit을_사용한_구체적인_구현&amp;quot;&amp;gt;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;_kotlin_dsl_구성&amp;quot;&amp;gt;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;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;_mockito를_사용한_단위_테스트&amp;quot;&amp;gt;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;_의존성&amp;quot;&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;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;_git_동작_테스트&amp;quot;&amp;gt;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;개발자&amp;quot; as Dev
participant &amp;quot;Gradle 프로젝트 빌더&amp;quot; as Project
participant &amp;quot;생성사이트작업&amp;quot; as Task
participant &amp;quot;GitAdapter (모의)&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;_결론&amp;quot;&amp;gt;결론&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;책임의 분리 via`GitAdapter`Gradle 플러그인 개발에서 TDD를 완전히 적용할 수 있도록 해줍니다:&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;Git 호출은 추상화되고 테스트 가능합니다;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;DSL 구성은 깔끔하고 명확합니다;&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&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;이 접근 방식은 높은 테스트 가능성과 확장성(GitHub API, GitLab 등)을 보장합니다.&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;_다음_단계&amp;quot;&amp;gt;다음 단계&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;JBake 생성을 통합하는`BakeAdapter`&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;통합 테스트 추가하기`GradleRunner`&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;자동 파일 로드 지원`site.yml`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 : 효과적인 문서 작성을 위해 문법을 탐색하고 숙달하기</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;#_서론&amp;quot;&amp;gt;서론&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_asciidoc이란_무엇인가요&amp;quot;&amp;gt;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;#_왜_asciidoc을_선택해야_하나요&amp;quot;&amp;gt;왜 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;#_유스케이스_다이어그램_use_case&amp;quot;&amp;gt;유스케이스 다이어그램 (Use Case)&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_마인드_맵_다이어그램&amp;quot;&amp;gt;마인드 맵 다이어그램&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_흐름도_플럭스&amp;quot;&amp;gt;흐름도 (플럭스)&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_asciidoc_파일의_기본_구조&amp;quot;&amp;gt;AsciiDoc 파일의 기본 구조&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_기본_구문&amp;quot;&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;#_제목과_섹션&amp;quot;&amp;gt;제목과 섹션&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_진하게_표시된_텍스트_기울임_꼴_고정폭_글꼴&amp;quot;&amp;gt;진하게 표시된 텍스트, 기울임 꼴, 고정폭 글꼴&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_불릿_목록_및_번호가_매긴_목록&amp;quot;&amp;gt;불릿 목록 및 번호가 매긴 목록&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_링크와_이미지&amp;quot;&amp;gt;링크와 이미지&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_코드_블록&amp;quot;&amp;gt;코드 블록&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_표&amp;quot;&amp;gt;표&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_주석_및_경고&amp;quot;&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;#_속성_및_변수_사용&amp;quot;&amp;gt;속성 및 변수 사용&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_일반적인_사용_사례&amp;quot;&amp;gt;일반적인 사용 사례&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_모범_사례&amp;quot;&amp;gt;모범 사례&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_결론&amp;quot;&amp;gt;결론&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_더_나아가기&amp;quot;&amp;gt;더 나아가기&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;_서론&amp;quot;&amp;gt;서론&amp;lt;/h2&amp;gt;
&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;대상 :&amp;lt;/em&amp;gt; 개발자, 기술 작성자, 그리고 기술 문서나 풍부한 기사 작성을 원하는 모든 사람.&amp;lt;/div&amp;gt;
&amp;lt;p&amp;gt;AsciiDoc은 구조화된 기술 문서, 기사, 책 또는 프레젠테이션을 작성하기 위해 설계된 경량 마크업 언어입니다. 가독성, 풍부한 문법 및 HTML, PDF, DocBook 등 다양한 포맷을 생성할 수 있는 능력으로 두드러집니다. 이 글에서는 AsciiDoc의 필수 구문을 탐색하고 빠르게 시작할 수 있는 팁을 제시하겠습니다.&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;_asciidoc이란_무엇인가요&amp;quot;&amp;gt;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는 문서 설명 언어이며, Markdown과 비슷하지만 더 강력합니다. 텍스트, 제목, 목록, 표, 코드 블록 등을 효율적으로 구조화할 수 있습니다. 그 다재다능함 덕분에 오픈 소스 프로젝트 문서화, 저서 작성, 웹 출판에 인기 있는 선택이 됩니다.&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;_왜_asciidoc을_선택해야_하나요&amp;quot;&amp;gt;왜 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;읽기 쉬우면서 직관적인 구문&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;복잡한 구조(표, 주석, 경고 등)의 네이티브 지원.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;멀티 형식 생성 (HTML, PDF, ePub, DocBook…)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;JBake 또는 Antora와 같은 정적 사이트 생성기와 쉽게 통합.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&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&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_유스케이스_다이어그램_use_case&amp;quot;&amp;gt;유스케이스 다이어그램 (Use Case)&amp;lt;/h2&amp;gt;
&amp;lt;div 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;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;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;_마인드_맵_다이어그램&amp;quot;&amp;gt;마인드 맵 다이어그램&amp;lt;/h2&amp;gt;
&amp;lt;div 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과 관련된 개념을 탐색하고 그 관계를 파악하기에 적합합니다 :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;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;_흐름도_플럭스&amp;quot;&amp;gt;흐름도 (플럭스)&amp;lt;/h2&amp;gt;
&amp;lt;div 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 문서 생성 과정을 설명할 수 있습니다 :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;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;_asciidoc_파일의_기본_구조&amp;quot;&amp;gt;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 파일은 일반적으로 제목으로 시작하고, 선택적 속성이 뒤이어 오며, 그 다음 구조화된 콘텐츠가 옵니다. 다음은 최소한의 예시:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;_기본_구문&amp;quot;&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;_제목과_섹션&amp;quot;&amp;gt;제목과 섹션&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;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;= 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;_진하게_표시된_텍스트_기울임_꼴_고정폭_글꼴&amp;quot;&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-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;_불릿_목록_및_번호가_매긴_목록&amp;quot;&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-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;_링크와_이미지&amp;quot;&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-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;_코드_블록&amp;quot;&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-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;안녕 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;_표&amp;quot;&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-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;_주석_및_경고&amp;quot;&amp;gt;주석 및 경고&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;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;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;_속성_및_변수_사용&amp;quot;&amp;gt;속성 및 변수 사용&amp;lt;/h2&amp;gt;
&amp;lt;div 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;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;_일반적인_사용_사례&amp;quot;&amp;gt;일반적인 사용 사례&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;오픈 소스 프로젝트 문서 (README, 기술 가이드) 도서 및 전자책 작성 - 자동 정적 웹사이트 생성 (JBake, Antora) - 기술 발표&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;_모범_사례&amp;quot;&amp;gt;모범 사례&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;일관된 제목과 자동 목차(:toc:) 를 사용하세요. - 경고를 사용하여 핵심 사항에 주의를 끌세요. - 파일을 구조화하여 유지보수를 쉽게 하세요. - 주석이 달린 코드 블록을 활용하여 예시를 보여주세요.&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;_결론&amp;quot;&amp;gt;결론&amp;lt;/h2&amp;gt;
&amp;lt;div 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은 기술 문서나 구조화된 기사를 작성하기 위한 강력하고 접근하기 쉬운 도구입니다. 풍부한 구문과 멀티 포맷 생성이 결합되어, 까다로운 개발자와 작가에게 이상적인 도구가 됩니다. 다음 프로젝트에서 AsciiDoc을 사용해 보고 차이를 경험해 보세요!&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;_더_나아가기&amp;quot;&amp;gt;더 나아가기&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://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와 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;경험과 팁을 댓글로 공유해 주세요!&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>우분투에서 로컬 서비스를 인터넷에 노출하기: 포트 포워딩 완전 가이드</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;#_문제_nat와_개인_주소&amp;quot;&amp;gt;문제: NAT와 개인 주소&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_단계_1_공용_ip_주소_식별&amp;quot;&amp;gt;단계 1 : 공용 IP 주소 식별&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_단계_2_ubuntu_기기의_로컬_ip_주소를_확인합니다&amp;quot;&amp;gt;단계 2: Ubuntu 기기의 로컬 IP 주소를 확인합니다&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_3단계_박스에서_포트_포워딩_설정하기&amp;quot;&amp;gt;3단계: 박스에서 포트 포워딩 설정하기&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;#_관리자_인터페이스에_접근&amp;quot;&amp;gt;관리자 인터페이스에 접근&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_리다이렉션_구성&amp;quot;&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;#_4단계_ubuntu에서_서비스_구성&amp;quot;&amp;gt;4단계 : Ubuntu에서 서비스 구성&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_5단계_ubuntu_방화벽_구성&amp;quot;&amp;gt;5단계: Ubuntu 방화벽 구성&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_단계_6_테스트_및_검증&amp;quot;&amp;gt;단계 6: 테스트 및 검증&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;#_로컬_네트워크에서_테스트&amp;quot;&amp;gt;로컬 네트워크에서 테스트&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_외부에서_테스트&amp;quot;&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;#_모범_사례_및_보안&amp;quot;&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;#_기본_보안&amp;quot;&amp;gt;기본 보안&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_연결_모니터링&amp;quot;&amp;gt;연결 모니터링&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_리버스_프록시_사용&amp;quot;&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;#_포트_포워딩_대안&amp;quot;&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;#_클라우드_솔루션&amp;quot;&amp;gt;클라우드 솔루션&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_vps_및_리버스_프록시&amp;quot;&amp;gt;VPS 및 리버스 프록시&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;#_문제_해결&amp;quot;&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;#_일반적인_문제&amp;quot;&amp;gt;일반적인 문제&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_네트워크_디버그&amp;quot;&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;#_결론&amp;quot;&amp;gt;결론&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;l 대상 : 중간&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;전제 조건&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;기술적 개념&amp;lt;/strong&amp;gt; : NAT, 포트 포워딩, 방화벽, 역방향 프록시&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;시스템 명령어&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;고급 섹션&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 class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_문제_nat와_개인_주소&amp;quot;&amp;gt;문제: NAT와 개인 주소&amp;lt;/h2&amp;gt;
&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;문제 : Ubuntu 머신에서 웹 애플리케이션을 개발하고 있고, 이를 가정 네트워크 외부에서 접근 가능하게 만들고 싶으신가요? 그렇다면 올바른 곳에 오셨습니다! 이 글에서는 인터넷 박스의 포트 포워딩을 설정하여 로컬 서비스(예: 포트 8080)를 인터넷에 노출하는 방법을 알아볼게요.&amp;lt;/div&amp;gt;
&amp;lt;p&amp;gt;기본적으로, 귀하의 인터넷 박스는 NAT(네트워크 주소 변환)를 사용하여 가정 네트워크의 모든 기기 간에 하나의 공용 IP 주소를 공유합니다. 로컬 머신은 인터넷에서 라우팅될 수 없는 사설 IP 주소(192.168.x.x, 10.x.x.x 등)를 사용합니다.&amp;lt;/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 머신에서 서비스를 실행할 때(예: 포트 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;_단계_1_공용_ip_주소_식별&amp;quot;&amp;gt;단계 1 : 공용 IP 주소 식별&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;먼저, 인터넷 연결의 공개 IP 주소를 가져옵시다:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;이 IP 주소는 사용자들이 귀하의 서비스에 접속할 때 사용할 주소입니다.&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;_단계_2_ubuntu_기기의_로컬_ip_주소를_확인합니다&amp;quot;&amp;gt;단계 2: Ubuntu 기기의 로컬 IP 주소를 확인합니다&amp;lt;/h2&amp;gt;
&amp;lt;div 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 머신은 로컬 네트워크에서 사설 IP 주소를 가지고 있습니다. 이를 확인하려면 :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;이 주소를 기록해 두세요 (예시`192.168.1.100`우리는 이것이 구성을 위해 필요할 것입니다.&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단계_박스에서_포트_포워딩_설정하기&amp;quot;&amp;gt;3단계: 박스에서 포트 포워딩 설정하기&amp;lt;/h2&amp;gt;
&amp;lt;div 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;/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;_관리자_인터페이스에_접근&amp;quot;&amp;gt;관리자 인터페이스에 접근&amp;lt;/h3&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;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;라이브박스 오렌지&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 박스&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;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;_리다이렉션_구성&amp;quot;&amp;gt;리다이렉션 구성&amp;lt;/h3&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;ulist&amp;quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;포트 포워딩&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;포트 포워딩&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&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;/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;필드&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;가치&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;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;Ubuntu 웹 서비스&amp;quot; (또는 다른 명시적인 이름)&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;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;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;내부 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;Ubuntu 머신의 IP 주소 (예: 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;내부 포트 (또는 프라이빗 포트)&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;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;프로토콜&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;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;상태&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_4단계_ubuntu에서_서비스_구성&amp;quot;&amp;gt;4단계 : 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;서비스가 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;서비스가 올바르게 수신 중인지 확인하려면 :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;당신은 무언가를 보는 것처럼 보여야 합니다.&amp;lt;code&amp;gt;0.0.0.0:8080`그리고 아니다`127.0.0.1:8080&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;_5단계_ubuntu_방화벽_구성&amp;quot;&amp;gt;5단계: 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는 기본적으로 UFW(Uncomplicated Firewall)를 사용합니다. 상태를 확인하고 필요하면 포트를 허용하세요 :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;_단계_6_테스트_및_검증&amp;quot;&amp;gt;단계 6: 테스트 및 검증&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;_로컬_네트워크에서_테스트&amp;quot;&amp;gt;로컬 네트워크에서 테스트&amp;lt;/h3&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-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;_외부에서_테스트&amp;quot;&amp;gt;외부에서 테스트&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;지금, 공용 IP를 사용하여 인터넷에서 테스트해 보세요 :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;또한 온라인 도구를 사용할 수 있습니다. 예를 들어:&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;_모범_사례_및_보안&amp;quot;&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;_기본_보안&amp;quot;&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-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;_연결_모니터링&amp;quot;&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-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;_리버스_프록시_사용&amp;quot;&amp;gt;리버스 프록시 사용&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;보안과 유연성을 위해 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;_포트_포워딩_대안&amp;quot;&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;_클라우드_솔루션&amp;quot;&amp;gt;클라우드 솔루션&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;임시 보안 터널&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 터널&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;serveo.net&amp;lt;/strong&amp;gt;: 간단한 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;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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_및_리버스_프록시&amp;quot;&amp;gt;VPS 및 리버스 프록시&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;전문적인 용도로는 VPS를 사용하고, 로컬 인프라를 향하는 리버스 프록시를 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;_문제_해결&amp;quot;&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;_일반적인_문제&amp;quot;&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;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;문제&amp;lt;/th&amp;gt;
&amp;lt;th class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;해결책&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;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;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.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;연결이 거부되었습니다.&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;서비스가 시작되지 않았거나 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;연결 타임아웃&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;방화벽 문제(박스 또는 Ubuntu) 또는 포트 포워딩이 잘못 구성됨&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는 정기적으로 변경됩니다&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&amp;quot;tableblock halign-left valign-top&amp;quot;&amp;gt;&amp;lt;p class=&amp;quot;tableblock&amp;quot;&amp;gt;동적 DNS(DynDNS, No-IP 등)를 구성하세요.&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;_네트워크_디버그&amp;quot;&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-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;_결론&amp;quot;&amp;gt;결론&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;인터넷에서 로컬 서비스를 노출하려면 포트 포워딩, 보안, 모니터링을 신중하게 구성해야 합니다. 이 접근 방식은 개발이나 개인 프로젝트에는 완벽하지만, 프로덕션 환경에서는 VPS, CDN, 클라우드 서비스와 같은 더 견고한 대안을 고려해야 합니다.&amp;lt;/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;paragraph&amp;quot;&amp;gt;
&amp;lt;p&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>Mockito 디버깅: Gradle 플러그인 테스트에서 &#39;Wanted but not invoked&#39; 오류 해결하기</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;#_서론&amp;quot;&amp;gt;서론&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_컨텍스트_plugin_gradle_bakery&amp;quot;&amp;gt;컨텍스트 : Plugin Gradle Bakery&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_문제_1_잘못된_모의mock_확인&amp;quot;&amp;gt;문제 #1 : 잘못된 모의(mock) 확인&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;#_진단&amp;quot;&amp;gt;진단&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_해결책&amp;quot;&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;#_문제_2_연속된_unfinishedstubbingexception&amp;quot;&amp;gt;문제 #2: 연속된 UnfinishedStubbingException&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&amp;quot;&amp;gt;진단&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_해결책_2&amp;quot;&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;#_문제_3_구성_파일이_존재하지_않습니다&amp;quot;&amp;gt;문제 #3 : 구성 파일이 존재하지 않습니다.&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&amp;quot;&amp;gt;진단&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_해결책_3&amp;quot;&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;#_문제_4_afterevaluate_와_nullpointerexception&amp;quot;&amp;gt;문제 #4 : afterEvaluate 와 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;#_진단_4&amp;quot;&amp;gt;진단&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_해결책_4&amp;quot;&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;#_완전한_솔루션&amp;quot;&amp;gt;완전한 솔루션&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_배운_점&amp;quot;&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;#_1_올바른_모의_객체_확인&amp;quot;&amp;gt;1. 올바른 모의 객체 확인&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_2_중첩된_모의_객체_피하기&amp;quot;&amp;gt;2. 중첩된 모의 객체 피하기&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_3_평가_콜백을_모의하기&amp;quot;&amp;gt;3. 평가 콜백을 모의하기&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_4_경로_해결_테스트&amp;quot;&amp;gt;4. 경로 해결 테스트&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;#_최종_테스트_아키텍처&amp;quot;&amp;gt;최종 테스트 아키텍처&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_결론&amp;quot;&amp;gt;결론&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_자원&amp;quot;&amp;gt;자원&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;_서론&amp;quot;&amp;gt;서론&amp;lt;/h2&amp;gt;
&amp;lt;div 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 플러그인 개발 중&amp;lt;strong&amp;gt;베이커리&amp;lt;/strong&amp;gt;내 JBake 블로그에서 겉보기에 간단한 문제를 만났습니다: 오류와 함께 실패하는 단위 테스트가 있었습니다.&amp;lt;code&amp;gt;Wanted but not invoked&amp;lt;/code&amp;gt;. 이것은 사소한 버그로 보였지만 Mockito와 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;이 글에서는 체계적인 디버깅 여정을 안내하며, 각 해결책이 새로운 문제를 드러내고 최종 해결에 이르기까지의 과정을 보여줍니다.&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;_컨텍스트_plugin_gradle_bakery&amp;quot;&amp;gt;컨텍스트 : 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;Bakery 플러그인은 JBake를 감싸는 래퍼로, 정적 사이트 게시를 용이하게 합니다. 다음은 간소화된 구조 :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;실패하던 테스트는 간단했습니다 :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;오류 :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;_문제_1_잘못된_모의mock_확인&amp;quot;&amp;gt;문제 #1 : 잘못된 모의(mock) 확인&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;_진단&amp;quot;&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&amp;gt;@startuml
actor Test
participant &amp;quot;project.plugins&amp;quot; as PP
participant &amp;quot;모의플러그인컨테이너&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;모의플러그인컨테이너&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;문제: Mockito는 상호작용을 추적할 수 없습니다.&amp;lt;code&amp;gt;project.plugins`그냥 진짜 모의를 반환하는 getter일 뿐이다`mockPluginContainer&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;_해결책&amp;quot;&amp;gt;해결책&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;수정`createMockProject()`두 객체를 반환하기 위해 :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;그리고 테스트를 조정:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;_문제_2_연속된_unfinishedstubbingexception&amp;quot;&amp;gt;문제 #2: 연속된 UnfinishedStubbingException&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;_진단_2&amp;quot;&amp;gt;진단&amp;lt;/h3&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-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;문제가 있는 코드에서는 Mockito-Kotlin의 DSL 구문을 사용했습니다 :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;모크 빌더&amp;quot; as MB
participant &amp;quot;인라인 모의&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;_해결책_2&amp;quot;&amp;gt;해결책&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;모두&amp;lt;/strong&amp;gt;그리고 이를 구성합니다`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;황금 규칙&amp;lt;/strong&amp;gt;: 절대 부르지 마`mock()`모의 구성 블록 안에. 항상 먼저 mock을 생성한 다음 구성하세요.&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;_문제_3_구성_파일이_존재하지_않습니다&amp;quot;&amp;gt;문제 #3 : 구성 파일이 존재하지 않습니다.&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;_진단_3&amp;quot;&amp;gt;진단&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;올바른 목(mock) 객체가 있더라도, 플러그인이 구성 파일을 찾지 못해서 테스트는 항상 실패했습니다 :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;베이커리플러그인&amp;quot; as BP
participant &amp;quot;프로젝트 레이아웃&amp;quot; as PL
participant &amp;quot;확장&amp;quot; as EXT
participant &amp;quot;파일 시스템&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;_해결책_3&amp;quot;&amp;gt;해결책&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;경로 해결이 작동하도록 모의(mock)를 구성합니다 :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;모의 구성&amp;quot; {
    card &amp;quot;projectDirectory.asFile&amp;quot; SUCCESS
    card &amp;quot;반환: /real/path/to/project&amp;quot; SUCCESS

    card &amp;quot;configPath.get()&amp;quot; SUCCESS
    card &amp;quot;반환: 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;존재()&amp;quot; SUCCESS
    card &amp;quot;참 ✅&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;_문제_4_afterevaluate_와_nullpointerexception&amp;quot;&amp;gt;문제 #4 : afterEvaluate 와 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;_진단_4&amp;quot;&amp;gt;진단&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;플러그인은 블록에서 JBake를 적용합니다.&amp;lt;code&amp;gt;afterEvaluate&amp;lt;/code&amp;gt;, 그리고 접근한다`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;모의`buildDirectory.dir()&amp;lt;code&amp;gt;돌아가고 있었다`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;_해결책_4&amp;quot;&amp;gt;해결책&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;조롱하다`afterEvaluate`즉시 실행되도록 하고 완전히 설정하다`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;테스트&amp;quot; as T
participant &amp;quot;베이커리 플러그인&amp;quot; as BP
participant &amp;quot;모의 프로젝트&amp;quot; as MP
participant &amp;quot;후평가 액션&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;구우다&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;_완전한_솔루션&amp;quot;&amp;gt;완전한 솔루션&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;여기에 함수입니다.`createMockProject()`마지막으로, 모든 문제를 해결하는 :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;그리고 최종 테스트가 통과됩니다:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;_배운_점&amp;quot;&amp;gt;배운 점&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;_1_올바른_모의_객체_확인&amp;quot;&amp;gt;1. 올바른 모의 객체 확인&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_중첩된_모의_객체_피하기&amp;quot;&amp;gt;2. 중첩된 모의 객체 피하기&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_평가_콜백을_모의하기&amp;quot;&amp;gt;3. 평가 콜백을 모의하기&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_경로_해결_테스트&amp;quot;&amp;gt;4. 경로 해결 테스트&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;_최종_테스트_아키텍처&amp;quot;&amp;gt;최종 테스트 아키텍처&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;테스트 레이어&amp;quot; {
    [Test Class] --&amp;amp;gt; [createMockProject()]
}

package &amp;quot;모의 창조&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;모의 객체&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;플러그인 테스트 중&amp;quot; {
    [Test Class] --&amp;amp;gt; [BakeryPlugin]
    [BakeryPlugin] --&amp;amp;gt; [Project Mock]
    [BakeryPlugin] --&amp;amp;gt; [PluginContainer Mock]
}

package &amp;quot;검증&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;_결론&amp;quot;&amp;gt;결론&amp;lt;/h2&amp;gt;
&amp;lt;div 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;/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;Mockito와 Kotlin의 세부 사항&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;올바른 객체를 모의하는 것의 중요성&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;테스트에서의 비동기 콜백 관리&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;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;체계적인 디버깅으로 문제를 구성하는 각 층을 이해함으로써 견고하고 유지 가능한 솔루션에 도달할 수 있었다.&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;당신의 테스트를 위한 조언&amp;lt;/strong&amp;gt;Mockito에서 &amp;quot;Wanted but not invoked&amp;quot;을(를) 마주치면 항상 스스로에게 물어보세요:
1. 올바른 mock을 확인하고 있나요?
2. 모든 mock이 구성 블록 외부에서 생성됩니까?
3. 내 콜백이 정말 실행되고 있나요?
4. 내 경로가 올바르게 해결되고 있나요?&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;_자원&amp;quot;&amp;gt;자원&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;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;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 정적 사이트 생성기&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;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>Kotlin Arrow : 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;#_소개&amp;quot;&amp;gt;소개&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_either_모나드가_무엇인가요&amp;quot;&amp;gt;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;#_왜_either를_사용해야_하나요&amp;quot;&amp;gt;왜 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;#_맥락&amp;quot;&amp;gt;맥락&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_다양한_접근_방식&amp;quot;&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;#_클래식한_접근_방식과_when&amp;quot;&amp;gt;클래식한 접근 방식과 `when&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_fold를_사용한_더_간결한_접근&amp;quot;&amp;gt;fold`를 사용한 더 간결한 접근&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_변환_map와_mapleft&amp;quot;&amp;gt;변환 `map`와 `mapLeft&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_getorelse를_사용한_에러_처리&amp;quot;&amp;gt;getOrElse`를 사용한 에러 처리&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_측면_동작에_onleft와_onright&amp;quot;&amp;gt;측면 동작에 `onLeft`와 `onRight&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_flatmap을_사용한_연산_체이닝&amp;quot;&amp;gt;flatMap`을 사용한 연산 체이닝&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_bimap을_사용한_양방향_변환&amp;quot;&amp;gt;bimap`을 사용한 양방향 변환&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_swap_로_측면_교환&amp;quot;&amp;gt;swap` 로 측면 교환&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_사용법_tap_그리고_tapleft&amp;quot;&amp;gt;사용법 &amp;lt;code&amp;gt;tap&amp;lt;/code&amp;gt; 그리고 &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;#_사용법_recover&amp;quot;&amp;gt;사용법 &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;#_실용적인_사용_사례&amp;quot;&amp;gt;실용적인 사용 사례&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_결론&amp;quot;&amp;gt;결론&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_더_깊이&amp;quot;&amp;gt;더 깊이&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;&amp;lt;em&amp;gt;읽는 데 걸리는 시간 :&amp;lt;/em&amp;gt; 12~15분.&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;_소개&amp;quot;&amp;gt;소개&amp;lt;/h2&amp;gt;
&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;대상 :&amp;lt;/em&amp;gt; 중급에서 고급 수준의 Kotlin 개발자.&amp;lt;/div&amp;gt;
&amp;lt;p&amp;gt;Kotlin의 함수형 프로그래밍 세계에서 Arrow 라이브러리는 오류와 대체 사례를 처리하는 강력한 도구를 제공합니다. 이러한 도구들 중에서 모나드`Either`두 가지 가능한 상태: 성공(Right) 또는 실패(Left). 하지만 이 두 상태 사이에서 효율적으로 어떻게 탐색할 수 있을까요? 이것이 바로 이 글에서 우리가 탐구할 내용입니다.&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;_either_모나드가_무엇인가요&amp;quot;&amp;gt;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;모나드`Either`Either` 는 두 가지 상호 배타적인 가능성을 나타내는 데이터 구조입니다. Kotlin과 Arrow에서, 이는 작업의 성공 및 실패 사례를 처리하는 데 자주 사용되며, 전통적인 예외에 대한 우아한 대안을 제공합니다.&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;_왜_either를_사용해야_하나요&amp;quot;&amp;gt;왜 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;명시적 오류 처리 : 개발자에게 실패 사례를 고려하도록 강요합니다.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;함수형 구성: 실패할 수 있는 작업의 체이닝을 용이하게 합니다.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;타입 안전성 : 오류가 타입 지정되어 있어 더 정확하게 처리하는 데 도움이 됩니다.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&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&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_맥락&amp;quot;&amp;gt;맥락&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;당신이 REST API를 개발하고 있다고 상상해 보세요https://spring.io/projects/spring-boot/[Spring Boot,windows=읽기-나중] et &amp;lt;a href=&amp;quot;https://kotlinlang.org/&amp;quot;&amp;gt;Kotlin&amp;lt;/a&amp;gt;, 라이브러리를 사용하여https://arrow-kt.io/[화살,창문=읽기-나중]. 당신은 하나의 기능이 있습니다.&amp;lt;code&amp;gt;findOneUserByEmail`반환하는`Either&amp;amp;lt;Throwable, User&amp;amp;gt;&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;우리는 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;_다양한_접근_방식&amp;quot;&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;_클래식한_접근_방식과_when&amp;quot;&amp;gt;클래식한 접근 방식과 `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;이 방법은 간단하고 가독성이 좋지만, 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;_fold를_사용한_더_간결한_접근&amp;quot;&amp;gt;fold`를 사용한 더 간결한 접근&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`두 경우(Left와 Right)에 대해 간결하고 우아하게 동작을 정의할 수 있습니다.&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;_변환_map와_mapleft&amp;quot;&amp;gt;변환 `map`와 `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;이 접근 방식은 Either에 포함된 값을 변환하면서 구조를 보존할 수 있게 하여, 보다 복잡한 처리 체인에 적합합니다.&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;_getorelse를_사용한_에러_처리&amp;quot;&amp;gt;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`기본 값을 제공함으로써 우아한 오류 관리를 제공합니다.&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;_측면_동작에_onleft와_onright&amp;quot;&amp;gt;측면 동작에 `onLeft`와 `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;이러한 메서드는 Either를 수정하지 않고 각 쪽에서 작업을 수행할 수 있게 해 주며, 로깅이나 가벼운 부작용에 적합합니다.&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;_flatmap을_사용한_연산_체이닝&amp;quot;&amp;gt;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`연쇄적으로 실행되는 작업을 이어가기에 유용합니다. 즉, 작업을 실행하면 자신이 다시 작업을 반환합니다.`Either&amp;lt;/code&amp;gt;, 그렇게 하여 &amp;amp;#8230;&amp;amp;#8203;을 피함`Either`중첩된.&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;_bimap을_사용한_양방향_변환&amp;quot;&amp;gt;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`양쪽을 동시에 한 번의 작업으로 변환할 수 있습니다.&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;_swap_로_측면_교환&amp;quot;&amp;gt;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`는 당신이 어떤 것의 옆을 뒤집고 싶을 때 유용합니다`Either&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;_사용법_tap_그리고_tapleft&amp;quot;&amp;gt;사용법 &amp;lt;code&amp;gt;tap&amp;lt;/code&amp;gt; 그리고 &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;비슷한`onLeft` et &amp;lt;code&amp;gt;onRight&amp;lt;/code&amp;gt;, 하지만 이러한 메서드는 원래의 Either를 반환하며, 이는 연산 체이닝에 유용합니다.&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;_사용법_recover&amp;quot;&amp;gt;사용법 &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;이 메서드는 대체 값을 제공하여 Either.Left를 Either.Right로 변환할 수 있습니다.&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;_실용적인_사용_사례&amp;quot;&amp;gt;실용적인 사용 사례&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;사용하세요`fold`간단한 작업에 대해 각 경우에 처리가 필요한 경우. - 선호하세요`map` et &amp;lt;code&amp;gt;mapLeft`데이터 변환을 위해 구조를 바꾸지 않고`Either&amp;lt;/code&amp;gt;. - 선택하세요`flatMap`실패할 수 있는 작업을 연쇄할 때 사용하세요`recover`오류 시 기본값을 제공하기 위해. - 선택하세요`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;) 부수적인 효과, 예를 들어 로깅과 같은 것을 위해. - 사용하세요`bimap`양쪽을 하나의 작업으로 변환하기. - 적용하세요`swap`함수의 인터페이스를 다른 함수의 인터페이스에 맞춰야 할 때.&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;_결론&amp;quot;&amp;gt;결론&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;이러한 접근 방식 각각은 사용 맥락에 따라 장점이 있습니다. 방법과 같은`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`는 여러 작업을 연결하거나 데이터를 함수형 방식으로 변환할 때 특히 유용합니다.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;일원자`Either`dArrow는 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;다음 프로젝트에서 이러한 기법을 탐색하여 Kotlin과 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;_더_깊이&amp;quot;&amp;gt;더 깊이&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;Arrow 공식 문서 :https://arrow-kt.io/docs/apidocs/arrow-core/arrow.core/-either/[Arrow Either] - Kotlin 코루틴과 Arrow :https://arrow-kt.io/docs/fx/[화살 이펙트 코루틴]&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;경험과 선호하는 기술을 공유하는 것을 잊지 마세요`Either`아래 댓글에서!&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>실용 가이드 : 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;#_소개&amp;quot;&amp;gt;소개&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_명령어_find_사용&amp;quot;&amp;gt;명령어 &amp;lt;code&amp;gt;find&amp;lt;/code&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;#_find에_대한_추가_옵션&amp;quot;&amp;gt;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;#_명령어_사용_locate&amp;quot;&amp;gt;명령어 사용 `locate&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_grep_명령어를_사용하여_파일_내에서_텍스트를_검색&amp;quot;&amp;gt;grep` 명령어를 사용하여 파일 내에서 텍스트를 검색&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_결론&amp;quot;&amp;gt;결론&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;_소개&amp;quot;&amp;gt;소개&amp;lt;/h2&amp;gt;
&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;/div&amp;gt;
&amp;lt;p&amp;gt;이 글에서는 Zsh 터미널을 사용하여 Ubuntu에서 파일을 검색하는 방법을 탐구해 보겠습니다. Zsh(Z shell)는 유연성과 고급 기능으로 인해 널리 사용되는 강력한 명령어 해석기입니다.&amp;lt;/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은(는) 인기 있는 Linux 배포판으로, 파일을 검색하는 여러 명령줄 도구를 제공합니다. 우리는 다음과 같은 명령어를 사용하는 방법을 살펴볼 것입니다.&amp;lt;code&amp;gt;find&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;locate&amp;lt;/code&amp;gt; et `grep`터미널에서 파일을 빠르게 효율적으로 찾기 위해&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;_명령어_find_사용&amp;quot;&amp;gt;명령어 &amp;lt;code&amp;gt;find&amp;lt;/code&amp;gt; 사용&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;명령어`find`특정 디렉토리와 그 하위 디렉토리에서 파일을 검색할 수 있습니다. 기본 구문은 다음과 같습니다 :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;다음은 이름을 가진 파일을 찾는 실용적인 예시입니다.&amp;lt;code&amp;gt;example.txt`디렉토리 안에&amp;lt;/code&amp;gt;/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;이 명령은 모든 하위 디렉터리를 탐색할 것입니다.`/home`그리고 파일이 발견되면 파일 전체 경로를 표시합니다.&amp;lt;/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;명령어 온라인 매뉴얼`find` : &amp;lt;a href=&amp;quot;https://man.archlinux.org/man/find.1.fr/&amp;quot;&amp;gt;남자 찾다&amp;lt;/a&amp;gt;&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;_find에_대한_추가_옵션&amp;quot;&amp;gt;find`에 대한 추가 옵션&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;명령어`find`는 매우 강력하고 많은 옵션을 제공합니다. 다음은 몇 가지 유용한 예시 :&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;파일 유형별로 검색&amp;lt;/strong&amp;gt;파일만 검색하려면 사용하세요`-type f`, 그리고 디렉터리를 검색하려면, 사용하세요`-type d`. - &amp;lt;strong&amp;gt;크기별로 검색&amp;lt;/strong&amp;gt;파일 크기에 따라 파일을 필터링할 수도 있습니다. 옵션을 사용해서`-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;_명령어_사용_locate&amp;quot;&amp;gt;명령어 사용 `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;명령`locate`더 빠른`find`이유는 사전 인덱싱된 시스템에 존재하는 파일들의 데이터베이스를 사용하기 때문입니다. 먼저 다음 명령으로 이 데이터베이스를 업데이트해야 합니다:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;다음에 다음과 같이 파일을 검색할 수 있습니다:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;사용하고 있어요https://fr.wikipedia.org/wiki/Z_Shell[zsh]와/과https://github.com/ohmyzsh/ohmyzsh/wiki[oh-my-zsh], 명령이라는 것이 드러났습니다`locate`접근할 수 없습니다`zsh`하지만 가능한`bash`, 이를 보완하기 위해, 논거`-c`명령의`bash`명령을 실행할 수 있게 함`bash`다른 종류의 터미널에서.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;여기서 저는 패턴으로 끝나는 파일을 찾고 있습니다.`001.pdf`사용자 디렉터리 안에&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;&amp;lt;strong&amp;gt;명령의 온라인 매뉴얼`locate`:https://man.archlinux.org/man/locate.1.fr/[man locate, windows=읽기-나중]&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;_grep_명령어를_사용하여_파일_내에서_텍스트를_검색&amp;quot;&amp;gt;grep` 명령어를 사용하여 파일 내에서 텍스트를 검색&amp;lt;/h2&amp;gt;
&amp;lt;div 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;grep&amp;lt;/code&amp;gt;. 예를 들어, 모든 파일에서 &amp;quot;Ubuntu&amp;quot; 단어의 발생을 검색하려면`.txt`현재 디렉터리에서 사용 :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;_결론&amp;quot;&amp;gt;결론&amp;lt;/h2&amp;gt;
&amp;lt;div 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 터미널을 통해 파일을 찾는 작업은 여러 강력한 도구를 사용하여 수행할 수 있습니다.`find`유연하며 다양한 옵션을 제공하지만`locate`를 통해 사전 구축된 데이터베이스를 이용한 빠른 검색이 가능합니다. 마지막으로,`grep`파일 내부에서 텍스트 발생을 찾을 수 있습니다.&amp;lt;/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;man&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>리눅스 메모</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;#_키보드&amp;quot;&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;#_키_ctrl&amp;quot;&amp;gt;&amp;lt;em&amp;gt;키 &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;#_허가승인&amp;quot;&amp;gt;허가(승인)&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_파일_검색&amp;quot;&amp;gt;파일 검색&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;_키보드&amp;quot;&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;_키_ctrl&amp;quot;&amp;gt;&amp;lt;em&amp;gt;키 &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;링크&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;다른 링크&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;_허가승인&amp;quot;&amp;gt;허가(승인)&amp;lt;/h2&amp;gt;
&amp;lt;div 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은 파일의 권한을 변경하는 데 사용됩니다.&amp;lt;/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;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 파일명 rwx r-x r-x     chmod 755 파일명 rw- rw- r--     chmod 664 filename rw- r-- r--     chmod 644 파일 이름&amp;lt;/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 = 세계&amp;lt;/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 = Readable w = 쓰기 가능 x = 실행 파일 - = 권한 없음&amp;lt;/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;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;400     소유자가 읽음 040     그룹이 읽음 004     누구든지 읽을 수 있음 (다른) 200     소유자가 작성 020     그룹별로 쓰기 누구나 쓸 수 있다 100     소유자에 의해 실행 010     그룹별로 실행 001     누구에 의해 실행&amp;lt;/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;br&amp;gt;
소유자가 읽고, 쓰고, 실행할 수 있도록,&amp;lt;br&amp;gt;
읽은, 실행된, 그룹에 의해 실행된 및 누구에 의해 실행된,&amp;lt;br&amp;gt;
당신은 400+200+100+040+010+001을 더하면 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;_파일_검색&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;/blog/2024/0074_seach_file_linux_post.html&amp;quot;&amp;gt;파일 검색&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>Windows 호스트에 Linux 하위 시스템 포트 노출</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></description>
        </item><item>
            <title>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;읽는 데 걸리는 시간 : 3분&amp;lt;/em&amp;gt; Pandoc는 범용 문서 변환기입니다.&amp;lt;/div&amp;gt;
&amp;lt;p&amp;gt;Pandoc는 서로 다른 형식 간에 문서를 변환하는 데 사용할 수 있는 오픈 소스 텍스트 처리 소프트웨어입니다. 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;_다운로드_페이지에서_설치하기&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;다운로드 페이지에서 설치하기&amp;lt;/strong&amp;gt;&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;Pandoc을 다운로드 페이지에서 설치하려면 다음과 같이 진행하세요:&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;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;운영 체제용 패키지 설치 프로그램을 다운로드하세요.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;설치 프로그램을 실행하여 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;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;Pandoc를 zip 파일에서 설치하려면 다음 단계를 따르세요:&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;Pandoc 바이너리와 설명서가 포함된 zip 파일을 다운로드하세요.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;ZIP 파일을 압축 해제하세요.&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&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;strong&amp;gt;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;Chocolatey를 사용하여 Pandoc을 설치하려면 다음 단계를 따르세요:&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;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&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;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;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;winget을 사용하여 Pandoc을 설치하려면 다음 단계를 따르세요 :&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;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&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;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;권고&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;기본적으로 Pandoc은 LaTeX를 사용하여 PDF를 만듭니다. LaTeX를 MiKTeX를 통해 설치하는 것이 권장됩니다. 옵션과 함께`--pdf-engine`, 그러나 이 작업에 다른 프로그램을 지정할 수 있습니다.&amp;lt;/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;/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;여러 설치 방법을 사용하면 Pandoc이 두 개의 별도 설치로 이어질 수 있습니다. 대체 설치 방법으로 전환하기 전에 Pandoc을 올바르게 제거하는 것이 좋습니다.&amp;lt;/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;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;패키지 관리자에 있는 pandoc 버전이 오래되지 않았는지 확인하세요. Pandoc은 Debian, Ubuntu, Slackware, Arch, Fedora, NixOS, openSUSE, gentoo 및 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;최신 버전을 얻으려면, amd64 아키텍처용 바이너리 패키지를 다운로드 페이지에서 제공합니다.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;실행 파일은 정적으로 연결되어 있고 동적 의존성도 없으며 외부 데이터 파일에 대한 의존성도 없습니다. 주의: 정적 링크 때문에 이 패키지의 pandoc 바이너리는 C로 작성된 외부 Lua 모듈을 필요로 하는 Lua 필터를 사용할 수 없습니다.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;tarball과 deb 설치 프로그램이 제공됩니다. 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;작성 시점의 예시:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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`다운로드된 deb 파일의 경로입니다. 이 실행 파일을 설치합니다.`pandoc`그리고 매뉴얼 페이지.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;RPM 기반 배포판을 사용 중이라면, 우리 다운로드 페이지의 deb 파일을 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;어떤 배포판이든 tarball에서 설치할 수 있습니다`$DEST`(예를 들어,&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;) 하기 :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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`다운로드된 압축 tarball의 경로입니다. Pandoc 2.0 이전 버전은 tarball을 제공하지 않으므로 대신 다음을 시도해 보세요 :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;또한 소스에서 설치할 수도 있으며, 아래 지침을 사용하여 설치할 수도 있습니다`Compilation à partir de la source`. 대부분의 배포판이 Haskell 플랫폼을 저장소에 포함하고 있다는 점에 유의하세요. 예를 들어, Debian/Ubuntu에서는 다음으로 설치할 수 있습니다.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;기본적으로 Pandoc은 LaTeX를 사용하여 PDF를 생성합니다. 우리는 패키지 관리자를 통해 TeX Live 설치를 권장합니다. (Debian/Ubuntu에서,&amp;lt;code&amp;gt;apt-get install texlive&amp;lt;/code&amp;gt;). 옵션과 함께`--pdf-engine`, 그러나 다른 프로그램을 지정할 수 있습니다.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>JavaScript, TypeScript, Python에서의 kotlin.Triple 구현</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;읽는 시간: 2분&amp;lt;/em&amp;gt; 함수형 프로그래밍을 사용해 JavaScript, TypeScript, Python에서 kotlin.Triple 클래스의 완전한 구현을 만드세요.&amp;lt;/div&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Note :&amp;lt;/strong&amp;gt;유형`Maybe`, &amp;lt;code&amp;gt;Either&amp;lt;/code&amp;gt;, `Result`자바스크립트 또는 타입스크립트의 기본 언어에 포함되지 않으므로 우리가 직접 구현해야 합니다.&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;_자바스크립트&amp;quot;&amp;gt;자바스크립트&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;_타입스크립트&amp;quot;&amp;gt;타입스크립트&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;Python을 사용하여 구현을 만들려면`pydantic`타입 검증 및`pymonad`모나드들을 위해:&amp;lt;/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-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;이 예시에서 :&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`클래스를 정의하기 위해`Triple`타입이 지정된 필드가 있는. - 방법`divide_by`모나드를 사용합니다`Either&amp;lt;/code&amp;gt; de `pymonad`0으로 나누기 결과 발생할 수 있는 오류를 처리하기 위해 - 맞춤형 검증을 통해 값이`b`0이 아닙니다. 만약 0이라면 예외가 발생하고, 그리고`pymonad`이 예외를 잡아서 결과를 반환`Left`오류를 나타내고.&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>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;_목차&amp;quot;&amp;gt;목차&amp;lt;/h2&amp;gt;
&amp;lt;div 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;읽는 데 걸리는 시간 : 30분&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;#서론&amp;quot;&amp;gt;[서론]&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;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;#_google_apps_script의_타입_및_변수&amp;quot;&amp;gt;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;상수, 변수 및 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;#Google Apps Script의 연산과 연산자&amp;quot;&amp;gt;[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;#Google Apps Script의 Var&amp;quot;&amp;gt;Let, Const&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;#구글 앱스 스크립트의 제어 구조&amp;quot;&amp;gt;[구글 앱스 스크립트의 제어 구조]&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;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;#_google_apps_script의_객체&amp;quot;&amp;gt;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;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의 매개변수와 스코프&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;#_google_apps_script의_rest_및_spread_연산자&amp;quot;&amp;gt;Google Apps Script의 Rest 및 Spread 연산자&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;#Google Apps Script에서의 비구조화 할당&amp;quot;&amp;gt;[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;Google Apps Script의 컬렉션 및 선언적 프로그래밍&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;_서론&amp;quot;&amp;gt;서론&amp;lt;/h2&amp;gt;
&amp;lt;div 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)는 Google Sheets, Google Docs, Google Forms과 같은 다양한 Google 서비스와 연관된 스크립트 개발 플랫폼입니다. 이를 통해 작업을 자동화하고 맞춤형 기능을 추가하며 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;이 메모에서는 구체적인 예를 바탕으로 Google Apps Script의 주요 개념을 탐구합니다. 변수, 루프, 함수 등 다양한 개념을 다루며, 이를 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;_google_apps_script의_기본_구조&amp;quot;&amp;gt;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의 기본 구조를 살펴봅시다.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;_google_apps_script의_타입_및_변수&amp;quot;&amp;gt;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는 프로그래밍 언어로 JavaScript를 사용합니다. 데이터 유형과 변수는 표준 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;/**
 * 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;_작업_및_연산자_google_apps_script에서&amp;quot;&amp;gt;작업 및 연산자 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의 연산과 연산자는 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;_google_apps_script에서_var_let_및_const&amp;quot;&amp;gt;Google Apps Script에서 Var, Let 및 Const&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;GAS에서는 개념의`var`, &amp;lt;code&amp;gt;let&amp;lt;/code&amp;gt;, et `const`변수를 선언하는 데 사용됩니다. 그러나, 차이점과 영향을 이해하는 것이 필수적입니다.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;_google_apps_script의_제어_구조&amp;quot;&amp;gt;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;제어 구조는 예를 들어`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`이것들은 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;_google_apps_script에서_매개변수_및_스코프&amp;quot;&amp;gt;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;매개변수와 스코프는 GAS에서 스크립트 개발에서 중요한 역할을 합니다. 어떻게 작동하는지 이해하여 예기치 않은 동작을 피하세요.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;_google_apps_script에서_상수_변수_및_문자&amp;quot;&amp;gt;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;차이점을 이해하는`const`, &amp;lt;code&amp;gt;var&amp;lt;/code&amp;gt;, et `let`은(는) 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;_구글_앱스_스크립트에서의_반복문&amp;quot;&amp;gt;구글 앱스 스크립트에서의 반복문&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;루프, 예를 들어`for` et `while`요소를 반복하고 반복적인 작업을 수행하는 데 필수적입니다.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;_google_apps_script의_객체&amp;quot;&amp;gt;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;객체는 데이터를 구조화하는 데 사용됩니다. GAS에서는 많은 내장 객체가 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;_google_apps_script에서_상수_변수_그리고_문자&amp;quot;&amp;gt;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;차이를 이해하는`const`, &amp;lt;code&amp;gt;var&amp;lt;/code&amp;gt;, et `let`이는 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;_google_apps_script의_함수&amp;quot;&amp;gt;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;함수는 프로그래밍의 기본 요소입니다. GAS에서는 다양한 방법으로 선언하고 호출할 수 있습니다.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;_google_apps_script의_rest_및_spread_연산자&amp;quot;&amp;gt;Google Apps Script의 Rest 및 Spread 연산자&amp;lt;/h2&amp;gt;
&amp;lt;div 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;&amp;amp;#8230;&amp;amp;#8203;&amp;lt;/code&amp;gt;) 그리고 의 전파 (&amp;lt;code&amp;gt;&amp;amp;#8230;&amp;amp;#8203;&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;/**
 * 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;_google_apps_script의_분해_할당&amp;quot;&amp;gt;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;구조 해체 할당은 값들을 객체와 배열에서 간결하게 추출할 수 있게 해주는 강력한 기능입니다.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;_google_apps_script에서의_컬렉션_및_선언적_프로그래밍&amp;quot;&amp;gt;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은 배열과 같은 데이터 구조를 지원하며, 함수형 프로그래밍과 선언적 반복(명령형 대비)과 같은`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;_결론&amp;quot;&amp;gt;결론&amp;lt;/h2&amp;gt;
&amp;lt;div 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의 기본 개념을 다뤘습니다. 이 지식을 활용하여 일상적인 작업을 자동화하고, 문서를 맞춤 설정하며, 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>Google Form 스크립트 및 자동화에 대한 Express 교육</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;시간: 30분&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;_목표&amp;quot;&amp;gt;목표&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;Forms에서 Google Apps Script 기초 이해하기 - 스크립트 편집기에 접근하고 트리거에서 명령을 실행하는 방법을 배우다&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;_지연&amp;quot;&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;strong&amp;gt;Google Form 스크립트 소개&amp;lt;/strong&amp;gt;(5분)&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;Google Form Script 빠른 개요 - 주요 기능 소개&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;Google 폼 생성 및 연결된 스크립트&amp;lt;/strong&amp;gt;(5 분)&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;Google Forms로 간단한 양식 만들기 - 연관된 스크립트 편집기 소개&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;명령을 실행&amp;lt;/strong&amp;gt;(10분)&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;&amp;quot;on start&amp;quot; 트리거 구성 - &amp;quot;on submit&amp;quot; 트리거 구성 - 명령을 실행하기 위한 스크립트 작성 - 빠른 테스트를 수행하여 일지에서 정상 작동을 확인합니다&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;결과 분석 및 우수 사례&amp;lt;/strong&amp;gt;(10분)&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;명령어 실행 결과 검증 스크립트 예시와 자동화가 시간을 절약할 수 있는 방법에 대한 논의&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;_선수_조건&amp;quot;&amp;gt;선수 조건&amp;lt;/h2&amp;gt;
&amp;lt;div 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 계정이 있어야 하며, Google Forms와 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;_장비&amp;quot;&amp;gt;장비&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;/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;_주석&amp;quot;&amp;gt;주석&amp;lt;/h2&amp;gt;
&amp;lt;div 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 Form Script를 사용한 자동화에 대한 빠른 소개를 제공합니다. 더 깊은 이해를 위해서는 더 긴 교육이 권장됩니다.&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_google_form_스크립트_소개&amp;quot;&amp;gt;1 - Google Form 스크립트 소개&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_google_form_script_빠른_개요&amp;quot;&amp;gt;Slide 1: 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는 Google Forms에 통합된 스크립팅 플랫폼입니다. - 그녀는 맞춤형 기능을 추가하고 작업을 자동화할 수 있습니다.&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_주요_기능_소개&amp;quot;&amp;gt;슬라이드 2: 주요 기능 소개&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 : Google Apps Script를 작성하기 위한 내장 스크립트 편집기. - 트리거: &amp;quot;on submit&amp;quot;과 같은 이벤트가 스크립트 실행을 활성화합니다.&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;_슬라이드_3_google_form_script_사용의_장점&amp;quot;&amp;gt;슬라이드 3: 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 서비스와 스크립트를 통합하여 보다 풍부한 경험을 제공합니다.&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;_발표자를_위한_메모&amp;quot;&amp;gt;발표자를 위한 메모:&amp;lt;/h3&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;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=fr&amp;quot; class=&amp;quot;bare&amp;quot;&amp;gt;https://developers.google.com/apps-script/add-ons/editors/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?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=fr&amp;quot; class=&amp;quot;bare&amp;quot;&amp;gt;https://developers.google.com/apps-script/reference/forms/form-app?hl=fr&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;_google_폼과_연관된_스크립트_생성_5분&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;Google 폼과 연관된 스크립트 생성&amp;lt;/strong&amp;gt; (5분)&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>파이썬 메모</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;_windows에_python_설치&amp;quot;&amp;gt;Windows에 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://ninite.com/pythonx3/&amp;quot;&amp;gt;윈도우에서 파이썬&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;_파이썬_실행_중&amp;quot;&amp;gt;파이썬 실행 중&amp;lt;/h2&amp;gt;
&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;문제점: Python venv&amp;lt;/div&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-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;_설치_요구사항&amp;quot;&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-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;_입문&amp;quot;&amp;gt;입문&amp;lt;/h2&amp;gt;
&amp;lt;div 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;파이썬 객체 지향 프로그래밍&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;_파이썬에서의_gof_디자인_패턴&amp;quot;&amp;gt;파이썬에서의 GoF 디자인 패턴&amp;lt;/h2&amp;gt;
&amp;lt;div 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;구현 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;구현 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;완전한 책&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;GoF 디자인 패턴은 《Design Patterns: Elements of Reusable Object-Oriented Software》이라는 책에 기술된 23가지 디자인 패턴의 집합입니다.&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. 여기 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;이름&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;팩토리 메서드&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;추상 팩토리&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;건설업자&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;프로토타입&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;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;클래스의 인스턴스화를 하나의 객체로 제한합니다.&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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;어댑터&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;브리지&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;복합&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;데코레이터&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;정면&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;플라이웨이트&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;프록시&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;책임 연쇄&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;명령&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;통역사&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;이터레이터&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;중재자&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;메멘토&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;관찰자&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;상태&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;전략&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;템플릿 메서드&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;방문자&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&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_함수형_프로그래밍&amp;quot;&amp;gt;함수형 프로그래밍&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;도서관&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;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;설명&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는 Haskell 및 F#과 같은 프로그래밍 언어를 기반으로 한 Python의 모나딕 데이터 구조 구현으로, 함수형 프로그래밍에서 가장 일반적으로 사용되는 모나드 유형에 대한 구현을 제공합니다.https://www.miguelfarrajota.com/2021/06/monads-in-python-with-pymonad/[기사]&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;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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은 함수형 프로그래밍을 위한 파이썬 라이브러리로, 순수 함수와 이터러블을 다루기 위한 도구를 제공합니다.&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;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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는 순수 함수와 이터러블을 다루는 데 사용할 수 있는 도구를 제공하는 함수형 프로그래밍을 위한 Python 라이브러리이다.&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는 순수 함수와 이터러블 작업에 사용할 도구를 제공하는 함수형 프로그래밍을 위한 Python 라이브러리입니다.&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;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class=&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은 함수형 프로그래밍을 위해 Python에 여러 가지 기능을 추가하며, 여기에는 패턴 매칭도 포함됩니다.https://stackoverflow.com/questions/11909681/are-there-pattern-matching-functions-in-python-like-this[대화 소프]&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;_부작용&amp;quot;&amp;gt;부작용&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;_루프&amp;quot;&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-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>메모 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;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;Intro 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;타입스크립트의 타입&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;#함수&amp;quot;&amp;gt;함수&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;amp;lt;/think&amp;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;열거형과 튜플&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과 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;클래스와 인터페이스&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;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;모듈&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;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;#async-await&amp;quot;&amp;gt;async/await&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;#제네릭&amp;quot;&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;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;타입스크립트의 타입&amp;lt;/h2&amp;gt;
&amp;lt;div 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;Intro 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;함수&amp;quot;&amp;gt;함수&amp;lt;/h2&amp;gt;
&amp;lt;div 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;/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-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;Intro 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;데이터 구조&amp;lt;/h2&amp;gt;
&amp;lt;div 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;Intro 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;열거형과 튜플&amp;lt;/h2&amp;gt;
&amp;lt;div 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;Intro 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과 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_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;// 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;Intro 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;클래스와 인터페이스&amp;lt;/h2&amp;gt;
&amp;lt;div 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;Intro 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;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;Intro 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;모듈&amp;lt;/h2&amp;gt;
&amp;lt;div 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;/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-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;Intro 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;&amp;quot;&amp;gt;&amp;lt;a id=&amp;quot;약속&amp;quot;&amp;gt;&amp;lt;/a&amp;gt;&amp;lt;/h2&amp;gt;
&amp;lt;div 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;/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-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;Intro 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는 프로미스 관리를 간소화합니다.&amp;lt;/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-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;Intro 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;generiques&amp;quot;&amp;gt;제네릭&amp;lt;/h2&amp;gt;
&amp;lt;div 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;/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-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>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;목차&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;좋은 링크&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;리포지토리와 코드 샘플&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_시작&amp;quot;&amp;gt;[toc_시작]&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;좋은 링크&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;목차&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;리포지토리와 코드 샘플&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;깃허브 저장소:https://github.com/cheroliv/css-codebase[css 코드베이스, 창 = &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;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;#début_intro&amp;quot;&amp;gt;소개 및 준비&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;#시작_텍스트&amp;quot;&amp;gt;[시작_텍스트]&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;#시작_배경&amp;quot;&amp;gt;[시작_배경]&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;#시작_박스&amp;quot;&amp;gt;[시작_박스]&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;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;#시작_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;#시작_abs_pos&amp;quot;&amp;gt;[시작_abs_pos]&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;#시작_반응형&amp;quot;&amp;gt;[시작_반응형]&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;#결론_시작&amp;quot;&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;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;#toc&amp;quot;&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&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;소개 및 준비&amp;lt;/h2&amp;gt;
&amp;lt;div 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;/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;깃허브 저장소:https://github.com/cheroliv/css-codebase/début_css/[시작 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 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 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;html 페이지에 style.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-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;#목차 시작&amp;quot;&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_texts&amp;quot;&amp;gt;텍스트&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;#목차 시작&amp;quot;&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_background&amp;quot;&amp;gt;배경&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_시작&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;시작_boxes&amp;quot;&amp;gt;상자들 (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;#목차_시작&amp;quot;&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_flexbox&amp;quot;&amp;gt;플렉스박스&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;#목차_시작&amp;quot;&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_grid&amp;quot;&amp;gt;그리드&amp;lt;/h2&amp;gt;
&amp;lt;div 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;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;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;절대 위치&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_시작&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;début_responsive&amp;quot;&amp;gt;반응형&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;&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;début_conclusion&amp;quot;&amp;gt;디버그 &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;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;#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 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>메모 프레임</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;_구도&amp;quot;&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;_워크숍_1_비전&amp;quot;&amp;gt;워크숍 1 : 비전&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;sect3&amp;quot;&amp;gt;
&amp;lt;h4 id=&amp;quot;_1_패턴_qqoqcp_누구_무엇_어디_언제_어떻게_왜&amp;quot;&amp;gt;1. 패턴 “QQOQCP” – 누구, 무엇, 어디, 언제, 어떻게, 왜&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_제품_비전_보드&amp;quot;&amp;gt;2. 제품 비전 보드&amp;lt;/h4&amp;gt;
&amp;lt;div class=&amp;quot;sect4&amp;quot;&amp;gt;
&amp;lt;h5 id=&amp;quot;_대상_그룹&amp;quot;&amp;gt;대상 그룹&amp;lt;/h5&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&amp;gt;
&amp;lt;div class=&amp;quot;sect4&amp;quot;&amp;gt;
&amp;lt;h5 id=&amp;quot;_필요&amp;quot;&amp;gt;필요&amp;lt;/h5&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;핵심 사용자의 주요 5가지 필요는 무엇인가요?&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;_제품&amp;quot;&amp;gt;제품&amp;lt;/h5&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;내 가치 제안은 무엇인가요? 제품의 5가지 주요 기능은 어떤 것들에 대응하기 위해 제공되나요? 필요&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;_비즈니스_목표&amp;quot;&amp;gt;비즈니스 목표&amp;lt;/h5&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&amp;gt;
&amp;lt;div class=&amp;quot;sect4&amp;quot;&amp;gt;
&amp;lt;h5 id=&amp;quot;_비전&amp;quot;&amp;gt;비전&amp;lt;/h5&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;4개의 열이 모두 채워지면, Product Vision의 비전 부분을 채워야 합니다. 보드를 완전한 문장으로&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;_워크숍_2_퍼소나사용자_여정&amp;quot;&amp;gt;워크숍 2: 퍼소나/사용자 여정&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;각 페르소나에 대한 사용자 경로를 정의:&amp;lt;/div&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;누가 내 서비스를 이용할까요? (대상)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&amp;gt;그가 뭘 하고 싶어? (하지만)&amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;p&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 class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_워크숍_3_영역_정의_스토리보드_사이트맵&amp;quot;&amp;gt;워크숍 3: 영역 정의, 스토리보드, 사이트맵&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;sect3&amp;quot;&amp;gt;
&amp;lt;h4 id=&amp;quot;_1_용도_구역&amp;quot;&amp;gt;1. 용도 구역&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_스토리보드&amp;quot;&amp;gt;2. 스토리보드&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_사이트맵&amp;quot;&amp;gt;3. 사이트맵&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;_워크숍_4_와이어프레임무드보드&amp;quot;&amp;gt;워크숍 4: 와이어프레임/무드보드&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;sect3&amp;quot;&amp;gt;
&amp;lt;h4 id=&amp;quot;_1_와이어프레임&amp;quot;&amp;gt;1. 와이어프레임&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_무드보드&amp;quot;&amp;gt;2. 무드보드&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;_워크숍_5_스토리_맵핑_사용자_스토리&amp;quot;&amp;gt;워크숍 5: 스토리 맵핑/ 사용자 스토리&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;sect3&amp;quot;&amp;gt;
&amp;lt;h4 id=&amp;quot;_스토리_매핑_사용자_스토리_우선순위&amp;quot;&amp;gt;스토리 매핑 (사용자 스토리 우선순위)&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;_type_dutilisateur로서_나는_un_objectif를_원한다_왜냐하면_une_raison_때문이다&amp;quot;&amp;gt;&amp;amp;lt;type d&amp;amp;#8217;utilisateur&amp;amp;gt;로서, 나는 &amp;amp;lt;un objectif&amp;amp;gt;를 원한다, 왜냐하면 &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;_워크숍_6_백로그&amp;quot;&amp;gt;워크숍 6: 백로그&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;백로그들&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;_아뜰리에_7_기술_및_건축&amp;quot;&amp;gt;아뜰리에 7: 기술 및 건축&amp;lt;/h3&amp;gt;

&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>메모 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;_좋은_링크&amp;quot;&amp;gt;좋은 링크&amp;lt;/h2&amp;gt;
&amp;lt;div 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;코틀린랭 플레이그라운드&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;/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:모델 뷰 인텐트&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;필수적인&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;분해하다&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&amp;gt;
&amp;lt;div class=&amp;quot;sect1&amp;quot;&amp;gt;
&amp;lt;h2 id=&amp;quot;_다양한_코드_조각&amp;quot;&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;_맵리듀스&amp;quot;&amp;gt;맵리듀스&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;바이트 리스트를 문자열 리스트로 변환하고 문자열 리스트를 요약합니다.&amp;lt;br&amp;gt;
연결이 포함된 문자열 안 요청(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_및_sealed_클래스&amp;quot;&amp;gt;enum 및 sealed 클래스&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;0038_training_kotlin_enum_sealed_class_post&amp;quot;&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 class=&amp;quot;sect2&amp;quot;&amp;gt;
&amp;lt;h3 id=&amp;quot;_함수형_인터페이스와_메서드_참조&amp;quot;&amp;gt;함수형 인터페이스와 메서드 참조&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;엔티티를 DTO 도메인 모델로 변환하는 참조 메서드(함수형 스타일, 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;하나https://stackoverflow.com/a/22245383/837404[좋은 설명]&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;_표준_출력_캡처&amp;quot;&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;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;_kotlin에서_apply_run_let_also_use_with의_차이는_무엇인가요&amp;quot;&amp;gt;Kotlin에서 apply, run, let, also, use, 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;Kotlin 함수 비교 및 람다 형식 사용 참고&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;_적용하다&amp;quot;&amp;gt;적용하다&amp;lt;/h2&amp;gt;
&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;서론 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`다른 방법으로 객체를 처리할 수 있는 방법을 제공합니다. 각각은 자체 사용 사례와 동작을 가집니다. 게다가, 람다 참조 형태는 기존 람다 함수를 참조함으로써 코드를 더 가독성 있고 재사용 가능하게 만들 수 있습니다.&amp;lt;/div&amp;gt;
&amp;lt;p&amp;gt;함수`apply`생성 중에 객체를 구성하는 데 사용됩니다. 호출된 객체를 반환합니다.&amp;lt;/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-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;람다를 사용한 예시 :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;함수`run`객체에서 코드 블록을 실행하여 그 결과를 반환하는 데 사용됩니다.&amp;lt;/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-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;예제와 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;함수`let`객체에서 코드 블록을 실행하고 코드 블록의 결과를 반환하는 데 사용됩니다.&amp;lt;/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-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;람다와 함께 예시 :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;_또한&amp;quot;&amp;gt;또한&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;함수`also`객체에 추가적인 작업을 수행하고, 호출된 객체를 반환하는 데 사용됩니다.&amp;lt;/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-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;람다 예시 :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;_사용&amp;quot;&amp;gt;사용&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;함수`use`는 사용 후 닫아야 하는 리소스를 다룰 때 사용됩니다. 자동으로 함수를 호출합니다.`close`블록의 끝에.&amp;lt;/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-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;람다와 함께하는 예시 :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;_와_함께&amp;quot;&amp;gt;와 함께&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;함수`with`는 여러 메서드를 객체에 대해 호출할 때 이름을 반복하지 않도록 사용되며, 마지막 식의 결과를 반환합니다.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;예시와 lambda reference :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;람다를 사용한 예시 :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;람다 참조 형태 또는 블록을 사용해`{}`, 로직을 별도의 함수에 캡슐화할 수 있습니다, 이를 통해 코드의 가독성과 재사용성을 향상시킵니다.&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에서_kotlin&amp;quot;&amp;gt;let Function에서 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;let이 객체에 적용된 부작용과 함께 객체를 반환하는지, 아니면 입력의 초기 상태 그대로 반환하는지, (let)에 따라 달라지나요 ?&amp;lt;/div&amp;gt;
&amp;lt;p&amp;gt;함수`let`Kotlin에서 개체에 대한 작업을 수행하고 다른 결과를 반환하는 데 사용됩니다. 그러나 함수에 대해 주목할 중요한 점이 있습니다.`let`호출된 객체의 초기 상태를 수정하지 않습니다.&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;서명&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;사용&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;함수`let`이는 객체에 변형을 적용하고 그 변형을 기반으로 결과를 얻는 데 일반적으로 사용됩니다.&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;결과&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;함수에서 반환된 값`let`는 인수로 전달된 lambda 표현식의 결과이며, 일반적으로 객체에서 수행된 작업의 결과입니다.&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;부작용&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;함수이지만`let`람다 표현식에서 사용될 때 객체에 부작용을 일으킬 수 있지만, 객체의 초기 상태를 수정하지는 않는다.&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;따라서, 함수`let`객체에 대한 작업을 수행하면서 파생된 결과를 얻면서 원래 객체의 무결성을 보존하는 한 가지 우아한 방법이다.&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>메모 그레이들</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;#_도움말_보기&amp;quot;&amp;gt;도움말 보기&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_jvm_시스템_속성을_정의합니다_d_system_prop&amp;quot;&amp;gt;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;#_빌드_스크립트의_프로젝트_속성을_정의하세요_pmypropmyvalue&amp;quot;&amp;gt;빌드 스크립트의 프로젝트 속성을 정의하세요 : `-Pmyprop=myvalue&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_gradle_시작_디렉터리를_지정합니다_기본값은_현재_디렉터리입니다&amp;quot;&amp;gt;Gradle 시작 디렉터리를 지정합니다. 기본값은 현재 디렉터리입니다.&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_명령줄을_통해_param_component_속성을_오버라이드하세요&amp;quot;&amp;gt;명령줄을 통해 &amp;#39;param_component&amp;#39; 속성을 오버라이드하세요:&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_일반_프로젝트에서_kotlin_dsl을_사용하여_소스_세트에_폴더_추가&amp;quot;&amp;gt;일반 프로젝트에서 Kotlin DSL을 사용하여 소스 세트에 폴더 추가&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_groovy_dsl을_사용해_android_프로젝트의_소스_세트에_폴더_추가&amp;quot;&amp;gt;Groovy DSL을 사용해 Android 프로젝트의 소스 세트에 폴더 추가&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_서브_모듈을_구축하고_테스트하지_마세요&amp;quot;&amp;gt;서브 모듈을 구축하고 테스트하지 마세요&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_파일을_가져오다&amp;quot;&amp;gt;파일을 가져오다&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_빌드의_의존성_표시&amp;quot;&amp;gt;빌드의 의존성 표시&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_reduce_사용_예시_리스트를_문자열에_연결하기&amp;quot;&amp;gt;reduce 사용 예시 : 리스트를 문자열에 연결하기&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_특정_작업의_설명을_검색하세요&amp;quot;&amp;gt;특정 작업의 설명을 검색하세요&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_workingdir_루트에서_작업을_시작하려면_작업_앞에_를_붙이세요&amp;quot;&amp;gt;workingDir 루트에서 작업을 시작하려면 작업 앞에 `:`를 붙이세요.&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_옵션_rerun_tasks를_사용하십시오&amp;quot;&amp;gt;옵션 `--rerun-tasks`를 사용하십시오.&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#_다른_디렉터리에서_특정_프로젝트의_작업을_실행&amp;quot;&amp;gt;다른 디렉터리에서 특정 프로젝트의 작업을 실행&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;_도움말_보기&amp;quot;&amp;gt;도움말 보기&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;_jvm_시스템_속성을_정의합니다_d_system_prop&amp;quot;&amp;gt;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;_빌드_스크립트의_프로젝트_속성을_정의하세요_pmypropmyvalue&amp;quot;&amp;gt;빌드 스크립트의 프로젝트 속성을 정의하세요 : `-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;_gradle_시작_디렉터리를_지정합니다_기본값은_현재_디렉터리입니다&amp;quot;&amp;gt;Gradle 시작 디렉터리를 지정합니다. 기본값은 현재 디렉터리입니다.&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;_명령줄을_통해_param_component_속성을_오버라이드하세요&amp;quot;&amp;gt;명령줄을 통해 &amp;#39;param_component&amp;#39; 속성을 오버라이드하세요:&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;_일반_프로젝트에서_kotlin_dsl을_사용하여_소스_세트에_폴더_추가&amp;quot;&amp;gt;일반 프로젝트에서 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;_groovy_dsl을_사용해_android_프로젝트의_소스_세트에_폴더_추가&amp;quot;&amp;gt;Groovy DSL을 사용해 Android 프로젝트의 소스 세트에 폴더 추가&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;_서브_모듈을_구축하고_테스트하지_마세요&amp;quot;&amp;gt;서브 모듈을 구축하고 테스트하지 마세요&amp;lt;/h2&amp;gt;
&amp;lt;div 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 build -x :excluded-module:check -x :excluded-module:assemble -x :excluded-module:build&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;/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;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;_파일을_가져오다&amp;quot;&amp;gt;파일을 가져오다&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;_빌드의_의존성_표시&amp;quot;&amp;gt;빌드의 의존성 표시&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;_reduce_사용_예시_리스트를_문자열에_연결하기&amp;quot;&amp;gt;reduce 사용 예시 : 리스트를 문자열에 연결하기&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;_특정_작업의_설명을_검색하세요&amp;quot;&amp;gt;특정 작업의 설명을 검색하세요&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;_workingdir_루트에서_작업을_시작하려면_작업_앞에_를_붙이세요&amp;quot;&amp;gt;workingDir 루트에서 작업을 시작하려면 작업 앞에 `:`를 붙이세요.&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;_옵션_rerun_tasks를_사용하십시오&amp;quot;&amp;gt;옵션 `--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;_다른_디렉터리에서_특정_프로젝트의_작업을_실행&amp;quot;&amp;gt;다른 디렉터리에서 특정 프로젝트의 작업을 실행&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;sectionbody&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;옵션`-p` (ou &amp;lt;code&amp;gt;--project-dir&amp;lt;/code&amp;gt;) 를 사용하면 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;예를 들어, 작업을 시작하려면`build`프로젝트 위치에 있는`~/projets/mon-autre-projet`다른 디렉터리에 있는 동안 :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;이것은 중앙 위치에서 여러 프로젝트에 걸쳐 작업을 스크립트하는 데 특히 유용합니다.&amp;lt;/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;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/command_line_interface.html#sec:command_line_warnings&amp;quot;&amp;gt;참조&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;참조&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;토론&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>훈련 - 코틀린: enum, sealed 클래스</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;밀봉된 클래스는 열거형이 아닙니다(키워드: enum).&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-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;대신에, 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;이 접근 방식의 장점은 이제 우리가 데이터를 전송할 수 있게 되었다는 것과 과 함께 상태:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;일반적으로, sealed classes는 데이터를 연결하고 싶을 때 좋습니다. 국가.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>교육 - 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;_마인드_맵&amp;quot;&amp;gt;마인드 맵&amp;lt;/h2&amp;gt;
&amp;lt;div 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;_함수형_프로그래밍&amp;quot;&amp;gt;함수형 프로그래밍&amp;lt;/h2&amp;gt;
&amp;lt;div 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;br&amp;gt;
튜토리얼로 배우는 Kotlin 함수형 프로그래밍&amp;lt;br&amp;gt;
Massimo Carli가 작성함&amp;lt;br&amp;gt;
저장소: &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;_구문&amp;quot;&amp;gt;구문&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;sect3&amp;quot;&amp;gt;
&amp;lt;h4 id=&amp;quot;_첫_프로그램_테스트&amp;quot;&amp;gt;첫 프로그램 테스트&amp;lt;/h4&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;개념: 화면 표시, 기능&amp;lt;br&amp;gt;
첫 프로그램 테스트:https://github.com/cheroliv/cheroliv.com/blob/master/codes/src/test/kotlin/programming/FirstProgramTest.kt[source]+&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;_연결_함수_테스트&amp;quot;&amp;gt;연결 함수 테스트&amp;lt;/h4&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;개념: 메모리, 변수, 값, 객체, 함수 확장&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;출처&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;_생일_메시지_테스트_출력&amp;quot;&amp;gt;생일 메시지 테스트 출력&amp;lt;/h4&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;개념: 집합, 루프&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;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[출처]+&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;_코틀린_강의&amp;quot;&amp;gt;코틀린 강의&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;_자바_컬렉션에_대한_튜토리얼&amp;quot;&amp;gt;자바 컬렉션에 대한 튜토리얼&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;컬렉션&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;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;컬렉션&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;_기본적인_기능&amp;quot;&amp;gt;기본적인 기능&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;sect3&amp;quot;&amp;gt;
&amp;lt;h4 id=&amp;quot;_선언적_x_명령형_접근&amp;quot;&amp;gt;선언적 X 명령형 접근&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;_연습_1_1&amp;quot;&amp;gt;연습 1.1&amp;lt;/h4&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;sumInRange 함수를 구현하라, 이 함수는 값들을 더한다&amp;lt;br&amp;gt;
주어진 구간 내의 List&amp;amp;lt;String&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-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;해보세요, 그리고 라와 함께 답변을 확인하세요https://github.com/cheroliv/cheroliv.com/blob/master/codes/src/test/kotlin/functional/DeclarativeTests.kt[해결책, 창=&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;_고차_함수&amp;quot;&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-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;_문제_1_2&amp;quot;&amp;gt;문제 1.2&amp;lt;/h4&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;타입인 함수를 받아들이는 chrono를 구현하세요`() &amp;amp;#8594;`&amp;lt;br&amp;gt;
입력으로 받고 실행에 걸린 시간을 반환합니다. 서명 :&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;해보고 당신의 답을 확인해 보세요와 함께https://github.com/cheroliv/cheroliv.com/blob/master/codes/src/test/kotlin/functional/BasicsHOFTests.kt[솔루션, 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;_구성&amp;quot;&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-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;_순수_함수와_테스트_가능성&amp;quot;&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-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;_예외_처리&amp;quot;&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-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;_핵심_포인트&amp;quot;&amp;gt;핵심 포인트&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;/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;/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;/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;/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;/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;/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;/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;오류 관리는 부작용의 전형적인 사례이며, Kotlin은 도구를 제공합니다. 것들을 기능적으로 관리하기 위해&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;_자바8_심화_람다_표현식_및_함수형_인터페이스&amp;quot;&amp;gt;자바8 심화: 람다 표현식 및 함수형 인터페이스&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;_범주론&amp;quot;&amp;gt;범주론&amp;lt;/h3&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;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;_함수의_기초&amp;quot;&amp;gt;함수의 기초&amp;lt;/h3&amp;gt;
&amp;lt;div class=&amp;quot;sect3&amp;quot;&amp;gt;
&amp;lt;h4 id=&amp;quot;_연습_2_1&amp;quot;&amp;gt;연습 2.1&amp;lt;/h4&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;구분된 값을 매핑하는 함수의 예를 작성할 수 있나요?&amp;lt;br&amp;gt;
도메인이 범위 내에서 중복된 값을 가지는 경우, 아래 그림과 같이 f(b)와 f(c)와 같은 ?&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;br&amp;gt;
링크를 따라가면 조언과 설명을 찾을 수 있습니다. &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;해결책&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;_연습_2_2&amp;quot;&amp;gt;연습 2.2&amp;lt;/h4&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;두 배 함수의 역함수를 작성할 수 있나요?&amp;lt;br&amp;gt;
역함수의 정의역과 치역은 무엇인가요?&amp;lt;br&amp;gt;
해결책을 보려면 챌린지 프로젝트와 부록 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-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;시도해 보세요 그리고 당신의 답을 확인해 보세요https://github.com/cheroliv/cheroliv.com/blob/master/codes/src/test/kotlin/functional/BasicsHOFTests.kt[해결책, 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>메모 디자인 시스템</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;_도구&amp;quot;&amp;gt;도구&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;삽화&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;당신이 상상하고 만들 수 있는 모든 아이디어를 위한 오픈 소스 일러스트레이션.&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;황당한 디자인&amp;lt;/a&amp;gt;&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;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;멋진 무료 일러스트레이션들. 주간 업데이트.&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;아이콘, 일러스트레이션, 사진, 음악, 디자인 도구&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;홍조.디자인&amp;lt;/a&amp;gt;&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;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;사람 일러스트를 믹스 앤 매치하고 디자인 라이브러리와 함께 사용하세요.&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;색상&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;초고속 색상 스키마 생성기!&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;경향&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;핀터레스트&amp;lt;/a&amp;gt;&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;dt class=&amp;quot;hdlist1&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;https://dribbble.com&amp;quot;&amp;gt;드리블&amp;lt;/a&amp;gt;&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 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와 디자인 시스템&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;구글 머터리얼 디자인&amp;lt;/a&amp;gt;&amp;lt;/dt&amp;gt;
&amp;lt;dd&amp;gt;
&amp;lt;p&amp;gt;통일되고 적응 가능한 사용자 인터페이스를 위한 Google의 디자인 시스템&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;Apple Human Interface Guidelines&amp;lt;/a&amp;gt;&amp;lt;/dt&amp;gt;
&amp;lt;dd&amp;gt;
&amp;lt;p&amp;gt;Apple의 iOS 및 macOS용 직관적인 애플리케이션 설계 권장 사항.&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;디자인 및 협업을 위한 프로토타입과 디자인 시스템을 생성하는 플랫폼&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;브랜드 관리 및 디자인 시스템 플랫폼으로 일관성을 보장합니다.&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;디자인 시스템 레포&amp;lt;/a&amp;gt;&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 class=&amp;quot;dlist&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;개발 프론트엔드&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;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;아이콘 라이브러리&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; class=&amp;quot;bare&amp;quot;&amp;gt;https://www.toptal.com/designers/htmlarrows/symbols/&amp;lt;/a&amp;gt;[toptal.com/designers/htmlarrows/symbols, 창 = \&amp;quot;_blank\]&amp;lt;/dt&amp;gt;
&amp;lt;dd&amp;gt;
&amp;lt;p&amp;gt;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;CSS에 필수적이며, 현대적인 프론트엔드 기사, 튜토리얼, 팁이 포함되어 있습니다.&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;프론트엔드 멘토&amp;lt;/a&amp;gt;&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;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;개발자 커뮤니티: 기사, 튜토리얼 및 프로젝트 공유&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;코드펜&amp;lt;/a&amp;gt;&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 class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;AI 지원 프론트엔드 개발&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;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는 자연어 설명을 코드와 사용자 인터페이스로 변환합니다.&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;위자드&amp;lt;/a&amp;gt;&amp;lt;/dt&amp;gt;
&amp;lt;dd&amp;gt;
&amp;lt;p&amp;gt;Uizard는 디자인 모형(이미지, 스케치)를 HTML, CSS 및 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는 자연 언어로 원하는 인터페이스를 설명할 수 있게 해 주며, IA는 편집 가능한 Figma 디자인을 생성합니다.&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는 AI를 활용하여 시각적으로 웹사이트를 구축하고 배포할 수 있도록 돕는 로우코드 프론트엔드 개발 플랫폼입니다.&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: 첫걸음</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;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;_마이크로k8s_패키지_설치&amp;quot;&amp;gt;마이크로K8s 패키지 설치:&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: 테스트가 최신 상태일 때도 테스트를 다시 실행</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>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;_출처&amp;quot;&amp;gt;출처:&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[크세노베이션] *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;_git에서_원격_저장소_변경하기&amp;quot;&amp;gt;Git에서 원격 저장소 변경하기&amp;lt;/h4&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;현재 원격 저장소 URL 표시:&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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>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/
----

대상 폴더를 지정하려면 -P 인수를 추가한 다음 경로를 제공하세요.
----&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>CLI에서 대상 폴더에 압축 풀기</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;_링크_askubuntu&amp;quot;&amp;gt;링크: &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;특정 대상 폴더로 추출하려면, 다음과 같이 사용할 수 있습니다: &amp;lt;code&amp;gt;`  $ unzip fichier.zip -d 대상_디렉토리 &amp;lt;/code&amp;gt;` 소스 디렉터리와 대상 디렉터리가 동일한 경우, 단순히 다음과 같이 할 수 있습니다: &amp;lt;code&amp;gt;` $ unzip file.zip &amp;lt;/code&amp;gt;`&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 : 명령줄에서 .deb 파일 설치하기</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;명령줄에서 .deb 파일을 통해 Debian 패키지를 설치하려면.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;여기서 보기 위해https://unix.stackexchange.com/questions/159094/how-to-install-a-deb-file-by-dpkg-i-or-by-apt[참고]+&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>그루비 스크립트, Grab 및 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;티켓의 주제는 Gradle 빌드 스크립트의 구성이다.&amp;lt;br&amp;gt;
Groovy 스크립트를 참조하지 않고 사용하고 싶어요.&amp;lt;br&amp;gt;
내가 첨부한 Gradle 프로젝트에서&amp;lt;br&amp;gt;
나는 Gradle 외부에서 이 Groovy 스크립트를 실행할 수 있기를 원한다.&amp;lt;br&amp;gt;
외부 의존성 검색을 위한 Ivy 어노테이션이 없이&amp;lt;br&amp;gt;
성능 또는 컴파일 문제를 일으키지 않는다&amp;lt;br&amp;gt;
Gradle 빌드 스크립트의.&amp;lt;br&amp;gt;
구성을 설명하기 위해 두 개의 스크립트를 사용합니다:&amp;lt;br&amp;gt;
Gradle 빌드 스크립트와 Groovy 스크립트.&amp;lt;br&amp;gt;
Groovy 스크립트는 시작 부분에서 2번째 줄에 포함됩니다,&amp;lt;br&amp;gt;
외부 종속성을 끌어오는 명령어&amp;lt;br&amp;gt;
의존성 관리자 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;build.gradle 파일에서 이제 어떻게 조화를 맞출지 봅시다&amp;lt;br&amp;gt;
두 가지 의존성 관리자, Ivy와 Gradle. 두 사이의 합의는 선에서 이루어집니다:&amp;lt;br&amp;gt;
26, 30, 37, 38 et 39&amp;lt;br&amp;gt;
그러면 나는 내 Groovy 스크립트를 수동으로 실행할 수 있다,&amp;lt;br&amp;gt;
그리고 Gradle 컴파일은 @Grab에 의해 방해받지 않습니다.&amp;lt;br&amp;gt;
Groovy 스크립트의 종속성은 Gradle에 제공할 필요가 없습니다.&amp;lt;br&amp;gt;
하지만 나는 Gradle에서 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;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;티켓 참조에https://stackoverflow.com/a/18174033/837404[스택오버플로우]&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>Groovy: 약간의 대수, 최대공약수와 최소공배수</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;위키 PPMC 또는 최소 공배수&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;결과:&amp;lt;/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 최소공배수(790, 990) = 78210&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;</description>
        </item><item>
            <title>Bash : 확장자로 파일 찾기, 결과를 파일에 저장하여 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;이름 기준으로 파일을 검색하고 있습니다: 확장자가 .h와 .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;div class=&amp;quot;title&amp;quot;&amp;gt;h 및 .cpp 확장자를 가진 파일 목록&amp;lt;br&amp;gt;&amp;lt;/div&amp;gt;
&amp;lt;p&amp;gt;결과가 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;저는 확장자가 .chm인 파일의 목록을 찾고 있습니다.&amp;lt;br&amp;gt;
그리고 나는 이 목록을 파괴한다.&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>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;설치하고 싶지 않아https://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; 명령줄에 모든 것이 존재하지만.&amp;lt;br&amp;gt;
이를 위해서는 터미널을 실행하고 postgres 사용자로 로그인해야 합니다.&amp;lt;br&amp;gt;
postgres 사용자만이 기본적으로 psql 애플리케이션을 실행할 권한이 있습니다. psql은 명령줄 클라이언트입니다. (Empty output)&amp;lt;a href=&amp;quot;https://fr.wikipedia.org/wiki/Interface_en_ligne_de_commande&amp;quot;&amp;gt;CLI&amp;lt;/a&amp;gt;) postgresql,&amp;lt;br&amp;gt;
터미널에서 명령줄을 통해 온라인 데이터베이스와 대화하기 위해&amp;lt;br&amp;gt;
연결하려면 터미널을 실행하고 이것을 입력하세요:&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;PostgreSQL 명령 프롬프트에 직접 연결하고 이동하려면 다음을 입력하세요 :&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;listingblock&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;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;PostgreSQL 인스턴스의 데이터베이스를 나열하려면 다음 명령어 :&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;결과:&amp;lt;/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;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줄)&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;q를 눌러 결과 표시에서 나가세요&amp;lt;br&amp;gt;
&amp;lt;br&amp;gt;
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;결과&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;paragraph&amp;quot;&amp;gt;
&amp;lt;p&amp;gt;공개 information_schema pg_catalog pg_toast_temp_1 pg_temp_1 pg_toast (6줄)&amp;lt;/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;결과&amp;lt;/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 대중 (6줄)&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>
