<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://www.palgle.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https://www.palgle.com/" rel="alternate" type="text/html" /><updated>2026-07-08T00:55:42+09:00</updated><id>https://www.palgle.com/feed.xml</id><title type="html">팔글 (Palgle)</title><subtitle>26년 차 테크 블로거 이삼구의 AI 기반 초고속 사업화(repl.net) 및 고퀄리티 서비스 현대화·운영 안정화(etern.co.kr) 인사이트.</subtitle><author><name>Samgu Lee</name><email>cable8mm@gmail.com</email></author><entry><title type="html">GitHub Release 기반 npm 자동 배포 구축하기 (GitHub Actions)</title><link href="https://www.palgle.com/2026/07/08/github-release-npm-auto-publish/" rel="alternate" type="text/html" title="GitHub Release 기반 npm 자동 배포 구축하기 (GitHub Actions)" /><published>2026-07-08T00:11:00+09:00</published><updated>2026-07-08T00:11:00+09:00</updated><id>https://www.palgle.com/2026/07/08/github-release-npm-auto-publish</id><content type="html" xml:base="https://www.palgle.com/2026/07/08/github-release-npm-auto-publish/"><![CDATA[<p>npm 패키지는 <code class="language-plaintext highlighter-rouge">npm publish</code> 한 번이면 배포할 수 있습니다. 하지만, Github으로 개발을 하는 환경이 사실상 표준이 되면서 Go, PHP 등 GitHub Release를 기준으로 패키지를 배포하는 프로젝트도 점점 많아지고 있습니다.</p>

<p><img src="/assets/images/npm-homepage.png" alt="npm 홈페이지" /></p>

<p>그래서 이번에는 <strong>GitHub Release만 생성하면 npm 배포까지 자동으로 완료되는 파이프라인</strong>을 구축했습니다.</p>

<p>예제는 오래전에 개발해서 React 전에 사용하던 오픈소스 프로젝트에서 실제 사용 중인 Workflow를 기준으로 설명합니다.</p>

<p><a href="https://github.com/cable8mm/jquery-infinite-with-template">https://github.com/cable8mm/jquery-infinite-with-template</a></p>

<p>구현 자체는 어렵지 않았지만, 실제로는 몇 가지 예상하지 못한 문제를 만났습니다. 이 글에서는 최종적인 워크플로우와 함께, 실제로 겪었던 트러블슈팅을 공유합니다.</p>

<h2 id="목표">목표</h2>

<p>최종적으로 만들고 싶었던 흐름은 아주 단순했습니다.</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git push
      ↓
GitHub Release 생성
      ↓
GitHub Actions 실행
      ↓
npm publish
      ↓
배포 완료
</code></pre></div></div>

<p>개발자는 Release만 생성하면 되고, npm 로그인이나 수동 배포는 더 이상 하지 않는 것이 목표였습니다.</p>

<h2 id="github-actions-구성">GitHub Actions 구성</h2>

<p>Release가 생성되면 자동으로 실행되는 Workflow를 작성했습니다. 아래 action 은 최종본이며, 실제로 제가 사용하고 있습니다.</p>

<p>NPM_TOKEN 을 만드는 법은 글 하단에 설명하겠습니다.</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">name</span><span class="pi">:</span> <span class="s">Publish to npm</span>
<span class="na">on</span><span class="pi">:</span>
  <span class="na">release</span><span class="pi">:</span>
    <span class="na">types</span><span class="pi">:</span> <span class="pi">[</span><span class="nv">created</span><span class="pi">]</span>

<span class="na">jobs</span><span class="pi">:</span>
  <span class="na">build</span><span class="pi">:</span>
    <span class="na">runs-on</span><span class="pi">:</span> <span class="s">ubuntu-latest</span>
    <span class="na">steps</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">uses</span><span class="pi">:</span> <span class="s">actions/checkout@v7</span>
      <span class="pi">-</span> <span class="na">uses</span><span class="pi">:</span> <span class="s">actions/setup-node@v6</span>
        <span class="na">with</span><span class="pi">:</span>
          <span class="na">node-version</span><span class="pi">:</span> <span class="s2">"</span><span class="s">24"</span>
          <span class="na">registry-url</span><span class="pi">:</span> <span class="s2">"</span><span class="s">https://registry.npmjs.org"</span>

      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Update version</span>
        <span class="na">run</span><span class="pi">:</span> <span class="pi">|</span>
          <span class="s">VERSION=${GITHUB_REF#refs/tags/v}</span>
          <span class="s">npm version $VERSION --no-git-tag-version</span>

      <span class="pi">-</span> <span class="na">run</span><span class="pi">:</span> <span class="s">npm ci</span>
      <span class="pi">-</span> <span class="na">run</span><span class="pi">:</span> <span class="s">npm publish</span>
        <span class="na">env</span><span class="pi">:</span>
          <span class="na">NODE_AUTH_TOKEN</span><span class="pi">:</span> <span class="s">${{ secrets.NPM_TOKEN }}</span>
</code></pre></div></div>

<p>핵심은 <code class="language-plaintext highlighter-rouge">release.created</code> 이벤트입니다.</p>

<p>GitHub Release가 생성되는 순간 Actions가 실행되고, 필요한 의존성을 설치한 뒤 npm에 패키지를 배포합니다.</p>

<h2 id="첫-번째-문제">첫 번째 문제</h2>

<p>처음 만난 오류는 바로 이것이었습니다.</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm ERR!
You cannot publish over the previously published versions.
</code></pre></div></div>

<p>처음에는 GitHub Actions 설정이 잘못된 줄 알았습니다.</p>

<p>하지만 원인은 훨씬 단순했습니다.</p>

<p>npm은 <strong>이미 배포된 버전을 절대 다시 업로드할 수 없습니다.</strong></p>

<p>그리고, npm 은 <code class="language-plaintext highlighter-rouge">package.json</code>의 <code class="language-plaintext highlighter-rouge">version</code> 값을 사용합니다.</p>

<h3 id="해결">해결</h3>

<p>Release Tag와 <code class="language-plaintext highlighter-rouge">package.json</code>의 <code class="language-plaintext highlighter-rouge">version</code>을 항상 동일하게 맞추도록 변경했습니다.</p>

<p>GitHub의 Release 값을 사용하기 위해서 아래 코드를 추가했습니다.</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Update version</span>
  <span class="na">run</span><span class="pi">:</span> <span class="pi">|</span>
    <span class="s">VERSION=${GITHUB_REF#refs/tags/v}</span>
    <span class="s">npm version $VERSION --no-git-tag-version</span>
</code></pre></div></div>

<p>이 코드는 <code class="language-plaintext highlighter-rouge">package.json</code>의 <code class="language-plaintext highlighter-rouge">version</code> 값을 Github Release 값으로 바꿉니다.</p>

<h2 id="두-번째-문제">두 번째 문제</h2>

<blockquote>
  <p>npm error code EOTP</p>
</blockquote>

<p>다음으로 만난 오류는 이것입니다.</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm ERR! code EOTP
</code></pre></div></div>

<p>로컬에서는 정상적으로 Publish가 되는데 GitHub Actions에서만 실패했습니다.</p>

<p>원인은 npm 계정에 <strong>2단계 인증(2FA)</strong> 이 활성화되어 있었기 때문입니다.</p>

<p>GitHub Actions는 당연히 OTP를 입력할 수 없습니다.</p>

<h3 id="해결-1">해결</h3>

<p>npm에서 <strong>Access Token</strong>을 생성할 때 <code class="language-plaintext highlighter-rouge">Bypass two-factor authentication (2FA)</code> 옵션을 활성화했습니다.</p>

<p><img src="/assets/images/npm-create-access-token.png" alt="npm bypass 2FA 화면" /></p>

<p>이 토큰은 CI/CD 환경에서 사용할 수 있도록 만들어진 토큰이라 GitHub Actions에서도 정상적으로 Publish가 가능합니다.</p>

<p>생성한 토큰은 GitHub Repository의</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Settings
→ Secrets and variables
→ Actions
</code></pre></div></div>

<p>에</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>NPM_TOKEN
</code></pre></div></div>

<p>이라는 이름으로 저장했습니다.</p>

<p><img src="/assets/images/github-npm-token.png" alt="GitHub의 NPM_TOKEN 설명" /></p>

<p>이후 Workflow에서는</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">env</span><span class="pi">:</span>
    <span class="na">NODE_AUTH_TOKEN</span><span class="pi">:</span> <span class="s">${{ secrets.NPM_TOKEN }}</span>
</code></pre></div></div>

<p>만 추가하면 인증이 완료됩니다.</p>

<h2 id="구축-후-달라진-점">구축 후 달라진 점</h2>

<p>자동화 전에는 배포할 때마다 다음 작업을 반복했습니다.</p>

<ul>
  <li>npm 로그인</li>
  <li>버전 확인</li>
  <li>publish 실행</li>
  <li>결과 확인</li>
</ul>

<p>지금은 훨씬 단순합니다.</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>코드 작성
↓
Release 생성
↓
배포 완료
</code></pre></div></div>

<p>개인적으로는 Homebrew, Composer, npm 모두 GitHub Release를 시작점으로 사용하는 형태로 통일했습니다. 덕분에 어떤 언어의 패키지든 동일한 배포 경험을 유지할 수 있게 되었습니다.</p>

<h2 id="release-전에-자동으로-수행되는-작업">Release 전에 자동으로 수행되는 작업</h2>

<p>배포 전에는 PR로 코드가 업데이트 되는데요, 아래 기능이 모두 자동으로 작동합니다.</p>

<ul>
  <li>테스트 코드 실행</li>
  <li>lint</li>
  <li>changelog 자동 생성</li>
  <li>GitHub Release Note 자동 작성(GitHub 기본 기능)</li>
  <li>npm과 GitHub Release 버전 자동 동기화</li>
  <li>코드의 API 명세를 GitHub Pages로 자동 배포</li>
</ul>

<p>이렇게 구성하면 Release 버튼 하나만으로 배포가 끝나는 완전한 CI/CD 파이프라인이 됩니다.</p>

<h2 id="마무리">마무리</h2>

<p>GitHub Actions를 이용한 npm 자동 배포는 생각보다 구현이 어렵지 않습니다.</p>

<p>하지만 실제로 구축해 보면 대부분 다음 두 가지에서 시간을 많이 쓰게 됩니다.</p>

<ul>
  <li>package.json 버전 관리</li>
  <li>npm 인증(2FA)</li>
</ul>

<p>이 두 가지만 미리 알고 시작해도 시행착오를 크게 줄일 수 있습니다.</p>

<p>개인적으로는 GitHub Release를 배포의 단일 진실(Single Source of Truth)로 사용하는 방식을 선호합니다.</p>

<p>태그, 릴리스 노트, npm 버전이 모두 하나의 이벤트를 기준으로 관리되기 때문에 실수를 줄일 수 있었고, 여러 오픈소스 프로젝트를 운영하는 데도 큰 도움이 되었습니다.</p>]]></content><author><name>Samgu Lee</name></author><category term="development" /><category term="github" /><category term="gitHub-actions" /><category term="npm" /><category term="cicd" /><category term="javaScript" /><category term="automation" /><summary type="html"><![CDATA[npm 패키지는 npm publish 한 번이면 배포할 수 있습니다. 하지만, Github으로 개발을 하는 환경이 사실상 표준이 되면서 Go, PHP 등 GitHub Release를 기준으로 패키지를 배포하는 프로젝트도 점점 많아지고 있습니다.]]></summary></entry><entry><title type="html">Spec Kit Extension Override 분석</title><link href="https://www.palgle.com/2026/06/29/spec-kits-extension-override-analytics/" rel="alternate" type="text/html" title="Spec Kit Extension Override 분석" /><published>2026-06-29T15:53:00+09:00</published><updated>2026-06-29T15:53:00+09:00</updated><id>https://www.palgle.com/2026/06/29/spec-kits-extension-override-analytics</id><content type="html" xml:base="https://www.palgle.com/2026/06/29/spec-kits-extension-override-analytics/"><![CDATA[<p>몇 달간 바이브 코딩이 인기를 끌고 있고, 성공 사례와 <a href="/2026/06/24/ai-pivot-after-22-commits/">실패 사례</a>가 종종 유튜브나 블로그에 소개되고 있죠. 반면 그렇게 제작된 프로젝트가 어떻게 운영되고 있는지는 거의 소개되고 있지 않습니다.</p>

<p>만약 바이브 코딩이 답이라면 이미 한국에는 많은 서비스가 론칭이 되고, 이미 존재하는 스타트업 서비스를 위협해야 하는데, 그런 소식은 잘 들리지 않습니다. 그것이 영업이나 마케팅 문제일 수도 있습니다.</p>

<p>전 바이브 코딩을 이용해서 프로젝트를 완성할 수는 있지만, 운영할 수는 없다는 사실을 알게 되었는데요, 그것은 AI가 바보라서가 아니라 Context Memory 에 한계가 있기 때문이고, 그 메모리는 미래에 가도 해결되지 않는다는 사실도 알게 되었습니다.</p>

<p>따라서, 몇가지 제약을 markdown으로 만들고, 절차를 만들어서 프로젝트 제작과 운영에 사용하고 있는데요, 그것이 바로 <a href="https://www.repl.net">REPL Works라고 하는 AI-Native 개발방법론</a>입니다.</p>

<p>그와 비슷한 시기에 Github에서는 SDD(Spec Driven Development)를 구현할 수 있는 <a href="https://github.github.io/spec-kit/">Spec Kit</a>이라는 것을 오픈소스로 출시했습니다.</p>

<p><img src="/assets/images/github-spec-kit.png" alt="Github's Spec Kit" /></p>

<blockquote>
  <p>An open source toolkit that allows you to focus on product scenarios and predictable outcomes instead of vibe coding every piece from scratch.</p>
</blockquote>

<p>바이브 코딩을 정면으로 대치하는 오픈소스 툴킷이라고 자신을 소개하고 있습니다. 그리고 현재 Github Star가 11만 6천개에 달하고 있네요.</p>

<p>1인 개발자 혹은 소규모 AI-Native 개발환경을 운영하는 곳이라면 이미 나만의 개발 워크플로가 있을텐데요, Spec Kit에 나만의 워크플로를 넣을 수 있는 강력한 기능인 <code class="language-plaintext highlighter-rouge">Spec Kit Extension</code> 에 대한 소스코드를 분석한 내용을 공유합니다.(이 내용은 아쉽게도 아직 공식 메뉴얼에는 나오지 않습니다.)</p>

<h2 id="spec-kit-extension이-내장-command-프롬프트를-대체할-수-있는가">Spec Kit Extension이 내장 Command 프롬프트를 대체할 수 있는가?</h2>

<hr />

<h1 id="1-아키텍처-개요">1. 아키텍처 개요</h1>

<p>이번 분석과 관련하여 Spec Kit은 <strong>서로 구분되는 세 가지 계층</strong>으로 구성되어 있다.</p>

<table>
  <thead>
    <tr>
      <th>계층</th>
      <th>위치</th>
      <th>관리 주체</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>Core Command Templates</strong></td>
      <td><code class="language-plaintext highlighter-rouge">templates/commands/*.md</code> (원본) → <code class="language-plaintext highlighter-rouge">.specify/templates/commands/*.md</code> (프로젝트)</td>
      <td><code class="language-plaintext highlighter-rouge">shared_infra.py</code>를 통한 <code class="language-plaintext highlighter-rouge">specify init</code></td>
    </tr>
    <tr>
      <td><strong>Agent Command Files</strong></td>
      <td><code class="language-plaintext highlighter-rouge">.claude/skills/speckit-*/SKILL.md</code>, <code class="language-plaintext highlighter-rouge">.gemini/commands/speckit.*.toml</code> 등</td>
      <td><code class="language-plaintext highlighter-rouge">integrations/base.py</code>의 <code class="language-plaintext highlighter-rouge">setup()</code> 및 <code class="language-plaintext highlighter-rouge">agents.py</code>의 <code class="language-plaintext highlighter-rouge">register_commands()</code></td>
    </tr>
    <tr>
      <td><strong>Extension Hooks</strong></td>
      <td><code class="language-plaintext highlighter-rouge">.specify/extensions.yml</code></td>
      <td><code class="language-plaintext highlighter-rouge">extensions/__init__.py</code>의 <code class="language-plaintext highlighter-rouge">HookExecutor</code></td>
    </tr>
  </tbody>
</table>

<p><code class="language-plaintext highlighter-rouge">/speckit.specify</code>, <code class="language-plaintext highlighter-rouge">/speckit.plan</code>, <code class="language-plaintext highlighter-rouge">/speckit.tasks</code>는 <strong>Python 객체가 아니다.</strong></p>

<p>이들은 디스크에 기록되는 Markdown/TOML/YAML 파일이며, AI 에이전트는 이 파일들을 직접 읽어 자신의 프롬프트로 사용한다.</p>

<hr />

<h1 id="2-command-resolution-flow">2. Command Resolution Flow</h1>

<h2 id="21-command는-어디에서-등록되는가">2.1 Command는 어디에서 등록되는가?</h2>

<p><strong>출처:</strong> <code class="language-plaintext highlighter-rouge">src/specify_cli/agents.py</code></p>

<p><code class="language-plaintext highlighter-rouge">CommandRegistrar.register_commands()</code>는 실제로 Command 파일을 디스크에 생성하는 유일한 함수이다.</p>

<p>동작 과정은 다음과 같다.</p>

<ol>
  <li><code class="language-plaintext highlighter-rouge">source_dir</code>에서 <code class="language-plaintext highlighter-rouge">.md</code> 템플릿을 읽는다.</li>
  <li>Frontmatter를 파싱한다.</li>
  <li>인자 플레이스홀더를 변환한다.
    <ul>
      <li>예: <code class="language-plaintext highlighter-rouge">$ARGUMENTS</code></li>
      <li>Gemini용: ``</li>
    </ul>
  </li>
  <li>결과를 Agent별 Command 디렉터리에 기록한다.</li>
</ol>

<p>내장 Command는 <code class="language-plaintext highlighter-rouge">specify init</code> 실행 시 다음 흐름을 통해 생성된다.</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>specify init
  → commands/init.py: register()
    → resolved_integration.setup(...)
      → IntegrationBase.setup()
        → CommandRegistrar.register_commands()
          → .claude/skills/speckit-specify/SKILL.md 생성
          → .gemini/commands/speckit.specify.toml 생성
          → ...
</code></pre></div></div>

<p>템플릿은 다음 우선순위로 로드된다.</p>

<ol>
  <li><code class="language-plaintext highlighter-rouge">core_pack/commands/</code></li>
  <li><code class="language-plaintext highlighter-rouge">templates/commands/</code></li>
</ol>

<hr />

<h2 id="22-command-이름은-어디에서-검증되는가">2.2 Command 이름은 어디에서 검증되는가?</h2>

<p><code class="language-plaintext highlighter-rouge">extensions/__init__.py</code></p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">CORE_COMMAND_NAMES</span> <span class="o">=</span> <span class="nb">frozenset</span><span class="p">({</span>
    <span class="s">'analyze'</span><span class="p">,</span>
    <span class="s">'checklist'</span><span class="p">,</span>
    <span class="s">'clarify'</span><span class="p">,</span>
    <span class="s">'constitution'</span><span class="p">,</span>
    <span class="s">'converge'</span><span class="p">,</span>
    <span class="s">'implement'</span><span class="p">,</span>
    <span class="s">'plan'</span><span class="p">,</span>
    <span class="s">'specify'</span><span class="p">,</span>
    <span class="s">'tasks'</span><span class="p">,</span>
    <span class="s">'taskstoissues'</span>
<span class="p">})</span>
</code></pre></div></div>

<p>Extension Command는 다음 정규식을 반드시 만족해야 한다.</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>speckit.&lt;extension-id&gt;.&lt;command-name&gt;
</code></pre></div></div>

<hr />

<h2 id="23-command-lookup-우선순위">2.3 Command Lookup 우선순위</h2>

<p>Python에는 런타임 Command Dispatcher가 존재하지 않는다.</p>

<p>AI Agent가 자신의 Command 디렉터리에서 파일을 직접 찾는다.</p>

<p>따라서</p>

<ul>
  <li>Override Priority</li>
  <li>Dispatch Table</li>
  <li>Runtime Lookup</li>
</ul>

<p>모두 존재하지 않는다.</p>

<p>같은 위치에 마지막으로 기록된 파일이 사용된다.</p>

<hr />

<h1 id="3-extension-시스템이-할-수-있는-것과-할-수-없는-것">3. Extension 시스템이 할 수 있는 것과 할 수 없는 것</h1>

<h2 id="31-extension-command-namespace-제약">3.1 Extension Command Namespace 제약</h2>

<p>Extension은 자신의 ID를 Namespace로 사용해야 한다.</p>

<p>예를 들어</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">id</span><span class="pi">:</span> <span class="s">replworks</span>
</code></pre></div></div>

<p>이면</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>speckit.replworks.*
</code></pre></div></div>

<p>만 사용할 수 있다.</p>

<p>다음은 허용되지 않는다.</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>speckit.specify.*
</code></pre></div></div>

<p>왜냐하면 <code class="language-plaintext highlighter-rouge">specify</code>는 이미 Core Namespace이기 때문이다.</p>

<p>예시</p>

<table>
  <thead>
    <tr>
      <th>Command</th>
      <th>가능 여부</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">speckit.replworks.specify</code></td>
      <td>✅ 가능</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">speckit.specify.anything</code></td>
      <td>❌ 불가능</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">speckit.specify</code></td>
      <td>❌ 불가능</td>
    </tr>
  </tbody>
</table>

<hr />

<h2 id="32-extension-간-command-충돌">3.2 Extension 간 Command 충돌</h2>

<p>Extension 설치 시에는</p>

<ul>
  <li>다른 Extension Command와의 충돌</li>
</ul>

<p>만 검사한다.</p>

<p>Core Command와의 충돌은 별도의 Namespace 검증 단계에서 차단된다.</p>

<hr />

<h2 id="33-hook-시스템">3.3 Hook 시스템</h2>

<p>Hook은</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.specify/extensions.yml
</code></pre></div></div>

<p>에 저장된다.</p>

<p>예를 들어</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">hooks</span><span class="pi">:</span>
  <span class="na">before_specify</span><span class="pi">:</span>
</code></pre></div></div>

<p>는</p>

<p><code class="language-plaintext highlighter-rouge">templates/commands/specify.md</code></p>

<p>안에서 AI가 직접 읽는다.</p>

<p>즉,</p>

<p>Hook은</p>

<p>Python이 실행하는 것이 아니라</p>

<p>AI Prompt 안에 자연어 지시문을 삽입하는 방식이다.</p>

<hr />

<h3 id="지원되는-hook">지원되는 Hook</h3>

<table>
  <thead>
    <tr>
      <th>Command</th>
      <th>Before</th>
      <th>After</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>specify</td>
      <td>before_specify</td>
      <td>after_specify</td>
    </tr>
    <tr>
      <td>plan</td>
      <td>before_plan</td>
      <td>after_plan</td>
    </tr>
    <tr>
      <td>tasks</td>
      <td>before_tasks</td>
      <td>after_tasks</td>
    </tr>
    <tr>
      <td>clarify</td>
      <td>before_clarify</td>
      <td>after_clarify</td>
    </tr>
    <tr>
      <td>checklist</td>
      <td>before_checklist</td>
      <td>after_checklist</td>
    </tr>
    <tr>
      <td>analyze</td>
      <td>before_analyze</td>
      <td>after_analyze</td>
    </tr>
    <tr>
      <td>converge</td>
      <td>before_converge</td>
      <td>after_converge</td>
    </tr>
    <tr>
      <td>constitution</td>
      <td>before_constitution</td>
      <td>after_constitution</td>
    </tr>
    <tr>
      <td>implement</td>
      <td>before_implement</td>
      <td>after_implement</td>
    </tr>
    <tr>
      <td>taskstoissues</td>
      <td>before_taskstoissues</td>
      <td>after_taskstoissues</td>
    </tr>
  </tbody>
</table>

<hr />

<h2 id="34-hook는-실제로-무엇을-하는가">3.4 Hook는 실제로 무엇을 하는가?</h2>

<p>예를 들어</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">hooks</span><span class="pi">:</span>
  <span class="na">before_specify</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="na">extension</span><span class="pi">:</span> <span class="s">replworks</span>
      <span class="na">command</span><span class="pi">:</span> <span class="s">speckit.replworks.preflight</span>
      <span class="na">optional</span><span class="pi">:</span> <span class="no">false</span>
      <span class="na">priority</span><span class="pi">:</span> <span class="m">5</span>
</code></pre></div></div>

<p>이면</p>

<p>AI Prompt에는</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>EXECUTE_COMMAND: speckit.replworks.preflight
</code></pre></div></div>

<p>와 같은 자연어 지시가 추가된다.</p>

<p>중요한 점은</p>

<p>Python이 이를 강제하지 않는다는 것이다.</p>

<p>AI가 이 지시를 따를지 여부는 AI Agent의 구현에 달려 있다.</p>

<hr />

<h1 id="4-prompt-loading-flow">4. Prompt Loading Flow</h1>

<h2 id="41-prompt는-markdown-파일인가">4.1 Prompt는 Markdown 파일인가?</h2>

<p>그렇다.</p>

<p>모든 Core Prompt는 Markdown 파일이다.</p>

<p>예</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>templates/commands/specify.md
templates/commands/plan.md
templates/commands/tasks.md
</code></pre></div></div>

<hr />

<h2 id="42-언제-읽히는가">4.2 언제 읽히는가?</h2>

<p><code class="language-plaintext highlighter-rouge">specify init</code></p>

<p>실행 시 읽혀서</p>

<p>Agent 전용 Command 파일로 변환된다.</p>

<p>그 이후에는</p>

<p>Python CLI는 Prompt 실행 과정에 관여하지 않는다.</p>

<p>Agent가 자신의 Command 파일을 직접 읽는다.</p>

<hr />

<h2 id="43-source-code-안에-embedded-되어-있는가">4.3 Source Code 안에 Embedded 되어 있는가?</h2>

<p>아니다.</p>

<p>Prompt는 모두 외부 Markdown 파일이다.</p>

<p>Python은 이를 읽어서 Agent별 포맷으로 변환할 뿐이다.</p>

<hr />

<h2 id="44-extension이-prompt를-교체할-수-있는가">4.4 Extension이 Prompt를 교체할 수 있는가?</h2>

<p>직접적으로는 불가능하다.</p>

<p>하지만 다음 두 방법은 가능하다.</p>

<h3 id="preset">Preset</h3>

<p>Preset은</p>

<ul>
  <li>replace</li>
  <li>prepend</li>
  <li>append</li>
  <li>wrap</li>
</ul>

<p>전략을 지원한다.</p>

<p>즉</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>speckit.specify
</code></pre></div></div>

<p>Prompt 자체를 변경할 수 있다.</p>

<hr />

<h3 id="project-override">Project Override</h3>

<p>PresetResolver는</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.specify/templates/overrides/
</code></pre></div></div>

<p>를 최우선으로 읽는다.</p>

<p>여기에 동일한 Template가 있으면 그것이 사용된다.</p>

<hr />

<h1 id="5-init-process">5. Init Process</h1>

<p><code class="language-plaintext highlighter-rouge">specify init</code>는 다음 순서로 수행된다.</p>

<ol>
  <li>Agent Command 생성</li>
  <li><code class="language-plaintext highlighter-rouge">.specify/templates</code> 복사</li>
  <li>Constitution 생성</li>
  <li>기본 Workflow 설치</li>
  <li><code class="language-plaintext highlighter-rouge">agent-context</code> Extension 자동 설치</li>
  <li><code class="language-plaintext highlighter-rouge">init-options.json</code> 생성</li>
  <li><code class="language-plaintext highlighter-rouge">--preset</code> 설치</li>
</ol>

<hr />

<h2 id="extension은-init에-참여하는가">Extension은 Init에 참여하는가?</h2>

<p>아니다.</p>

<p>예외는</p>

<ul>
  <li>agent-context</li>
  <li>–preset</li>
</ul>

<p>뿐이다.</p>

<p>Extension에는</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>on_init
</code></pre></div></div>

<p>Hook가 존재하지 않는다.</p>

<hr />

<h2 id="preset은-init에-참여하는가">Preset은 Init에 참여하는가?</h2>

<p>그렇다.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>specify init --preset replworks
</code></pre></div></div>

<p>를 실행하면</p>

<p>Preset이 Template를 수정한 후 설치된다.</p>

<hr />

<h1 id="6-extension-lifecycle">6. Extension Lifecycle</h1>

<p>Extension에는 일반적인 의미의 Lifecycle Hook가 존재하지 않는다.</p>

<p>예를 들어</p>

<ul>
  <li>on_install</li>
  <li>on_init</li>
  <li>on_command_start</li>
</ul>

<p>같은 Callback은 없다.</p>

<p>Lifecycle은 다음과 같다.</p>

<table>
  <thead>
    <tr>
      <th>단계</th>
      <th>수행 방식</th>
      <th>Python 관여</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Install</td>
      <td><code class="language-plaintext highlighter-rouge">specify extension install</code></td>
      <td>있음</td>
    </tr>
    <tr>
      <td>Command 등록</td>
      <td><code class="language-plaintext highlighter-rouge">register_commands_for_all_agents()</code></td>
      <td>있음</td>
    </tr>
    <tr>
      <td>Hook 등록</td>
      <td><code class="language-plaintext highlighter-rouge">register_hooks()</code></td>
      <td>있음</td>
    </tr>
    <tr>
      <td>실행</td>
      <td>AI가 YAML을 읽음</td>
      <td>없음</td>
    </tr>
    <tr>
      <td>제거</td>
      <td><code class="language-plaintext highlighter-rouge">specify extension remove</code></td>
      <td>있음</td>
    </tr>
  </tbody>
</table>

<p>즉</p>

<p>실제 Command 실행 시에는 Python이 전혀 개입하지 않는다.</p>

<hr />

<h1 id="7-실제-가능한-것들">7. 실제 가능한 것들</h1>

<h2 id="a-speckitspecify를-완전히-교체">A. <code class="language-plaintext highlighter-rouge">/speckit.specify</code>를 완전히 교체</h2>

<p>Extension</p>

<p>❌ 불가능</p>

<p>Preset</p>

<p>✅ 가능 (<code class="language-plaintext highlighter-rouge">strategy: replace</code>)</p>

<hr />

<h2 id="b-speckitspecify-앞에-지시문-추가">B. <code class="language-plaintext highlighter-rouge">/speckit.specify</code> 앞에 지시문 추가</h2>

<p>Preset</p>

<p>✅ 가능 (<code class="language-plaintext highlighter-rouge">prepend</code>)</p>

<p>Hook</p>

<p>⚠️ 가능하지만 AI가 Hook를 무시할 수도 있다.</p>

<hr />

<h2 id="c-speckitspecify-뒤에-지시문-추가">C. <code class="language-plaintext highlighter-rouge">/speckit.specify</code> 뒤에 지시문 추가</h2>

<p>Preset</p>

<p>✅ 가능 (<code class="language-plaintext highlighter-rouge">append</code>)</p>

<p>Hook</p>

<p>⚠️ 가능</p>

<hr />

<h2 id="d-replworksspecify-같은-새로운-command-제공">D. <code class="language-plaintext highlighter-rouge">/replworks.specify</code> 같은 새로운 Command 제공</h2>

<p>Extension</p>

<p>✅ 가능</p>

<p>예</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="s">speckit.replworks.specify</span>
</code></pre></div></div>

<hr />

<h2 id="e-specify-init-중-다른-prompt-생성">E. <code class="language-plaintext highlighter-rouge">specify init</code> 중 다른 Prompt 생성</h2>

<p>Preset</p>

<p>✅ 가능</p>

<p>Extension</p>

<p>❌ 불가능</p>

<hr />

<h2 id="f-init-시-설치되는-agent-prompt-교체">F. Init 시 설치되는 Agent Prompt 교체</h2>

<p>Preset</p>

<p>✅ 가능</p>

<p>파일 직접 수정</p>

<p>✅ 가능</p>

<p>Extension</p>

<p>❌ 불가능</p>

<hr />

<h1 id="8-repl-works가-현실적으로-override-가능한-것">8. REPL Works가 현실적으로 Override 가능한 것</h1>

<table>
  <thead>
    <tr>
      <th>목표</th>
      <th>방법</th>
      <th>신뢰도</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>specify 전에 REPL Works 실행</td>
      <td>before_specify Hook</td>
      <td>보통</td>
    </tr>
    <tr>
      <td>specify 후 REPL Works 실행</td>
      <td>after_specify Hook</td>
      <td>보통</td>
    </tr>
    <tr>
      <td>Prompt 앞에 REPL Works 추가</td>
      <td>Preset prepend</td>
      <td>높음</td>
    </tr>
    <tr>
      <td>Prompt 뒤에 REPL Works 추가</td>
      <td>Preset append</td>
      <td>높음</td>
    </tr>
    <tr>
      <td>Prompt 전체 교체</td>
      <td>Preset replace</td>
      <td>매우 높음</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">/replworks.specify</code> 제공</td>
      <td>Extension Command</td>
      <td>매우 높음</td>
    </tr>
    <tr>
      <td>Init 시 Prompt 변경</td>
      <td><code class="language-plaintext highlighter-rouge">--preset</code></td>
      <td>매우 높음</td>
    </tr>
  </tbody>
</table>

<hr />

<h1 id="9-repl-works가-override할-수-없는-것">9. REPL Works가 Override할 수 없는 것</h1>

<table>
  <thead>
    <tr>
      <th>목표</th>
      <th>이유</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">/speckit.specify</code> 이름 자체 교체</td>
      <td>Core Namespace 보호</td>
    </tr>
    <tr>
      <td>Core Command 실행 시 Python 실행</td>
      <td>Callback 시스템 없음</td>
    </tr>
    <tr>
      <td>Prompt를 Agent에 전달하기 직전 가로채기</td>
      <td>Interception Layer 없음</td>
    </tr>
    <tr>
      <td>Extension만으로 Init 중 Override</td>
      <td>on_init Hook 없음</td>
    </tr>
    <tr>
      <td>Preset 설치 없이 Prompt Override</td>
      <td>Preset은 명시적 설치 필요</td>
    </tr>
  </tbody>
</table>

<hr />

<h1 id="10-repl-works-권장-통합-전략">10. REPL Works 권장 통합 전략</h1>

<p>소스 코드를 기준으로 판단하면</p>

<p>가장 안정적인 방법은</p>

<p><strong>Extension이 아니라 Preset</strong>이다.</p>

<p>이유는 다음과 같다.</p>

<ol>
  <li>Core Prompt를 직접 변경할 수 있다.</li>
  <li><code class="language-plaintext highlighter-rouge">specify init</code> 중 설치된다.</li>
  <li>모든 AI Agent에 동일하게 적용된다.</li>
</ol>

<p>권장 구성은 두 부분으로 나뉜다.</p>

<h2 id="part-1-repl-works-preset">Part 1. REPL Works Preset</h2>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">provides</span><span class="pi">:</span>
  <span class="na">templates</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="na">type</span><span class="pi">:</span> <span class="s">command</span>
      <span class="na">name</span><span class="pi">:</span> <span class="s">speckit.specify</span>
      <span class="na">strategy</span><span class="pi">:</span> <span class="s">wrap</span>

    <span class="pi">-</span> <span class="na">type</span><span class="pi">:</span> <span class="s">command</span>
      <span class="na">name</span><span class="pi">:</span> <span class="s">speckit.plan</span>
      <span class="na">strategy</span><span class="pi">:</span> <span class="s">prepend</span>

    <span class="pi">-</span> <span class="na">type</span><span class="pi">:</span> <span class="s">command</span>
      <span class="na">name</span><span class="pi">:</span> <span class="s">speckit.tasks</span>
      <span class="na">strategy</span><span class="pi">:</span> <span class="s">append</span>
</code></pre></div></div>

<hr />

<h2 id="part-2-repl-works-extension">Part 2. REPL Works Extension</h2>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">provides</span><span class="pi">:</span>
  <span class="na">commands</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">speckit.replworks.specify</span>

<span class="na">hooks</span><span class="pi">:</span>
  <span class="na">before_specify</span><span class="pi">:</span>
    <span class="na">command</span><span class="pi">:</span> <span class="s">speckit.replworks.preflight</span>
    <span class="na">optional</span><span class="pi">:</span> <span class="no">false</span>
</code></pre></div></div>

<hr />

<p>설치 순서는 다음과 같다.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>specify init my-project <span class="nt">--integration</span> claude <span class="nt">--preset</span> replworks

specify extension <span class="nb">install</span> /path/to/replworks-extension
</code></pre></div></div>

<blockquote>
  <p><strong>중요</strong></p>

  <p>Preset은 기존 Core Command Prompt의 내용을 수정한다.</p>

  <p>Extension은 새로운 Command와 Hook를 추가한다.</p>

  <p>두 기능은 서로 경쟁하는 것이 아니라 상호 보완적이다.</p>
</blockquote>

<blockquote>
  <p><strong>주의</strong></p>

  <p>Hook는 Python Runtime이 아니라 AI Agent가 Prompt를 읽어 수행한다.</p>

  <p>AI가 Hook를 무시하면 아무 일도 일어나지 않는다.</p>

  <p>비즈니스상 반드시 실행되어야 하는 로직은 Hook가 아니라 Preset을 통해 Prompt 자체에 포함시키는 것이 바람직하다.</p>
</blockquote>

<hr />

<h1 id="evidence-index">Evidence Index</h1>

<table>
  <thead>
    <tr>
      <th>내용</th>
      <th>파일</th>
      <th>Lines</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">CORE_COMMAND_NAMES</code> 정의</td>
      <td><code class="language-plaintext highlighter-rouge">extensions/__init__.py</code></td>
      <td>L36-86</td>
    </tr>
    <tr>
      <td>Extension Namespace 검증</td>
      <td><code class="language-plaintext highlighter-rouge">extensions/__init__.py</code></td>
      <td>L706-761</td>
    </tr>
    <tr>
      <td>Extension 충돌 검사</td>
      <td><code class="language-plaintext highlighter-rouge">extensions/__init__.py</code></td>
      <td>L793-809</td>
    </tr>
    <tr>
      <td>Hook 등록</td>
      <td><code class="language-plaintext highlighter-rouge">extensions/__init__.py</code></td>
      <td>L3092-3193</td>
    </tr>
    <tr>
      <td>Hook 메시지 생성</td>
      <td><code class="language-plaintext highlighter-rouge">extensions/__init__.py</code></td>
      <td>L3362-3409</td>
    </tr>
    <tr>
      <td>AI가 Hook 읽음 (before_specify)</td>
      <td><code class="language-plaintext highlighter-rouge">templates/commands/specify.md</code></td>
      <td>L22-54</td>
    </tr>
    <tr>
      <td>AI가 Hook 읽음 (after_specify)</td>
      <td><code class="language-plaintext highlighter-rouge">templates/commands/specify.md</code></td>
      <td>L241-260</td>
    </tr>
    <tr>
      <td>Init 시 Command 등록</td>
      <td><code class="language-plaintext highlighter-rouge">commands/init.py</code></td>
      <td>L395-437</td>
    </tr>
    <tr>
      <td>Extension에 on_init 없음</td>
      <td><code class="language-plaintext highlighter-rouge">commands/init.py</code></td>
      <td>L510-539</td>
    </tr>
    <tr>
      <td>Preset 전략</td>
      <td><code class="language-plaintext highlighter-rouge">presets/__init__.py</code></td>
      <td>L116-118</td>
    </tr>
    <tr>
      <td>prepend/append/wrap/replace 구현</td>
      <td><code class="language-plaintext highlighter-rouge">presets/__init__.py</code></td>
      <td>L3239-3263</td>
    </tr>
    <tr>
      <td>PresetResolver 우선순위</td>
      <td><code class="language-plaintext highlighter-rouge">presets/__init__.py</code></td>
      <td>L2540-2748</td>
    </tr>
    <tr>
      <td>Init 시 Template 복사</td>
      <td><code class="language-plaintext highlighter-rouge">shared_infra.py</code></td>
      <td>L121-140</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">register_commands()</code></td>
      <td><code class="language-plaintext highlighter-rouge">agents.py</code></td>
      <td>L552-805</td>
    </tr>
  </tbody>
</table>]]></content><author><name>Samgu Lee</name></author><category term="ai" /><category term="development" /><category term="ai" /><category term="spec-kits" /><category term="replworks" /><category term="sdd" /><summary type="html"><![CDATA[몇 달간 바이브 코딩이 인기를 끌고 있고, 성공 사례와 실패 사례가 종종 유튜브나 블로그에 소개되고 있죠. 반면 그렇게 제작된 프로젝트가 어떻게 운영되고 있는지는 거의 소개되고 있지 않습니다.]]></summary></entry><entry><title type="html">AI에게 나도 모르는 것을 시켰을 때 망한 이야기 (ft. 7시간 만에 22커밋 찍고 피벗한 사연)</title><link href="https://www.palgle.com/2026/06/24/ai-pivot-after-22-commits/" rel="alternate" type="text/html" title="AI에게 나도 모르는 것을 시켰을 때 망한 이야기 (ft. 7시간 만에 22커밋 찍고 피벗한 사연)" /><published>2026-06-24T00:40:00+09:00</published><updated>2026-06-24T00:40:00+09:00</updated><id>https://www.palgle.com/2026/06/24/ai-pivot-after-22-commits</id><content type="html" xml:base="https://www.palgle.com/2026/06/24/ai-pivot-after-22-commits/"><![CDATA[<h2 id="1-프롤로그-인간의-욕심은-끝이-없고">1. 프롤로그: 인간의 욕심은 끝이 없고</h2>

<p>ChatGPT와 Gemini는 성향이 너무 달라서 둘을 같이 쓰게 됩니다. ChatGPT는 냉정한 I성향이라면 Gemini는 활발한 E 성향이죠.</p>

<p>ChatGPT는 비교적 정확한 정보가 필요할때, Gemini는 아이디어를 넓게 탐색하거나 조금 더 편안한 대화를 하고 싶을 때 사용합니다.</p>

<p>그래서, 전 종종 이런 식으로 작업을 합니다.</p>

<ul>
  <li>ChatGPT에게 물어보고</li>
  <li>Gemini에게 그 내용을 전달하고</li>
  <li>ChatGPT에게 물어보고</li>
  <li>Gemini에게 그 내용을 전달하고</li>
  <li>…</li>
</ul>

<p>처음엔 몇번 이런 식으로 진행이 되었을 때 재밌다라는 생각을 했는데요, 생각보다 자주 이렇게 일을 하는 저 자신을 바라보면서 이게 무슨 삽질인가… 라는 생각이 들더군요.</p>

<blockquote>
  <p>“아, ChatGPT 답변 복사해서 Gemini에 붙여넣고,
또 그 답변 복사해서 ChatGPT에 넣는 거…
이거 누가 자동화 안 해주나?”</p>
</blockquote>

<p>소프트웨어 엔지니어로서 참을 수 없는 비효율이었습니다.</p>

<p>LLM이 나온 지금 누군가는 만들었겠지 라는 기대로 오픈소스와 상용서비스 모두 뒤져봤습니다. 왜인지는 모르겠지만, 모든 솔루션들이 <code class="language-plaintext highlighter-rouge">api</code>를 사용하고 있었습니다. <code class="language-plaintext highlighter-rouge">api</code>… 전 <code class="language-plaintext highlighter-rouge">api</code>보다 채팅창에서 대화하는 것을 좋아합니다.</p>

<p>그래서 두 거대 언어 모델(LLM)이 서로 릴레이식으로 대화하며 문서를 고도화하는 <strong>두 개의 모델이 서로 질문하고 답변하고 내가 중재를 할 수 있는 도구</strong>를 만들기로 결심했습니다.</p>

<p>조건은 단 하나였습니다.</p>

<blockquote>
  <p>“사용성이 우아할 것.”</p>
</blockquote>

<p>사용자가 평소 사용하던 크롬 브라우저와 로그인 세션을 그대로 활용하면서 동작하는 앱을 상상했습니다.</p>

<h2 id="2-폭풍-같았던-7시간-그리고-22개의-커밋">2. 폭풍 같았던 7시간, 그리고 22개의 커밋</h2>

<p>전 AI 와의 개발에 있어서 <a href="https://www.repl.net">REPL Works 프레임워크</a>를 사용합니다. AI의 메모리를 문서화 시키면서 step by step 으로 개발하는 방식입니다.</p>

<p>개발에 들어가기 전에 <code class="language-plaintext highlighter-rouge">PRODUCT_SPEC.md</code>, <code class="language-plaintext highlighter-rouge">ARCHITECTURE.md</code>, <code class="language-plaintext highlighter-rouge">FRAMEWORK.md</code> 그리고 <code class="language-plaintext highlighter-rouge">TASKS.md</code> <a href="https://www.repl.net/documents/">네 개의 문서</a>를 몇시간에 걸쳐서 만들고, 개발을 시작하자마자 AI 코딩 어시스턴트(Codex)와 호흡을 맞추며 엄청난 속도로 코드를 작성했습니다.</p>

<h3 id="1--3">#1 ~ #3</h3>

<p>프로젝트 기초를 다지고 CLI 인터페이스와 미션 상태 관리 시스템을 만들었습니다.</p>

<h3 id="4--7">#4 ~ #7</h3>

<p>브라우저 자동화 레이어, 메시지 추출 엔진, 라우팅 시스템을 구현했습니다.</p>

<p>이때까지만 해도 <code class="language-plaintext highlighter-rouge">9222</code> 포트로 기존 크롬에 연결하면 모든 것이 해결될 것이라고 생각했습니다.</p>

<h3 id="8--14">#8 ~ #14</h3>

<p>중간에 사용자가 개입할 수 있는 시스템, 멀티 탭 바인딩, 지속 가능한 미션 러너까지 추가했습니다.</p>

<p>린트와 포맷 설정까지 마치고 나니 정말 완성된 제품이 탄생하는 것처럼 느껴졌습니다.</p>

<p>터미널에서 테스트 코드가 돌아가는 모습을 보며 생각했습니다.</p>

<blockquote>
  <p>“역시 AI랑 코딩하니까 생산성이 미쳤네.”</p>
</blockquote>

<p>딱 5시간째가 되기 전까지는 말입니다.</p>

<h2 id="3-그거-안-되는데요-크롬-보안-모델에-머리가-아파지다">3. “그거 안 되는데요?” 크롬 보안 모델에 머리가 아파지다</h2>

<p>문제는 15번째 커밋 근처에서 터졌습니다.</p>

<p>코드는 완벽해 보였는데 크롬이 계속 연결을 거부했습니다.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>❯ curl http://127.0.0.1:9222/json/version
curl: <span class="o">(</span>7<span class="o">)</span> Failed to connect to 127.0.0.1 port 9222 after 0 ms: Couldn<span class="s1">'t connect to server
</span></code></pre></div></div>

<p>원인을 파악해 보니 2025년 3월 <a href="https://developer.chrome.com/blog/remote-debugging-port?hl=ko">크롬은 보안 강화를 위해 기본 사용자 프로필에 대한 원격 디버깅 사용을 제한</a>하고 있었습니다.</p>

<p>이미 로그인 정보와 개인정보가 들어 있는 프로필을 외부 프로그램이 마음대로 제어하는 것을 어렵게 만든 것입니다.</p>

<p>우회하려면 별도의 빈 프로필을 만들어야 했습니다.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>google-chrome <span class="se">\</span>
<span class="nt">--remote-debugging-port</span><span class="o">=</span>9222 <span class="se">\</span>
<span class="nt">--user-data-dir</span><span class="o">=</span>~/chrome-debug-profile
</code></pre></div></div>

<p>이렇게 하면 동작은 합니다.</p>

<p>하지만 치명적인 문제가 생깁니다.</p>

<ul>
  <li>매번 터미널을 열어야 합니다.</li>
  <li>새로운 프로필에서 크롬이 실행됩니다.</li>
  <li>ChatGPT와 Gemini에 다시 로그인해야 합니다.</li>
</ul>

<p>문득 이런 생각이 들었습니다.</p>

<blockquote>
  <p>자동화를 편하게 하려고 만드는 앱인데,
왜 사용자는 매번 터미널을 열고 구글 로그인을 다시 해야 하지?</p>
</blockquote>

<p>개발자인 저부터도 사용할 의향이 없었습니다.</p>

<p>결국 저는 제가 잘 알지 못하는 영역, 즉 크롬 보안 모델을 충분히 이해하지 못한 상태에서 AI가 할 수 있다는 말만 믿고,</p>

<blockquote>
  <p>“네가 제안한대로 알아서 구현해 줘.”</p>
</blockquote>

<p>라고 맡긴 셈이었습니다. AI는 저보다 똑똑할 테니까요.</p>

<p><img src="/assets/images/ai-pivot-after-22-commits.png" alt="Codex의 속삭임" /></p>

<p>그리고 기술적인 막다른 길(Dead End)에 도달했습니다.</p>

<h2 id="4-살릴-것인가-죽일-것인가">4. 살릴 것인가, 죽일 것인가</h2>

<p>하루 동안 만든 30-40여 개의 커밋과 20여 개의 PR이 주마등처럼 스쳐 지나갔습니다. <a href="/2026/06/14/introduce-ai-issue-to-you/">ai-issue</a>를 만들 떈 이런 일이 발생하지 않았습니다.</p>

<blockquote>
  <p>“이 프로젝트는 여기서 폐기해야 하나?”</p>
</blockquote>

<p>잠시 고민했습니다. Codex는 작동은 하니 일단 써(!)라고 제안을 하더군요.</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>My recommendation:

- If you want something that works reliably today, use a dedicated Chrome profile for the relay tool.
- If you want, I can help you set that up in the least painful way, including a launch command and a cleaner README note so it feels straightforward.
</code></pre></div></div>

<p>악마의 속삭임. 하지만, 하지만 그 순간 깨달았습니다. 저부터도 절대 안 쓸 것 같았습니다. 사실 이 앱은 설치용 앱으로 패키징을 할 예정이었습니다. Codex에게 솔직하게 말했습니다.</p>

<blockquote>
  <p>After reviewing the UX, I have decided to completely discard the –remote-debugging-port=9222 approach. Forcing the user to manually launch Chrome via the terminal with specific flags and re-authenticate their ChatGPT/Gemini accounts is an unacceptable user experience.</p>

  <p>What do you think about pivoting to a <strong>Chrome Extension + Node.js (MCP) Server Hybrid Architecture</strong>?</p>
</blockquote>

<p>AI도 의외로 담담하게 인정했습니다.</p>

<blockquote>
  <p>Yes, I think that pivot is materially better for the UX you want.</p>
</blockquote>

<p>그렇게 도출한 대안이</p>

<blockquote>
  <p><strong>Chrome Extension + Node.js MCP Server</strong></p>
</blockquote>

<p>구조였습니다.</p>

<p>외부 프로그램이 크롬에 침입하려고 하니까 막히는 것이라면,</p>

<p>브라우저 내부에서 동작하는 익스텐션이 DOM을 읽고 제어하고,</p>

<p>그 익스텐션과 Node.js 프로세스를 웹소켓으로 연결하는 방식입니다.</p>

<p>이 구조라면 사용자가 별도의 브라우저 프로필을 관리하지 않아도 되고,</p>

<p>평소 사용하던 로그인 세션을 대부분 그대로 활용할 수 있습니다.</p>

<h2 id="5-에필로그-코드를-지우기-전에-문서를-고쳤다">5. 에필로그: 코드를 지우기 전에 문서를 고쳤다</h2>

<p>방향이 정해지자마자 AI는 MVP코드를 만들어 준다고 하더군요. 그 방식은 REPL Works의 방식이 아닙니다.</p>

<p><code class="language-plaintext highlighter-rouge">PRODUCT_SPEC.md</code>와 <code class="language-plaintext highlighter-rouge">ARCHITECTURE.md</code>와 <code class="language-plaintext highlighter-rouge">FRAMEWORK.md</code>와 <code class="language-plaintext highlighter-rouge">TASKS.md</code>를 먼저 새 구조에 맞게 전부 개정했습니다.</p>

<p>9222 포트와 Playwright 중심 구조를 제거하고, 새로운 Extension 기반 구조를 문서에 반영했습니다.</p>

<p>그리고 마지막 커밋을 남겼습니다.</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docs: pivot the entire codes to incredible app (#22)
</code></pre></div></div>

<p>오늘 하루 동안 저는</p>

<ul>
  <li>삽질했고</li>
  <li>실패했고</li>
  <li>아키텍처를 갈아엎었고</li>
  <li>문서를 다시 썼습니다.</li>
</ul>

<p>그리고 그 모든 일을 하루 안에 끝냈습니다.</p>

<p>AI는 매우 유능한 프로그래머이자 아키텍쳐이자 디자이너이자 기획자입니다.</p>

<p>하지만 제가 크롬의 보안 모델을 이해하지 못한 상태에서</p>

<blockquote>
  <p>“(넌 나보다 머리가 좋으니)알아서 구현해 줘.”</p>
</blockquote>

<p>라고 맡긴 순간, 저는 결과물을 검증할 능력을 잃어버렸습니다.</p>

<p>결국 문제는 AI가 아니라 저였습니다.</p>

<p>AI는 막다른 길까지는 엄청 빠르게 데려다 줍니다.</p>

<p>하지만</p>

<blockquote>
  <p>“(너가 아무리 머리가 좋아도)이 길은 안 된다.”</p>
</blockquote>

<p>라고 판단하고 방향을 바꾸는 일은 아직 사람의 몫인 것 같습니다.</p>

<p>AI 시대의 개발자는 더 이상 모든 것을 직접 구현할 필요는 없을지도 모릅니다.</p>

<p>하지만 최소한,</p>

<p><strong>“이 길은 막다른 길이다.”</strong></p>

<p>라고 판단할 수 있을 정도의 이해는 여전히 필요하다는 것을 배웠습니다.</p>]]></content><author><name>Samgu Lee</name></author><category term="ai" /><category term="development" /><category term="ai" /><category term="llm" /><category term="chrome-extension" /><category term="mcp" /><category term="codex" /><category term="development" /><summary type="html"><![CDATA[1. 프롤로그: 인간의 욕심은 끝이 없고]]></summary></entry><entry><title type="html">잘 쓰던 내 CLI 도구가 ‘멀티 조직(Multi-Org)’ 벽에 부딪혔을 때: GitHub Device Flow 전환기</title><link href="https://www.palgle.com/2026/06/22/ai-issue-multi-org-device-flow/" rel="alternate" type="text/html" title="잘 쓰던 내 CLI 도구가 ‘멀티 조직(Multi-Org)’ 벽에 부딪혔을 때: GitHub Device Flow 전환기" /><published>2026-06-22T21:50:00+09:00</published><updated>2026-06-22T21:50:00+09:00</updated><id>https://www.palgle.com/2026/06/22/ai-issue-multi-org-device-flow</id><content type="html" xml:base="https://www.palgle.com/2026/06/22/ai-issue-multi-org-device-flow/"><![CDATA[<p>제가 만든 <a href="/2026/06/14/introduce-ai-issue-to-you/">AI 기반 GitHub 이슈 생성 CLI 도구인 <strong>ai-issue</strong></a>를 로컬에서 아주 만족스럽게 사용하고 있었습니다. 터미널에서 명령어 하나로 이슈를 발행해 주는 경험이 꽤 마음에 들었습니다.</p>

<p>그런데 어느 날 예상하지 못한 장벽을 만나게 되었습니다.</p>

<p><img src="/assets/images/replworks-ai-issue-issues.png" alt="replworks/ai-issue의 이슈들" /></p>

<p>바로 <strong>다른 회사, 다른 조직(Organization)의 저장소에도 이슈를 올려야 하는 상황</strong>이 생긴 것입니다.</p>

<h2 id="1-개인-토큰pat의-한계">1. 개인 토큰(PAT)의 한계</h2>

<p>기존 <code class="language-plaintext highlighter-rouge">ai-issue</code>는 개발자의 개인 액세스 토큰(PAT)을 환경 변수나 설정 파일에 저장해 사용하는 방식이었습니다.</p>

<p>혼자 사용하는 도구라면 크게 문제 될 것이 없었습니다. 하지만 <code class="language-plaintext highlighter-rouge">replworks</code> 조직과 <code class="language-plaintext highlighter-rouge">eternops</code> 조직을 오가며 사용하기 시작하자 불편함이 생겼습니다.</p>

<ul>
  <li>조직마다 다른 PAT를 관리해야 하거나</li>
  <li>하나의 토큰에 여러 조직 권한을 몰아 넣어야 하거나</li>
  <li>토큰을 발급하고 복사해서 붙여넣는 과정 자체가 번거로웠습니다.</li>
</ul>

<p>물론 Fine-grained PAT를 사용하면 권한 범위를 제한할 수는 있습니다. 하지만 여러 조직을 오가며 사용하는 CLI 도구의 사용자 경험으로는 여전히 만족스럽지 않았습니다.</p>

<p>개발자에게 가장 큰 적은 결국 <strong>마찰(Friction)</strong> 이라고 생각합니다.</p>

<p>사용자에게 긴 문자열 형태의 토큰을 요구하는 대신, TV나 넷플릭스 로그인처럼 브라우저에서 한 번 승인하면 끝나는 경험이 더 자연스럽다고 생각했습니다.</p>

<p>그래서 인증 구조를 <strong>GitHub App 기반 인증 + Device Flow 방식</strong>으로 전환하기로 했습니다.</p>

<h2 id="2-github-app-세팅과-생각지-못한-반전">2. GitHub App 세팅과 생각지 못한 반전</h2>

<p>GitHub App을 만들면서 몇 가지 흥미로운 점을 발견했습니다.</p>

<h3 id="1-설치-범위는-any-account">1) 설치 범위는 <code class="language-plaintext highlighter-rouge">Any account</code></h3>

<p>앱 생성 과정에서 다음 항목은 반드시 <strong>Any account</strong> 로 설정해야 했습니다.</p>

<blockquote>
  <p>Where can this GitHub App be installed?</p>
</blockquote>

<p>그래야 <code class="language-plaintext highlighter-rouge">replworks</code> 뿐만 아니라 <code class="language-plaintext highlighter-rouge">eternops</code> 같은 다른 조직에서도 동일한 앱을 설치하여 사용할 수 있습니다.</p>

<h3 id="2-webhook은-필요하지-않았습니다">2) Webhook은 필요하지 않았습니다</h3>

<p><code class="language-plaintext highlighter-rouge">ai-issue</code>는 서버 애플리케이션이 아니라 로컬 터미널에서 동작하는 CLI 도구입니다.</p>

<p>GitHub 이벤트를 수신할 일이 없으므로 Webhook 기능은 비활성화하는 편이 더 깔끔했습니다.</p>

<h3 id="3-private-key는-런타임에서는-사용하지-않았습니다">3) Private Key는 런타임에서는 사용하지 않았습니다</h3>

<p>GitHub App을 생성한 뒤 설치하려고 하니 GitHub은 Private Key(<code class="language-plaintext highlighter-rouge">.pem</code>)를 생성하라고 안내했습니다.</p>

<p>이번 프로젝트에서는 Device Flow를 통해 사용자 인증만 수행하기 때문에, 실제 CLI 실행 시점에는 Private Key를 사용하지 않습니다.</p>

<p>다만 GitHub App 생성 과정에서는 최소 한 번 Private Key를 생성해야 설치 기능이 활성화되므로, 키를 생성해 다운로드한 뒤 그대로 보관만 해두었습니다.</p>

<p>결국 CLI 내부에는 Client ID만 포함하면 충분했습니다.</p>

<p>사용자는 이제 다음과 같이 로그인할 수 있습니다.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>❯ ai-issue login
Open this URL <span class="k">in </span>your browser:
https://github.com/login/device
Enter this code: FCCC-5421
Browser opened automatically.
Login successful. Token saved locally.
</code></pre></div></div>

<p><img src="/assets/images/github-device-activation.png" alt="Device Activation 화면" /></p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>❯ ai-issue diagnose
<span class="o">===</span> AI Issue Publisher Diagnostics <span class="o">===</span>
✅ Git: OK
✅ Repository validation: replworks/ai-issue
✅ Publisher validation: @replworks-bot
✅ Clipboard support: Available
✅ GitHub App token: Loaded
✅ Repository access: replworks/ai-issue

Run <span class="sb">`</span>ai-issue<span class="sb">`</span> to publish.
</code></pre></div></div>

<p>그러면 브라우저가 열리고 GitHub 인증을 한 번 수행한 뒤 여러 조직의 저장소에 자유롭게 이슈를 발행할 수 있게 됩니다.</p>

<h2 id="3-로컬-테스트-하려다-만난-go-124-의존성-지옥">3. 로컬 테스트 하려다 만난 Go 1.24 의존성 지옥</h2>

<p>인증 구조를 바꾸고 로컬에서 테스트를 진행하던 중 또 다른 문제가 나타났습니다.</p>

<p><code class="language-plaintext highlighter-rouge">golangci-lint</code>를 설치하지 않고 <code class="language-plaintext highlighter-rouge">go run</code> 방식으로 실행했더니, 깔끔하던 <code class="language-plaintext highlighter-rouge">go.mod</code> 파일에 수백 줄의 <code class="language-plaintext highlighter-rouge">// indirect</code> 의존성이 추가된 것입니다.</p>

<p>린트 도구 하나 실행했을 뿐인데 프로젝트의 가독성이 무너지는 모습은 보고 싶지 않았습니다.</p>

<p>그래서 선택한건 <code class="language-plaintext highlighter-rouge">homebrew</code> 였습니다.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 더러워진 go.mod 복구</span>
go mod tidy

<span class="c"># homebrew 로 설치</span>
brew <span class="nb">install </span>golangci-lint
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">Makefile</code>은 다음처럼 구성했습니다.</p>

<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">lint</span><span class="o">:</span> <span class="c">##</span><span class="nf"> runs golangci-lint via go run</span>
 <span class="err">golangci-lint</span> <span class="err">run</span> <span class="err">./...</span>
</code></pre></div></div>

<p>덕분에 프로젝트 의존성과 개발 도구 의존성을 깔끔하게 분리할 수 있었습니다.</p>

<h2 id="마치며">마치며</h2>

<p>이번 작업을 하면서 느낀 것은, 잘 동작하던 도구도 사용 범위가 넓어지는 순간 기존 아키텍처의 한계가 드러난다는 점이었습니다.</p>

<p>PAT 기반 인증은 단순하고 익숙하지만, 여러 조직을 오가며 사용하는 CLI 도구에서는 Device Flow가 훨씬 자연스러운 사용자 경험을 제공해 주었습니다.</p>

<p>아직 완성된 것은 아니지만, 적어도 이제는 토큰을 발급하고 복사해서 붙여넣는 과정 없이 브라우저 인증 한 번으로 여러 조직의 저장소를 다룰 수 있게 되었습니다.</p>

<p>혹시 비슷한 문제를 겪고 계신다면 GitHub App과 Device Flow 조합을 한 번 검토해 보시는 것도 괜찮을 것 같습니다.</p>

<p>p.s.</p>

<p>1인 개발 프로젝트지만, <a href="https://www.repl.net/workflow/">저는 결정, 승인 및 테스트를 하고, Discussion AI는 제안을, Execution AI는 코딩을 담당</a>하는데요, 그것을 전통적인 개발팀이 개발하는 Git 개발 프로세스 그대로 운영하고 있습니다.</p>

<p>어떻게 운영되는지는 <a href="https://github.com/replworks/ai-issue/issues?q=is%3Aissue%20state%3Aclosed">replworks/ai-issue 의 이슈메뉴</a>에서 보실 수 있습니다.</p>]]></content><author><name>Samgu Lee</name></author><category term="ai" /><category term="development" /><category term="github" /><category term="github-app" /><category term="device-flow" /><category term="golang" /><category term="cli" /><category term="dx" /><summary type="html"><![CDATA[제가 만든 AI 기반 GitHub 이슈 생성 CLI 도구인 ai-issue를 로컬에서 아주 만족스럽게 사용하고 있었습니다. 터미널에서 명령어 하나로 이슈를 발행해 주는 경험이 꽤 마음에 들었습니다.]]></summary></entry><entry><title type="html">Replworks AI Pipeline: 왜 우리는 WORKING_SPEC이라는 중간 계층을 생각하게 되었나</title><link href="https://www.palgle.com/2026/06/22/replworks-ai-pipeline-introducing-working-spec/" rel="alternate" type="text/html" title="Replworks AI Pipeline: 왜 우리는 WORKING_SPEC이라는 중간 계층을 생각하게 되었나" /><published>2026-06-22T12:25:00+09:00</published><updated>2026-06-22T12:25:00+09:00</updated><id>https://www.palgle.com/2026/06/22/replworks-ai-pipeline-introducing-working-spec</id><content type="html" xml:base="https://www.palgle.com/2026/06/22/replworks-ai-pipeline-introducing-working-spec/"><![CDATA[<p>AI로 코드를 생성하는 방식은 점점 자연스러워지고 있습니다.<br />
하지만 실제로 개발을 해보면 한 가지 반복되는 문제가 있습니다.</p>

<blockquote>
  <p>같은 요구사항을 줘도 결과 코드의 방향이 계속 달라진다.</p>
</blockquote>

<p>이 문제는 단순한 “프롬프트 문제”라기보다는, 조금 더 구조적인 문제일 가능성이 있습니다.</p>

<p><img src="/assets/images/llm-development-compiler.png" alt="LLM Development Compiler" /></p>

<h2 id="1-현재-llm-기반-개발-흐름의-구조">1. 현재 LLM 기반 개발 흐름의 구조</h2>

<p>대부분의 AI 기반 개발 흐름은 대략 다음과 같이 구성됩니다.</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>IDEAS.md
→ PRODUCT_SPEC.md
→ ARCHITECTURE.md
→ FRAMEWORK.md
→ TASKS.md
→ AI Code Generation
</code></pre></div></div>

<p>이 구조는 충분히 잘 작동합니다. 하지만 실제로 TASK 단위로 AI를 실행해보면 문제가 발생합니다.</p>

<h2 id="2-문제-매번-전체-문서를-다시-해석합니다">2. 문제: 매번 “전체 문서를 다시 해석”합니다</h2>

<p>AI는 TASK 하나를 수행할 때마다 다음 과정을 반복합니다.</p>

<ul>
  <li>PRODUCT_SPEC 전체를 다시 읽고</li>
  <li>ARCHITECTURE를 다시 해석하고</li>
  <li>FRAMEWORK 제약을 다시 적용하고</li>
  <li>TASK 정의를 다시 매핑합니다</li>
</ul>

<p>이 과정은 명시적으로 보이지 않지만 항상 발생합니다.</p>

<p>그리고 여기서 다음과 같은 문제가 생깁니다:</p>

<ul>
  <li>문서 간 중요도 충돌</li>
  <li>모델마다 다른 해석</li>
  <li>TASK마다 일관성 흔들림</li>
  <li>불필요한 컨텍스트 노이즈</li>
</ul>

<p>결과적으로 “같은 시스템인데 코드 스타일이 계속 달라지는 문제”가 발생합니다.</p>

<h2 id="3-아이디어-working_spec이라는-컴파일-결과물">3. 아이디어: WORKING_SPEC이라는 “컴파일 결과물”</h2>

<p>여기서 나온 아이디어가 하나 있습니다.</p>

<blockquote>
  <p>전체 설계를 매번 해석하지 말고,<br />
TASK 단위로 “압축된 실행 명세”를 만들자</p>
</blockquote>

<p>그 결과물이 WORKING_SPEC입니다.</p>

<h2 id="4-working_spec의-개념">4. WORKING_SPEC의 개념</h2>

<p>WORKING_SPEC은 다음과 같은 역할을 합니다.</p>

<ul>
  <li>PRODUCT_SPEC + ARCHITECTURE + FRAMEWORK + TASKS를 입력으로 받습니다</li>
  <li>현재 TASK 기준으로 중요한 것만 남깁니다</li>
  <li>나머지 정보는 제거하거나 비우선화합니다</li>
  <li>실행에 필요한 정보만 남깁니다</li>
</ul>

<p>즉:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>PRODUCT_SPEC
ARCHITECTURE
FRAMEWORK
TASKS
↓
WORKING_SPEC (compressed execution IR)
↓
AI Code Generation
</code></pre></div></div>

<hr />

<h2 id="5-working_spec은-현재-작업용-사양서입니다">5. WORKING_SPEC은 “현재 작업용 사양서”입니다</h2>

<p>WORKING_SPEC은 전체 시스템의 정의가 아닙니다.</p>

<p>오히려 반대에 가깝습니다.</p>

<blockquote>
  <p>“이번 TASK를 수행하기 위한 최소한의 실행 명세”</p>
</blockquote>

<p>예를 들면 다음과 같습니다.</p>

<p><strong>Task: VoiceSession cancellation</strong></p>

<ul>
  <li>Functional goal: interruption 시 TTS 즉시 중단</li>
  <li>Non-negotiable: state machine consistency 유지</li>
  <li>Constraint: Swift Concurrency only</li>
  <li>Constraint: Combine 사용 금지</li>
  <li>Requirement: cancellation latency &lt; 100ms</li>
  <li>Requirement: OSLog event 반드시 기록</li>
</ul>

<p>핵심은 이것입니다:</p>

<blockquote>
  <p>“AI가 지금 무엇을 중요하게 봐야 하는지”를 명시적으로 정의하는 것입니다.</p>
</blockquote>

<h2 id="6-왜-이-구조가-의미가-있을-수-있는가">6. 왜 이 구조가 의미가 있을 수 있는가</h2>

<p>이 구조의 핵심 가설은 단순합니다.</p>

<h3 id="기존-방식">기존 방식</h3>

<p>AI는 매번 전체 문서를 해석합니다.</p>

<ul>
  <li>해석 비용이 매번 발생합니다</li>
  <li>중요도 판단이 매번 달라질 수 있습니다</li>
  <li>결과 코드도 흔들릴 수 있습니다</li>
</ul>

<h3 id="working_spec-방식">WORKING_SPEC 방식</h3>

<p>해석을 구조적으로 줄이고, 실행에 집중합니다.</p>

<ul>
  <li>TASK에 맞는 압축된 컨텍스트 제공</li>
  <li>중요도 이미 정렬됨</li>
  <li>모델은 “구현”에 집중</li>
</ul>

<h2 id="7-사실-이것은-새로운-개념은-아닙니다">7. 사실 이것은 새로운 개념은 아닙니다</h2>

<p>이 아이디어는 완전히 새로운 것은 아닙니다.</p>

<p>이미 유사한 개념들이 존재합니다:</p>

<ul>
  <li>Compiler IR</li>
  <li>LLVM pass 구조</li>
  <li>Prompt chaining</li>
  <li>DSPy optimization</li>
  <li>Eval-driven prompt tuning</li>
</ul>

<p>하지만 차이점이 하나 있습니다.</p>

<blockquote>
  <p>LLM 개발 전체를 “컴파일 파이프라인”으로 보는 관점입니다</p>
</blockquote>

<h2 id="8-backend-llm-분기-가능성">8. Backend LLM 분기 가능성</h2>

<p>이 구조가 흥미로운 이유는 여기서 확장됩니다.</p>

<p>WORKING_SPEC는 모델별로 변형될 수 있습니다.</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>WORKING_SPEC.gpt.md
WORKING_SPEC.claude.md
WORKING_SPEC.gemini.md
</code></pre></div></div>

<p>각 모델은 선호하는 입력 구조가 다르기 때문에:</p>

<ul>
  <li>GPT: 간결한 constraint 중심</li>
  <li>Claude: 철학 + 구조 중심</li>
  <li>Gemini: 예시 중심</li>
</ul>

<p>같은 WORKING_SPEC이라도 backend pass를 통해 변형할 수 있습니다.</p>

<h2 id="9-결과적으로-생기는-구조">9. 결과적으로 생기는 구조</h2>

<p>이 관점에서 보면 전체 시스템은 다음과 같이 정리할 수 있습니다.</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>IDEAS.md

→ Design Layer
PRODUCT_SPEC.md
ARCHITECTURE.md
FRAMEWORK.md

→ Planning Layer
TASKS.md

→ Compilation Layer
WORKING_SPEC.md

→ Execution Layer
LLM Backend
</code></pre></div></div>

<h2 id="10-결론">10. 결론</h2>

<p>이 아이디어의 핵심은 단순합니다.</p>

<blockquote>
  <p>LLM에게 “문서 전체를 이해하라”고 요구하는 것이 아니라<br />
“지금 이 작업을 위한 최소 실행 사양만 보게 만드는 것”입니다</p>
</blockquote>

<p>WORKING_SPEC는 모델을 더 똑똑하게 만드는 것이 아닙니다.</p>

<p>대신 다음을 목표로 합니다:</p>

<ul>
  <li>해석 비용을 줄이고</li>
  <li>중요도를 미리 정렬하고</li>
  <li>실행 단위를 명확하게 만드는 것</li>
</ul>

<p>결과적으로 우리는 LLM을 단순한 코드 생성기가 아니라</p>

<blockquote>
  <p>“컴파일 타겟으로 취급할 수 있는 실행 엔진”</p>
</blockquote>

<p>으로 바라보게 됩니다.</p>

<p>p.s.</p>

<p>이 개념에 대한 후속 논의는<br />
<a href="https://github.com/replworks/replworks.github.io/issues/63">https://github.com/replworks/replworks.github.io/issues/63</a><br />
에서 이어갈 예정입니다.</p>]]></content><author><name>Samgu Lee</name></author><category term="ai" /><category term="ai" /><category term="llm" /><category term="prompt-engineering" /><category term="software-architecture" /><category term="compiler-thinking" /><category term="replworks" /><category term="working-spec" /><summary type="html"><![CDATA[AI로 코드를 생성하는 방식은 점점 자연스러워지고 있습니다. 하지만 실제로 개발을 해보면 한 가지 반복되는 문제가 있습니다.]]></summary></entry><entry><title type="html">창업자의 Source of Truth는 왜 사람마다 다를까?</title><link href="https://www.palgle.com/2026/06/20/why-is-source-of-truth-different/" rel="alternate" type="text/html" title="창업자의 Source of Truth는 왜 사람마다 다를까?" /><published>2026-06-20T16:25:00+09:00</published><updated>2026-06-20T16:25:00+09:00</updated><id>https://www.palgle.com/2026/06/20/why-is-source-of-truth-different</id><content type="html" xml:base="https://www.palgle.com/2026/06/20/why-is-source-of-truth-different/"><![CDATA[<p><strong>Source of Truth</strong>라는 말은 AI가 유일한 진실로 인지하는 단 하나의 소스를 말합니다. 그것은 사람에게도 마찬가지입니다. 수 많은 문서가 서로 다른 이야기를 할 때 어떤 문서를 봐야 하는가?</p>

<p>AI와 함께 서비스를 개발하고 운영하면서 깨달은 것이 하나 있습니다.</p>

<p>창업자에게 가장 중요한 문서는 과연 무엇일까요?</p>

<p>많은 창업 관련 서적이나 투자자들은 스타트업을 시작할 때 다양한 문서를 준비하라고 이야기합니다.</p>

<ul>
  <li>PRD</li>
  <li>BUSINESS.md</li>
  <li>METRICS.md</li>
  <li>EXECUTIVE_SUMMARY.md</li>
  <li>ROADMAP.md</li>
</ul>

<p>저 역시 처음에는 그렇게 믿었습니다.</p>

<p>문서를 촘촘하게 만들고 체계적으로 관리할수록 사업이 더 명확해지고, 불확실성도 줄어들 것이라고 생각했습니다.</p>

<p>하지만 실제로 AI를 팀원처럼 활용하여 서비스를 처음부터 개발하기 시작하면서, 예상과는 다른 현상을 경험하게 되었습니다.</p>

<p>문서가 많아질수록 오히려 개발 속도가 느려지고, 시스템 전체에는 작은 균열들이 생기기 시작했던 것입니다.</p>

<p>가장 위험했던 문제는 <strong>Conflict of Sources</strong>, 즉 서로 다른 문서들이 서로 다른 진실을 말하는 상황이었습니다.</p>

<h2 id="ai는-행간을-읽지-않습니다">AI는 행간을 읽지 않습니다</h2>

<p>예를 들어 <code class="language-plaintext highlighter-rouge">BUSINESS.md</code>에는 마진율이 30%라고 적혀 있고, <code class="language-plaintext highlighter-rouge">METRICS.md</code>에는 40%, <code class="language-plaintext highlighter-rouge">EXECUTIVE_SUMMARY.md</code>에는 35%라고 적혀 있다고 가정해 보겠습니다.</p>

<p>사람은 이런 상황을 보면 대체로 쉽게 이해합니다.</p>

<blockquote>
  <p>“아, 문서를 수정하는 과정에서 숫자가 일치하지 않게 남아 있었구나.”</p>
</blockquote>

<p>하지만 AI는 그렇게 생각하지 않습니다.</p>

<p>AI는 어떤 문서가 가장 최근에 수정되었는지 알지 못합니다.</p>

<p>어떤 문서가 더 중요하게 관리되고 있는지도 판단하지 못합니다.</p>

<p>AI에게는 세 개의 문서가 모두 동일한 무게를 가진 사실입니다.</p>

<p>그리고 어느 순간 존재하지 않는 숫자를 만들어내기 시작합니다.</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>30%

40%

35%

↓

32.5%
</code></pre></div></div>

<p>AI는 사실 관계를 스스로 판단하지 않습니다.</p>

<p>인간이 제공한 문서를 사실로 받아들일 뿐입니다.</p>

<p>그래서 AI와 함께 일하는 시대에는 문서의 양보다,</p>

<blockquote>
  <p><strong>오염되지 않은 단 하나의 Source of Truth를 정의하는 것</strong></p>
</blockquote>

<p>이 훨씬 중요해진다고 생각하게 되었습니다.</p>

<p>그런데 흥미로운 점은, 그 Source of Truth가 사람마다 완전히 다를 수 있다는 사실입니다.</p>

<h2 id="저는-왜-pitching_scriptmd만-계속-읽게-되었을까요">저는 왜 PITCHING_SCRIPT.md만 계속 읽게 되었을까요?</h2>

<p>처음에는 저도 다른 사람들처럼 <a href="https://www.repl.net/documents/">다양한 문서</a>를 만들었습니다.</p>

<p>PRD를 작성했고, <code class="language-plaintext highlighter-rouge">BUSINESS.md</code>도 만들었으며, <code class="language-plaintext highlighter-rouge">METRICS.md</code>와 <code class="language-plaintext highlighter-rouge">EXECUTIVE_SUMMARY.md</code>도 준비했습니다.</p>

<p>문서 자체는 모두 잘 작성되어 있었습니다.</p>

<p>그런데 몇 주가 지나자 이상한 일이 생겼습니다.</p>

<p>분명 많은 시간을 들여 만들었던 문서들인데, 정작 저는 거의 읽지 않게 되었습니다.</p>

<p>반대로 <code class="language-plaintext highlighter-rouge">PITCHING_SCRIPT.md</code>는 계속 읽고 있었습니다. 심지어 가장 먼저 만든 <code class="language-plaintext highlighter-rouge">IDEAS.md</code> 조차 몇일 몇주가 지나고 나서는 이해하기가 쉽지 않았습니다.</p>

<p><img src="/assets/images/2026-06-20-why-is-source-of-truth-different.png" alt="Why is source of truth different" /></p>

<p>투자자, 파트너 혹은 사업 제안 당사자에게 보여주기 위해 만든 발표 대본이었는데도, 새로운 기능을 추가하려고 할 때, 사업 방향이 흔들릴 때, 우선순위를 다시 정해야 할 때, 저는 항상 이 문서를 다시 열어보게 되었습니다.</p>

<p>왜 그럴까요?</p>

<p>돌이켜보니 저는 사업을 문서가 아니라 <strong>하나의 이야기로 이해하는 사람</strong>이었습니다.</p>

<p>제 머릿속에서 사업은 항상 다음과 같은 순서로 정리됩니다.</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>왜 이 문제가 존재하는가?
↓
왜 지금 해결할 수 있는가?
↓
왜 내가 잘할 수 있는가?
↓
어떻게 돈을 버는가?
↓
어디까지 확장할 수 있는가?
</code></pre></div></div>

<p>저는 사업을 기능 목록으로 기억하지 않았습니다.</p>

<p>숫자로 기억하지도 않았습니다.</p>

<p>하나의 이야기로 기억하고 있었습니다.</p>

<p>그래서 저에게는 <code class="language-plaintext highlighter-rouge">PITCHING_SCRIPT.md</code>가 가장 좋은 Source of Truth였던 것입니다.</p>

<h2 id="그런데-다른-창업자는-완전히-다를-수도-있습니다">그런데 다른 창업자는 완전히 다를 수도 있습니다</h2>

<p>어떤 사람들은 사업을 사용자 경험의 흐름으로 이해합니다.</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>User
↓
Pain Point
↓
Feature
↓
Metric
↓
Experiment
</code></pre></div></div>

<p>이런 분들에게는 <code class="language-plaintext highlighter-rouge">PRD</code>가 최고의 Source of Truth가 될 수 있습니다.</p>

<hr />

<p>어떤 사람들은 사업을 시스템으로 이해합니다.</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Input
↓
Process
↓
Output
↓
Failure Mode
</code></pre></div></div>

<p>이런 분들에게는 <code class="language-plaintext highlighter-rouge">ARCHITECTURE.md</code>가 더 중요할 수도 있습니다.</p>

<hr />

<p>또 어떤 사람들은 숫자로 사업을 이해합니다.</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Traffic
↓
Conversion
↓
Revenue
↓
Margin
</code></pre></div></div>

<p>이런 분들에게는 <code class="language-plaintext highlighter-rouge">METRICS.md</code>가 사업의 중심 문서가 될 가능성이 높습니다.</p>

<h2 id="중요한-것은-문서의-종류가-아닙니다">중요한 것은 문서의 종류가 아닙니다</h2>

<p>저는 이제 Source of Truth는 문서의 이름이 아니라,</p>

<blockquote>
  <p><strong>창업자의 인지 구조(Cognitive Style)에 의해 결정된다</strong></p>
</blockquote>

<p>고 생각하게 되었습니다.</p>

<p>그리고 좋은 Source of Truth는 세 가지 조건을 만족해야 합니다.</p>

<h3 id="1-창업자가-가장-자주-읽는-문서인가">1. 창업자가 가장 자주 읽는 문서인가?</h3>

<p>읽지 않는 문서는 존재하지 않는 것과 크게 다르지 않습니다.</p>

<h3 id="2-ai가-읽었을-때-모순이-없는가">2. AI가 읽었을 때 모순이 없는가?</h3>

<p>AI는 인간처럼 행간을 읽지 못합니다.</p>

<p>문서를 믿습니다.</p>

<p>따라서 모순된 문서는 결국 AI를 혼란스럽게 만들게 됩니다.</p>

<h3 id="3-6개월-후의-내가-읽어도-즉시-이해할-수-있는가">3. 6개월 후의 내가 읽어도 즉시 이해할 수 있는가?</h3>

<p>좋은 문서는 남을 설득하기 위한 문서가 아니라, 기억을 잃어버린 미래의 나를 설득하기 위한 문서라고 생각합니다.</p>

<p>AI 시대에 우리가 던져야 할 질문은,</p>

<blockquote>
  <p>“남들은 어떤 문서를 사용하는가?”</p>
</blockquote>

<p>가 아니라,</p>

<blockquote>
  <p>“나는 어떤 방식으로 세상을 이해하는 사람인가?”</p>
</blockquote>

<p>일지도 모릅니다.</p>

<p>그리고 그 질문에 대한 답이, 미래의 나와 AI가 같은 방향을 바라보게 만드는 진짜 Source of Truth를 결정한다고 생각합니다.</p>

<p>새로운 기능을 추가하려고 할 때, 여러분은 가장 먼저 어떤 문서를 열어보시나요?</p>

<ul>
  <li>Pitching Script</li>
  <li>PRD</li>
  <li>Architecture</li>
  <li>Metrics Dashboard</li>
</ul>

<p>아마 그 문서가, 여러분의 진짜 Source of Truth일 가능성이 높습니다.</p>

<p>돌이켜보면, 좋은 문서는 많이 만들어 둔 문서가 아니라, <strong>시간이 지나도 계속 다시 열어보게 되는 문서</strong>였던 것 같습니다.</p>]]></content><author><name>Samgu Lee</name></author><category term="ai" /><category term="ai" /><category term="sot" /><category term="prd" /><category term="business" /><category term="metrics" /><category term="roadmap" /><summary type="html"><![CDATA[Source of Truth라는 말은 AI가 유일한 진실로 인지하는 단 하나의 소스를 말합니다. 그것은 사람에게도 마찬가지입니다. 수 많은 문서가 서로 다른 이야기를 할 때 어떤 문서를 봐야 하는가?]]></summary></entry><entry><title type="html">AI 개발 입문자라면 Agent Framework를 공부하지 마세요</title><link href="https://www.palgle.com/2026/06/20/tell-me-successful-projects-made-by-agent-frameworks/" rel="alternate" type="text/html" title="AI 개발 입문자라면 Agent Framework를 공부하지 마세요" /><published>2026-06-20T14:55:00+09:00</published><updated>2026-06-20T14:55:00+09:00</updated><id>https://www.palgle.com/2026/06/20/tell-me-successful-projects-made-by-agent-frameworks</id><content type="html" xml:base="https://www.palgle.com/2026/06/20/tell-me-successful-projects-made-by-agent-frameworks/"><![CDATA[<h2 id="저는-2주를-쓰고-product_specmd로-돌아왔습니다">저는 2주를 쓰고 PRODUCT_SPEC.md로 돌아왔습니다</h2>

<p>AI와 개발을 시작한 지 세 달 반 정도 되었습니다. 그리고, <a href="https://github.com/replworks/ai-issue">몇개의 오픈소스를 공개</a>하고, 활용하고 있습니다.</p>

<p><img src="/assets/images/agent-frameworks-are-not-good-at-development.png" alt="Agent frameworks are not good at development" /></p>

<p>처음에는 저도 멀티 에이전트 프레임워크가 미래라고 생각했습니다. 직접 <a href="https://crewai.com/">CrewAI</a>를 사용해 보았고, <a href="https://www.langchain.com/langgraph">LangGraph</a>와 <a href="https://www.microsoft.com/en-us/research/project/autogen/">AutoGen</a>도 공부했습니다.</p>

<p>“PM Agent”, “Architect Agent”, “Developer Agent”, “QA Agent”가 서로 협업하면서 서비스를 만들어 주는 모습을 상상하면 꽤 그럴듯해 보입니다.</p>

<p>하지만 결론부터 이야기하면, 저는 개발 프로젝트에서는 아직 사용할 이유를 찾지 못했습니다.</p>

<p>물론 제가 틀릴 수도 있습니다.</p>

<p>하지만 적어도 <strong>인하우스 서비스 개발과 운영 관점에서는 그렇습니다.</strong></p>

<h2 id="운영되는-서비스는-끝나지-않습니다">운영되는 서비스는 끝나지 않습니다</h2>

<p>제가 20년 넘게 IT 업계에 있으면서 얻은 결론은 단순합니다.</p>

<blockquote>
  <p><a href="/2026/06/20/codes-are-not-assets/">운영되지 않는 코드는 자산이 아닙니다.</a></p>
</blockquote>

<p>서비스는 출시하면 끝나는 것이 아닙니다.</p>

<p>사용자 피드백이 들어오고,</p>

<p>기능이 추가되고,</p>

<p>아키텍처가 수정되고,</p>

<p>때로는 기존 기능을 삭제해야 합니다.</p>

<p>이 과정은 몇 달이 아니라 몇 년 동안 반복됩니다.</p>

<p>그래서 중요한 것은 누가 무엇을 하는지가 아니라, <strong>무엇이 바뀌었는가</strong> 입니다.</p>

<h2 id="실제-애자일-팀은-역할이-계속-바뀝니다">실제 애자일 팀은 역할이 계속 바뀝니다</h2>

<p>인하우스 서비스를 운영하는 팀에서는 역할이 고정되어 있지 않습니다.</p>

<ul>
  <li>PM이 직접 코드를 읽습니다.</li>
  <li>개발자가 기획을 수정합니다.</li>
  <li>QA가 아키텍처 변경을 제안합니다.</li>
  <li>CTO가 지표를 보고 기능 삭제를 결정합니다.</li>
</ul>

<p>하지만 대부분의 Agent Framework는 다음과 같은 구조를 전제로 합니다.</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>PM Agent
-&gt; Architect Agent
-&gt; Developer Agent
-&gt; QA Agent
</code></pre></div></div>

<p>인간 조직을 모델링하는 방식입니다.</p>

<p>하지만 운영 중인 서비스는 조직도를 따라 진화하지 않습니다.</p>

<p>운영 중인 서비스는 문제를 발견하고,</p>

<p>명세를 수정하고,</p>

<p>아키텍처를 변경하고,</p>

<p>다시 배포하는 피드백 루프로 진화합니다.</p>

<h2 id="저는-결국-문서-몇-개만-남겼습니다">저는 결국 문서 몇 개만 남겼습니다</h2>

<p>2주 정도 Agent Framework를 공부한 뒤, 저는 오히려 <a href="https://www.repl.net/workflow/">더 단순한 구조</a>로 돌아왔습니다.</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>PRODUCT_SPEC.md
↓
ARCHITECTURE.md
↓
FRAMEWORK.md
↓
TASKS.md
↓
AGENTS.md
↓
AI
↓
Code
</code></pre></div></div>

<p>아키텍처가 바뀌면 <code class="language-plaintext highlighter-rouge">ARCHITECTURE.md</code>를 수정합니다.</p>

<p>기능이 추가되면 <code class="language-plaintext highlighter-rouge">TASKS.md</code>를 수정합니다.</p>

<p>AI가 지켜야 할 규칙은 <code class="language-plaintext highlighter-rouge">AGENTS.md</code>에 정의합니다.</p>

<p>문서의 변경 이력은 Git이 관리합니다.</p>

<p>서비스를 운영하다가 문제가 발생하면 문서를 수정하는 것이 아니라 GitHub Issue를 생성합니다.</p>

<p>그리고 사람이 판단해서 다음 작업을 <code class="language-plaintext highlighter-rouge">TASKS.md</code>에 반영합니다.</p>

<p>현재까지는 이 방식이 제가 찾은 <a href="https://www.repl.net/">가장 단순하고 관리 가능한 AI 개발 방식</a>입니다.</p>

<h2 id="agent-framework가-실패한다고-생각하지는-않습니다">Agent Framework가 실패한다고 생각하지는 않습니다</h2>

<p>저는 CrewAI, LangGraph, AutoGen이 실패할 것이라고 주장하지 않습니다.</p>

<p>다만 아직까지는 개발 프로젝트를 운영하면서 계속 진화시키는 문제를 해결하기에는 너무 조직 중심적이라고 느꼈습니다.</p>

<p>그리고 AI와 개발을 막 시작한 사람이라면, CrewAI를 공부하는데 2주를 쓰기 전에, 먼저 <code class="language-plaintext highlighter-rouge">PRODUCT_SPEC.md</code> 하나를 만들고 서비스를 세상에 배포해 보는 것을 추천합니다.</p>

<p>서비스는 운영되기 시작하는 순간부터 배움을 줍니다.</p>

<p>운영되는 제품은 자산이 될 수 있습니다.</p>

<p>하지만 아직 운영되지 않는 Agent Graph는 적어도 제게는 자산처럼 느껴지지 않았습니다.</p>

<p>2주를 쓰고 얻은 개인적인 결론입니다.</p>

<p>혹시 저처럼 AI 개발을 시작하며 Agent Framework를 공부하고 계신다면, 잠시 멈추고 스스로에게 질문해 보시기 바랍니다.</p>

<blockquote>
  <p>내가 만들고 싶은 것은 AI 조직인가?</p>

  <p>아니면 운영되는 제품인가?</p>
</blockquote>

<p>p.s. 혹시 Agent Framework 를 사용해서 개발 프로젝트 온보딩과 운영을 성공적으로 하신 분이 있다면 공유해 주세요.</p>]]></content><author><name>Samgu Lee</name></author><category term="ai" /><category term="development" /><category term="ai" /><category term="CrewAI" /><category term="LangGraph" /><category term="AuthGen" /><category term="ReplWorks" /><summary type="html"><![CDATA[저는 2주를 쓰고 PRODUCT_SPEC.md로 돌아왔습니다]]></summary></entry><entry><title type="html">코드는 자산이 아니다 — 회계 장부가 말하는 AI 시대 개발자의 착각</title><link href="https://www.palgle.com/2026/06/20/codes-are-not-assets/" rel="alternate" type="text/html" title="코드는 자산이 아니다 — 회계 장부가 말하는 AI 시대 개발자의 착각" /><published>2026-06-20T05:10:00+09:00</published><updated>2026-06-20T05:10:00+09:00</updated><id>https://www.palgle.com/2026/06/20/codes-are-not-assets</id><content type="html" xml:base="https://www.palgle.com/2026/06/20/codes-are-not-assets/"><![CDATA[<p>지난 1년 동안 AI 코딩은 개발 문화를 완전히 바꾸어 놓았습니다.</p>

<p>Cursor, Claude Code, Lovable 같은 도구를 이용하면 몇 줄의 자연어만으로 웹 서비스와 모바일 앱을 만들 수 있습니다. 유튜브와 커뮤니티에는 “개발을 몰라도 앱 만드는 법”, “10분 만에 SaaS 출시하기” 같은 콘텐츠가 넘쳐나고, AI 코딩을 가르치는 강사들도 빠르게 늘어나고 있습니다.</p>

<p>기술의 발전 자체는 분명 놀랍습니다.</p>

<p>하지만 20년 넘게 IT 업계에서 여러 회사의 성장과 실패, 그리고 청산 과정을 지켜본 사람으로서 한 가지 우려가 있습니다.</p>

<p>AI를 이용해 개발하는 사람들이 많아졌지만, 아직까지 AI를 이용해 만든 서비스가 독립적인 산업을 만들거나 거대한 부가가치를 창출했다는 사례는 많지 않습니다.</p>

<p>오히려 가장 먼저 눈에 띄는 수익 모델은 AI 코딩 교육 시장인 것처럼 보입니다.</p>

<p>물론 이것이 AI 코딩이 실패했다는 뜻은 아닙니다.</p>

<p>저는 오히려 AI가 개발자와 창업자에게 주어진 가장 강력한 생산성 도구라고 생각합니다.</p>

<p>다만 많은 사람들이 한 가지 사실을 오해하고 있는 것 같습니다.</p>

<p><img src="/assets/images/code-is-not-an-asset.png" alt="Code is not an asset" /></p>

<blockquote>
  <p><strong>코드는 자산이 아닙니다.</strong></p>

  <p>정확히 말하면,</p>

  <p><strong>운영되고 있는 시스템만이 자산입니다.</strong></p>
</blockquote>

<h2 id="개발자들이-잘-모르는-회계와-세법의-현실">개발자들이 잘 모르는 회계와 세법의 현실</h2>

<p>많은 개발자들은 자신이 만든 코드가 회사의 자산이라고 생각합니다.</p>

<p>하지만 회계와 세법은 생각보다 훨씬 냉정합니다.</p>

<p>회사가 직접 개발한 ERP, 그룹웨어, 플랫폼 같은 인하우스(In-house) 시스템은 자동으로 자산이 되지 않습니다.</p>

<p>회계기준(K-IFRS)은 다음 조건을 모두 만족하는 경우에만 개발비를 무형자산으로 인식하도록 허용합니다.</p>

<ul>
  <li>기술적으로 완성 가능할 것</li>
  <li>완성 후 사용할 의도가 있을 것</li>
  <li>사용할 능력이 있을 것</li>
  <li>미래 경제적 효익이 예상될 것</li>
  <li>개발 완료에 필요한 자원을 확보할 수 있을 것</li>
  <li>개발 비용을 신뢰성 있게 측정할 수 있을 것</li>
</ul>

<p>이 조건을 만족하지 못하면 개발자의 인건비는 그해 비용으로 처리되고 손익계산서에서 사라집니다.</p>

<p>설령 무형자산으로 인식되더라도 끝이 아닙니다.</p>

<p>소프트웨어가 오픈되면 일반적으로 수년간 감가상각되며, 기대했던 경제적 효익을 창출하지 못하면 손상차손(Impairment Loss)을 인식해야 합니다.</p>

<p>즉, 장부상 수십억 원으로 기록되어 있던 개발비가 몇 년 뒤 거의 가치가 없는 자산으로 정리되는 일은 생각보다 흔합니다.</p>

<p>저는 20년 넘게 IT 업계에 있으면서 여러 시스템의 탄생과 죽음을 보았습니다.</p>

<p>수십억 원을 들여 만든 ERP와 플랫폼,</p>

<p>몇 년 동안 수십 명의 개발자가 투입된 서비스,</p>

<p>대형 SI 프로젝트의 결과물들.</p>

<p>하지만 회사가 청산되는 순간 그 코드를 사겠다는 사람은 거의 없었습니다.</p>

<p>인수자가 사는 것은 코드가 아니라,</p>

<ul>
  <li>고객</li>
  <li>계약</li>
  <li>데이터</li>
  <li>운영 프로세스</li>
  <li>현금 흐름</li>
</ul>

<p>이었습니다.</p>

<p>개발자들이 흔히 외치는</p>

<blockquote>
  <p>“코드는 자산이다”</p>
</blockquote>

<p>라는 말은 비즈니스 관점뿐 아니라 회계 장부 위에서도 상당 부분 성립하지 않습니다.</p>

<h2 id="ai-시대의-성공-사례들은-무엇을-말하고-있을까">AI 시대의 성공 사례들은 무엇을 말하고 있을까?</h2>

<p>AI를 적극 활용해 빠르게 성장한 서비스들은 이미 등장하고 있습니다.</p>

<h3 id="base44">Base44</h3>

<p>이스라엘 개발자 Maor Shlomo는 AI를 적극 활용해 Base44를 개발했습니다.</p>

<p>서비스 출시 후 약 6개월 만에 약 25만 명의 사용자를 확보했고, 2025년 6월에는 <a href="https://www.wix.com/blog/post/wix-acquires-base44">Wix가 Base44를 8천만 달러 현금으로 인수</a>했습니다.</p>

<p>흥미로운 점은 Wix가 산 것이 소스코드 파일이 아니라는 것입니다.</p>

<p>Base44는 이미 운영되고 있었고, 사용자가 존재했고, 매출이 발생하고 있었으며, 제품-시장 적합성(Product-Market Fit)을 검증한 상태였습니다.</p>

<h3 id="lovable">Lovable</h3>

<p>AI 기반 앱 빌더인 Lovable 역시 <a href="https://www.businessinsider.com/lovables-hit-400-million-arr-doubling-in-a-few-months-2026-3">매우 작은 조직으로 빠르게 성장</a>하고 있습니다.</p>

<p>Lovable의 경쟁력은 코드 생성 기술 자체가 아니라,</p>

<p>사용자들이 서비스를 만들고,</p>

<p>배포하고,</p>

<p>실패하고,</p>

<p>다시 시도하는 과정에서 축적되는 운영 데이터와 고객 경험입니다.</p>

<h3 id="ai-코딩의-진짜-가치">AI 코딩의 진짜 가치</h3>

<p>AI는 코드를 대신 작성해 줄 수 있습니다.</p>

<p>하지만 사용자의 불만을 해결해주지는 않습니다.</p>

<p>AI는 결제 시스템을 붙여줄 수는 있어도 고객이 왜 돈을 내는지는 알려주지 않습니다.</p>

<p>AI는 로그인 화면을 만들어줄 수는 있어도 시장이 원하는 제품이 무엇인지는 대신 찾아주지 않습니다.</p>

<p>AI 코딩의 진짜 가치는 코드를 더 많이 생산하는 데 있는 것이 아닙니다.</p>

<p>AI 코딩의 진짜 가치는</p>

<blockquote>
  <p><strong>시장 검증까지 걸리는 시간을 극단적으로 줄여주는 것</strong></p>
</blockquote>

<p>에 있습니다.</p>

<p>예전에는 아이디어 하나를 검증하기 위해 몇 달이 필요했다면, 지금은 며칠 안에도 운영 가능한 수준까지 도달할 수 있습니다.</p>

<h2 id="ai-시대-개발자들의-목표는-달라져야-한다">AI 시대 개발자들의 목표는 달라져야 한다</h2>

<p>서비스를 만들었다면 가능한 한 빨리 공개하십시오.</p>

<p>사용자가 한 명이라도 들어오게 하십시오.</p>

<p>누군가 실제로 돈을 내는지 확인하십시오.</p>

<p>반응이 없다면 미련 없이 버리고 다음 아이디어를 시도하십시오.</p>

<p>AI 시대의 경쟁력은 얼마나 많은 코드를 작성했는지가 아니라,</p>

<blockquote>
  <p><strong>얼마나 많은 운영 사이클을 돌렸는가</strong></p>
</blockquote>

<p>에 달려 있다고 생각합니다.</p>

<p>AI를 배우는 사람들이 많아지는 것은 좋은 일입니다.</p>

<p>하지만 1년 뒤에도 대부분의 사람들이 여전히</p>

<blockquote>
  <p>“만들어 본 프로젝트”</p>
</blockquote>

<p>만 이야기하고 있고,</p>

<p>실제로 운영하며 수익을 내는 사람은 거의 없다면 우리는 AI를 잘못 사용하고 있는 것인지도 모릅니다.</p>

<p>AI는 우리를 부자로 만들어주는 마법 지팡이가 아닙니다.</p>

<p>AI는 단지 더 빨리 만들고,</p>

<p>더 빨리 실패하고,</p>

<p>더 빨리 운영 단계에 도달하게 해주는 도구입니다.</p>

<p>그리고 결국 살아남는 것은 가장 많은 코드를 만든 사람이 아니라,</p>

<p>가장 빨리 세상에 서비스를 던지고,</p>

<p>가장 오래 운영한 사람일 것입니다.</p>

<blockquote>
  <p><strong>개발자들은 코드를 자산이라고 믿지만, 회계 장부는 대부분의 코드를 비용으로 처리합니다.</strong></p>

  <p><strong>AI 시대에 희소한 것은 코드가 아니라 운영입니다.</strong></p>
</blockquote>]]></content><author><name>Samgu Lee</name></author><category term="opinion" /><category term="ai" /><category term="바이브코딩" /><category term="tco" /><category term="codex" /><category term="claude_code" /><summary type="html"><![CDATA[지난 1년 동안 AI 코딩은 개발 문화를 완전히 바꾸어 놓았습니다.]]></summary></entry><entry><title type="html">개발자를 위한 중국 출장 가이드: 만리방화벽을 뚫는 필수 개발 환경 설정</title><link href="https://www.palgle.com/2026/06/18/china-business-trip-developer-guide/" rel="alternate" type="text/html" title="개발자를 위한 중국 출장 가이드: 만리방화벽을 뚫는 필수 개발 환경 설정" /><published>2026-06-18T16:00:00+09:00</published><updated>2026-06-18T16:00:00+09:00</updated><id>https://www.palgle.com/2026/06/18/china-business-trip-developer-guide</id><content type="html" xml:base="https://www.palgle.com/2026/06/18/china-business-trip-developer-guide/"><![CDATA[<p>개발자나 스타트업 관계자에게 중국 출장은 생각보다 거대한 도전입니다. 현지에 도착해 노트북을 켜는 순간, 구글, 슬랙(Slack) 등은 차단되며, GitHub, npm, Docker Hub는 느리거나 불안정해지는 경우가 많습니다.</p>

<p><img src="/assets/images/china-business-trip-for-developer.png" alt="개발자를 위한 중국 출장 가이드" /></p>

<p>중국 정부의 강력한 인터넷 차단 시스템인 <strong>만리방화벽(Great Firewall, GFW)</strong> 때문인데요. 현지에서도 평소처럼 막힘없이 키보드를 두드리기 위해 <strong>출국 전 반드시 설정해야 할 필수 개발 환경과 네트워크 설정</strong>을 정리했습니다.</p>

<h2 id="0-로밍-회선-하나는-반드시-준비하기">0. 로밍 회선 하나는 반드시 준비하기</h2>

<p>VPN이 모두 막히거나 설정이 꼬였을 때를 대비해, 한국 통신사의 데이터 로밍이나 중국 외 지역 경유형 eSIM을 준비해 두는 것이 좋습니다.</p>

<p>많은 경우 로밍 회선은 만리방화벽의 영향을 덜 받기 때문에 긴급 상황에서 GitHub, Slack, Google Workspace 등에 접속할 수 있는 최후의 백업 수단이 됩니다.</p>

<h2 id="1-네트워크의-핵심-중국-대응-vpn-설정">1. 네트워크의 핵심: 중국 대응 VPN 설정</h2>

<p>중국에서 일반적인 OpenVPN, WireGuard 연결은 DPI에 의해 불안정하거나 차단될 수 있습니다. 따라서 일반 유료 VPN을 그냥 켜는 것만으로는 부족합니다.</p>

<ul>
  <li><strong>출국 전 결제 및 설치 필수:</strong> 중국에 도착하면 VPN 공식 홈페이지와 앱스토어 다운로드 주소 자체가 차단됩니다. 반드시 한국에서 메인과 백업용(최소 2개 추천) VPN을 설치하고 로그인까지 끝내두세요.</li>
  <li><strong>난독화(Obfuscation) 기능 활성화:</strong> VPN 서비스가 제공하는 난독화 서버나 중국 전용 서버를 사용합니다. ExpressVPN, Astrill 등 일부 서비스는 자체 우회 기술을 제공하며 중국에서 상대적으로 안정적인 편입니다.</li>
  <li><strong>라우팅 분할(Split Tunneling) 활용:</strong> 모든 트래픽을 VPN으로 보내면 속도가 느려집니다. GitHub나 개발 관련 툴만 VPN을 타게 하고, 중국 현지 인프라나 바이두 등은 바이패스하도록 설정하면 쾌적합니다.</li>
</ul>

<h2 id="2-패키지-매니저-및-도커docker-미러-서버-변경">2. 패키지 매니저 및 도커(Docker) 미러 서버 변경</h2>

<p>VPN이 가끔 불안정하거나 끊길 때를 대비해, 패키지 다운로드 속도가 기어가지 않도록 환경 설정 파일(<code class="language-plaintext highlighter-rouge">rc</code> 파일 등)에 미리 글로벌/중국 로컬 미러 서버를 등록해 두는 것이 좋습니다.</p>

<h3 id="npm--yarn-nodejs">NPM / Yarn (Node.js)</h3>

<p>중국 내에서 가장 빠른 타오바오(Taobao) 미러 서버로 임시 변경해 둡니다.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># NPM 미러 변경</span>
npm config <span class="nb">set </span>registry https://registry.npmmirror.com

<span class="c"># Yarn 미러 변경(Yarn 버전에 따라 설정 방식이 다를 수 있음)</span>
yarn config <span class="nb">set </span>registry https://registry.npmmirror.com
</code></pre></div></div>

<h3 id="docker-hub">Docker Hub</h3>

<p>공용 Docker Hub 미러 대부분이 중단되었기 때문에, VPN/프록시를 통한 직접 접근이나 사설 레지스트리 사용을 준비하는 것이 더 현실적입니다.</p>

<h2 id="3-ide-및-깃git-프록시proxy-강제-지정">3. IDE 및 깃(Git) 프록시(Proxy) 강제 지정</h2>

<p>IDE(IntelliJ, VS Code 등) 내부에서 플러그인을 업데이트하거나, 터미널에서 <code class="language-plaintext highlighter-rouge">git clone</code>을 받을 때 VPN이 켜져 있어도 split tunnel 설정, TUN 모드 여부, 로컬 프록시 방식 등에 따라 달라집니다. 로컬 프록시 방식 VPN을 사용하는 경우에는 Git이나 IDE가 프록시를 사용하지 않아 우회가 되지 않을 수 있습니다.</p>

<h3 id="git-글로벌-프록시-설정-로컬-vpn-포트-기준">Git 글로벌 프록시 설정 (로컬 VPN 포트 기준)</h3>

<p>유료 VPN들이 로컬에 열어두는 프록시 포트(보통 고유 설정에서 확인 가능, 예: 1080 또는 7890 등)를 Git에 강제로 지정합니다.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># HTTP/HTTPS 프록시 적용</span>
git config <span class="nt">--global</span> http.proxy http://127.0.0.1:포트번호
git config <span class="nt">--global</span> https.proxy http://127.0.0.1:포트번호

<span class="c"># 출장 복귀 후 해제할 때</span>
git config <span class="nt">--global</span> <span class="nt">--unset</span> http.proxy
git config <span class="nt">--global</span> <span class="nt">--unset</span> https.proxy
</code></pre></div></div>

<h3 id="ide-내부-proxy-설정">IDE 내부 Proxy 설정</h3>

<ul>
  <li><strong>IntelliJ / WebStorm:</strong> <code class="language-plaintext highlighter-rouge">Settings -&gt; Appearance &amp; Behavior -&gt; System Settings -&gt; HTTP Proxy</code> 이동 후 <code class="language-plaintext highlighter-rouge">Manual proxy configuration</code>에서 로컬 VPN 호스트와 포트를 입력해 둡니다.</li>
  <li><strong>VS Code:</strong> 설정에서 <code class="language-plaintext highlighter-rouge">Http: Proxy</code> 항목에 로컬 프록시 주소(예: <code class="language-plaintext highlighter-rouge">http://127.0.0.1:7890</code>)를 지정하면 Copilot 등의 확장 프로그램이 보다 안정적으로 동작합니다.</li>
</ul>

<h2 id="4-2차-인증mfa-수단-오프라인-백업">4. 2차 인증(MFA) 수단 오프라인 백업</h2>

<p>의외로 많은 개발자가 놓쳐서 멘붕이 오는 지점입니다. GitHub나 사내 시스템 로그인 시 2중 인증 등을 사용하실 텐데요.</p>

<ul>
  <li>중국에서 유심을 갈아끼우거나 로밍 처리를 하다가 기기 인증이 풀리는 경우가 있습니다.</li>
  <li>만약 SMS 인증으로만 2차 인증을 해두었다면, 중국 현지에서 한국 문자가 제때 수신되지 않아 아예 계정이 잠길 수 있습니다.</li>
  <li><strong>대책:</strong> 출국 전, GitHub, AWS, Google Workspace, 사내 SSO 등의 MFA 복구 코드를 미리 백업해 두세요.</li>
</ul>

<h2 id="-출장-출발-전-최종-체크리스트">🚀 출장 출발 전 최종 체크리스트</h2>

<ul class="task-list">
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />노트북 및 스마트폰에 유료 VPN 2개 이상 설치 완료 (난독화 서버 구동 확인)</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />해외 로밍 또는 중국 외 지역(홍콩·대만 등) 경유형 데이터 eSIM 준비 완료</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />GitHub, AWS, 사내 인프라 계정의 ‘오프라인 복구 코드’ 백업 완료</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />프로젝트 의존성 설치 및 초기 빌드가 정상 동작하는지 한국에서 미리 검증</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />GitHub Copilot, Claude Code, Cursor 등의 AI 개발 도구가 VPN 환경에서 정상 동작하는지 사전 확인</li>
</ul>

<p>만리방화벽은 생각보다 촘촘하지만, 사전 준비를 잘 해두면 대부분의 개발 업무를 큰 문제 없이 이어갈 수 있습니다. 미리 설정하셔서 현지에서 당황하는 일 없으시길 바랍니다!</p>]]></content><author><name>Samgu Lee</name></author><category term="development" /><category term="중국출장," /><category term="개발환경," /><category term="IDE," /><category term="VPN," /><category term="개발자," /><category term="만리방화벽" /><summary type="html"><![CDATA[개발자나 스타트업 관계자에게 중국 출장은 생각보다 거대한 도전입니다. 현지에 도착해 노트북을 켜는 순간, 구글, 슬랙(Slack) 등은 차단되며, GitHub, npm, Docker Hub는 느리거나 불안정해지는 경우가 많습니다.]]></summary></entry><entry><title type="html">중국에서 구글 접속하는 방법 (eSIM · VPN · 로밍 비교)</title><link href="https://www.palgle.com/2026/06/18/truth-of-china-google-url/" rel="alternate" type="text/html" title="중국에서 구글 접속하는 방법 (eSIM · VPN · 로밍 비교)" /><published>2026-06-18T15:40:00+09:00</published><updated>2026-06-18T15:40:00+09:00</updated><id>https://www.palgle.com/2026/06/18/truth-of-china-google-url</id><content type="html" xml:base="https://www.palgle.com/2026/06/18/truth-of-china-google-url/"><![CDATA[<p>중국 출장이나 여행을 앞두고 계신 분들, 혹은 현지에서 갑자기 구글 검색이 막혀 당황하신 분들이 가장 많이 검색하는 문장이 있습니다.</p>

<blockquote>
  <p><em>“중국 구글 주소가 따로 있나요? <code class="language-plaintext highlighter-rouge">google.cn</code>으로 들어가면 되나요?”</em></p>
</blockquote>

<h2 id="중국에서-막히는-서비스는-무엇일까">중국에서 막히는 서비스는 무엇일까?</h2>

<p>중국 본토에서는 다음과 같은 해외 서비스가 제한되는 경우가 많습니다.</p>

<ul>
  <li>Google</li>
  <li>Gmail</li>
  <li>YouTube</li>
  <li>Google Maps</li>
  <li>Facebook</li>
  <li>Instagram</li>
  <li>X(Twitter)</li>
  <li>WhatsApp</li>
</ul>

<p>결론부터 말씀드리면, <strong>단순히 주소(URL)만 입력해서 들어갈 수 있는 ‘우회용 중국 구글 주소’는 존재하지 않습니다.</strong> <a href="/2006/02/21/availble-connect-google-cn/">과거 구글의 흔적이 남은 주소</a>는 있지만, 실제 검색을 하려면 주소가 아니라 <strong>접속하는 망(IP)</strong>을 바꿔야 합니다.</p>

<p>오늘은 중국 구글 주소에 얽힌 흥미로운 진실과 함께, 현지에서 구글 및 필수 서비스(유튜브, 카톡 등)를 이용할 수 있는 현실적인 가이드를 정리해 드립니다.</p>

<h2 id="1-중국-구글-주소-googlecn의-진실">1. 중국 구글 주소 <code class="language-plaintext highlighter-rouge">google.cn</code>의 진실</h2>

<p>많은 분들이 궁금해하시는 <strong><code class="language-plaintext highlighter-rouge">google.cn</code></strong>은 실제로 구글이 소유한 중국 공식 도메인이 맞습니다. 하지만 지금 이 주소로 접속하면 다음과 같은 현상이 일어납니다.</p>

<ul>
  <li><strong>홍콩 구글로 자동 전환:</strong> 과거에는 <code class="language-plaintext highlighter-rouge">google.cn</code>을 입력하면 검색 기능이 살아있는 홍콩 구글 주소(<code class="language-plaintext highlighter-rouge">google.com.hk</code>)로 리다이렉트되었습니다.(과거 2010년 구글이 중국 정부의 검열 정책에 반대하며 본토 검색 서비스를 철수했기 때문입니다.)</li>
  <li><strong>결국은 차단:</strong> 우회 프로그램을 쓰지 않은 중국 본토 안에서는 이 홍콩 구글 주소마저 만리방화벽(Great Firewall)에 막혀 “사이트에 연결할 수 없음” 메시지만 뜨게 됩니다.</li>
  <li><strong>현재의 용도:</strong> 지금은 리다이렉트는 중단되고 링크만 살아있는 랜딩페이지가 노출되고 있습니다.</li>
</ul>

<p><img src="/assets/images/google-cn-homepage.png" alt="google.cn 홈페이지" /></p>

<h2 id="2-중국에서-구글을-뚫는-가장-확실한-방법-3가지">2. 중국에서 구글을 뚫는 가장 확실한 방법 3가지</h2>

<p>주소 입력만으로는 해결할 수 없기 때문에, 우리는 만리방화벽을 우회하는 기술을 써야 합니다. 실무 환경과 체류 기간에 따라 가장 알맞은 방법을 선택하세요.</p>

<h3 id="방법-a-해외-esim이심-사용-단기-출장여행-강추">방법 A. 해외 eSIM(이심) 사용 (단기 출장/여행 강추)</h3>

<p>인터넷에서 ‘중국 전용 데이터 eSIM’을 구매해 스마트폰에 등록하는 방법입니다.</p>

<ul>
  <li><strong>원리:</strong> 중국 현지 통신망이 아닌, 홍콩·대만·한국 등 해외 망을 경유하여 로밍 형태로 데이터를 처리합니다. 만리방화벽의 검열 대상에서 아예 제외되기 때문에 별도의 VPN 앱을 켤 필요 없이 기존 구글 주소(<code class="language-plaintext highlighter-rouge">google.com</code>)로 즉시 접속됩니다.</li>
  <li><strong>장점:</strong> 속도가 가장 안정적이고 배터리 소모가 적습니다.</li>
  <li><strong>주의:</strong> VPN이나 기업 전용 회선 혹은 일부 국제 회선이 아닌 일반적인 상황에서 호텔이나 카페의 현지 와이파이(Wi-Fi)에 연결하는 순간 다시 구글이 막히므로, 현지에서는 와이파이를 끄고 모바일 데이터만 사용해야 합니다.</li>
</ul>

<h3 id="방법-b-난독화-기능이-포함된-유료-vpn-장기-체류-필수">방법 B. 난독화 기능이 포함된 유료 VPN (장기 체류 필수)</h3>

<p>노트북 작업을 해야 하거나 현지 와이파이를 무제한으로 써야 한다면 중국 대응 기능(Obfuscation, Stealth Protocol 등)을 제공하는 유료 VPN이 필수입니다.</p>

<ul>
  <li><strong>핵심 팁:</strong> 중국의 인터넷 검열 시스템은 일반적인 VPN 트래픽도 탐지하여 차단하는 경우가 많습니다. 따라서 단순 연결이 아닌, 앱 설정에서 <strong>‘난독화(Obfuscation) 프로토콜’</strong> 옵션을 반드시 활성화해야 검열 시스템을 속이고 구글에 접속할 수 있습니다.</li>
  <li><strong>⚠️ 출국 전 필수 사항:</strong> <strong>반드시 한국에서 미리 앱을 다운로드하고 결제까지 완료</strong>해야 합니다. 중국 공항에 내리는 순간 VPN 공급업체의 홈페이지 자체가 차단되어 앱 설치가 불가능해집니다.</li>
</ul>

<h3 id="방법-c-국내-통신사-로밍-요금제">방법 C. 국내 통신사 로밍 요금제</h3>

<p>기존에 사용하던 스마트폰 통신사(SKT, KT, LGU+)의 로밍 서비스를 신청하는 방법입니다.</p>

<ul>
  <li><strong>원리:</strong> eSIM과 마찬가지로 국내 통신망을 거쳐 나가므로 구글, 유튜브, 카카오톡 모두 아무런 제약 없이 작동합니다.</li>
  <li><strong>장점:</strong> 한국에서 오는 전화나 문자(본인 인증 등)를 그대로 수신해야 하는 비즈니스 업무 시 가장 안전합니다.</li>
</ul>

<h2 id="-요약-가이드">🚀 요약 가이드</h2>

<p>중국에서 구글을 사용하려면 특별한 URL을 찾는 것이 아니라, 중국 검열 시스템(만리방화벽)의 영향을 받지 않는 인터넷 경로를 확보해야 합니다. 실제로 여행자와 출장자들은 국제 로밍, 해외 eSIM, 또는 중국 대응 기능이 포함된 VPN을 이용하는 경우가 많습니다.</p>

<ol>
  <li><strong>“비밀 주소는 없다”:</strong> <code class="language-plaintext highlighter-rouge">google.cn</code>이든 뭐든 주소만으로는 안 됩니다. 일반 <code class="language-plaintext highlighter-rouge">google.com</code> 주소를 쓰되 망을 우회해야 합니다.</li>
  <li><strong>3박 4일 내외 가벼운 일정:</strong> 복잡한 설정 없이 뇌 비우고 쓸 수 있는 <strong>해외 데이터 eSIM</strong>이 가성비와 정신 건강에 가장 좋습니다.</li>
  <li><strong>노트북 작업이 많은 개발자/스타트업:</strong> 출국 전 성능 좋은 <strong>유료 VPN을 미리 세팅</strong>하고 현지 와이파이와 조합하여 사용하세요.</li>
</ol>]]></content><author><name>Samgu Lee</name></author><category term="google" /><category term="중국구글," /><category term="중국여행," /><category term="중국출장," /><category term="VPN," /><category term="eSIM," /><category term="만리방화벽," /><category term="Great" /><category term="Firewall" /><summary type="html"><![CDATA[중국 출장이나 여행을 앞두고 계신 분들, 혹은 현지에서 갑자기 구글 검색이 막혀 당황하신 분들이 가장 많이 검색하는 문장이 있습니다.]]></summary></entry></feed>