<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:media="http://search.yahoo.com/mrss/">
  <channel>
    <title>Coding with AI Blog</title>
    <link>https://blog.funq.kr/en</link>
    <description>Practical guides to AI-assisted coding, LLMs, automation, and full-stack development.</description>
    <language>en</language>
    <lastBuildDate>Fri, 25 Sep 2026 15:20:56 GMT</lastBuildDate>
    <atom:link href="https://blog.funq.kr/en/feed.xml" rel="self" type="application/rss+xml"/>
    
    <item>
      <title><![CDATA[Sharing Claude Code and Codex Accounts to Use Spare Capacity]]></title>
      <link>https://blog.funq.kr/en/blogs/claude-codex-accounts-20260923-v01</link>
      <guid isPermaLink="true">https://blog.funq.kr/en/blogs/claude-codex-accounts-20260923-v01</guid>
      <description><![CDATA[How multiple users share spare capacity across Claude Code or Codex accounts, with account isolation, credential rotation, and session restarts.]]></description>
      <category><![CDATA[ai]]></category><category><![CDATA[claude-code]]></category><category><![CDATA[codex]]></category><category><![CDATA[account-sharing]]></category><category><![CDATA[authentication]]></category>
      <media:content url="https://api.funq.kr/blog/images/cccdc75f-3795-4094-a474-63b51382e72c.png" medium="image" type="image/png"/>
      <pubDate>Fri, 25 Sep 2026 15:12:57 GMT</pubDate>
      <author>funqdev@gmail.com (fundev)</author>
      <content:encoded><![CDATA[<style>
.post-body .cc-table { max-width:100%; overflow-x:auto; margin:1.6rem 0; border:1px solid #ddd4e7; border-radius:.7rem; }
.post-body .cc-table table { width:100%; min-width:35rem; border-collapse:collapse; margin:0; font-size:.9em; }
.post-body .cc-table th,.post-body .cc-table td { padding:.85rem 1rem; text-align:left; vertical-align:top; border-bottom:1px solid #e9e2ef; line-height:1.75; }
.post-body .cc-table th { background:#f5eefb; color:#4e1574; font-weight:700; }
.post-body .cc-table tr:last-child td { border-bottom:0; }
.post-body pre.code-block { max-width:100%; overflow-x:auto; background:#24292e; color:#e1e4e8; padding:1.2rem; border-radius:.7rem; line-height:1.8; font-size:.85em; margin:1.75rem 0; }
.post-body pre.code-block code { color:inherit; background:transparent; padding:0; white-space:pre; font-size:inherit; }
.post-body .cc-scope-note { margin-top:3rem; border-left:.2rem solid #7B00D3; padding:1rem 1.2rem; background:#f8f4fc; color:#5d4e68; font-size:.87em; }
.post-body .cc-scope-note em { font-style:normal; }
.post-body .cc-figure { margin:2rem 0; }
.post-body .cc-diagram { max-width:100%; overflow-x:auto; border:1px solid #ddd4e7; border-radius:.7rem; background:#fff; }
.post-body .cc-diagram img { display:block; width:100%; min-width:48rem; height:auto; margin:0; }
.post-body .cc-diagram .cc-diagram-dark { display:none; }
.post-body .cc-figure figcaption { margin-top:.75rem; color:#5d4e68; font-size:.85em; line-height:1.7; }
.dark .post-body .cc-table { border-color:#4d4737; }
.dark .post-body .cc-table th { background:#302a19; color:#ffdb4d; }
.dark .post-body .cc-table td { border-color:#423e32; }
.dark .post-body .cc-scope-note { border-color:#ffdb4d; background:#28251d; color:#dbd2bd; }
.dark .post-body .cc-diagram { border-color:#423e32; background:#020617; }
.dark .post-body .cc-diagram .cc-diagram-light { display:none; }
.dark .post-body .cc-diagram .cc-diagram-dark { display:block; }
.dark .post-body .cc-figure figcaption { color:#dbd2bd; }
</style>

<p lang="en">This post assumes <strong>several accounts are already available for Claude Code or Codex, and multiple people want to share those accounts' unused capacity</strong>. One person might manage several accounts, or several people might contribute accounts to a shared pool. Each user does not need to own multiple accounts. The pool they can use needs to contain more than one.</p>
<p lang="en">For example, suppose three people share Claude Code accounts A, B, and C. One person has nearly exhausted A's allowance during a long task, while B and C still have capacity. Instead of checking each account and manually changing logins every time, a server selects an account with room left and assigns it to the person who needs it.</p>
<p lang="en">Claude Code needs a pool of Claude Code accounts, and Codex needs a pool of Codex accounts. If you use only one tool, you only need accounts for that tool. A Claude Code account and a Codex account cannot exchange their unused allowances.</p>
<p lang="en">Each account's usage limits still apply. People assigned the same account share that account's allowance. Connecting one account to several computers does not increase its limit, and once every account in the pool has reached its limit, there is no spare capacity left to assign. The purpose of this setup is to make use of unused capacity across accounts that are already available.</p>
<p lang="en">A server that registers the accounts, checks their status, and assigns credentials must also be in place. I call this the <strong>credential server</strong> throughout this post. Users receive credentials for the account it selects and run the tool on their own computers. As usage accumulates, the server prepares credentials for another account with capacity available.</p>
<p lang="en">The rest of this post assumes that credential server already exists and explains how to connect shared accounts on a personal computer. I normally work with my own subscription account and switch to a shared account when needed. The setup keeps the two accounts' logins and histories separate, passes credentials only to the process that needs them, and prepares credentials for the next launch. I also cover how to apply a different account to a session that is already running.</p>
<h2 lang="en" id="두-도구는-인증정보를-어디서-읽나">Where the two tools read credentials</h2>
<p lang="en">At startup, Claude Code checks several credential sources in a defined order. For the sources relevant to this setup, shell variables such as <code>ANTHROPIC_AUTH_TOKEN</code> or <code>ANTHROPIC_API_KEY</code> take precedence. Next comes <code>CLAUDE_CODE_OAUTH_TOKEN</code>, which holds a subscription token, followed by the login saved through <code>/login</code>. I pass the shared account's token through <code>CLAUDE_CODE_OAUTH_TOKEN</code>. If the first two variables remain in the shell, they take precedence over it, so I clear them. <a href="https://code.claude.com/docs/en/authentication" rel="noopener noreferrer">Claude Code authentication documentation</a></p>
<p lang="en">Logins are stored separately for each configuration directory. The default is <code>~/.claude</code>, and <code>CLAUDE_CONFIG_DIR</code> changes it. On macOS, login credentials are stored in Keychain. A different configuration directory also gets its own associated Keychain entry.</p>
<p lang="en">For Codex, <code>codex login</code> creates the <code>auth.json</code> file used for authentication. The file lives under <code>CODEX_HOME</code>, which defaults to <code>~/.codex</code>. The CLI, IDE extension, and desktop app share this login. Depending on the configuration, credentials can also be stored in the operating system's credential store; this post uses file storage. <a href="https://learn.chatgpt.com/docs/auth" rel="noopener noreferrer">Codex authentication documentation</a></p>
<div aria-label="Scrollable table" class="cc-table" role="region" tabindex="0"><table>
<thead>
<tr>
<th>Item</th>
<th>Claude Code</th>
<th>Codex</th>
</tr>
</thead>
<tbody>
<tr>
<td>Where shared credentials go</td>
<td>The <code>CLAUDE_CODE_OAUTH_TOKEN</code> environment variable</td>
<td>The <code>auth.json</code> file</td>
</tr>
<tr>
<td>How accounts are separated</td>
<td><code>CLAUDE_CONFIG_DIR</code></td>
<td><code>CODEX_HOME</code></td>
</tr>
<tr>
<td>When new credentials take effect</td>
<td>When a new session starts</td>
<td>When a new session starts</td>
</tr>
<tr>
<td>What to watch for with shared accounts</td>
<td>A leftover <code>ANTHROPIC_API_KEY</code> takes precedence</td>
<td>Logging out or logging in again can disconnect other clients using those credentials</td>
</tr>
</tbody>
</table></div>
<p lang="en">Both tools select their credentials when a session starts and continue using that account for the session. A configuration directory can hold only one account at a time; using two accounts in the same directory can overwrite login state and history. The rest of the setup follows from these two constraints.</p>
<h2 lang="en" id="공유-계정을-각자의-환경에-연결하는-구조">Connecting shared accounts on each computer</h2>
<p lang="en">The setup has four parts. The diagram shows Claude Code; I explain the differences for Codex below.</p>
<figure lang="en" class="cc-figure"><div class="cc-diagram" role="region" aria-label="Scrollable diagram" tabindex="0"><img class="cc-diagram-light" alt="Claude Code setup. The rotation worker and shared launcher fetch tokens from the credential server. The launcher passes a token only to the shared session and falls back to the Keychain copy if the server does not respond. The personal session uses the login in the default directory." src="data:image/svg+xml;base64,PHN2ZyBkYXRhLXRoZW1lPSJsaWdodCIgdmlld0JveD0iMCAwIDY2MCAzMTQiIHJvbGU9ImltZyIgbGFuZz0iZW4iIHhtbDpsYW5nPSJlbiIgYXJpYS1sYWJlbGxlZGJ5PSJhcmNoaWZ5LWRpYWdyYW0tdGl0bGUgYXJjaGlmeS1kaWFncmFtLWRlc2NyaXB0aW9uIiBkYXRhLXByZXNldD0iY2xhc3NpYyIgZGF0YS1xdWFsaXR5LXByb2ZpbGU9InNob3djYXNlIiB3aWR0aD0iNjYwIiBoZWlnaHQ9IjMxNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48c3R5bGU+c3ZnIHsgZm9udC1mYW1pbHk6IC1hcHBsZS1zeXN0ZW0sIEJsaW5rTWFjU3lzdGVtRm9udCwgIkFwcGxlIFNEIEdvdGhpYyBOZW8iLCAiTWFsZ3VuIEdvdGhpYyIsICJOb3RvIFNhbnMgS1IiLCAiTm90byBTYW5zIENKSyBLUiIsIHNhbnMtc2VyaWY7IH0KOnJvb3QsIFtkYXRhLXRoZW1lPSJkYXJrIl0geyAtLWJnOiAjMDIwNjE3OyAtLWdyaWQ6ICMxZTI5M2I7IC0tdGV4dDogI2ZmZmZmZjsgLS10ZXh0LW11dGVkOiAjOTRhM2I4OyAtLXRleHQtZGltOiAjNDc1NTY5OyAtLXRleHQtZmFpbnQ6ICM3ZDhkYTE7IC0tcGFuZWw6IHJnYmEoMTUsIDIzLCA0MiwgMC41KTsgLS1wYW5lbC1ib3JkZXI6ICMxZTI5M2I7IC0tbGFuZS1maWxsOiByZ2JhKDE1LCAyMywgNDIsIDAuMjIpOyAtLWxhbmUtc3Ryb2tlOiAjMzM0MTU1OyAtLWFycm93OiAjNjQ3NDhiOyAtLWFycm93LWVtcGhhc2lzOiAjMzRkMzk5OyAtLW1hc2s6ICMwZjE3MmE7IC0tZnJvbnRlbmQtZmlsbDogcmdiYSg4LCA1MSwgNjgsIDAuNCk7IC0tZnJvbnRlbmQtc3Ryb2tlOiAjMjJkM2VlOyAtLWJhY2tlbmQtZmlsbDogcmdiYSg2LCA3OCwgNTksIDAuNCk7IC0tYmFja2VuZC1zdHJva2U6ICMzNGQzOTk7IC0tZGF0YWJhc2UtZmlsbDogcmdiYSg3NiwgMjksIDE0OSwgMC40KTsgLS1kYXRhYmFzZS1zdHJva2U6ICNhNzhiZmE7IC0tY2xvdWQtZmlsbDogcmdiYSgxMjAsIDUzLCAxNSwgMC4zKTsgLS1jbG91ZC1zdHJva2U6ICNmYmJmMjQ7IC0tc2VjdXJpdHktZmlsbDogcmdiYSgxMzYsIDE5LCA1NSwgMC40KTsgLS1zZWN1cml0eS1zdHJva2U6ICNmYjcxODU7IC0tbWVzc2FnZWJ1cy1maWxsOiByZ2JhKDI1MSwgMTQ2LCA2MCwgMC4zKTsgLS1tZXNzYWdlYnVzLXN0cm9rZTogI2ZiOTIzYzsgLS1leHRlcm5hbC1maWxsOiByZ2JhKDMwLCA0MSwgNTksIDAuNSk7IC0tZXh0ZXJuYWwtc3Ryb2tlOiAjOTRhM2I4OyAtLXRvb2xiYXItYmc6IHJnYmEoMTUsIDIzLCA0MiwgMC44KTsgLS10b29sYmFyLWJvcmRlcjogIzMzNDE1NTsgLS10b29sYmFyLXRleHQ6ICNlMmU4ZjA7IC0tdG9vbGJhci1ob3ZlcjogcmdiYSgxNSwgMjMsIDQyLCAwLjk1KTsgLS10b29sYmFyLW1lbnUtYmc6ICMwZjE3MmE7IH0KW2RhdGEtdGhlbWU9ImxpZ2h0Il0geyAtLWJnOiAjZjhmYWZjOyAtLWdyaWQ6ICNlMmU4ZjA7IC0tdGV4dDogIzBmMTcyYTsgLS10ZXh0LW11dGVkOiAjNjQ3NDhiOyAtLXRleHQtZGltOiAjOTRhM2I4OyAtLXRleHQtZmFpbnQ6ICM2NDc0OGI7IC0tcGFuZWw6ICNmZmZmZmY7IC0tcGFuZWwtYm9yZGVyOiAjZTJlOGYwOyAtLWxhbmUtZmlsbDogcmdiYSgyNDgsIDI1MCwgMjUyLCAwLjY1KTsgLS1sYW5lLXN0cm9rZTogI2NiZDVlMTsgLS1hcnJvdzogIzk0YTNiODsgLS1hcnJvdy1lbXBoYXNpczogIzA1OTY2OTsgLS1tYXNrOiAjZmZmZmZmOyAtLWZyb250ZW5kLWZpbGw6IHJnYmEoMzQsIDIxMSwgMjM4LCAwLjE1KTsgLS1mcm9udGVuZC1zdHJva2U6ICMwODkxYjI7IC0tYmFja2VuZC1maWxsOiByZ2JhKDUyLCAyMTEsIDE1MywgMC4xOCk7IC0tYmFja2VuZC1zdHJva2U6ICMwNTk2Njk7IC0tZGF0YWJhc2UtZmlsbDogcmdiYSgxNjcsIDEzOSwgMjUwLCAwLjIpOyAtLWRhdGFiYXNlLXN0cm9rZTogIzdjM2FlZDsgLS1jbG91ZC1maWxsOiByZ2JhKDI1MSwgMTkxLCAzNiwgMC4xOCk7IC0tY2xvdWQtc3Ryb2tlOiAjZDk3NzA2OyAtLXNlY3VyaXR5LWZpbGw6IHJnYmEoMjUxLCAxMTMsIDEzMywgMC4xNSk7IC0tc2VjdXJpdHktc3Ryb2tlOiAjZTExZDQ4OyAtLW1lc3NhZ2VidXMtZmlsbDogcmdiYSgyNTEsIDE0NiwgNjAsIDAuMTUpOyAtLW1lc3NhZ2VidXMtc3Ryb2tlOiAjZWE1ODBjOyAtLWV4dGVybmFsLWZpbGw6IHJnYmEoMTQ4LCAxNjMsIDE4NCwgMC4xOCk7IC0tZXh0ZXJuYWwtc3Ryb2tlOiAjNjQ3NDhiOyAtLXRvb2xiYXItYmc6IHJnYmEoMjU1LCAyNTUsIDI1NSwgMC45Mik7IC0tdG9vbGJhci1ib3JkZXI6ICNjYmQ1ZTE7IC0tdG9vbGJhci10ZXh0OiAjMzM0MTU1OyAtLXRvb2xiYXItaG92ZXI6ICNmZmZmZmY7IC0tdG9vbGJhci1tZW51LWJnOiAjZmZmZmZmOyB9CnN2ZyB7IHdpZHRoOiAxMDAlOyBtaW4td2lkdGg6IG1pbig5MDBweCwgMTAwJSk7IGRpc3BsYXk6IGJsb2NrOyB0cmFuc2Zvcm0tb3JpZ2luOiAwcHggMHB4OyB0cmFuc2l0aW9uOiB0cmFuc2Zvcm0gMC4xOHM7IH0KLmMtZ3JpZCB7IHN0cm9rZTogdmFyKC0tZ3JpZCk7IGZpbGw6IG5vbmU7IH0KLmMtbWFzayB7IGZpbGw6IHZhcigtLW1hc2spOyBzdHJva2U6IG5vbmU7IH0KLmMtZnJvbnRlbmQgeyBmaWxsOiB2YXIoLS1mcm9udGVuZC1maWxsKTsgc3Ryb2tlOiB2YXIoLS1mcm9udGVuZC1zdHJva2UpOyB9Ci5jLWJhY2tlbmQgeyBmaWxsOiB2YXIoLS1iYWNrZW5kLWZpbGwpOyBzdHJva2U6IHZhcigtLWJhY2tlbmQtc3Ryb2tlKTsgfQouYy1zZWN1cml0eSB7IGZpbGw6IHZhcigtLXNlY3VyaXR5LWZpbGwpOyBzdHJva2U6IHZhcigtLXNlY3VyaXR5LXN0cm9rZSk7IH0KLmMtZXh0ZXJuYWwgeyBmaWxsOiB2YXIoLS1leHRlcm5hbC1maWxsKTsgc3Ryb2tlOiB2YXIoLS1leHRlcm5hbC1zdHJva2UpOyB9Ci50LXByaW1hcnkgeyBmaWxsOiB2YXIoLS10ZXh0KTsgfQoudC1tdXRlZCB7IGZpbGw6IHZhcigtLXRleHQtbXV0ZWQpOyB9Ci50LWJhY2tlbmQgeyBmaWxsOiB2YXIoLS1iYWNrZW5kLXN0cm9rZSk7IH0KLnQtc2VjdXJpdHkgeyBmaWxsOiB2YXIoLS1zZWN1cml0eS1zdHJva2UpOyB9Ci50LW1lc3NhZ2VidXMgeyBmaWxsOiB2YXIoLS1tZXNzYWdlYnVzLXN0cm9rZSk7IH0Kc3ZnIC5zZW1hbnRpYy1zaWdpbCB7IGZpbGw6IG5vbmU7IHN0cm9rZTogY3VycmVudGNvbG9yOyBzdHJva2Utd2lkdGg6IDEuMzU7IHN0cm9rZS1saW5lY2FwOiByb3VuZDsgc3Ryb2tlLWxpbmVqb2luOiByb3VuZDsgb3BhY2l0eTogMC43NjsgcG9pbnRlci1ldmVudHM6IG5vbmU7IH0Kc3ZnIC5zZW1hbnRpYy1zaWdpbCA+ICogeyB2ZWN0b3ItZWZmZWN0OiBub24tc2NhbGluZy1zdHJva2U7IH0Kc3ZnIC5zZW1hbnRpYy1zaWdpbCAuc2lnaWwtZmlsbCB7IGZpbGw6IGN1cnJlbnRjb2xvcjsgc3Ryb2tlOiBub25lOyB9CnN2ZyAucy1mcm9udGVuZCB7IGNvbG9yOiB2YXIoLS1mcm9udGVuZC1zdHJva2UpOyB9CnN2ZyAucy1iYWNrZW5kIHsgY29sb3I6IHZhcigtLWJhY2tlbmQtc3Ryb2tlKTsgfQpzdmcgLnMtc2VjdXJpdHkgeyBjb2xvcjogdmFyKC0tc2VjdXJpdHktc3Ryb2tlKTsgfQpzdmcgLnMtZXh0ZXJuYWwgeyBjb2xvcjogdmFyKC0tZXh0ZXJuYWwtc3Ryb2tlKTsgfQouYS1kZWZhdWx0IHsgc3Ryb2tlOiB2YXIoLS1hcnJvdyk7IGZpbGw6IG5vbmU7IH0KLmEtZW1waGFzaXMgeyBzdHJva2U6IHZhcigtLWFycm93LWVtcGhhc2lzKTsgZmlsbDogbm9uZTsgfQouYS1zZWN1cml0eSB7IHN0cm9rZTogdmFyKC0tc2VjdXJpdHktc3Ryb2tlKTsgZmlsbDogbm9uZTsgc3Ryb2tlLWRhc2hhcnJheTogNSwgNTsgfQouYS1kYXNoZWQgeyBzdHJva2U6IHZhcigtLWRhdGFiYXNlLXN0cm9rZSk7IGZpbGw6IG5vbmU7IHN0cm9rZS1kYXNoYXJyYXk6IDQsIDQ7IH0KLm0tZGVmYXVsdCB7IGZpbGw6IHZhcigtLWFycm93KTsgfQoubS1lbXBoYXNpcyB7IGZpbGw6IHZhcigtLWFycm93LWVtcGhhc2lzKTsgfQoubS1zZWN1cml0eSB7IGZpbGw6IHZhcigtLXNlY3VyaXR5LXN0cm9rZSk7IH0KLm0tZGFzaGVkIHsgZmlsbDogdmFyKC0tZGF0YWJhc2Utc3Ryb2tlKTsgfQpzdmcgW2RhdGEtbm9kZS1pZF0geyBjdXJzb3I6IHBvaW50ZXI7IHRyYW5zaXRpb246IG9wYWNpdHkgMC4xOHMsIGZpbHRlciAwLjE4czsgfQpzdmcgW2RhdGEtZWRnZS1mcm9tXSB7IHRyYW5zaXRpb246IG9wYWNpdHkgMC4xOHMsIGZpbHRlciAwLjE4czsgfQo6cm9vdCwgc3ZnIHsgLS1iZzogIzAyMDYxNzsgLS1ncmlkOiAjMWUyOTNiOyAtLXRleHQ6ICNmZmZmZmY7IC0tdGV4dC1tdXRlZDogIzk0YTNiODsgLS10ZXh0LWRpbTogIzQ3NTU2OTsgLS10ZXh0LWZhaW50OiAjN2Q4ZGExOyAtLXBhbmVsOiByZ2JhKDE1LCAyMywgNDIsIDAuNSk7IC0tcGFuZWwtYm9yZGVyOiAjMWUyOTNiOyAtLWxhbmUtZmlsbDogcmdiYSgxNSwgMjMsIDQyLCAwLjIyKTsgLS1sYW5lLXN0cm9rZTogIzMzNDE1NTsgLS1hcnJvdzogIzY0NzQ4YjsgLS1hcnJvdy1lbXBoYXNpczogIzM0ZDM5OTsgLS1tYXNrOiAjMGYxNzJhOyAtLWZyb250ZW5kLWZpbGw6IHJnYmEoOCwgNTEsIDY4LCAwLjQpOyAtLWZyb250ZW5kLXN0cm9rZTogIzIyZDNlZTsgLS1iYWNrZW5kLWZpbGw6IHJnYmEoNiwgNzgsIDU5LCAwLjQpOyAtLWJhY2tlbmQtc3Ryb2tlOiAjMzRkMzk5OyAtLWRhdGFiYXNlLWZpbGw6IHJnYmEoNzYsIDI5LCAxNDksIDAuNCk7IC0tZGF0YWJhc2Utc3Ryb2tlOiAjYTc4YmZhOyAtLWNsb3VkLWZpbGw6IHJnYmEoMTIwLCA1MywgMTUsIDAuMyk7IC0tY2xvdWQtc3Ryb2tlOiAjZmJiZjI0OyAtLXNlY3VyaXR5LWZpbGw6IHJnYmEoMTM2LCAxOSwgNTUsIDAuNCk7IC0tc2VjdXJpdHktc3Ryb2tlOiAjZmI3MTg1OyAtLW1lc3NhZ2VidXMtZmlsbDogcmdiYSgyNTEsIDE0NiwgNjAsIDAuMyk7IC0tbWVzc2FnZWJ1cy1zdHJva2U6ICNmYjkyM2M7IC0tZXh0ZXJuYWwtZmlsbDogcmdiYSgzMCwgNDEsIDU5LCAwLjUpOyAtLWV4dGVybmFsLXN0cm9rZTogIzk0YTNiODsgLS10b29sYmFyLWJnOiByZ2JhKDE1LCAyMywgNDIsIDAuOCk7IC0tdG9vbGJhci1ib3JkZXI6ICMzMzQxNTU7IC0tdG9vbGJhci10ZXh0OiAjZTJlOGYwOyAtLXRvb2xiYXItaG92ZXI6IHJnYmEoMTUsIDIzLCA0MiwgMC45NSk7IC0tdG9vbGJhci1tZW51LWJnOiAjMGYxNzJhOyB9CnN2Z1tkYXRhLXRoZW1lPSJsaWdodCJdIHsgLS1iZzogI2Y4ZmFmYzsgLS1ncmlkOiAjZTJlOGYwOyAtLXRleHQ6ICMwZjE3MmE7IC0tdGV4dC1tdXRlZDogIzY0NzQ4YjsgLS10ZXh0LWRpbTogIzk0YTNiODsgLS10ZXh0LWZhaW50OiAjNjQ3NDhiOyAtLXBhbmVsOiAjZmZmZmZmOyAtLXBhbmVsLWJvcmRlcjogI2UyZThmMDsgLS1sYW5lLWZpbGw6IHJnYmEoMjQ4LCAyNTAsIDI1MiwgMC42NSk7IC0tbGFuZS1zdHJva2U6ICNjYmQ1ZTE7IC0tYXJyb3c6ICM5NGEzYjg7IC0tYXJyb3ctZW1waGFzaXM6ICMwNTk2Njk7IC0tbWFzazogI2ZmZmZmZjsgLS1mcm9udGVuZC1maWxsOiByZ2JhKDM0LCAyMTEsIDIzOCwgMC4xNSk7IC0tZnJvbnRlbmQtc3Ryb2tlOiAjMDg5MWIyOyAtLWJhY2tlbmQtZmlsbDogcmdiYSg1MiwgMjExLCAxNTMsIDAuMTgpOyAtLWJhY2tlbmQtc3Ryb2tlOiAjMDU5NjY5OyAtLWRhdGFiYXNlLWZpbGw6IHJnYmEoMTY3LCAxMzksIDI1MCwgMC4yKTsgLS1kYXRhYmFzZS1zdHJva2U6ICM3YzNhZWQ7IC0tY2xvdWQtZmlsbDogcmdiYSgyNTEsIDE5MSwgMzYsIDAuMTgpOyAtLWNsb3VkLXN0cm9rZTogI2Q5NzcwNjsgLS1zZWN1cml0eS1maWxsOiByZ2JhKDI1MSwgMTEzLCAxMzMsIDAuMTUpOyAtLXNlY3VyaXR5LXN0cm9rZTogI2UxMWQ0ODsgLS1tZXNzYWdlYnVzLWZpbGw6IHJnYmEoMjUxLCAxNDYsIDYwLCAwLjE1KTsgLS1tZXNzYWdlYnVzLXN0cm9rZTogI2VhNTgwYzsgLS1leHRlcm5hbC1maWxsOiByZ2JhKDE0OCwgMTYzLCAxODQsIDAuMTgpOyAtLWV4dGVybmFsLXN0cm9rZTogIzY0NzQ4YjsgLS10b29sYmFyLWJnOiByZ2JhKDI1NSwgMjU1LCAyNTUsIDAuOTIpOyAtLXRvb2xiYXItYm9yZGVyOiAjY2JkNWUxOyAtLXRvb2xiYXItdGV4dDogIzMzNDE1NTsgLS10b29sYmFyLWhvdmVyOiAjZmZmZmZmOyAtLXRvb2xiYXItbWVudS1iZzogI2ZmZmZmZjsgfQpyZWN0LmMtYmctcmVjdCB7IGZpbGw6IHZhcigtLWJnKTsgfTwvc3R5bGU+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgY2xhc3M9ImMtYmctcmVjdCIvPgogICAgICAgIDx0aXRsZSBpZD0iYXJjaGlmeS1kaWFncmFtLXRpdGxlIj5DbGF1ZGUgQ29kZSBsYXVuY2ggcGF0aHMgYW5kIGJhY2tncm91bmQgY3JlZGVudGlhbCByb3RhdGlvbjwvdGl0bGU+CiAgICAgICAgPGRlc2MgaWQ9ImFyY2hpZnktZGlhZ3JhbS1kZXNjcmlwdGlvbiI+QW4gYXJjaGl0ZWN0dXJlIGRpYWdyYW0gZ2VuZXJhdGVkIGJ5IEFyY2hpZnkuPC9kZXNjPgogICAgICAgIDwhLS0gRGVmaW5pdGlvbnMgLS0+CiAgICAgICAgPGRlZnM+CiAgICAgICAgICA8bWFya2VyIGlkPSJhcnJvd2hlYWQiIG1hcmtlcldpZHRoPSIxMCIgbWFya2VySGVpZ2h0PSI3IiByZWZYPSI5IiByZWZZPSIzLjUiIG9yaWVudD0iYXV0byI+CiAgICAgICAgICAgIDxwb2x5Z29uIHBvaW50cz0iMCAwLCAxMCAzLjUsIDAgNyIgY2xhc3M9Im0tZGVmYXVsdCIvPgogICAgICAgICAgPC9tYXJrZXI+CiAgICAgICAgICA8bWFya2VyIGlkPSJhcnJvd2hlYWQtZW1waGFzaXMiIG1hcmtlcldpZHRoPSIxMCIgbWFya2VySGVpZ2h0PSI3IiByZWZYPSI5IiByZWZZPSIzLjUiIG9yaWVudD0iYXV0byI+CiAgICAgICAgICAgIDxwb2x5Z29uIHBvaW50cz0iMCAwLCAxMCAzLjUsIDAgNyIgY2xhc3M9Im0tZW1waGFzaXMiLz4KICAgICAgICAgIDwvbWFya2VyPgogICAgICAgICAgPG1hcmtlciBpZD0iYXJyb3doZWFkLXNlY3VyaXR5IiBtYXJrZXJXaWR0aD0iMTAiIG1hcmtlckhlaWdodD0iNyIgcmVmWD0iOSIgcmVmWT0iMy41IiBvcmllbnQ9ImF1dG8iPgogICAgICAgICAgICA8cG9seWdvbiBwb2ludHM9IjAgMCwgMTAgMy41LCAwIDciIGNsYXNzPSJtLXNlY3VyaXR5Ii8+CiAgICAgICAgICA8L21hcmtlcj4KICAgICAgICAgIDxtYXJrZXIgaWQ9ImFycm93aGVhZC1kYXNoZWQiIG1hcmtlcldpZHRoPSIxMCIgbWFya2VySGVpZ2h0PSI3IiByZWZYPSI5IiByZWZZPSIzLjUiIG9yaWVudD0iYXV0byI+CiAgICAgICAgICAgIDxwb2x5Z29uIHBvaW50cz0iMCAwLCAxMCAzLjUsIDAgNyIgY2xhc3M9Im0tZGFzaGVkIi8+CiAgICAgICAgICA8L21hcmtlcj4KICAgICAgICAgIDxwYXR0ZXJuIGlkPSJncmlkIiB3aWR0aD0iNDAiIGhlaWdodD0iNDAiIHBhdHRlcm5Vbml0cz0idXNlclNwYWNlT25Vc2UiPgogICAgICAgICAgICA8cGF0aCBkPSJNIDQwIDAgTCAwIDAgMCA0MCIgY2xhc3M9ImMtZ3JpZCIgc3Ryb2tlLXdpZHRoPSIwLjUiLz4KICAgICAgICAgIDwvcGF0dGVybj4KICAgICAgICA8L2RlZnM+CgogICAgICAgIDwhLS0gQmFja2dyb3VuZCBHcmlkIC0tPgogICAgICAgIDxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JpZCkiLz4KCiAgICAgICAgPCEtLSBCb3VuZGFyaWVzIChiZWhpbmQgZXZlcnl0aGluZykgLS0+CgoKICAgICAgICA8IS0tIENvbm5lY3Rpb24gcGF0aHMgKGJlZm9yZSBjb21wb25lbnRzIGZvciBjb3JyZWN0IHotb3JkZXIpIC0tPgogICAgICAgIDxwYXRoIGRhdGEtZWRnZS1mcm9tPSJyb3RhdG9yIiBkYXRhLWVkZ2UtdG89InN1cHBsaWVyIiBkYXRhLWVkZ2UtbGFiZWw9IkNoZWNrIGV2ZXJ5IDEwIG1pbiIgZGF0YS1lZGdlLWtleT0iMCIgZGF0YS1lZGdlLWlkPSJyb3RhdG9yLWNoZWNrcy1zdXBwbGllciIgZGF0YS1jb21wb3NpdGlvbi1wb2ludHM9IjE2OCw1NjsyNTUsNTYiIGQ9Ik0gMTY4IDU2IEwgMjU1IDU2IiBjbGFzcz0iYS1kZWZhdWx0IiBzdHJva2Utd2lkdGg9IjEuNSIgbWFya2VyLWVuZD0idXJsKCNhcnJvd2hlYWQpIi8+CiAgICAgICAgPHBhdGggZGF0YS1lZGdlLWZyb209ImxhdW5jaGVyIiBkYXRhLWVkZ2UtdG89InN1cHBsaWVyIiBkYXRhLWVkZ2UtbGFiZWw9IkZldGNoIGF0IGxhdW5jaCIgZGF0YS1lZGdlLWtleT0iMSIgZGF0YS1lZGdlLWlkPSJsYXVuY2hlci1mZXRjaGVzLWxhdGVzdCIgZGF0YS1jb21wb3NpdGlvbi1wb2ludHM9IjQ5Miw1Njs0MDUsNTYiIGQ9Ik0gNDkyIDU2IEwgNDA1IDU2IiBjbGFzcz0iYS1lbXBoYXNpcyIgc3Ryb2tlLXdpZHRoPSIxLjgiIG1hcmtlci1lbmQ9InVybCgjYXJyb3doZWFkLWVtcGhhc2lzKSIvPgogICAgICAgIDxwYXRoIGRhdGEtZWRnZS1mcm9tPSJyb3RhdG9yIiBkYXRhLWVkZ2UtdG89ImtleWNoYWluIiBkYXRhLWVkZ2UtbGFiZWw9IlJlcGxhY2UgY29weSIgZGF0YS1lZGdlLWtleT0iMiIgZGF0YS1lZGdlLWlkPSJyb3RhdG9yLXNhdmVzLWNvcHkiIGRhdGEtY29tcG9zaXRpb24tcG9pbnRzPSI5Myw4ODs5MywxNTc7MzIzLDE1NzszMjMsMjI2IiBkPSJNIDkzIDg4IEwgOTMgMTQ5IFEgOTMgMTU3IDEwMSAxNTcgTCAzMTUgMTU3IFEgMzIzIDE1NyAzMjMgMTY1IEwgMzIzIDIyNiIgY2xhc3M9ImEtc2VjdXJpdHkiIHN0cm9rZS13aWR0aD0iMS41IiBtYXJrZXItZW5kPSJ1cmwoI2Fycm93aGVhZC1zZWN1cml0eSkiLz4KICAgICAgICA8cGF0aCBkYXRhLWVkZ2UtZnJvbT0ibGF1bmNoZXIiIGRhdGEtZWRnZS10bz0ia2V5Y2hhaW4iIGRhdGEtZWRnZS1sYWJlbD0iRmFsbGJhY2sgY29weSIgZGF0YS1lZGdlLWtleT0iMyIgZGF0YS1lZGdlLWlkPSJsYXVuY2hlci1mYWxscy1iYWNrIiBkYXRhLWNvbXBvc2l0aW9uLXBvaW50cz0iNTY3LDg4OzU2NywxNTc7MzM3LDE1NzszMzcsMjI2IiBkPSJNIDU2NyA4OCBMIDU2NyAxNDkgUSA1NjcgMTU3IDU1OSAxNTcgTCAzNDUgMTU3IFEgMzM3IDE1NyAzMzcgMTY1IEwgMzM3IDIyNiIgY2xhc3M9ImEtZGFzaGVkIiBzdHJva2Utd2lkdGg9IjEuNSIgbWFya2VyLWVuZD0idXJsKCNhcnJvd2hlYWQtZGFzaGVkKSIvPgogICAgICAgIDxwYXRoIGRhdGEtZWRnZS1mcm9tPSJsYXVuY2hlciIgZGF0YS1lZGdlLXRvPSJzZXNzaW9uLXNoYXJlZCIgZGF0YS1lZGdlLWxhYmVsPSJPbmx5IHRoaXMgcHJvY2VzcyIgZGF0YS1lZGdlLWtleT0iNCIgZGF0YS1lZGdlLWlkPSJsYXVuY2hlci1pbmplY3RzIiBkYXRhLWNvbXBvc2l0aW9uLXBvaW50cz0iNTY3LDg4OzU2NywyMjYiIGQ9Ik0gNTY3IDg4IEwgNTY3IDIyNiIgY2xhc3M9ImEtZW1waGFzaXMiIHN0cm9rZS13aWR0aD0iMS44IiBtYXJrZXItZW5kPSJ1cmwoI2Fycm93aGVhZC1lbXBoYXNpcykiLz4KCiAgICAgICAgPCEtLSBDb21wb25lbnRzIC0tPgogICAgICAgIDxnIGlkPSJub2RlLXJvdGF0b3IiIGRhdGEtbm9kZS1pZD0icm90YXRvciIgZGF0YS1ub2RlLWxhYmVsPSJSb3RhdGlvbiB3b3JrZXIiIHRhYmluZGV4PSIwIiByb2xlPSJidXR0b24iIGFyaWEtbGFiZWw9IkZvY3VzIFJvdGF0aW9uIHdvcmtlciwgbGF1bmNoZCDCtyBldmVyeSAxMCBtaW4sIEFyY2hpdGVjdHVyZSBjb21wb25lbnQiIGFyaWEtcHJlc3NlZD0iZmFsc2UiIGRhdGEtbm9kZS1raW5kPSJiYWNrZW5kIiBkYXRhLW5vZGUtc3VibGFiZWw9ImxhdW5jaGQgwrcgZXZlcnkgMTAgbWluIiBkYXRhLW5vZGUtY29udGV4dD0iQXJjaGl0ZWN0dXJlIGNvbXBvbmVudCI+CiAgICAgICAgICA8dGl0bGU+Um90YXRpb24gd29ya2VyIMK3IGxhdW5jaGQgwrcgZXZlcnkgMTAgbWluIMK3IEFyY2hpdGVjdHVyZSBjb21wb25lbnQ8L3RpdGxlPgogICAgICAgICAgPHJlY3QgeD0iMTgiIHk9IjI0IiB3aWR0aD0iMTUwIiBoZWlnaHQ9IjY0IiByeD0iNiIgY2xhc3M9ImMtbWFzayIvPgogICAgICAgICAgPHJlY3QgeD0iMTgiIHk9IjI0IiB3aWR0aD0iMTUwIiBoZWlnaHQ9IjY0IiByeD0iNiIgY2xhc3M9ImMtYmFja2VuZCIgc3Ryb2tlLXdpZHRoPSIxLjUiLz4KICAgICAgICAgIDxnIGFyaWEtaGlkZGVuPSJ0cnVlIiBkYXRhLXNlbWFudGljLXNpZ2lsPSJiYWNrZW5kIiBjbGFzcz0ic2VtYW50aWMtc2lnaWwgcy1iYWNrZW5kIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgyNCAzMCkgc2NhbGUoMC42ODc1KSI+CiAgICAgICAgICAgIDxwYXRoIGQ9Ik02IDMgMyA4bDMgNU0xMCAzbDMgNS0zIDUiLz4KICAgICAgICAgIDwvZz4KICAgICAgICAgIDx0ZXh0IGRhdGEtbm9kZS1sYWJlbD0iIiB4PSI5MyIgeT0iNTQiIGNsYXNzPSJ0LXByaW1hcnkiIGZvbnQtc2l6ZT0iMTEiIGZvbnQtd2VpZ2h0PSI2MDAiIHRleHQtYW5jaG9yPSJtaWRkbGUiPlJvdGF0aW9uIHdvcmtlcjwvdGV4dD4KICAgICAgICA8dGV4dCB4PSI5MyIgeT0iNzAiIGNsYXNzPSJ0LW11dGVkIiBmb250LXNpemU9IjkiIHRleHQtYW5jaG9yPSJtaWRkbGUiPmxhdW5jaGQgwrcgZXZlcnkgMTAgbWluPC90ZXh0PgogICAgICAgIDwvZz4KCiAgICAgICAgPGcgaWQ9Im5vZGUtc3VwcGxpZXIiIGRhdGEtbm9kZS1pZD0ic3VwcGxpZXIiIGRhdGEtbm9kZS1sYWJlbD0iQ3JlZGVudGlhbCBzZXJ2ZXIiIHRhYmluZGV4PSIwIiByb2xlPSJidXR0b24iIGFyaWEtbGFiZWw9IkZvY3VzIENyZWRlbnRpYWwgc2VydmVyLCBQcm92aWRlcyB1c2FibGUgdG9rZW5zLCBBcmNoaXRlY3R1cmUgY29tcG9uZW50IiBhcmlhLXByZXNzZWQ9ImZhbHNlIiBkYXRhLW5vZGUta2luZD0iZXh0ZXJuYWwiIGRhdGEtbm9kZS1zdWJsYWJlbD0iUHJvdmlkZXMgdXNhYmxlIHRva2VucyIgZGF0YS1ub2RlLWNvbnRleHQ9IkFyY2hpdGVjdHVyZSBjb21wb25lbnQiPgogICAgICAgICAgPHRpdGxlPkNyZWRlbnRpYWwgc2VydmVyIMK3IFByb3ZpZGVzIHVzYWJsZSB0b2tlbnMgwrcgQXJjaGl0ZWN0dXJlIGNvbXBvbmVudDwvdGl0bGU+CiAgICAgICAgICA8cmVjdCB4PSIyNTUiIHk9IjI0IiB3aWR0aD0iMTUwIiBoZWlnaHQ9IjY0IiByeD0iNiIgY2xhc3M9ImMtbWFzayIvPgogICAgICAgICAgPHJlY3QgeD0iMjU1IiB5PSIyNCIgd2lkdGg9IjE1MCIgaGVpZ2h0PSI2NCIgcng9IjYiIGNsYXNzPSJjLWV4dGVybmFsIiBzdHJva2Utd2lkdGg9IjEuNSIvPgogICAgICAgICAgPGcgYXJpYS1oaWRkZW49InRydWUiIGRhdGEtc2VtYW50aWMtc2lnaWw9ImV4dGVybmFsIiBjbGFzcz0ic2VtYW50aWMtc2lnaWwgcy1leHRlcm5hbCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMjYxIDMwKSBzY2FsZSgwLjY4NzUpIj4KICAgICAgICAgICAgPHJlY3QgeD0iMi41IiB5PSI1IiB3aWR0aD0iOC41IiBoZWlnaHQ9IjgiIHJ4PSIxLjUiLz4KICAgICAgICAgICAgPHBhdGggZD0iTTggMi41aDUuNVY4TTEzLjUgMi41IDcuNSA4LjUiLz4KICAgICAgICAgIDwvZz4KICAgICAgICAgIDx0ZXh0IGRhdGEtbm9kZS1sYWJlbD0iIiB4PSIzMzAiIHk9IjU0IiBjbGFzcz0idC1wcmltYXJ5IiBmb250LXNpemU9IjExIiBmb250LXdlaWdodD0iNjAwIiB0ZXh0LWFuY2hvcj0ibWlkZGxlIj5DcmVkZW50aWFsIHNlcnZlcjwvdGV4dD4KICAgICAgICA8dGV4dCB4PSIzMzAiIHk9IjcwIiBjbGFzcz0idC1tdXRlZCIgZm9udC1zaXplPSI5IiB0ZXh0LWFuY2hvcj0ibWlkZGxlIj5Qcm92aWRlcyB1c2FibGUgdG9rZW5zPC90ZXh0PgogICAgICAgIDwvZz4KCiAgICAgICAgPGcgaWQ9Im5vZGUtbGF1bmNoZXIiIGRhdGEtbm9kZS1pZD0ibGF1bmNoZXIiIGRhdGEtbm9kZS1sYWJlbD0iU2hhcmVkIGxhdW5jaGVyIiB0YWJpbmRleD0iMCIgcm9sZT0iYnV0dG9uIiBhcmlhLWxhYmVsPSJGb2N1cyBTaGFyZWQgbGF1bmNoZXIsIEZldGNoZXMgYW5kIGluamVjdHMgYSB0b2tlbiwgQXJjaGl0ZWN0dXJlIGNvbXBvbmVudCIgYXJpYS1wcmVzc2VkPSJmYWxzZSIgZGF0YS1ub2RlLWtpbmQ9ImJhY2tlbmQiIGRhdGEtbm9kZS1zdWJsYWJlbD0iRmV0Y2hlcyBhbmQgaW5qZWN0cyBhIHRva2VuIiBkYXRhLW5vZGUtY29udGV4dD0iQXJjaGl0ZWN0dXJlIGNvbXBvbmVudCI+CiAgICAgICAgICA8dGl0bGU+U2hhcmVkIGxhdW5jaGVyIMK3IEZldGNoZXMgYW5kIGluamVjdHMgYSB0b2tlbiDCtyBBcmNoaXRlY3R1cmUgY29tcG9uZW50PC90aXRsZT4KICAgICAgICAgIDxyZWN0IHg9IjQ5MiIgeT0iMjQiIHdpZHRoPSIxNTAiIGhlaWdodD0iNjQiIHJ4PSI2IiBjbGFzcz0iYy1tYXNrIi8+CiAgICAgICAgICA8cmVjdCB4PSI0OTIiIHk9IjI0IiB3aWR0aD0iMTUwIiBoZWlnaHQ9IjY0IiByeD0iNiIgY2xhc3M9ImMtYmFja2VuZCIgc3Ryb2tlLXdpZHRoPSIxLjUiLz4KICAgICAgICAgIDxnIGFyaWEtaGlkZGVuPSJ0cnVlIiBkYXRhLXNlbWFudGljLXNpZ2lsPSJiYWNrZW5kIiBjbGFzcz0ic2VtYW50aWMtc2lnaWwgcy1iYWNrZW5kIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSg0OTggMzApIHNjYWxlKDAuNjg3NSkiPgogICAgICAgICAgICA8cGF0aCBkPSJNNiAzIDMgOGwzIDVNMTAgM2wzIDUtMyA1Ii8+CiAgICAgICAgICA8L2c+CiAgICAgICAgICA8dGV4dCBkYXRhLW5vZGUtbGFiZWw9IiIgeD0iNTY3IiB5PSI1NCIgY2xhc3M9InQtcHJpbWFyeSIgZm9udC1zaXplPSIxMSIgZm9udC13ZWlnaHQ9IjYwMCIgdGV4dC1hbmNob3I9Im1pZGRsZSI+U2hhcmVkIGxhdW5jaGVyPC90ZXh0PgogICAgICAgIDx0ZXh0IHg9IjU2NyIgeT0iNzAiIGNsYXNzPSJ0LW11dGVkIiBmb250LXNpemU9IjkiIHRleHQtYW5jaG9yPSJtaWRkbGUiPkZldGNoZXMgYW5kIGluamVjdHMgYSB0b2tlbjwvdGV4dD4KICAgICAgICA8L2c+CgogICAgICAgIDxnIGlkPSJub2RlLXNlc3Npb24tbWluZSIgZGF0YS1ub2RlLWlkPSJzZXNzaW9uLW1pbmUiIGRhdGEtbm9kZS1sYWJlbD0iUGVyc29uYWwgc2Vzc2lvbiIgdGFiaW5kZXg9IjAiIHJvbGU9ImJ1dHRvbiIgYXJpYS1sYWJlbD0iRm9jdXMgUGVyc29uYWwgc2Vzc2lvbiwgRGVmYXVsdCBkaXJlY3RvcnkgwrcgbXkgbG9naW4sIEFyY2hpdGVjdHVyZSBjb21wb25lbnQiIGFyaWEtcHJlc3NlZD0iZmFsc2UiIGRhdGEtbm9kZS1raW5kPSJmcm9udGVuZCIgZGF0YS1ub2RlLXN1YmxhYmVsPSJEZWZhdWx0IGRpcmVjdG9yeSDCtyBteSBsb2dpbiIgZGF0YS1ub2RlLWNvbnRleHQ9IkFyY2hpdGVjdHVyZSBjb21wb25lbnQiPgogICAgICAgICAgPHRpdGxlPlBlcnNvbmFsIHNlc3Npb24gwrcgRGVmYXVsdCBkaXJlY3RvcnkgwrcgbXkgbG9naW4gwrcgQXJjaGl0ZWN0dXJlIGNvbXBvbmVudDwvdGl0bGU+CiAgICAgICAgICA8cmVjdCB4PSIxOCIgeT0iMjI2IiB3aWR0aD0iMTUwIiBoZWlnaHQ9IjY0IiByeD0iNiIgY2xhc3M9ImMtbWFzayIvPgogICAgICAgICAgPHJlY3QgeD0iMTgiIHk9IjIyNiIgd2lkdGg9IjE1MCIgaGVpZ2h0PSI2NCIgcng9IjYiIGNsYXNzPSJjLWZyb250ZW5kIiBzdHJva2Utd2lkdGg9IjEuNSIvPgogICAgICAgICAgPGcgYXJpYS1oaWRkZW49InRydWUiIGRhdGEtc2VtYW50aWMtc2lnaWw9ImZyb250ZW5kIiBjbGFzcz0ic2VtYW50aWMtc2lnaWwgcy1mcm9udGVuZCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMjQgMjMyKSBzY2FsZSgwLjY4NzUpIj4KICAgICAgICAgICAgPHJlY3QgeD0iMiIgeT0iMyIgd2lkdGg9IjEyIiBoZWlnaHQ9IjEwIiByeD0iMiIvPgogICAgICAgICAgICA8cGF0aCBkPSJNMiA2LjVoMTIiLz4KICAgICAgICAgICAgPGNpcmNsZSBjeD0iNC4xIiBjeT0iNC44IiByPSIuNyIgY2xhc3M9InNpZ2lsLWZpbGwiLz4KICAgICAgICAgICAgPGNpcmNsZSBjeD0iNi4zIiBjeT0iNC44IiByPSIuNyIgY2xhc3M9InNpZ2lsLWZpbGwiLz4KICAgICAgICAgIDwvZz4KICAgICAgICAgIDx0ZXh0IGRhdGEtbm9kZS1sYWJlbD0iIiB4PSI5MyIgeT0iMjU2IiBjbGFzcz0idC1wcmltYXJ5IiBmb250LXNpemU9IjExIiBmb250LXdlaWdodD0iNjAwIiB0ZXh0LWFuY2hvcj0ibWlkZGxlIj5QZXJzb25hbCBzZXNzaW9uPC90ZXh0PgogICAgICAgIDx0ZXh0IHg9IjkzIiB5PSIyNzIiIGNsYXNzPSJ0LW11dGVkIiBmb250LXNpemU9IjkiIHRleHQtYW5jaG9yPSJtaWRkbGUiPkRlZmF1bHQgZGlyZWN0b3J5IMK3IG15IGxvZ2luPC90ZXh0PgogICAgICAgIDwvZz4KCiAgICAgICAgPGcgaWQ9Im5vZGUta2V5Y2hhaW4iIGRhdGEtbm9kZS1pZD0ia2V5Y2hhaW4iIGRhdGEtbm9kZS1sYWJlbD0iS2V5Y2hhaW4iIHRhYmluZGV4PSIwIiByb2xlPSJidXR0b24iIGFyaWEtbGFiZWw9IkZvY3VzIEtleWNoYWluLCBBY2Nlc3Mga2V5IMK3IGNhY2hlZCB0b2tlbiwgQXJjaGl0ZWN0dXJlIGNvbXBvbmVudCIgYXJpYS1wcmVzc2VkPSJmYWxzZSIgZGF0YS1ub2RlLWtpbmQ9InNlY3VyaXR5IiBkYXRhLW5vZGUtc3VibGFiZWw9IkFjY2VzcyBrZXkgwrcgY2FjaGVkIHRva2VuIiBkYXRhLW5vZGUtY29udGV4dD0iQXJjaGl0ZWN0dXJlIGNvbXBvbmVudCI+CiAgICAgICAgICA8dGl0bGU+S2V5Y2hhaW4gwrcgQWNjZXNzIGtleSDCtyBjYWNoZWQgdG9rZW4gwrcgQXJjaGl0ZWN0dXJlIGNvbXBvbmVudDwvdGl0bGU+CiAgICAgICAgICA8cmVjdCB4PSIyNTUiIHk9IjIyNiIgd2lkdGg9IjE1MCIgaGVpZ2h0PSI2NCIgcng9IjYiIGNsYXNzPSJjLW1hc2siLz4KICAgICAgICAgIDxyZWN0IHg9IjI1NSIgeT0iMjI2IiB3aWR0aD0iMTUwIiBoZWlnaHQ9IjY0IiByeD0iNiIgY2xhc3M9ImMtc2VjdXJpdHkiIHN0cm9rZS13aWR0aD0iMS41Ii8+CiAgICAgICAgICA8ZyBhcmlhLWhpZGRlbj0idHJ1ZSIgZGF0YS1zZW1hbnRpYy1zaWdpbD0ic2VjdXJpdHkiIGNsYXNzPSJzZW1hbnRpYy1zaWdpbCBzLXNlY3VyaXR5IiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgyNjEgMjMyKSBzY2FsZSgwLjY4NzUpIj4KICAgICAgICAgICAgPHBhdGggZD0iTTggMi4yIDEzIDR2My41YzAgMy4xLTEuOCA1LjQtNSA2LjUtMy4yLTEuMS01LTMuNC01LTYuNVY0WiIvPgogICAgICAgICAgICA8cGF0aCBkPSJtNS44IDggMS41IDEuNSAzLTMiLz4KICAgICAgICAgIDwvZz4KICAgICAgICAgIDx0ZXh0IGRhdGEtbm9kZS1sYWJlbD0iIiB4PSIzMzAiIHk9IjI1NiIgY2xhc3M9InQtcHJpbWFyeSIgZm9udC1zaXplPSIxMSIgZm9udC13ZWlnaHQ9IjYwMCIgdGV4dC1hbmNob3I9Im1pZGRsZSI+S2V5Y2hhaW48L3RleHQ+CiAgICAgICAgPHRleHQgeD0iMzMwIiB5PSIyNzIiIGNsYXNzPSJ0LW11dGVkIiBmb250LXNpemU9IjkiIHRleHQtYW5jaG9yPSJtaWRkbGUiPkFjY2VzcyBrZXkgwrcgY2FjaGVkIHRva2VuPC90ZXh0PgogICAgICAgIDwvZz4KCiAgICAgICAgPGcgaWQ9Im5vZGUtc2Vzc2lvbi1zaGFyZWQiIGRhdGEtbm9kZS1pZD0ic2Vzc2lvbi1zaGFyZWQiIGRhdGEtbm9kZS1sYWJlbD0iU2hhcmVkIHNlc3Npb24iIHRhYmluZGV4PSIwIiByb2xlPSJidXR0b24iIGFyaWEtbGFiZWw9IkZvY3VzIFNoYXJlZCBzZXNzaW9uLCBEZWRpY2F0ZWQgZGlyZWN0b3J5IMK3IHRva2VuLCBBcmNoaXRlY3R1cmUgY29tcG9uZW50IiBhcmlhLXByZXNzZWQ9ImZhbHNlIiBkYXRhLW5vZGUta2luZD0iZnJvbnRlbmQiIGRhdGEtbm9kZS1zdWJsYWJlbD0iRGVkaWNhdGVkIGRpcmVjdG9yeSDCtyB0b2tlbiIgZGF0YS1ub2RlLWNvbnRleHQ9IkFyY2hpdGVjdHVyZSBjb21wb25lbnQiPgogICAgICAgICAgPHRpdGxlPlNoYXJlZCBzZXNzaW9uIMK3IERlZGljYXRlZCBkaXJlY3RvcnkgwrcgdG9rZW4gwrcgQXJjaGl0ZWN0dXJlIGNvbXBvbmVudDwvdGl0bGU+CiAgICAgICAgICA8cmVjdCB4PSI0OTIiIHk9IjIyNiIgd2lkdGg9IjE1MCIgaGVpZ2h0PSI2NCIgcng9IjYiIGNsYXNzPSJjLW1hc2siLz4KICAgICAgICAgIDxyZWN0IHg9IjQ5MiIgeT0iMjI2IiB3aWR0aD0iMTUwIiBoZWlnaHQ9IjY0IiByeD0iNiIgY2xhc3M9ImMtZnJvbnRlbmQiIHN0cm9rZS13aWR0aD0iMS41Ii8+CiAgICAgICAgICA8ZyBhcmlhLWhpZGRlbj0idHJ1ZSIgZGF0YS1zZW1hbnRpYy1zaWdpbD0iZnJvbnRlbmQiIGNsYXNzPSJzZW1hbnRpYy1zaWdpbCBzLWZyb250ZW5kIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSg0OTggMjMyKSBzY2FsZSgwLjY4NzUpIj4KICAgICAgICAgICAgPHJlY3QgeD0iMiIgeT0iMyIgd2lkdGg9IjEyIiBoZWlnaHQ9IjEwIiByeD0iMiIvPgogICAgICAgICAgICA8cGF0aCBkPSJNMiA2LjVoMTIiLz4KICAgICAgICAgICAgPGNpcmNsZSBjeD0iNC4xIiBjeT0iNC44IiByPSIuNyIgY2xhc3M9InNpZ2lsLWZpbGwiLz4KICAgICAgICAgICAgPGNpcmNsZSBjeD0iNi4zIiBjeT0iNC44IiByPSIuNyIgY2xhc3M9InNpZ2lsLWZpbGwiLz4KICAgICAgICAgIDwvZz4KICAgICAgICAgIDx0ZXh0IGRhdGEtbm9kZS1sYWJlbD0iIiB4PSI1NjciIHk9IjI1NiIgY2xhc3M9InQtcHJpbWFyeSIgZm9udC1zaXplPSIxMSIgZm9udC13ZWlnaHQ9IjYwMCIgdGV4dC1hbmNob3I9Im1pZGRsZSI+U2hhcmVkIHNlc3Npb248L3RleHQ+CiAgICAgICAgPHRleHQgeD0iNTY3IiB5PSIyNzIiIGNsYXNzPSJ0LW11dGVkIiBmb250LXNpemU9IjkiIHRleHQtYW5jaG9yPSJtaWRkbGUiPkRlZGljYXRlZCBkaXJlY3RvcnkgwrcgdG9rZW48L3RleHQ+CiAgICAgICAgPC9nPgoKICAgICAgICA8IS0tIENvbm5lY3Rpb24gbGFiZWxzIC0tPgogICAgICAgIDxnIGRhdGEtZWRnZS1mcm9tPSJyb3RhdG9yIiBkYXRhLWVkZ2UtdG89InN1cHBsaWVyIiBkYXRhLWVkZ2UtbGFiZWw9IkNoZWNrIGV2ZXJ5IDEwIG1pbiIgZGF0YS1lZGdlLWtleT0iMCIgZGF0YS1lZGdlLWlkPSJyb3RhdG9yLWNoZWNrcy1zdXBwbGllciI+CiAgICAgICAgICA8cmVjdCB4PSIxNzUuMyIgeT0iMzYiIHdpZHRoPSI3Mi40IiBoZWlnaHQ9IjE0IiByeD0iMyIgY2xhc3M9ImMtbWFzayIvPgogICAgICAgICAgPHRleHQgeD0iMjExLjUiIHk9IjQ2IiBjbGFzcz0idC1tdXRlZCIgZm9udC1zaXplPSI4IiB0ZXh0LWFuY2hvcj0ibWlkZGxlIj5DaGVjayBldmVyeSAxMCBtaW48L3RleHQ+CiAgICAgICAgPC9nPgogICAgICAgIDxnIGRhdGEtZWRnZS1mcm9tPSJsYXVuY2hlciIgZGF0YS1lZGdlLXRvPSJzdXBwbGllciIgZGF0YS1lZGdlLWxhYmVsPSJGZXRjaCBhdCBsYXVuY2giIGRhdGEtZWRnZS1rZXk9IjEiIGRhdGEtZWRnZS1pZD0ibGF1bmNoZXItZmV0Y2hlcy1sYXRlc3QiPgogICAgICAgICAgPHJlY3QgeD0iNDA5LjkiIHk9IjM2IiB3aWR0aD0iNzcuMiIgaGVpZ2h0PSIxNCIgcng9IjMiIGNsYXNzPSJjLW1hc2siLz4KICAgICAgICAgIDx0ZXh0IHg9IjQ0OC41IiB5PSI0NiIgY2xhc3M9InQtYmFja2VuZCIgZm9udC1zaXplPSI4IiB0ZXh0LWFuY2hvcj0ibWlkZGxlIj5GZXRjaCBhdCBsYXVuY2g8L3RleHQ+CiAgICAgICAgPC9nPgogICAgICAgIDxnIGRhdGEtZWRnZS1mcm9tPSJyb3RhdG9yIiBkYXRhLWVkZ2UtdG89ImtleWNoYWluIiBkYXRhLWVkZ2UtbGFiZWw9IlJlcGxhY2UgY29weSIgZGF0YS1lZGdlLWtleT0iMiIgZGF0YS1lZGdlLWlkPSJyb3RhdG9yLXNhdmVzLWNvcHkiPgogICAgICAgICAgPHJlY3QgeD0iMTgxLjQiIHk9IjEzNyIgd2lkdGg9IjUzLjE5OTk5OTk5OTk5OTk5NiIgaGVpZ2h0PSIxNCIgcng9IjMiIGNsYXNzPSJjLW1hc2siLz4KICAgICAgICAgIDx0ZXh0IHg9IjIwOCIgeT0iMTQ3IiBjbGFzcz0idC1zZWN1cml0eSIgZm9udC1zaXplPSI4IiB0ZXh0LWFuY2hvcj0ibWlkZGxlIj5SZXBsYWNlIGNvcHk8L3RleHQ+CiAgICAgICAgPC9nPgogICAgICAgIDxnIGRhdGEtZWRnZS1mcm9tPSJsYXVuY2hlciIgZGF0YS1lZGdlLXRvPSJrZXljaGFpbiIgZGF0YS1lZGdlLWxhYmVsPSJGYWxsYmFjayBjb3B5IiBkYXRhLWVkZ2Uta2V5PSIzIiBkYXRhLWVkZ2UtaWQ9ImxhdW5jaGVyLWZhbGxzLWJhY2siPgogICAgICAgICAgPHJlY3QgeD0iNDE4LjIiIHk9IjEzNyIgd2lkdGg9IjY3LjYiIGhlaWdodD0iMTQiIHJ4PSIzIiBjbGFzcz0iYy1tYXNrIi8+CiAgICAgICAgICA8dGV4dCB4PSI0NTIiIHk9IjE0NyIgY2xhc3M9InQtbWVzc2FnZWJ1cyIgZm9udC1zaXplPSI4IiB0ZXh0LWFuY2hvcj0ibWlkZGxlIj5GYWxsYmFjayBjb3B5PC90ZXh0PgogICAgICAgIDwvZz4KICAgICAgICA8ZyBkYXRhLWVkZ2UtZnJvbT0ibGF1bmNoZXIiIGRhdGEtZWRnZS10bz0ic2Vzc2lvbi1zaGFyZWQiIGRhdGEtZWRnZS1sYWJlbD0iT25seSB0aGlzIHByb2Nlc3MiIGRhdGEtZWRnZS1rZXk9IjQiIGRhdGEtZWRnZS1pZD0ibGF1bmNoZXItaW5qZWN0cyI+CiAgICAgICAgICA8cmVjdCB4PSI1MzAuNiIgeT0iMTgwIiB3aWR0aD0iODYuOCIgaGVpZ2h0PSIxNCIgcng9IjMiIGNsYXNzPSJjLW1hc2siLz4KICAgICAgICAgIDx0ZXh0IHg9IjU3NCIgeT0iMTkwIiBjbGFzcz0idC1iYWNrZW5kIiBmb250LXNpemU9IjgiIHRleHQtYW5jaG9yPSJtaWRkbGUiPk9ubHkgdGhpcyBwcm9jZXNzPC90ZXh0PgogICAgICAgIDwvZz4KCiAgICAgICAgPCEtLSBCb3VuZGFyeSBsYWJlbHMgKGZvcmVncm91bmQgbWFza3Mga2VlcCByb3V0ZXMgb3V0IG9mIHRpdGxlcykgLS0+CgoKICAgICAgICA8IS0tIExlZ2VuZCAtLT4KCiAgICAgIDwvc3ZnPg==" loading="lazy"><img class="cc-diagram-dark" alt="Claude Code setup. The rotation worker and shared launcher fetch tokens from the credential server. The launcher passes a token only to the shared session and falls back to the Keychain copy if the server does not respond. The personal session uses the login in the default directory." src="data:image/svg+xml;base64,PHN2ZyBkYXRhLXRoZW1lPSJkYXJrIiB2aWV3Qm94PSIwIDAgNjYwIDMxNCIgcm9sZT0iaW1nIiBsYW5nPSJlbiIgeG1sOmxhbmc9ImVuIiBhcmlhLWxhYmVsbGVkYnk9ImFyY2hpZnktZGlhZ3JhbS10aXRsZSBhcmNoaWZ5LWRpYWdyYW0tZGVzY3JpcHRpb24iIGRhdGEtcHJlc2V0PSJjbGFzc2ljIiBkYXRhLXF1YWxpdHktcHJvZmlsZT0ic2hvd2Nhc2UiIHdpZHRoPSI2NjAiIGhlaWdodD0iMzE0IiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxzdHlsZT5zdmcgeyBmb250LWZhbWlseTogLWFwcGxlLXN5c3RlbSwgQmxpbmtNYWNTeXN0ZW1Gb250LCAiQXBwbGUgU0QgR290aGljIE5lbyIsICJNYWxndW4gR290aGljIiwgIk5vdG8gU2FucyBLUiIsICJOb3RvIFNhbnMgQ0pLIEtSIiwgc2Fucy1zZXJpZjsgfQo6cm9vdCwgW2RhdGEtdGhlbWU9ImRhcmsiXSB7IC0tYmc6ICMwMjA2MTc7IC0tZ3JpZDogIzFlMjkzYjsgLS10ZXh0OiAjZmZmZmZmOyAtLXRleHQtbXV0ZWQ6ICM5NGEzYjg7IC0tdGV4dC1kaW06ICM0NzU1Njk7IC0tdGV4dC1mYWludDogIzdkOGRhMTsgLS1wYW5lbDogcmdiYSgxNSwgMjMsIDQyLCAwLjUpOyAtLXBhbmVsLWJvcmRlcjogIzFlMjkzYjsgLS1sYW5lLWZpbGw6IHJnYmEoMTUsIDIzLCA0MiwgMC4yMik7IC0tbGFuZS1zdHJva2U6ICMzMzQxNTU7IC0tYXJyb3c6ICM2NDc0OGI7IC0tYXJyb3ctZW1waGFzaXM6ICMzNGQzOTk7IC0tbWFzazogIzBmMTcyYTsgLS1mcm9udGVuZC1maWxsOiByZ2JhKDgsIDUxLCA2OCwgMC40KTsgLS1mcm9udGVuZC1zdHJva2U6ICMyMmQzZWU7IC0tYmFja2VuZC1maWxsOiByZ2JhKDYsIDc4LCA1OSwgMC40KTsgLS1iYWNrZW5kLXN0cm9rZTogIzM0ZDM5OTsgLS1kYXRhYmFzZS1maWxsOiByZ2JhKDc2LCAyOSwgMTQ5LCAwLjQpOyAtLWRhdGFiYXNlLXN0cm9rZTogI2E3OGJmYTsgLS1jbG91ZC1maWxsOiByZ2JhKDEyMCwgNTMsIDE1LCAwLjMpOyAtLWNsb3VkLXN0cm9rZTogI2ZiYmYyNDsgLS1zZWN1cml0eS1maWxsOiByZ2JhKDEzNiwgMTksIDU1LCAwLjQpOyAtLXNlY3VyaXR5LXN0cm9rZTogI2ZiNzE4NTsgLS1tZXNzYWdlYnVzLWZpbGw6IHJnYmEoMjUxLCAxNDYsIDYwLCAwLjMpOyAtLW1lc3NhZ2VidXMtc3Ryb2tlOiAjZmI5MjNjOyAtLWV4dGVybmFsLWZpbGw6IHJnYmEoMzAsIDQxLCA1OSwgMC41KTsgLS1leHRlcm5hbC1zdHJva2U6ICM5NGEzYjg7IC0tdG9vbGJhci1iZzogcmdiYSgxNSwgMjMsIDQyLCAwLjgpOyAtLXRvb2xiYXItYm9yZGVyOiAjMzM0MTU1OyAtLXRvb2xiYXItdGV4dDogI2UyZThmMDsgLS10b29sYmFyLWhvdmVyOiByZ2JhKDE1LCAyMywgNDIsIDAuOTUpOyAtLXRvb2xiYXItbWVudS1iZzogIzBmMTcyYTsgfQpzdmcgeyB3aWR0aDogMTAwJTsgbWluLXdpZHRoOiBtaW4oOTAwcHgsIDEwMCUpOyBkaXNwbGF5OiBibG9jazsgdHJhbnNmb3JtLW9yaWdpbjogMHB4IDBweDsgdHJhbnNpdGlvbjogdHJhbnNmb3JtIDAuMThzOyB9Ci5jLWdyaWQgeyBzdHJva2U6IHZhcigtLWdyaWQpOyBmaWxsOiBub25lOyB9Ci5jLW1hc2sgeyBmaWxsOiB2YXIoLS1tYXNrKTsgc3Ryb2tlOiBub25lOyB9Ci5jLWZyb250ZW5kIHsgZmlsbDogdmFyKC0tZnJvbnRlbmQtZmlsbCk7IHN0cm9rZTogdmFyKC0tZnJvbnRlbmQtc3Ryb2tlKTsgfQouYy1iYWNrZW5kIHsgZmlsbDogdmFyKC0tYmFja2VuZC1maWxsKTsgc3Ryb2tlOiB2YXIoLS1iYWNrZW5kLXN0cm9rZSk7IH0KLmMtc2VjdXJpdHkgeyBmaWxsOiB2YXIoLS1zZWN1cml0eS1maWxsKTsgc3Ryb2tlOiB2YXIoLS1zZWN1cml0eS1zdHJva2UpOyB9Ci5jLWV4dGVybmFsIHsgZmlsbDogdmFyKC0tZXh0ZXJuYWwtZmlsbCk7IHN0cm9rZTogdmFyKC0tZXh0ZXJuYWwtc3Ryb2tlKTsgfQoudC1wcmltYXJ5IHsgZmlsbDogdmFyKC0tdGV4dCk7IH0KLnQtbXV0ZWQgeyBmaWxsOiB2YXIoLS10ZXh0LW11dGVkKTsgfQoudC1iYWNrZW5kIHsgZmlsbDogdmFyKC0tYmFja2VuZC1zdHJva2UpOyB9Ci50LXNlY3VyaXR5IHsgZmlsbDogdmFyKC0tc2VjdXJpdHktc3Ryb2tlKTsgfQoudC1tZXNzYWdlYnVzIHsgZmlsbDogdmFyKC0tbWVzc2FnZWJ1cy1zdHJva2UpOyB9CnN2ZyAuc2VtYW50aWMtc2lnaWwgeyBmaWxsOiBub25lOyBzdHJva2U6IGN1cnJlbnRjb2xvcjsgc3Ryb2tlLXdpZHRoOiAxLjM1OyBzdHJva2UtbGluZWNhcDogcm91bmQ7IHN0cm9rZS1saW5lam9pbjogcm91bmQ7IG9wYWNpdHk6IDAuNzY7IHBvaW50ZXItZXZlbnRzOiBub25lOyB9CnN2ZyAuc2VtYW50aWMtc2lnaWwgPiAqIHsgdmVjdG9yLWVmZmVjdDogbm9uLXNjYWxpbmctc3Ryb2tlOyB9CnN2ZyAuc2VtYW50aWMtc2lnaWwgLnNpZ2lsLWZpbGwgeyBmaWxsOiBjdXJyZW50Y29sb3I7IHN0cm9rZTogbm9uZTsgfQpzdmcgLnMtZnJvbnRlbmQgeyBjb2xvcjogdmFyKC0tZnJvbnRlbmQtc3Ryb2tlKTsgfQpzdmcgLnMtYmFja2VuZCB7IGNvbG9yOiB2YXIoLS1iYWNrZW5kLXN0cm9rZSk7IH0Kc3ZnIC5zLXNlY3VyaXR5IHsgY29sb3I6IHZhcigtLXNlY3VyaXR5LXN0cm9rZSk7IH0Kc3ZnIC5zLWV4dGVybmFsIHsgY29sb3I6IHZhcigtLWV4dGVybmFsLXN0cm9rZSk7IH0KLmEtZGVmYXVsdCB7IHN0cm9rZTogdmFyKC0tYXJyb3cpOyBmaWxsOiBub25lOyB9Ci5hLWVtcGhhc2lzIHsgc3Ryb2tlOiB2YXIoLS1hcnJvdy1lbXBoYXNpcyk7IGZpbGw6IG5vbmU7IH0KLmEtc2VjdXJpdHkgeyBzdHJva2U6IHZhcigtLXNlY3VyaXR5LXN0cm9rZSk7IGZpbGw6IG5vbmU7IHN0cm9rZS1kYXNoYXJyYXk6IDUsIDU7IH0KLmEtZGFzaGVkIHsgc3Ryb2tlOiB2YXIoLS1kYXRhYmFzZS1zdHJva2UpOyBmaWxsOiBub25lOyBzdHJva2UtZGFzaGFycmF5OiA0LCA0OyB9Ci5tLWRlZmF1bHQgeyBmaWxsOiB2YXIoLS1hcnJvdyk7IH0KLm0tZW1waGFzaXMgeyBmaWxsOiB2YXIoLS1hcnJvdy1lbXBoYXNpcyk7IH0KLm0tc2VjdXJpdHkgeyBmaWxsOiB2YXIoLS1zZWN1cml0eS1zdHJva2UpOyB9Ci5tLWRhc2hlZCB7IGZpbGw6IHZhcigtLWRhdGFiYXNlLXN0cm9rZSk7IH0Kc3ZnIFtkYXRhLW5vZGUtaWRdIHsgY3Vyc29yOiBwb2ludGVyOyB0cmFuc2l0aW9uOiBvcGFjaXR5IDAuMThzLCBmaWx0ZXIgMC4xOHM7IH0Kc3ZnIFtkYXRhLWVkZ2UtZnJvbV0geyB0cmFuc2l0aW9uOiBvcGFjaXR5IDAuMThzLCBmaWx0ZXIgMC4xOHM7IH0KOnJvb3QsIHN2ZyB7IC0tYmc6ICMwMjA2MTc7IC0tZ3JpZDogIzFlMjkzYjsgLS10ZXh0OiAjZmZmZmZmOyAtLXRleHQtbXV0ZWQ6ICM5NGEzYjg7IC0tdGV4dC1kaW06ICM0NzU1Njk7IC0tdGV4dC1mYWludDogIzdkOGRhMTsgLS1wYW5lbDogcmdiYSgxNSwgMjMsIDQyLCAwLjUpOyAtLXBhbmVsLWJvcmRlcjogIzFlMjkzYjsgLS1sYW5lLWZpbGw6IHJnYmEoMTUsIDIzLCA0MiwgMC4yMik7IC0tbGFuZS1zdHJva2U6ICMzMzQxNTU7IC0tYXJyb3c6ICM2NDc0OGI7IC0tYXJyb3ctZW1waGFzaXM6ICMzNGQzOTk7IC0tbWFzazogIzBmMTcyYTsgLS1mcm9udGVuZC1maWxsOiByZ2JhKDgsIDUxLCA2OCwgMC40KTsgLS1mcm9udGVuZC1zdHJva2U6ICMyMmQzZWU7IC0tYmFja2VuZC1maWxsOiByZ2JhKDYsIDc4LCA1OSwgMC40KTsgLS1iYWNrZW5kLXN0cm9rZTogIzM0ZDM5OTsgLS1kYXRhYmFzZS1maWxsOiByZ2JhKDc2LCAyOSwgMTQ5LCAwLjQpOyAtLWRhdGFiYXNlLXN0cm9rZTogI2E3OGJmYTsgLS1jbG91ZC1maWxsOiByZ2JhKDEyMCwgNTMsIDE1LCAwLjMpOyAtLWNsb3VkLXN0cm9rZTogI2ZiYmYyNDsgLS1zZWN1cml0eS1maWxsOiByZ2JhKDEzNiwgMTksIDU1LCAwLjQpOyAtLXNlY3VyaXR5LXN0cm9rZTogI2ZiNzE4NTsgLS1tZXNzYWdlYnVzLWZpbGw6IHJnYmEoMjUxLCAxNDYsIDYwLCAwLjMpOyAtLW1lc3NhZ2VidXMtc3Ryb2tlOiAjZmI5MjNjOyAtLWV4dGVybmFsLWZpbGw6IHJnYmEoMzAsIDQxLCA1OSwgMC41KTsgLS1leHRlcm5hbC1zdHJva2U6ICM5NGEzYjg7IC0tdG9vbGJhci1iZzogcmdiYSgxNSwgMjMsIDQyLCAwLjgpOyAtLXRvb2xiYXItYm9yZGVyOiAjMzM0MTU1OyAtLXRvb2xiYXItdGV4dDogI2UyZThmMDsgLS10b29sYmFyLWhvdmVyOiByZ2JhKDE1LCAyMywgNDIsIDAuOTUpOyAtLXRvb2xiYXItbWVudS1iZzogIzBmMTcyYTsgfQpzdmdbZGF0YS10aGVtZT0iZGFyayJdIHsgLS1iZzogIzAyMDYxNzsgLS1ncmlkOiAjMWUyOTNiOyAtLXRleHQ6ICNmZmZmZmY7IC0tdGV4dC1tdXRlZDogIzk0YTNiODsgLS10ZXh0LWRpbTogIzQ3NTU2OTsgLS10ZXh0LWZhaW50OiAjN2Q4ZGExOyAtLXBhbmVsOiByZ2JhKDE1LCAyMywgNDIsIDAuNSk7IC0tcGFuZWwtYm9yZGVyOiAjMWUyOTNiOyAtLWxhbmUtZmlsbDogcmdiYSgxNSwgMjMsIDQyLCAwLjIyKTsgLS1sYW5lLXN0cm9rZTogIzMzNDE1NTsgLS1hcnJvdzogIzY0NzQ4YjsgLS1hcnJvdy1lbXBoYXNpczogIzM0ZDM5OTsgLS1tYXNrOiAjMGYxNzJhOyAtLWZyb250ZW5kLWZpbGw6IHJnYmEoOCwgNTEsIDY4LCAwLjQpOyAtLWZyb250ZW5kLXN0cm9rZTogIzIyZDNlZTsgLS1iYWNrZW5kLWZpbGw6IHJnYmEoNiwgNzgsIDU5LCAwLjQpOyAtLWJhY2tlbmQtc3Ryb2tlOiAjMzRkMzk5OyAtLWRhdGFiYXNlLWZpbGw6IHJnYmEoNzYsIDI5LCAxNDksIDAuNCk7IC0tZGF0YWJhc2Utc3Ryb2tlOiAjYTc4YmZhOyAtLWNsb3VkLWZpbGw6IHJnYmEoMTIwLCA1MywgMTUsIDAuMyk7IC0tY2xvdWQtc3Ryb2tlOiAjZmJiZjI0OyAtLXNlY3VyaXR5LWZpbGw6IHJnYmEoMTM2LCAxOSwgNTUsIDAuNCk7IC0tc2VjdXJpdHktc3Ryb2tlOiAjZmI3MTg1OyAtLW1lc3NhZ2VidXMtZmlsbDogcmdiYSgyNTEsIDE0NiwgNjAsIDAuMyk7IC0tbWVzc2FnZWJ1cy1zdHJva2U6ICNmYjkyM2M7IC0tZXh0ZXJuYWwtZmlsbDogcmdiYSgzMCwgNDEsIDU5LCAwLjUpOyAtLWV4dGVybmFsLXN0cm9rZTogIzk0YTNiODsgLS10b29sYmFyLWJnOiByZ2JhKDE1LCAyMywgNDIsIDAuOCk7IC0tdG9vbGJhci1ib3JkZXI6ICMzMzQxNTU7IC0tdG9vbGJhci10ZXh0OiAjZTJlOGYwOyAtLXRvb2xiYXItaG92ZXI6IHJnYmEoMTUsIDIzLCA0MiwgMC45NSk7IC0tdG9vbGJhci1tZW51LWJnOiAjMGYxNzJhOyB9CnJlY3QuYy1iZy1yZWN0IHsgZmlsbDogdmFyKC0tYmcpOyB9PC9zdHlsZT48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBjbGFzcz0iYy1iZy1yZWN0Ii8+CiAgICAgICAgPHRpdGxlIGlkPSJhcmNoaWZ5LWRpYWdyYW0tdGl0bGUiPkNsYXVkZSBDb2RlIGxhdW5jaCBwYXRocyBhbmQgYmFja2dyb3VuZCBjcmVkZW50aWFsIHJvdGF0aW9uPC90aXRsZT4KICAgICAgICA8ZGVzYyBpZD0iYXJjaGlmeS1kaWFncmFtLWRlc2NyaXB0aW9uIj5BbiBhcmNoaXRlY3R1cmUgZGlhZ3JhbSBnZW5lcmF0ZWQgYnkgQXJjaGlmeS48L2Rlc2M+CiAgICAgICAgPCEtLSBEZWZpbml0aW9ucyAtLT4KICAgICAgICA8ZGVmcz4KICAgICAgICAgIDxtYXJrZXIgaWQ9ImFycm93aGVhZCIgbWFya2VyV2lkdGg9IjEwIiBtYXJrZXJIZWlnaHQ9IjciIHJlZlg9IjkiIHJlZlk9IjMuNSIgb3JpZW50PSJhdXRvIj4KICAgICAgICAgICAgPHBvbHlnb24gcG9pbnRzPSIwIDAsIDEwIDMuNSwgMCA3IiBjbGFzcz0ibS1kZWZhdWx0Ii8+CiAgICAgICAgICA8L21hcmtlcj4KICAgICAgICAgIDxtYXJrZXIgaWQ9ImFycm93aGVhZC1lbXBoYXNpcyIgbWFya2VyV2lkdGg9IjEwIiBtYXJrZXJIZWlnaHQ9IjciIHJlZlg9IjkiIHJlZlk9IjMuNSIgb3JpZW50PSJhdXRvIj4KICAgICAgICAgICAgPHBvbHlnb24gcG9pbnRzPSIwIDAsIDEwIDMuNSwgMCA3IiBjbGFzcz0ibS1lbXBoYXNpcyIvPgogICAgICAgICAgPC9tYXJrZXI+CiAgICAgICAgICA8bWFya2VyIGlkPSJhcnJvd2hlYWQtc2VjdXJpdHkiIG1hcmtlcldpZHRoPSIxMCIgbWFya2VySGVpZ2h0PSI3IiByZWZYPSI5IiByZWZZPSIzLjUiIG9yaWVudD0iYXV0byI+CiAgICAgICAgICAgIDxwb2x5Z29uIHBvaW50cz0iMCAwLCAxMCAzLjUsIDAgNyIgY2xhc3M9Im0tc2VjdXJpdHkiLz4KICAgICAgICAgIDwvbWFya2VyPgogICAgICAgICAgPG1hcmtlciBpZD0iYXJyb3doZWFkLWRhc2hlZCIgbWFya2VyV2lkdGg9IjEwIiBtYXJrZXJIZWlnaHQ9IjciIHJlZlg9IjkiIHJlZlk9IjMuNSIgb3JpZW50PSJhdXRvIj4KICAgICAgICAgICAgPHBvbHlnb24gcG9pbnRzPSIwIDAsIDEwIDMuNSwgMCA3IiBjbGFzcz0ibS1kYXNoZWQiLz4KICAgICAgICAgIDwvbWFya2VyPgogICAgICAgICAgPHBhdHRlcm4gaWQ9ImdyaWQiIHdpZHRoPSI0MCIgaGVpZ2h0PSI0MCIgcGF0dGVyblVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+CiAgICAgICAgICAgIDxwYXRoIGQ9Ik0gNDAgMCBMIDAgMCAwIDQwIiBjbGFzcz0iYy1ncmlkIiBzdHJva2Utd2lkdGg9IjAuNSIvPgogICAgICAgICAgPC9wYXR0ZXJuPgogICAgICAgIDwvZGVmcz4KCiAgICAgICAgPCEtLSBCYWNrZ3JvdW5kIEdyaWQgLS0+CiAgICAgICAgPHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0idXJsKCNncmlkKSIvPgoKICAgICAgICA8IS0tIEJvdW5kYXJpZXMgKGJlaGluZCBldmVyeXRoaW5nKSAtLT4KCgogICAgICAgIDwhLS0gQ29ubmVjdGlvbiBwYXRocyAoYmVmb3JlIGNvbXBvbmVudHMgZm9yIGNvcnJlY3Qgei1vcmRlcikgLS0+CiAgICAgICAgPHBhdGggZGF0YS1lZGdlLWZyb209InJvdGF0b3IiIGRhdGEtZWRnZS10bz0ic3VwcGxpZXIiIGRhdGEtZWRnZS1sYWJlbD0iQ2hlY2sgZXZlcnkgMTAgbWluIiBkYXRhLWVkZ2Uta2V5PSIwIiBkYXRhLWVkZ2UtaWQ9InJvdGF0b3ItY2hlY2tzLXN1cHBsaWVyIiBkYXRhLWNvbXBvc2l0aW9uLXBvaW50cz0iMTY4LDU2OzI1NSw1NiIgZD0iTSAxNjggNTYgTCAyNTUgNTYiIGNsYXNzPSJhLWRlZmF1bHQiIHN0cm9rZS13aWR0aD0iMS41IiBtYXJrZXItZW5kPSJ1cmwoI2Fycm93aGVhZCkiLz4KICAgICAgICA8cGF0aCBkYXRhLWVkZ2UtZnJvbT0ibGF1bmNoZXIiIGRhdGEtZWRnZS10bz0ic3VwcGxpZXIiIGRhdGEtZWRnZS1sYWJlbD0iRmV0Y2ggYXQgbGF1bmNoIiBkYXRhLWVkZ2Uta2V5PSIxIiBkYXRhLWVkZ2UtaWQ9ImxhdW5jaGVyLWZldGNoZXMtbGF0ZXN0IiBkYXRhLWNvbXBvc2l0aW9uLXBvaW50cz0iNDkyLDU2OzQwNSw1NiIgZD0iTSA0OTIgNTYgTCA0MDUgNTYiIGNsYXNzPSJhLWVtcGhhc2lzIiBzdHJva2Utd2lkdGg9IjEuOCIgbWFya2VyLWVuZD0idXJsKCNhcnJvd2hlYWQtZW1waGFzaXMpIi8+CiAgICAgICAgPHBhdGggZGF0YS1lZGdlLWZyb209InJvdGF0b3IiIGRhdGEtZWRnZS10bz0ia2V5Y2hhaW4iIGRhdGEtZWRnZS1sYWJlbD0iUmVwbGFjZSBjb3B5IiBkYXRhLWVkZ2Uta2V5PSIyIiBkYXRhLWVkZ2UtaWQ9InJvdGF0b3Itc2F2ZXMtY29weSIgZGF0YS1jb21wb3NpdGlvbi1wb2ludHM9IjkzLDg4OzkzLDE1NzszMjMsMTU3OzMyMywyMjYiIGQ9Ik0gOTMgODggTCA5MyAxNDkgUSA5MyAxNTcgMTAxIDE1NyBMIDMxNSAxNTcgUSAzMjMgMTU3IDMyMyAxNjUgTCAzMjMgMjI2IiBjbGFzcz0iYS1zZWN1cml0eSIgc3Ryb2tlLXdpZHRoPSIxLjUiIG1hcmtlci1lbmQ9InVybCgjYXJyb3doZWFkLXNlY3VyaXR5KSIvPgogICAgICAgIDxwYXRoIGRhdGEtZWRnZS1mcm9tPSJsYXVuY2hlciIgZGF0YS1lZGdlLXRvPSJrZXljaGFpbiIgZGF0YS1lZGdlLWxhYmVsPSJGYWxsYmFjayBjb3B5IiBkYXRhLWVkZ2Uta2V5PSIzIiBkYXRhLWVkZ2UtaWQ9ImxhdW5jaGVyLWZhbGxzLWJhY2siIGRhdGEtY29tcG9zaXRpb24tcG9pbnRzPSI1NjcsODg7NTY3LDE1NzszMzcsMTU3OzMzNywyMjYiIGQ9Ik0gNTY3IDg4IEwgNTY3IDE0OSBRIDU2NyAxNTcgNTU5IDE1NyBMIDM0NSAxNTcgUSAzMzcgMTU3IDMzNyAxNjUgTCAzMzcgMjI2IiBjbGFzcz0iYS1kYXNoZWQiIHN0cm9rZS13aWR0aD0iMS41IiBtYXJrZXItZW5kPSJ1cmwoI2Fycm93aGVhZC1kYXNoZWQpIi8+CiAgICAgICAgPHBhdGggZGF0YS1lZGdlLWZyb209ImxhdW5jaGVyIiBkYXRhLWVkZ2UtdG89InNlc3Npb24tc2hhcmVkIiBkYXRhLWVkZ2UtbGFiZWw9Ik9ubHkgdGhpcyBwcm9jZXNzIiBkYXRhLWVkZ2Uta2V5PSI0IiBkYXRhLWVkZ2UtaWQ9ImxhdW5jaGVyLWluamVjdHMiIGRhdGEtY29tcG9zaXRpb24tcG9pbnRzPSI1NjcsODg7NTY3LDIyNiIgZD0iTSA1NjcgODggTCA1NjcgMjI2IiBjbGFzcz0iYS1lbXBoYXNpcyIgc3Ryb2tlLXdpZHRoPSIxLjgiIG1hcmtlci1lbmQ9InVybCgjYXJyb3doZWFkLWVtcGhhc2lzKSIvPgoKICAgICAgICA8IS0tIENvbXBvbmVudHMgLS0+CiAgICAgICAgPGcgaWQ9Im5vZGUtcm90YXRvciIgZGF0YS1ub2RlLWlkPSJyb3RhdG9yIiBkYXRhLW5vZGUtbGFiZWw9IlJvdGF0aW9uIHdvcmtlciIgdGFiaW5kZXg9IjAiIHJvbGU9ImJ1dHRvbiIgYXJpYS1sYWJlbD0iRm9jdXMgUm90YXRpb24gd29ya2VyLCBsYXVuY2hkIMK3IGV2ZXJ5IDEwIG1pbiwgQXJjaGl0ZWN0dXJlIGNvbXBvbmVudCIgYXJpYS1wcmVzc2VkPSJmYWxzZSIgZGF0YS1ub2RlLWtpbmQ9ImJhY2tlbmQiIGRhdGEtbm9kZS1zdWJsYWJlbD0ibGF1bmNoZCDCtyBldmVyeSAxMCBtaW4iIGRhdGEtbm9kZS1jb250ZXh0PSJBcmNoaXRlY3R1cmUgY29tcG9uZW50Ij4KICAgICAgICAgIDx0aXRsZT5Sb3RhdGlvbiB3b3JrZXIgwrcgbGF1bmNoZCDCtyBldmVyeSAxMCBtaW4gwrcgQXJjaGl0ZWN0dXJlIGNvbXBvbmVudDwvdGl0bGU+CiAgICAgICAgICA8cmVjdCB4PSIxOCIgeT0iMjQiIHdpZHRoPSIxNTAiIGhlaWdodD0iNjQiIHJ4PSI2IiBjbGFzcz0iYy1tYXNrIi8+CiAgICAgICAgICA8cmVjdCB4PSIxOCIgeT0iMjQiIHdpZHRoPSIxNTAiIGhlaWdodD0iNjQiIHJ4PSI2IiBjbGFzcz0iYy1iYWNrZW5kIiBzdHJva2Utd2lkdGg9IjEuNSIvPgogICAgICAgICAgPGcgYXJpYS1oaWRkZW49InRydWUiIGRhdGEtc2VtYW50aWMtc2lnaWw9ImJhY2tlbmQiIGNsYXNzPSJzZW1hbnRpYy1zaWdpbCBzLWJhY2tlbmQiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDI0IDMwKSBzY2FsZSgwLjY4NzUpIj4KICAgICAgICAgICAgPHBhdGggZD0iTTYgMyAzIDhsMyA1TTEwIDNsMyA1LTMgNSIvPgogICAgICAgICAgPC9nPgogICAgICAgICAgPHRleHQgZGF0YS1ub2RlLWxhYmVsPSIiIHg9IjkzIiB5PSI1NCIgY2xhc3M9InQtcHJpbWFyeSIgZm9udC1zaXplPSIxMSIgZm9udC13ZWlnaHQ9IjYwMCIgdGV4dC1hbmNob3I9Im1pZGRsZSI+Um90YXRpb24gd29ya2VyPC90ZXh0PgogICAgICAgIDx0ZXh0IHg9IjkzIiB5PSI3MCIgY2xhc3M9InQtbXV0ZWQiIGZvbnQtc2l6ZT0iOSIgdGV4dC1hbmNob3I9Im1pZGRsZSI+bGF1bmNoZCDCtyBldmVyeSAxMCBtaW48L3RleHQ+CiAgICAgICAgPC9nPgoKICAgICAgICA8ZyBpZD0ibm9kZS1zdXBwbGllciIgZGF0YS1ub2RlLWlkPSJzdXBwbGllciIgZGF0YS1ub2RlLWxhYmVsPSJDcmVkZW50aWFsIHNlcnZlciIgdGFiaW5kZXg9IjAiIHJvbGU9ImJ1dHRvbiIgYXJpYS1sYWJlbD0iRm9jdXMgQ3JlZGVudGlhbCBzZXJ2ZXIsIFByb3ZpZGVzIHVzYWJsZSB0b2tlbnMsIEFyY2hpdGVjdHVyZSBjb21wb25lbnQiIGFyaWEtcHJlc3NlZD0iZmFsc2UiIGRhdGEtbm9kZS1raW5kPSJleHRlcm5hbCIgZGF0YS1ub2RlLXN1YmxhYmVsPSJQcm92aWRlcyB1c2FibGUgdG9rZW5zIiBkYXRhLW5vZGUtY29udGV4dD0iQXJjaGl0ZWN0dXJlIGNvbXBvbmVudCI+CiAgICAgICAgICA8dGl0bGU+Q3JlZGVudGlhbCBzZXJ2ZXIgwrcgUHJvdmlkZXMgdXNhYmxlIHRva2VucyDCtyBBcmNoaXRlY3R1cmUgY29tcG9uZW50PC90aXRsZT4KICAgICAgICAgIDxyZWN0IHg9IjI1NSIgeT0iMjQiIHdpZHRoPSIxNTAiIGhlaWdodD0iNjQiIHJ4PSI2IiBjbGFzcz0iYy1tYXNrIi8+CiAgICAgICAgICA8cmVjdCB4PSIyNTUiIHk9IjI0IiB3aWR0aD0iMTUwIiBoZWlnaHQ9IjY0IiByeD0iNiIgY2xhc3M9ImMtZXh0ZXJuYWwiIHN0cm9rZS13aWR0aD0iMS41Ii8+CiAgICAgICAgICA8ZyBhcmlhLWhpZGRlbj0idHJ1ZSIgZGF0YS1zZW1hbnRpYy1zaWdpbD0iZXh0ZXJuYWwiIGNsYXNzPSJzZW1hbnRpYy1zaWdpbCBzLWV4dGVybmFsIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgyNjEgMzApIHNjYWxlKDAuNjg3NSkiPgogICAgICAgICAgICA8cmVjdCB4PSIyLjUiIHk9IjUiIHdpZHRoPSI4LjUiIGhlaWdodD0iOCIgcng9IjEuNSIvPgogICAgICAgICAgICA8cGF0aCBkPSJNOCAyLjVoNS41VjhNMTMuNSAyLjUgNy41IDguNSIvPgogICAgICAgICAgPC9nPgogICAgICAgICAgPHRleHQgZGF0YS1ub2RlLWxhYmVsPSIiIHg9IjMzMCIgeT0iNTQiIGNsYXNzPSJ0LXByaW1hcnkiIGZvbnQtc2l6ZT0iMTEiIGZvbnQtd2VpZ2h0PSI2MDAiIHRleHQtYW5jaG9yPSJtaWRkbGUiPkNyZWRlbnRpYWwgc2VydmVyPC90ZXh0PgogICAgICAgIDx0ZXh0IHg9IjMzMCIgeT0iNzAiIGNsYXNzPSJ0LW11dGVkIiBmb250LXNpemU9IjkiIHRleHQtYW5jaG9yPSJtaWRkbGUiPlByb3ZpZGVzIHVzYWJsZSB0b2tlbnM8L3RleHQ+CiAgICAgICAgPC9nPgoKICAgICAgICA8ZyBpZD0ibm9kZS1sYXVuY2hlciIgZGF0YS1ub2RlLWlkPSJsYXVuY2hlciIgZGF0YS1ub2RlLWxhYmVsPSJTaGFyZWQgbGF1bmNoZXIiIHRhYmluZGV4PSIwIiByb2xlPSJidXR0b24iIGFyaWEtbGFiZWw9IkZvY3VzIFNoYXJlZCBsYXVuY2hlciwgRmV0Y2hlcyBhbmQgaW5qZWN0cyBhIHRva2VuLCBBcmNoaXRlY3R1cmUgY29tcG9uZW50IiBhcmlhLXByZXNzZWQ9ImZhbHNlIiBkYXRhLW5vZGUta2luZD0iYmFja2VuZCIgZGF0YS1ub2RlLXN1YmxhYmVsPSJGZXRjaGVzIGFuZCBpbmplY3RzIGEgdG9rZW4iIGRhdGEtbm9kZS1jb250ZXh0PSJBcmNoaXRlY3R1cmUgY29tcG9uZW50Ij4KICAgICAgICAgIDx0aXRsZT5TaGFyZWQgbGF1bmNoZXIgwrcgRmV0Y2hlcyBhbmQgaW5qZWN0cyBhIHRva2VuIMK3IEFyY2hpdGVjdHVyZSBjb21wb25lbnQ8L3RpdGxlPgogICAgICAgICAgPHJlY3QgeD0iNDkyIiB5PSIyNCIgd2lkdGg9IjE1MCIgaGVpZ2h0PSI2NCIgcng9IjYiIGNsYXNzPSJjLW1hc2siLz4KICAgICAgICAgIDxyZWN0IHg9IjQ5MiIgeT0iMjQiIHdpZHRoPSIxNTAiIGhlaWdodD0iNjQiIHJ4PSI2IiBjbGFzcz0iYy1iYWNrZW5kIiBzdHJva2Utd2lkdGg9IjEuNSIvPgogICAgICAgICAgPGcgYXJpYS1oaWRkZW49InRydWUiIGRhdGEtc2VtYW50aWMtc2lnaWw9ImJhY2tlbmQiIGNsYXNzPSJzZW1hbnRpYy1zaWdpbCBzLWJhY2tlbmQiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDQ5OCAzMCkgc2NhbGUoMC42ODc1KSI+CiAgICAgICAgICAgIDxwYXRoIGQ9Ik02IDMgMyA4bDMgNU0xMCAzbDMgNS0zIDUiLz4KICAgICAgICAgIDwvZz4KICAgICAgICAgIDx0ZXh0IGRhdGEtbm9kZS1sYWJlbD0iIiB4PSI1NjciIHk9IjU0IiBjbGFzcz0idC1wcmltYXJ5IiBmb250LXNpemU9IjExIiBmb250LXdlaWdodD0iNjAwIiB0ZXh0LWFuY2hvcj0ibWlkZGxlIj5TaGFyZWQgbGF1bmNoZXI8L3RleHQ+CiAgICAgICAgPHRleHQgeD0iNTY3IiB5PSI3MCIgY2xhc3M9InQtbXV0ZWQiIGZvbnQtc2l6ZT0iOSIgdGV4dC1hbmNob3I9Im1pZGRsZSI+RmV0Y2hlcyBhbmQgaW5qZWN0cyBhIHRva2VuPC90ZXh0PgogICAgICAgIDwvZz4KCiAgICAgICAgPGcgaWQ9Im5vZGUtc2Vzc2lvbi1taW5lIiBkYXRhLW5vZGUtaWQ9InNlc3Npb24tbWluZSIgZGF0YS1ub2RlLWxhYmVsPSJQZXJzb25hbCBzZXNzaW9uIiB0YWJpbmRleD0iMCIgcm9sZT0iYnV0dG9uIiBhcmlhLWxhYmVsPSJGb2N1cyBQZXJzb25hbCBzZXNzaW9uLCBEZWZhdWx0IGRpcmVjdG9yeSDCtyBteSBsb2dpbiwgQXJjaGl0ZWN0dXJlIGNvbXBvbmVudCIgYXJpYS1wcmVzc2VkPSJmYWxzZSIgZGF0YS1ub2RlLWtpbmQ9ImZyb250ZW5kIiBkYXRhLW5vZGUtc3VibGFiZWw9IkRlZmF1bHQgZGlyZWN0b3J5IMK3IG15IGxvZ2luIiBkYXRhLW5vZGUtY29udGV4dD0iQXJjaGl0ZWN0dXJlIGNvbXBvbmVudCI+CiAgICAgICAgICA8dGl0bGU+UGVyc29uYWwgc2Vzc2lvbiDCtyBEZWZhdWx0IGRpcmVjdG9yeSDCtyBteSBsb2dpbiDCtyBBcmNoaXRlY3R1cmUgY29tcG9uZW50PC90aXRsZT4KICAgICAgICAgIDxyZWN0IHg9IjE4IiB5PSIyMjYiIHdpZHRoPSIxNTAiIGhlaWdodD0iNjQiIHJ4PSI2IiBjbGFzcz0iYy1tYXNrIi8+CiAgICAgICAgICA8cmVjdCB4PSIxOCIgeT0iMjI2IiB3aWR0aD0iMTUwIiBoZWlnaHQ9IjY0IiByeD0iNiIgY2xhc3M9ImMtZnJvbnRlbmQiIHN0cm9rZS13aWR0aD0iMS41Ii8+CiAgICAgICAgICA8ZyBhcmlhLWhpZGRlbj0idHJ1ZSIgZGF0YS1zZW1hbnRpYy1zaWdpbD0iZnJvbnRlbmQiIGNsYXNzPSJzZW1hbnRpYy1zaWdpbCBzLWZyb250ZW5kIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgyNCAyMzIpIHNjYWxlKDAuNjg3NSkiPgogICAgICAgICAgICA8cmVjdCB4PSIyIiB5PSIzIiB3aWR0aD0iMTIiIGhlaWdodD0iMTAiIHJ4PSIyIi8+CiAgICAgICAgICAgIDxwYXRoIGQ9Ik0yIDYuNWgxMiIvPgogICAgICAgICAgICA8Y2lyY2xlIGN4PSI0LjEiIGN5PSI0LjgiIHI9Ii43IiBjbGFzcz0ic2lnaWwtZmlsbCIvPgogICAgICAgICAgICA8Y2lyY2xlIGN4PSI2LjMiIGN5PSI0LjgiIHI9Ii43IiBjbGFzcz0ic2lnaWwtZmlsbCIvPgogICAgICAgICAgPC9nPgogICAgICAgICAgPHRleHQgZGF0YS1ub2RlLWxhYmVsPSIiIHg9IjkzIiB5PSIyNTYiIGNsYXNzPSJ0LXByaW1hcnkiIGZvbnQtc2l6ZT0iMTEiIGZvbnQtd2VpZ2h0PSI2MDAiIHRleHQtYW5jaG9yPSJtaWRkbGUiPlBlcnNvbmFsIHNlc3Npb248L3RleHQ+CiAgICAgICAgPHRleHQgeD0iOTMiIHk9IjI3MiIgY2xhc3M9InQtbXV0ZWQiIGZvbnQtc2l6ZT0iOSIgdGV4dC1hbmNob3I9Im1pZGRsZSI+RGVmYXVsdCBkaXJlY3RvcnkgwrcgbXkgbG9naW48L3RleHQ+CiAgICAgICAgPC9nPgoKICAgICAgICA8ZyBpZD0ibm9kZS1rZXljaGFpbiIgZGF0YS1ub2RlLWlkPSJrZXljaGFpbiIgZGF0YS1ub2RlLWxhYmVsPSJLZXljaGFpbiIgdGFiaW5kZXg9IjAiIHJvbGU9ImJ1dHRvbiIgYXJpYS1sYWJlbD0iRm9jdXMgS2V5Y2hhaW4sIEFjY2VzcyBrZXkgwrcgY2FjaGVkIHRva2VuLCBBcmNoaXRlY3R1cmUgY29tcG9uZW50IiBhcmlhLXByZXNzZWQ9ImZhbHNlIiBkYXRhLW5vZGUta2luZD0ic2VjdXJpdHkiIGRhdGEtbm9kZS1zdWJsYWJlbD0iQWNjZXNzIGtleSDCtyBjYWNoZWQgdG9rZW4iIGRhdGEtbm9kZS1jb250ZXh0PSJBcmNoaXRlY3R1cmUgY29tcG9uZW50Ij4KICAgICAgICAgIDx0aXRsZT5LZXljaGFpbiDCtyBBY2Nlc3Mga2V5IMK3IGNhY2hlZCB0b2tlbiDCtyBBcmNoaXRlY3R1cmUgY29tcG9uZW50PC90aXRsZT4KICAgICAgICAgIDxyZWN0IHg9IjI1NSIgeT0iMjI2IiB3aWR0aD0iMTUwIiBoZWlnaHQ9IjY0IiByeD0iNiIgY2xhc3M9ImMtbWFzayIvPgogICAgICAgICAgPHJlY3QgeD0iMjU1IiB5PSIyMjYiIHdpZHRoPSIxNTAiIGhlaWdodD0iNjQiIHJ4PSI2IiBjbGFzcz0iYy1zZWN1cml0eSIgc3Ryb2tlLXdpZHRoPSIxLjUiLz4KICAgICAgICAgIDxnIGFyaWEtaGlkZGVuPSJ0cnVlIiBkYXRhLXNlbWFudGljLXNpZ2lsPSJzZWN1cml0eSIgY2xhc3M9InNlbWFudGljLXNpZ2lsIHMtc2VjdXJpdHkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDI2MSAyMzIpIHNjYWxlKDAuNjg3NSkiPgogICAgICAgICAgICA8cGF0aCBkPSJNOCAyLjIgMTMgNHYzLjVjMCAzLjEtMS44IDUuNC01IDYuNS0zLjItMS4xLTUtMy40LTUtNi41VjRaIi8+CiAgICAgICAgICAgIDxwYXRoIGQ9Im01LjggOCAxLjUgMS41IDMtMyIvPgogICAgICAgICAgPC9nPgogICAgICAgICAgPHRleHQgZGF0YS1ub2RlLWxhYmVsPSIiIHg9IjMzMCIgeT0iMjU2IiBjbGFzcz0idC1wcmltYXJ5IiBmb250LXNpemU9IjExIiBmb250LXdlaWdodD0iNjAwIiB0ZXh0LWFuY2hvcj0ibWlkZGxlIj5LZXljaGFpbjwvdGV4dD4KICAgICAgICA8dGV4dCB4PSIzMzAiIHk9IjI3MiIgY2xhc3M9InQtbXV0ZWQiIGZvbnQtc2l6ZT0iOSIgdGV4dC1hbmNob3I9Im1pZGRsZSI+QWNjZXNzIGtleSDCtyBjYWNoZWQgdG9rZW48L3RleHQ+CiAgICAgICAgPC9nPgoKICAgICAgICA8ZyBpZD0ibm9kZS1zZXNzaW9uLXNoYXJlZCIgZGF0YS1ub2RlLWlkPSJzZXNzaW9uLXNoYXJlZCIgZGF0YS1ub2RlLWxhYmVsPSJTaGFyZWQgc2Vzc2lvbiIgdGFiaW5kZXg9IjAiIHJvbGU9ImJ1dHRvbiIgYXJpYS1sYWJlbD0iRm9jdXMgU2hhcmVkIHNlc3Npb24sIERlZGljYXRlZCBkaXJlY3RvcnkgwrcgdG9rZW4sIEFyY2hpdGVjdHVyZSBjb21wb25lbnQiIGFyaWEtcHJlc3NlZD0iZmFsc2UiIGRhdGEtbm9kZS1raW5kPSJmcm9udGVuZCIgZGF0YS1ub2RlLXN1YmxhYmVsPSJEZWRpY2F0ZWQgZGlyZWN0b3J5IMK3IHRva2VuIiBkYXRhLW5vZGUtY29udGV4dD0iQXJjaGl0ZWN0dXJlIGNvbXBvbmVudCI+CiAgICAgICAgICA8dGl0bGU+U2hhcmVkIHNlc3Npb24gwrcgRGVkaWNhdGVkIGRpcmVjdG9yeSDCtyB0b2tlbiDCtyBBcmNoaXRlY3R1cmUgY29tcG9uZW50PC90aXRsZT4KICAgICAgICAgIDxyZWN0IHg9IjQ5MiIgeT0iMjI2IiB3aWR0aD0iMTUwIiBoZWlnaHQ9IjY0IiByeD0iNiIgY2xhc3M9ImMtbWFzayIvPgogICAgICAgICAgPHJlY3QgeD0iNDkyIiB5PSIyMjYiIHdpZHRoPSIxNTAiIGhlaWdodD0iNjQiIHJ4PSI2IiBjbGFzcz0iYy1mcm9udGVuZCIgc3Ryb2tlLXdpZHRoPSIxLjUiLz4KICAgICAgICAgIDxnIGFyaWEtaGlkZGVuPSJ0cnVlIiBkYXRhLXNlbWFudGljLXNpZ2lsPSJmcm9udGVuZCIgY2xhc3M9InNlbWFudGljLXNpZ2lsIHMtZnJvbnRlbmQiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDQ5OCAyMzIpIHNjYWxlKDAuNjg3NSkiPgogICAgICAgICAgICA8cmVjdCB4PSIyIiB5PSIzIiB3aWR0aD0iMTIiIGhlaWdodD0iMTAiIHJ4PSIyIi8+CiAgICAgICAgICAgIDxwYXRoIGQ9Ik0yIDYuNWgxMiIvPgogICAgICAgICAgICA8Y2lyY2xlIGN4PSI0LjEiIGN5PSI0LjgiIHI9Ii43IiBjbGFzcz0ic2lnaWwtZmlsbCIvPgogICAgICAgICAgICA8Y2lyY2xlIGN4PSI2LjMiIGN5PSI0LjgiIHI9Ii43IiBjbGFzcz0ic2lnaWwtZmlsbCIvPgogICAgICAgICAgPC9nPgogICAgICAgICAgPHRleHQgZGF0YS1ub2RlLWxhYmVsPSIiIHg9IjU2NyIgeT0iMjU2IiBjbGFzcz0idC1wcmltYXJ5IiBmb250LXNpemU9IjExIiBmb250LXdlaWdodD0iNjAwIiB0ZXh0LWFuY2hvcj0ibWlkZGxlIj5TaGFyZWQgc2Vzc2lvbjwvdGV4dD4KICAgICAgICA8dGV4dCB4PSI1NjciIHk9IjI3MiIgY2xhc3M9InQtbXV0ZWQiIGZvbnQtc2l6ZT0iOSIgdGV4dC1hbmNob3I9Im1pZGRsZSI+RGVkaWNhdGVkIGRpcmVjdG9yeSDCtyB0b2tlbjwvdGV4dD4KICAgICAgICA8L2c+CgogICAgICAgIDwhLS0gQ29ubmVjdGlvbiBsYWJlbHMgLS0+CiAgICAgICAgPGcgZGF0YS1lZGdlLWZyb209InJvdGF0b3IiIGRhdGEtZWRnZS10bz0ic3VwcGxpZXIiIGRhdGEtZWRnZS1sYWJlbD0iQ2hlY2sgZXZlcnkgMTAgbWluIiBkYXRhLWVkZ2Uta2V5PSIwIiBkYXRhLWVkZ2UtaWQ9InJvdGF0b3ItY2hlY2tzLXN1cHBsaWVyIj4KICAgICAgICAgIDxyZWN0IHg9IjE3NS4zIiB5PSIzNiIgd2lkdGg9IjcyLjQiIGhlaWdodD0iMTQiIHJ4PSIzIiBjbGFzcz0iYy1tYXNrIi8+CiAgICAgICAgICA8dGV4dCB4PSIyMTEuNSIgeT0iNDYiIGNsYXNzPSJ0LW11dGVkIiBmb250LXNpemU9IjgiIHRleHQtYW5jaG9yPSJtaWRkbGUiPkNoZWNrIGV2ZXJ5IDEwIG1pbjwvdGV4dD4KICAgICAgICA8L2c+CiAgICAgICAgPGcgZGF0YS1lZGdlLWZyb209ImxhdW5jaGVyIiBkYXRhLWVkZ2UtdG89InN1cHBsaWVyIiBkYXRhLWVkZ2UtbGFiZWw9IkZldGNoIGF0IGxhdW5jaCIgZGF0YS1lZGdlLWtleT0iMSIgZGF0YS1lZGdlLWlkPSJsYXVuY2hlci1mZXRjaGVzLWxhdGVzdCI+CiAgICAgICAgICA8cmVjdCB4PSI0MDkuOSIgeT0iMzYiIHdpZHRoPSI3Ny4yIiBoZWlnaHQ9IjE0IiByeD0iMyIgY2xhc3M9ImMtbWFzayIvPgogICAgICAgICAgPHRleHQgeD0iNDQ4LjUiIHk9IjQ2IiBjbGFzcz0idC1iYWNrZW5kIiBmb250LXNpemU9IjgiIHRleHQtYW5jaG9yPSJtaWRkbGUiPkZldGNoIGF0IGxhdW5jaDwvdGV4dD4KICAgICAgICA8L2c+CiAgICAgICAgPGcgZGF0YS1lZGdlLWZyb209InJvdGF0b3IiIGRhdGEtZWRnZS10bz0ia2V5Y2hhaW4iIGRhdGEtZWRnZS1sYWJlbD0iUmVwbGFjZSBjb3B5IiBkYXRhLWVkZ2Uta2V5PSIyIiBkYXRhLWVkZ2UtaWQ9InJvdGF0b3Itc2F2ZXMtY29weSI+CiAgICAgICAgICA8cmVjdCB4PSIxODEuNCIgeT0iMTM3IiB3aWR0aD0iNTMuMTk5OTk5OTk5OTk5OTk2IiBoZWlnaHQ9IjE0IiByeD0iMyIgY2xhc3M9ImMtbWFzayIvPgogICAgICAgICAgPHRleHQgeD0iMjA4IiB5PSIxNDciIGNsYXNzPSJ0LXNlY3VyaXR5IiBmb250LXNpemU9IjgiIHRleHQtYW5jaG9yPSJtaWRkbGUiPlJlcGxhY2UgY29weTwvdGV4dD4KICAgICAgICA8L2c+CiAgICAgICAgPGcgZGF0YS1lZGdlLWZyb209ImxhdW5jaGVyIiBkYXRhLWVkZ2UtdG89ImtleWNoYWluIiBkYXRhLWVkZ2UtbGFiZWw9IkZhbGxiYWNrIGNvcHkiIGRhdGEtZWRnZS1rZXk9IjMiIGRhdGEtZWRnZS1pZD0ibGF1bmNoZXItZmFsbHMtYmFjayI+CiAgICAgICAgICA8cmVjdCB4PSI0MTguMiIgeT0iMTM3IiB3aWR0aD0iNjcuNiIgaGVpZ2h0PSIxNCIgcng9IjMiIGNsYXNzPSJjLW1hc2siLz4KICAgICAgICAgIDx0ZXh0IHg9IjQ1MiIgeT0iMTQ3IiBjbGFzcz0idC1tZXNzYWdlYnVzIiBmb250LXNpemU9IjgiIHRleHQtYW5jaG9yPSJtaWRkbGUiPkZhbGxiYWNrIGNvcHk8L3RleHQ+CiAgICAgICAgPC9nPgogICAgICAgIDxnIGRhdGEtZWRnZS1mcm9tPSJsYXVuY2hlciIgZGF0YS1lZGdlLXRvPSJzZXNzaW9uLXNoYXJlZCIgZGF0YS1lZGdlLWxhYmVsPSJPbmx5IHRoaXMgcHJvY2VzcyIgZGF0YS1lZGdlLWtleT0iNCIgZGF0YS1lZGdlLWlkPSJsYXVuY2hlci1pbmplY3RzIj4KICAgICAgICAgIDxyZWN0IHg9IjUzMC42IiB5PSIxODAiIHdpZHRoPSI4Ni44IiBoZWlnaHQ9IjE0IiByeD0iMyIgY2xhc3M9ImMtbWFzayIvPgogICAgICAgICAgPHRleHQgeD0iNTc0IiB5PSIxOTAiIGNsYXNzPSJ0LWJhY2tlbmQiIGZvbnQtc2l6ZT0iOCIgdGV4dC1hbmNob3I9Im1pZGRsZSI+T25seSB0aGlzIHByb2Nlc3M8L3RleHQ+CiAgICAgICAgPC9nPgoKICAgICAgICA8IS0tIEJvdW5kYXJ5IGxhYmVscyAoZm9yZWdyb3VuZCBtYXNrcyBrZWVwIHJvdXRlcyBvdXQgb2YgdGl0bGVzKSAtLT4KCgogICAgICAgIDwhLS0gTGVnZW5kIC0tPgoKICAgICAgPC9zdmc+" loading="lazy"></div><figcaption>Claude Code setup. The rotation worker and shared launcher fetch tokens from the credential server. The launcher passes a token only to the shared session and falls back to the Keychain copy if the server does not respond. The personal session uses the login in the default directory.</figcaption></figure>
<ol lang="en">
<li><strong>Two configuration directories.</strong> My own account uses the default directory, while the shared account uses a dedicated one. Only settings that define how I work are shared through symbolic links.</li>
<li><strong>A shared-account launcher.</strong> This is a shell function. At launch, it fetches the latest token from the credential server, selects the dedicated directory, and passes the token only to that invocation. Running plain <code>claude</code> still uses my own account.</li>
<li><strong>Keychain.</strong> It stores the key used to access the credential server and a copy of the most recently received token. There are no plaintext files for those values.</li>
<li><strong>A rotation worker.</strong> This is a shell script kept running by launchd. Every 10 minutes, it checks the current token's status. When the account is approaching its limit, it fetches a new token and replaces the cached copy.</li>
</ol>
<p lang="en">Codex follows the same pattern. Its rotation worker updates <code>auth.json</code> in the dedicated directory instead of Keychain, and its launcher simply points <code>CODEX_HOME</code> at that directory.</p>
<h2 lang="en" id="계정은-설정-폴더로-나누고-설정은-링크로-공유한다">Separate accounts by directory and share settings through links</h2>
<p lang="en">Pointing <code>CLAUDE_CONFIG_DIR</code> at a new directory immediately separates the account. However, Claude Code uses that directory for both authentication and configuration. Starting with an empty directory means no plugins, skills, commands, or hooks: it looks like a fresh installation. I therefore separate account-specific data from working preferences, then link only the latter back to the default directory. Changing the directory also moves <code>~/.claude.json</code> to <code>.claude.json</code> inside the new directory. <a href="https://code.claude.com/docs/en/claude-directory" rel="noopener noreferrer">Claude Code directory documentation</a></p>
<div aria-label="Scrollable table" class="cc-table" role="region" tabindex="0"><table>
<thead>
<tr>
<th>Category</th>
<th>Examples</th>
<th>Treatment</th>
</tr>
</thead>
<tbody>
<tr>
<td>Account</td>
<td><code>.claude.json</code> (login session, onboarding state, project trust, personal MCP servers), Keychain login</td>
<td>Separate for each directory</td>
</tr>
<tr>
<td>Work history</td>
<td><code>projects/</code> (session history, automatic memory), <code>history.jsonl</code></td>
<td>Separate for each directory</td>
</tr>
<tr>
<td>Working preferences</td>
<td><code>settings.json</code>, <code>CLAUDE.md</code>, <code>skills/</code>, <code>commands/</code>, <code>plugins/</code></td>
<td>Symbolic links to the default directory</td>
</tr>
</tbody>
</table></div>
<p lang="en">The two accounts keep separate conversation histories. A conversation started with the shared account cannot be resumed from my personal account with <code>--resume</code>. Personal MCP servers must also be registered separately in the dedicated directory. Linked settings, however, stay shared: a change made from either side applies to both.</p>
<p lang="en">One detail can break those links. When an application writes settings to a temporary file and renames it into place, it may replace the symbolic link itself with a regular file instead of updating the link's target. After that, the two directories' settings drift apart. I encountered this with <code>settings.json</code>. If a change on one side does not appear on the other, first check whether the entry in the dedicated directory is still a symbolic link.</p>
<h3 lang="en" id="새-폴더의-첫-실행-화면">The first-run screens in a new directory</h3>
<p lang="en">Handling the first-run screens took the most time in this setup. A new directory has no onboarding state, so the first interactive launch shows theme and login-method selection screens. These are separate from token authentication, which makes the situation confusing: <code>claude -p</code> can work perfectly. If I casually log in at that point, my personal login gets saved in the dedicated directory. From then on, even the shared-account launcher connects to my own account.</p>
<p lang="en">To avoid that, I copy only the markers indicating completed onboarding from the default directory's <code>.claude.json</code> into the dedicated one. Copying <code>hasCompletedOnboarding</code> alone did not suppress the theme screen. Several onboarding-related keys were needed. I identified them by capturing and comparing the first-run screens in a pseudo-terminal. I do not copy <code>oauthAccount</code>, because it carries account identity with it.</p>
<p lang="en">The lesson was to test interactive mode separately even when <code>-p</code> works. After changing the setup, I open an interactive session and run <code>/status</code> to see which authentication is in use. A shared token appears as <code>CLAUDE_CODE_OAUTH_TOKEN</code>; my own account shows the login method and email address. If the login selection screen still appears, I exit without choosing anything.</p>
<h3 lang="en" id="codex는-폴더를-셋으로">Three directories for Codex</h3>
<p lang="en">Codex uses <code>CODEX_HOME</code> for the same purpose. I split it into three directories.</p>
<div aria-label="Scrollable table" class="cc-table" role="region" tabindex="0"><table>
<thead>
<tr>
<th>Directory</th>
<th>Account</th>
<th>What writes <code>auth.json</code></th>
</tr>
</thead>
<tbody>
<tr>
<td><code>~/.codex</code></td>
<td>My ChatGPT login</td>
<td><code>codex login</code></td>
</tr>
<tr>
<td>Dedicated shared-account directory</td>
<td>Shared account</td>
<td>The rotation worker, every 10 minutes</td>
</tr>
<tr>
<td>Dedicated API-key directory</td>
<td>OpenAI API key with usage-based billing</td>
<td>The launcher, using the Keychain key at launch</td>
</tr>
</tbody>
</table></div>
<p lang="en">I link <code>config.toml</code>, <code>AGENTS.md</code>, skills, and hooks to the default directory. Each directory keeps its own <code>auth.json</code>, sessions, history, and state database. Multiple processes writing to the same state database can corrupt it.</p>
<p lang="en">The main reason I separated Codex directories was the trouble caused by logging out. In this shared-account setup, running <code>codex logout</code> or <code>codex login</code> while connected to the shared account removes the local file and invalidates credentials on the provider side. Other people and devices using the same credentials can be disconnected too. Keeping the shared account in the default directory makes it easy to log out by habit and disrupt everyone else. A dedicated directory means plain <code>codex</code> acts on my own account instead. To remove only the local shared credentials, I delete that directory's <code>auth.json</code> rather than logging out. The rotation worker restores it within 10 minutes.</p>
<h2 lang="en" id="인증정보는-실행할-때-그-실행에만-넣는다">Pass credentials only to the process being launched</h2>
<p lang="en">The simplest way to use a shared token is to set <code>CLAUDE_CODE_OAUTH_TOKEN</code> and <code>CLAUDE_CONFIG_DIR</code> globally in a shell startup file. That convenience creates two problems. Because the environment variable takes precedence over the saved login, plain <code>claude</code> connects to the shared account instead of my own. Other processes launched from that shell, including editors, also inherit the token. I avoid passing tokens as command-line arguments as well, because they become visible in the process list.</p>
<p lang="en">The launcher therefore sets these variables only inside a subshell and starts the tool there. The token exists in that process and its children, but not in the shell from which I invoked it. This is not complete isolation, since child processes still inherit environment variables. It does, however, greatly narrow the scope compared with setting them for the entire shell.</p>
<p lang="en">At launch, I fetch the token directly from the credential server. The Keychain copy is refreshed every 10 minutes, so it can lag behind if the server rotates a token between updates. I have seen the server hold newer credentials than the local copy. It normally responds within 0.1 seconds, so the request adds little delay. I fall back to the cached copy only if there is no response within 5 seconds.</p>
<p lang="en">For Codex, the rotation worker keeps <code>auth.json</code> in the dedicated directory current, so the launcher only needs to set <code>CODEX_HOME</code>. The worker downloads credentials to a temporary file, checks that they are valid JSON, and replaces the existing file only if the contents differ. That avoids unnecessarily rewriting a file the running tool may be reading every 10 minutes. But it does not mean keeping an old copy indefinitely: the credential server handles token renewal, and even the same account can receive newly issued credentials. I have seen an old copy become invalid on the server.</p>
<h2 lang="en" id="키와-토큰은-keychain에-둔다">Store keys and token copies in Keychain</h2>
<p lang="en">I keep the credential server's access key and cached token only in macOS Keychain. Granting the <code>security</code> command access when storing them lets the launchd worker read them without a password prompt.</p>
<p lang="en">After setting things up, I check four places for exposed credentials: the invoking shell's environment, process arguments, shell history, and startup files such as <code>.zshrc</code>. I actually found a plaintext key in one of those files. A pasted shell configuration block used it as a fallback when an environment variable was missing. I changed it to read from Keychain and revoked the exposed key. Issuing a separate access key for each device also means that, if one device exposes its key, only that key needs to be revoked.</p>
<p lang="en">I learned three things while working with Keychain.</p>
<ul lang="en">
<li><strong>An entered value can be truncated to 128 characters.</strong> Supplying a value through the prompt or standard input to <code>security add-generic-password</code> truncated it at 128 characters in my setup. The prompt does not display the input, and the confirmation is truncated in exactly the same way, so nothing looks wrong during entry. The OpenAI project key I was using began with <code>sk-proj-</code> and was 164 characters long, so it was truncated. For that key, I passed the value as an argument and accepted that it would briefly appear in the process list. After storing a value, I check its length, not just whether it exists. Exactly 128 characters is a warning sign of truncation in this workflow.</li>
<li><strong>Updating an existing entry can be slow.</strong> Overwriting an entry took 20 or 30 seconds. Just after restarting the rotation worker, it once took nearly 4 minutes. Creating a new entry at the same time took 0.03 seconds. Deleting the old entry and creating a new one brought the operation down to 0.5 seconds. There is a brief gap between deletion and creation, but the launcher asks the credential server first, so it did not affect launches in this setup.</li>
<li><strong>Resetting the login password can isolate the entire login keychain.</strong> Changing the password without supplying the old one prevents macOS from re-encrypting the login keychain with the new password. Resetting it from another administrator account is one example. macOS instead renames the old keychain to <code>login_renamed_N</code> and creates an empty one. The isolated keychain still requires the old password that was last synchronized with it; a FileVault recovery key cannot unlock it. In my case, more than 90 entries were locked. A year-old <code>login_renamed_1</code> was already there too, showing that an earlier reset had silently done the same thing. I could not remember the old password, so I reissued the keys. To prevent this, change the password while logged in through <strong>System Settings → Touch ID &amp; Password → Change Password</strong>. That flow asks for the old password, allowing the keychain to be re-encrypted too.</li>
</ul>
<h2 lang="en" id="교체-작업은-백그라운드에서-실행하고-실패해도-종료하지-않는다">Run rotation in the background and keep it alive on failure</h2>
<p lang="en">One shell script handles rotation. It fetches a token once at startup and stores it in Keychain, then checks the token's status with the credential server every 10 minutes. If the account is close to its limit or the token is unusable, it fetches new credentials and replaces the cached copy. Logs contain status information, not token values.</p>
<p lang="en">I keep the script running with launchd. It is registered as a user LaunchAgent, with <code>RunAtLoad</code> starting it at login and <code>KeepAlive</code> restarting it after exit. A plist does not expand <code>~</code> or <code>$HOME</code>, so its paths must be absolute.</p>
<p lang="en">The first version exited with an error if it could not fetch the initial token. That caused a serious problem: launchd restarted the process after its restart interval, set to 30 seconds through <code>ThrottleInterval</code>. If the credential server was temporarily unable to issue a token, or the access key was missing from Keychain, the script exited and restarted repeatedly, hitting the server every 30 seconds. It restarted 1,700 times over two days when it could not obtain a token. I changed it to stay alive on failure. It waits inside the loop until the key appears, or until the next polling interval if token retrieval fails. For a supervised script, "exit on failure" effectively meant "retry every 30 seconds."</p>
<p lang="en">Gaps in the logs that looked like failures turned out to be sleep. When the Mac sleeps, the rotation worker sleeps too. Comparing sleep timestamps from <code>pmset -g log</code> with the gaps makes this clear. The credential server chooses the account with the most available capacity for each request, so frequent account changes in Codex's 10-minute refresh logs can be normal.</p>
<h2 lang="en" id="자동으로-되는-일과-사람이-할-일">What is automatic and what still needs a person</h2>
<p lang="en">I automated only the preparation of new credentials. Switching an already-running session remains a manual step.</p>
<figure lang="en" class="cc-figure"><div class="cc-diagram" role="region" aria-label="Scrollable diagram" tabindex="0"><img class="cc-diagram-light" alt="When a replacement token takes effect. While the session uses token A, the rotation worker replaces the cached copy with B. The session continues using A until it reaches the limit. Restarting the session makes it read B." src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGRhdGEtdGhlbWU9ImxpZ2h0IiB2aWV3Qm94PSIwIDAgNzcyIDQ5NiIgcm9sZT0iaW1nIiBsYW5nPSJlbiIgeG1sOmxhbmc9ImVuIiBhcmlhLWxhYmVsbGVkYnk9ImFyY2hpZnktZGlhZ3JhbS10aXRsZSBhcmNoaWZ5LWRpYWdyYW0tZGVzY3JpcHRpb24iIGRhdGEtcHJlc2V0PSJjbGFzc2ljIiBkYXRhLXF1YWxpdHktcHJvZmlsZT0ic2hvd2Nhc2UiIHdpZHRoPSI3NzIiIGhlaWdodD0iNDk2Ij48c3R5bGU+c3ZnIHsgZm9udC1mYW1pbHk6IC1hcHBsZS1zeXN0ZW0sIEJsaW5rTWFjU3lzdGVtRm9udCwgIkFwcGxlIFNEIEdvdGhpYyBOZW8iLCAiTWFsZ3VuIEdvdGhpYyIsICJOb3RvIFNhbnMgS1IiLCAiTm90byBTYW5zIENKSyBLUiIsIHNhbnMtc2VyaWY7IH0KOnJvb3QsIFtkYXRhLXRoZW1lPSJkYXJrIl0geyAtLWJnOiAjMDIwNjE3OyAtLWdyaWQ6ICMxZTI5M2I7IC0tdGV4dDogI2ZmZmZmZjsgLS10ZXh0LW11dGVkOiAjOTRhM2I4OyAtLXRleHQtZGltOiAjNDc1NTY5OyAtLXRleHQtZmFpbnQ6ICM3ZDhkYTE7IC0tcGFuZWw6IHJnYmEoMTUsIDIzLCA0MiwgMC41KTsgLS1wYW5lbC1ib3JkZXI6ICMxZTI5M2I7IC0tbGFuZS1maWxsOiByZ2JhKDE1LCAyMywgNDIsIDAuMjIpOyAtLWxhbmUtc3Ryb2tlOiAjMzM0MTU1OyAtLWFycm93OiAjNjQ3NDhiOyAtLWFycm93LWVtcGhhc2lzOiAjMzRkMzk5OyAtLW1hc2s6ICMwZjE3MmE7IC0tZnJvbnRlbmQtZmlsbDogcmdiYSg4LCA1MSwgNjgsIDAuNCk7IC0tZnJvbnRlbmQtc3Ryb2tlOiAjMjJkM2VlOyAtLWJhY2tlbmQtZmlsbDogcmdiYSg2LCA3OCwgNTksIDAuNCk7IC0tYmFja2VuZC1zdHJva2U6ICMzNGQzOTk7IC0tZGF0YWJhc2UtZmlsbDogcmdiYSg3NiwgMjksIDE0OSwgMC40KTsgLS1kYXRhYmFzZS1zdHJva2U6ICNhNzhiZmE7IC0tY2xvdWQtZmlsbDogcmdiYSgxMjAsIDUzLCAxNSwgMC4zKTsgLS1jbG91ZC1zdHJva2U6ICNmYmJmMjQ7IC0tc2VjdXJpdHktZmlsbDogcmdiYSgxMzYsIDE5LCA1NSwgMC40KTsgLS1zZWN1cml0eS1zdHJva2U6ICNmYjcxODU7IC0tbWVzc2FnZWJ1cy1maWxsOiByZ2JhKDI1MSwgMTQ2LCA2MCwgMC4zKTsgLS1tZXNzYWdlYnVzLXN0cm9rZTogI2ZiOTIzYzsgLS1leHRlcm5hbC1maWxsOiByZ2JhKDMwLCA0MSwgNTksIDAuNSk7IC0tZXh0ZXJuYWwtc3Ryb2tlOiAjOTRhM2I4OyAtLXRvb2xiYXItYmc6IHJnYmEoMTUsIDIzLCA0MiwgMC44KTsgLS10b29sYmFyLWJvcmRlcjogIzMzNDE1NTsgLS10b29sYmFyLXRleHQ6ICNlMmU4ZjA7IC0tdG9vbGJhci1ob3ZlcjogcmdiYSgxNSwgMjMsIDQyLCAwLjk1KTsgLS10b29sYmFyLW1lbnUtYmc6ICMwZjE3MmE7IH0KW2RhdGEtdGhlbWU9ImxpZ2h0Il0geyAtLWJnOiAjZjhmYWZjOyAtLWdyaWQ6ICNlMmU4ZjA7IC0tdGV4dDogIzBmMTcyYTsgLS10ZXh0LW11dGVkOiAjNjQ3NDhiOyAtLXRleHQtZGltOiAjOTRhM2I4OyAtLXRleHQtZmFpbnQ6ICM2NDc0OGI7IC0tcGFuZWw6ICNmZmZmZmY7IC0tcGFuZWwtYm9yZGVyOiAjZTJlOGYwOyAtLWxhbmUtZmlsbDogcmdiYSgyNDgsIDI1MCwgMjUyLCAwLjY1KTsgLS1sYW5lLXN0cm9rZTogI2NiZDVlMTsgLS1hcnJvdzogIzk0YTNiODsgLS1hcnJvdy1lbXBoYXNpczogIzA1OTY2OTsgLS1tYXNrOiAjZmZmZmZmOyAtLWZyb250ZW5kLWZpbGw6IHJnYmEoMzQsIDIxMSwgMjM4LCAwLjE1KTsgLS1mcm9udGVuZC1zdHJva2U6ICMwODkxYjI7IC0tYmFja2VuZC1maWxsOiByZ2JhKDUyLCAyMTEsIDE1MywgMC4xOCk7IC0tYmFja2VuZC1zdHJva2U6ICMwNTk2Njk7IC0tZGF0YWJhc2UtZmlsbDogcmdiYSgxNjcsIDEzOSwgMjUwLCAwLjIpOyAtLWRhdGFiYXNlLXN0cm9rZTogIzdjM2FlZDsgLS1jbG91ZC1maWxsOiByZ2JhKDI1MSwgMTkxLCAzNiwgMC4xOCk7IC0tY2xvdWQtc3Ryb2tlOiAjZDk3NzA2OyAtLXNlY3VyaXR5LWZpbGw6IHJnYmEoMjUxLCAxMTMsIDEzMywgMC4xNSk7IC0tc2VjdXJpdHktc3Ryb2tlOiAjZTExZDQ4OyAtLW1lc3NhZ2VidXMtZmlsbDogcmdiYSgyNTEsIDE0NiwgNjAsIDAuMTUpOyAtLW1lc3NhZ2VidXMtc3Ryb2tlOiAjZWE1ODBjOyAtLWV4dGVybmFsLWZpbGw6IHJnYmEoMTQ4LCAxNjMsIDE4NCwgMC4xOCk7IC0tZXh0ZXJuYWwtc3Ryb2tlOiAjNjQ3NDhiOyAtLXRvb2xiYXItYmc6IHJnYmEoMjU1LCAyNTUsIDI1NSwgMC45Mik7IC0tdG9vbGJhci1ib3JkZXI6ICNjYmQ1ZTE7IC0tdG9vbGJhci10ZXh0OiAjMzM0MTU1OyAtLXRvb2xiYXItaG92ZXI6ICNmZmZmZmY7IC0tdG9vbGJhci1tZW51LWJnOiAjZmZmZmZmOyB9CnN2ZyB7IHdpZHRoOiAxMDAlOyBtaW4td2lkdGg6IG1pbig5MDBweCwgMTAwJSk7IGRpc3BsYXk6IGJsb2NrOyB0cmFuc2Zvcm0tb3JpZ2luOiAwcHggMHB4OyB0cmFuc2l0aW9uOiB0cmFuc2Zvcm0gMC4xOHM7IH0KLmMtZ3JpZCB7IHN0cm9rZTogdmFyKC0tZ3JpZCk7IGZpbGw6IG5vbmU7IH0KLmMtbWFzayB7IGZpbGw6IHZhcigtLW1hc2spOyBzdHJva2U6IG5vbmU7IH0KLmMtZnJvbnRlbmQgeyBmaWxsOiB2YXIoLS1mcm9udGVuZC1maWxsKTsgc3Ryb2tlOiB2YXIoLS1mcm9udGVuZC1zdHJva2UpOyB9Ci5jLWJhY2tlbmQgeyBmaWxsOiB2YXIoLS1iYWNrZW5kLWZpbGwpOyBzdHJva2U6IHZhcigtLWJhY2tlbmQtc3Ryb2tlKTsgfQouYy1zZWN1cml0eSB7IGZpbGw6IHZhcigtLXNlY3VyaXR5LWZpbGwpOyBzdHJva2U6IHZhcigtLXNlY3VyaXR5LXN0cm9rZSk7IH0KLmMtZXh0ZXJuYWwgeyBmaWxsOiB2YXIoLS1leHRlcm5hbC1maWxsKTsgc3Ryb2tlOiB2YXIoLS1leHRlcm5hbC1zdHJva2UpOyB9Ci50LXByaW1hcnkgeyBmaWxsOiB2YXIoLS10ZXh0KTsgfQoudC1tdXRlZCB7IGZpbGw6IHZhcigtLXRleHQtbXV0ZWQpOyB9Ci50LWRpbSB7IGZpbGw6IHZhcigtLXRleHQtZGltKTsgfQoudC1iYWNrZW5kIHsgZmlsbDogdmFyKC0tYmFja2VuZC1zdHJva2UpOyB9Ci50LXNlY3VyaXR5IHsgZmlsbDogdmFyKC0tc2VjdXJpdHktc3Ryb2tlKTsgfQpzdmcgLnNlbWFudGljLXNpZ2lsIHsgZmlsbDogbm9uZTsgc3Ryb2tlOiBjdXJyZW50Y29sb3I7IHN0cm9rZS13aWR0aDogMS4zNTsgc3Ryb2tlLWxpbmVjYXA6IHJvdW5kOyBzdHJva2UtbGluZWpvaW46IHJvdW5kOyBvcGFjaXR5OiAwLjc2OyBwb2ludGVyLWV2ZW50czogbm9uZTsgfQpzdmcgLnNlbWFudGljLXNpZ2lsICZndDsgKiB7IHZlY3Rvci1lZmZlY3Q6IG5vbi1zY2FsaW5nLXN0cm9rZTsgfQpzdmcgLnNlbWFudGljLXNpZ2lsIC5zaWdpbC1maWxsIHsgZmlsbDogY3VycmVudGNvbG9yOyBzdHJva2U6IG5vbmU7IH0Kc3ZnIC5zLWZyb250ZW5kIHsgY29sb3I6IHZhcigtLWZyb250ZW5kLXN0cm9rZSk7IH0Kc3ZnIC5zLWJhY2tlbmQgeyBjb2xvcjogdmFyKC0tYmFja2VuZC1zdHJva2UpOyB9CnN2ZyAucy1zZWN1cml0eSB7IGNvbG9yOiB2YXIoLS1zZWN1cml0eS1zdHJva2UpOyB9CnN2ZyAucy1leHRlcm5hbCB7IGNvbG9yOiB2YXIoLS1leHRlcm5hbC1zdHJva2UpOyB9Ci5hLWRlZmF1bHQgeyBzdHJva2U6IHZhcigtLWFycm93KTsgZmlsbDogbm9uZTsgfQouYS1lbXBoYXNpcyB7IHN0cm9rZTogdmFyKC0tYXJyb3ctZW1waGFzaXMpOyBmaWxsOiBub25lOyB9Ci5hLXNlY3VyaXR5IHsgc3Ryb2tlOiB2YXIoLS1zZWN1cml0eS1zdHJva2UpOyBmaWxsOiBub25lOyBzdHJva2UtZGFzaGFycmF5OiA1LCA1OyB9Ci5tLWRlZmF1bHQgeyBmaWxsOiB2YXIoLS1hcnJvdyk7IH0KLm0tZW1waGFzaXMgeyBmaWxsOiB2YXIoLS1hcnJvdy1lbXBoYXNpcyk7IH0KLm0tc2VjdXJpdHkgeyBmaWxsOiB2YXIoLS1zZWN1cml0eS1zdHJva2UpOyB9Ci5tLWRhc2hlZCB7IGZpbGw6IHZhcigtLWRhdGFiYXNlLXN0cm9rZSk7IH0Kc3ZnIFtkYXRhLW5vZGUtaWRdIHsgY3Vyc29yOiBwb2ludGVyOyB0cmFuc2l0aW9uOiBvcGFjaXR5IDAuMThzLCBmaWx0ZXIgMC4xOHM7IH0Kc3ZnIFtkYXRhLWVkZ2UtZnJvbV0geyB0cmFuc2l0aW9uOiBvcGFjaXR5IDAuMThzLCBmaWx0ZXIgMC4xOHM7IH0KLmMtbGFuZSB7IGZpbGw6IHZhcigtLWxhbmUtZmlsbCk7IHN0cm9rZTogdmFyKC0tbGFuZS1zdHJva2UpOyBzdHJva2UtZGFzaGFycmF5OiA2LCA2OyB9Cjpyb290LCBzdmcgeyAtLWJnOiAjMDIwNjE3OyAtLWdyaWQ6ICMxZTI5M2I7IC0tdGV4dDogI2ZmZmZmZjsgLS10ZXh0LW11dGVkOiAjOTRhM2I4OyAtLXRleHQtZGltOiAjNDc1NTY5OyAtLXRleHQtZmFpbnQ6ICM3ZDhkYTE7IC0tcGFuZWw6IHJnYmEoMTUsIDIzLCA0MiwgMC41KTsgLS1wYW5lbC1ib3JkZXI6ICMxZTI5M2I7IC0tbGFuZS1maWxsOiByZ2JhKDE1LCAyMywgNDIsIDAuMjIpOyAtLWxhbmUtc3Ryb2tlOiAjMzM0MTU1OyAtLWFycm93OiAjNjQ3NDhiOyAtLWFycm93LWVtcGhhc2lzOiAjMzRkMzk5OyAtLW1hc2s6ICMwZjE3MmE7IC0tZnJvbnRlbmQtZmlsbDogcmdiYSg4LCA1MSwgNjgsIDAuNCk7IC0tZnJvbnRlbmQtc3Ryb2tlOiAjMjJkM2VlOyAtLWJhY2tlbmQtZmlsbDogcmdiYSg2LCA3OCwgNTksIDAuNCk7IC0tYmFja2VuZC1zdHJva2U6ICMzNGQzOTk7IC0tZGF0YWJhc2UtZmlsbDogcmdiYSg3NiwgMjksIDE0OSwgMC40KTsgLS1kYXRhYmFzZS1zdHJva2U6ICNhNzhiZmE7IC0tY2xvdWQtZmlsbDogcmdiYSgxMjAsIDUzLCAxNSwgMC4zKTsgLS1jbG91ZC1zdHJva2U6ICNmYmJmMjQ7IC0tc2VjdXJpdHktZmlsbDogcmdiYSgxMzYsIDE5LCA1NSwgMC40KTsgLS1zZWN1cml0eS1zdHJva2U6ICNmYjcxODU7IC0tbWVzc2FnZWJ1cy1maWxsOiByZ2JhKDI1MSwgMTQ2LCA2MCwgMC4zKTsgLS1tZXNzYWdlYnVzLXN0cm9rZTogI2ZiOTIzYzsgLS1leHRlcm5hbC1maWxsOiByZ2JhKDMwLCA0MSwgNTksIDAuNSk7IC0tZXh0ZXJuYWwtc3Ryb2tlOiAjOTRhM2I4OyAtLXRvb2xiYXItYmc6IHJnYmEoMTUsIDIzLCA0MiwgMC44KTsgLS10b29sYmFyLWJvcmRlcjogIzMzNDE1NTsgLS10b29sYmFyLXRleHQ6ICNlMmU4ZjA7IC0tdG9vbGJhci1ob3ZlcjogcmdiYSgxNSwgMjMsIDQyLCAwLjk1KTsgLS10b29sYmFyLW1lbnUtYmc6ICMwZjE3MmE7IH0Kc3ZnW2RhdGEtdGhlbWU9ImxpZ2h0Il0geyAtLWJnOiAjZjhmYWZjOyAtLWdyaWQ6ICNlMmU4ZjA7IC0tdGV4dDogIzBmMTcyYTsgLS10ZXh0LW11dGVkOiAjNjQ3NDhiOyAtLXRleHQtZGltOiAjOTRhM2I4OyAtLXRleHQtZmFpbnQ6ICM2NDc0OGI7IC0tcGFuZWw6ICNmZmZmZmY7IC0tcGFuZWwtYm9yZGVyOiAjZTJlOGYwOyAtLWxhbmUtZmlsbDogcmdiYSgyNDgsIDI1MCwgMjUyLCAwLjY1KTsgLS1sYW5lLXN0cm9rZTogI2NiZDVlMTsgLS1hcnJvdzogIzk0YTNiODsgLS1hcnJvdy1lbXBoYXNpczogIzA1OTY2OTsgLS1tYXNrOiAjZmZmZmZmOyAtLWZyb250ZW5kLWZpbGw6IHJnYmEoMzQsIDIxMSwgMjM4LCAwLjE1KTsgLS1mcm9udGVuZC1zdHJva2U6ICMwODkxYjI7IC0tYmFja2VuZC1maWxsOiByZ2JhKDUyLCAyMTEsIDE1MywgMC4xOCk7IC0tYmFja2VuZC1zdHJva2U6ICMwNTk2Njk7IC0tZGF0YWJhc2UtZmlsbDogcmdiYSgxNjcsIDEzOSwgMjUwLCAwLjIpOyAtLWRhdGFiYXNlLXN0cm9rZTogIzdjM2FlZDsgLS1jbG91ZC1maWxsOiByZ2JhKDI1MSwgMTkxLCAzNiwgMC4xOCk7IC0tY2xvdWQtc3Ryb2tlOiAjZDk3NzA2OyAtLXNlY3VyaXR5LWZpbGw6IHJnYmEoMjUxLCAxMTMsIDEzMywgMC4xNSk7IC0tc2VjdXJpdHktc3Ryb2tlOiAjZTExZDQ4OyAtLW1lc3NhZ2VidXMtZmlsbDogcmdiYSgyNTEsIDE0NiwgNjAsIDAuMTUpOyAtLW1lc3NhZ2VidXMtc3Ryb2tlOiAjZWE1ODBjOyAtLWV4dGVybmFsLWZpbGw6IHJnYmEoMTQ4LCAxNjMsIDE4NCwgMC4xOCk7IC0tZXh0ZXJuYWwtc3Ryb2tlOiAjNjQ3NDhiOyAtLXRvb2xiYXItYmc6IHJnYmEoMjU1LCAyNTUsIDI1NSwgMC45Mik7IC0tdG9vbGJhci1ib3JkZXI6ICNjYmQ1ZTE7IC0tdG9vbGJhci10ZXh0OiAjMzM0MTU1OyAtLXRvb2xiYXItaG92ZXI6ICNmZmZmZmY7IC0tdG9vbGJhci1tZW51LWJnOiAjZmZmZmZmOyB9CnJlY3QuYy1iZy1yZWN0IHsgZmlsbDogdmFyKC0tYmcpOyB9PC9zdHlsZT48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBjbGFzcz0iYy1iZy1yZWN0IiAvPgogICAgICAgIDx0aXRsZSBpZD0iYXJjaGlmeS1kaWFncmFtLXRpdGxlIj5XaGVuIGEgcmVwbGFjZW1lbnQgdG9rZW4gdGFrZXMgZWZmZWN0PC90aXRsZT4KICAgICAgICA8ZGVzYyBpZD0iYXJjaGlmeS1kaWFncmFtLWRlc2NyaXB0aW9uIj5BIHNlcXVlbmNlIGRpYWdyYW0gZ2VuZXJhdGVkIGJ5IEFyY2hpZnkuPC9kZXNjPgogICAgICAgIAogICAgICAgIDxkZWZzPgogICAgICAgICAgPG1hcmtlciBpZD0iYXJyb3doZWFkIiBtYXJrZXJXaWR0aD0iMTAiIG1hcmtlckhlaWdodD0iNyIgcmVmWD0iOSIgcmVmWT0iMy41IiBvcmllbnQ9ImF1dG8iPgogICAgICAgICAgICA8cG9seWdvbiBwb2ludHM9IjAgMCwgMTAgMy41LCAwIDciIGNsYXNzPSJtLWRlZmF1bHQiIC8+CiAgICAgICAgICA8L21hcmtlcj4KICAgICAgICAgIDxtYXJrZXIgaWQ9ImFycm93aGVhZC1lbXBoYXNpcyIgbWFya2VyV2lkdGg9IjEwIiBtYXJrZXJIZWlnaHQ9IjciIHJlZlg9IjkiIHJlZlk9IjMuNSIgb3JpZW50PSJhdXRvIj4KICAgICAgICAgICAgPHBvbHlnb24gcG9pbnRzPSIwIDAsIDEwIDMuNSwgMCA3IiBjbGFzcz0ibS1lbXBoYXNpcyIgLz4KICAgICAgICAgIDwvbWFya2VyPgogICAgICAgICAgPG1hcmtlciBpZD0iYXJyb3doZWFkLXNlY3VyaXR5IiBtYXJrZXJXaWR0aD0iMTAiIG1hcmtlckhlaWdodD0iNyIgcmVmWD0iOSIgcmVmWT0iMy41IiBvcmllbnQ9ImF1dG8iPgogICAgICAgICAgICA8cG9seWdvbiBwb2ludHM9IjAgMCwgMTAgMy41LCAwIDciIGNsYXNzPSJtLXNlY3VyaXR5IiAvPgogICAgICAgICAgPC9tYXJrZXI+CiAgICAgICAgICA8bWFya2VyIGlkPSJhcnJvd2hlYWQtZGFzaGVkIiBtYXJrZXJXaWR0aD0iMTAiIG1hcmtlckhlaWdodD0iNyIgcmVmWD0iOSIgcmVmWT0iMy41IiBvcmllbnQ9ImF1dG8iPgogICAgICAgICAgICA8cG9seWdvbiBwb2ludHM9IjAgMCwgMTAgMy41LCAwIDciIGNsYXNzPSJtLWRhc2hlZCIgLz4KICAgICAgICAgIDwvbWFya2VyPgogICAgICAgICAgPHBhdHRlcm4gaWQ9ImdyaWQiIHdpZHRoPSI0MCIgaGVpZ2h0PSI0MCIgcGF0dGVyblVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+CiAgICAgICAgICAgIDxwYXRoIGQ9Ik0gNDAgMCBMIDAgMCAwIDQwIiBjbGFzcz0iYy1ncmlkIiBzdHJva2Utd2lkdGg9IjAuNSIgLz4KICAgICAgICAgIDwvcGF0dGVybj4KICAgICAgICA8L2RlZnM+CgogICAgICAgIAogICAgICAgIDxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JpZCkiIC8+CgogICAgICAgIAogICAgICAgIDxyZWN0IGRhdGEtZ3JhcGgtcm9sZT0ic3RydWN0dXJhbC1mcmFtZSIgZGF0YS1jb21wb3NpdGlvbi1mcmFtZS1raW5kPSJzZWdtZW50IiBkYXRhLWNvbXBvc2l0aW9uLWZyYW1lLWlkPSIwIiB4PSI0OCIgeT0iMTY2IiB3aWR0aD0iNjc2IiBoZWlnaHQ9IjU2IiByeD0iMTAiIGNsYXNzPSJjLWxhbmUiIHN0cm9rZS13aWR0aD0iMSIgLz4KCiAgICAgICAgPHJlY3QgZGF0YS1ncmFwaC1yb2xlPSJzdHJ1Y3R1cmFsLWZyYW1lIiBkYXRhLWNvbXBvc2l0aW9uLWZyYW1lLWtpbmQ9InNlZ21lbnQiIGRhdGEtY29tcG9zaXRpb24tZnJhbWUtaWQ9IjEiIHg9IjQ4IiB5PSIyMzgiIHdpZHRoPSI2NzYiIGhlaWdodD0iMTEyIiByeD0iMTAiIGNsYXNzPSJjLWxhbmUiIHN0cm9rZS13aWR0aD0iMSIgLz4KCiAgICAgICAgPHJlY3QgZGF0YS1ncmFwaC1yb2xlPSJzdHJ1Y3R1cmFsLWZyYW1lIiBkYXRhLWNvbXBvc2l0aW9uLWZyYW1lLWtpbmQ9InNlZ21lbnQiIGRhdGEtY29tcG9zaXRpb24tZnJhbWUtaWQ9IjIiIHg9IjQ4IiB5PSIzNjYiIHdpZHRoPSI2NzYiIGhlaWdodD0iNTgiIHJ4PSIxMCIgY2xhc3M9ImMtbGFuZSIgc3Ryb2tlLXdpZHRoPSIxIiAvPgoKICAgICAgICAKICAgICAgICA8cGF0aCBkPSJNIDEwNSAxNDIgTCAxMDUgNDMxIiBjbGFzcz0iYS1kZWZhdWx0IiBzdHJva2Utd2lkdGg9IjAuOCIgc3Ryb2tlLWRhc2hhcnJheT0iMyw3IiAvPgogICAgICAgIDxwYXRoIGQ9Ik0gMjIxLjggMTQyIEwgMjIxLjggNDMxIiBjbGFzcz0iYS1kZWZhdWx0IiBzdHJva2Utd2lkdGg9IjAuOCIgc3Ryb2tlLWRhc2hhcnJheT0iMyw3IiAvPgogICAgICAgIDxwYXRoIGQ9Ik0gMzM4LjYgMTQyIEwgMzM4LjYgNDMxIiBjbGFzcz0iYS1kZWZhdWx0IiBzdHJva2Utd2lkdGg9IjAuOCIgc3Ryb2tlLWRhc2hhcnJheT0iMyw3IiAvPgogICAgICAgIDxwYXRoIGQ9Ik0gNDU1LjQgMTQyIEwgNDU1LjQgNDMxIiBjbGFzcz0iYS1kZWZhdWx0IiBzdHJva2Utd2lkdGg9IjAuOCIgc3Ryb2tlLWRhc2hhcnJheT0iMyw3IiAvPgogICAgICAgIDxwYXRoIGQ9Ik0gNTcyLjIgMTQyIEwgNTcyLjIgNDMxIiBjbGFzcz0iYS1kZWZhdWx0IiBzdHJva2Utd2lkdGg9IjAuOCIgc3Ryb2tlLWRhc2hhcnJheT0iMyw3IiAvPgogICAgICAgIDxwYXRoIGQ9Ik0gNjg5IDE0MiBMIDY4OSA0MzEiIGNsYXNzPSJhLWRlZmF1bHQiIHN0cm9rZS13aWR0aD0iMC44IiBzdHJva2UtZGFzaGFycmF5PSIzLDciIC8+CgogICAgICAgIAoKCiAgICAgICAgCiAgICAgICAgPGcgZGF0YS1lZGdlLWZyb209ImxhdW5jaGVyIiBkYXRhLWVkZ2UtdG89InN1cHBsaWVyIiBkYXRhLWVkZ2UtbGFiZWw9IkZldGNoIGxhdGVzdCB0b2tlbiIgZGF0YS1lZGdlLWtleT0iMCIgZGF0YS1lZGdlLWlkPSJmaXJzdC1mZXRjaCI+CiAgICAgICAgICA8cGF0aCBkYXRhLWNvbXBvc2l0aW9uLWVkZ2UtZnJvbT0ibGF1bmNoZXIiIGRhdGEtY29tcG9zaXRpb24tZWRnZS10bz0ic3VwcGxpZXIiIGRhdGEtY29tcG9zaXRpb24tZWRnZS1pZD0iZmlyc3QtZmV0Y2giIGRhdGEtY29tcG9zaXRpb24tcG9pbnRzPSI0NDguNCwxNzY7MzQ1LjYsMTc2IiBkPSJNIDQ0OC40IDE3NiBMIDM0NS42IDE3NiIgY2xhc3M9ImEtZGVmYXVsdCIgc3Ryb2tlLXdpZHRoPSIxLjQiIG1hcmtlci1lbmQ9InVybCgjYXJyb3doZWFkKSIgLz4KICAgICAgICA8Zz4KICAgICAgICAgIDxyZWN0IHg9IjM1NC42IiB5PSIxNTYiIHdpZHRoPSI4NC44IiBoZWlnaHQ9IjE2IiByeD0iMyIgY2xhc3M9ImMtbWFzayIgLz4KICAgICAgICAgIDx0ZXh0IHg9IjM5NyIgeT0iMTY2IiBjbGFzcz0idC1iYWNrZW5kIiBmb250LXNpemU9IjkiIHRleHQtYW5jaG9yPSJtaWRkbGUiPkZldGNoIGxhdGVzdCB0b2tlbjwvdGV4dD4KICAgICAgICA8L2c+CiAgICAgICAgPC9nPgoKICAgICAgICA8ZyBkYXRhLWVkZ2UtZnJvbT0ibGF1bmNoZXIiIGRhdGEtZWRnZS10bz0ic2Vzc2lvbiIgZGF0YS1lZGdlLWxhYmVsPSJTdGFydCB3aXRoIEEiIGRhdGEtZWRnZS1rZXk9IjEiIGRhdGEtZWRnZS1pZD0iZmlyc3Qtc3RhcnQiPgogICAgICAgICAgPHBhdGggZGF0YS1jb21wb3NpdGlvbi1lZGdlLWZyb209ImxhdW5jaGVyIiBkYXRhLWNvbXBvc2l0aW9uLWVkZ2UtdG89InNlc3Npb24iIGRhdGEtY29tcG9zaXRpb24tZWRnZS1pZD0iZmlyc3Qtc3RhcnQiIGRhdGEtY29tcG9zaXRpb24tcG9pbnRzPSI0NjIuNCwyMDQ7NTY1LjIsMjA0IiBkPSJNIDQ2Mi40IDIwNCBMIDU2NS4yIDIwNCIgY2xhc3M9ImEtZW1waGFzaXMiIHN0cm9rZS13aWR0aD0iMS44IiBtYXJrZXItZW5kPSJ1cmwoI2Fycm93aGVhZC1lbXBoYXNpcykiIC8+CiAgICAgICAgPGc+CiAgICAgICAgICA8cmVjdCB4PSI0NzMuOTk5OTk5OTk5OTk5OTQiIHk9IjE4NCIgd2lkdGg9Ijc5LjYwMDAwMDAwMDAwMDAxIiBoZWlnaHQ9IjE2IiByeD0iMyIgY2xhc3M9ImMtbWFzayIgLz4KICAgICAgICAgIDx0ZXh0IHg9IjUxMy44IiB5PSIxOTQiIGNsYXNzPSJ0LWJhY2tlbmQiIGZvbnQtc2l6ZT0iOSIgdGV4dC1hbmNob3I9Im1pZGRsZSI+U3RhcnQgd2l0aCBBPC90ZXh0PgogICAgICAgIDwvZz4KICAgICAgICA8L2c+CgogICAgICAgIDxnIGRhdGEtZWRnZS1mcm9tPSJzZXNzaW9uIiBkYXRhLWVkZ2UtdG89ImFwaSIgZGF0YS1lZGdlLWxhYmVsPSJSZXF1ZXN0IHdpdGggQSIgZGF0YS1lZGdlLWtleT0iMiIgZGF0YS1lZGdlLWlkPSJyZXF1ZXN0LWEiPgogICAgICAgICAgPHBhdGggZGF0YS1jb21wb3NpdGlvbi1lZGdlLWZyb209InNlc3Npb24iIGRhdGEtY29tcG9zaXRpb24tZWRnZS10bz0iYXBpIiBkYXRhLWNvbXBvc2l0aW9uLWVkZ2UtaWQ9InJlcXVlc3QtYSIgZGF0YS1jb21wb3NpdGlvbi1wb2ludHM9IjU3OS4yLDI1MDs2ODIsMjUwIiBkPSJNIDU3OS4yIDI1MCBMIDY4MiAyNTAiIGNsYXNzPSJhLWRlZmF1bHQiIHN0cm9rZS13aWR0aD0iMS40IiBtYXJrZXItZW5kPSJ1cmwoI2Fycm93aGVhZCkiIC8+CiAgICAgICAgPGc+CiAgICAgICAgICA8cmVjdCB4PSI1OTAuODAwMDAwMDAwMDAwMSIgeT0iMjMwIiB3aWR0aD0iNzkuNjAwMDAwMDAwMDAwMDEiIGhlaWdodD0iMTYiIHJ4PSIzIiBjbGFzcz0iYy1tYXNrIiAvPgogICAgICAgICAgPHRleHQgeD0iNjMwLjYiIHk9IjI0MCIgY2xhc3M9InQtYmFja2VuZCIgZm9udC1zaXplPSI5IiB0ZXh0LWFuY2hvcj0ibWlkZGxlIj5SZXF1ZXN0IHdpdGggQTwvdGV4dD4KICAgICAgICA8L2c+CiAgICAgICAgPC9nPgoKICAgICAgICA8ZyBkYXRhLWVkZ2UtZnJvbT0icm90YXRvciIgZGF0YS1lZGdlLXRvPSJzdXBwbGllciIgZGF0YS1lZGdlLWxhYmVsPSJDaGVjayBzdGF0dXMiIGRhdGEtZWRnZS1rZXk9IjMiIGRhdGEtZWRnZS1pZD0ic3RhdHVzLWNoZWNrIj4KICAgICAgICAgIDxwYXRoIGRhdGEtY29tcG9zaXRpb24tZWRnZS1mcm9tPSJyb3RhdG9yIiBkYXRhLWNvbXBvc2l0aW9uLWVkZ2UtdG89InN1cHBsaWVyIiBkYXRhLWNvbXBvc2l0aW9uLWVkZ2UtaWQ9InN0YXR1cy1jaGVjayIgZGF0YS1jb21wb3NpdGlvbi1wb2ludHM9IjExMiwyNTA7MzMxLjYsMjUwIiBkPSJNIDExMiAyNTAgTCAzMzEuNiAyNTAiIGNsYXNzPSJhLWRlZmF1bHQiIHN0cm9rZS13aWR0aD0iMS40IiBtYXJrZXItZW5kPSJ1cmwoI2Fycm93aGVhZCkiIC8+CiAgICAgICAgPGc+CiAgICAgICAgICA8cmVjdCB4PSIxOTIuNCIgeT0iMjMwIiB3aWR0aD0iNTguODAwMDAwMDAwMDAwMDA0IiBoZWlnaHQ9IjE2IiByeD0iMyIgY2xhc3M9ImMtbWFzayIgLz4KICAgICAgICAgIDx0ZXh0IHg9IjIyMS44IiB5PSIyNDAiIGNsYXNzPSJ0LWJhY2tlbmQiIGZvbnQtc2l6ZT0iOSIgdGV4dC1hbmNob3I9Im1pZGRsZSI+Q2hlY2sgc3RhdHVzPC90ZXh0PgogICAgICAgIDwvZz4KICAgICAgICA8L2c+CgogICAgICAgIDxnIGRhdGEtZWRnZS1mcm9tPSJhcGkiIGRhdGEtZWRnZS10bz0ic2Vzc2lvbiIgZGF0YS1lZGdlLWxhYmVsPSJSZXNwb25zZSIgZGF0YS1lZGdlLWtleT0iNCIgZGF0YS1lZGdlLWlkPSJyZXNwb25zZS1hIj4KICAgICAgICAgIDxwYXRoIGRhdGEtY29tcG9zaXRpb24tZWRnZS1mcm9tPSJhcGkiIGRhdGEtY29tcG9zaXRpb24tZWRnZS10bz0ic2Vzc2lvbiIgZGF0YS1jb21wb3NpdGlvbi1lZGdlLWlkPSJyZXNwb25zZS1hIiBkYXRhLWNvbXBvc2l0aW9uLXBvaW50cz0iNjgyLDI3ODs1NzkuMiwyNzgiIGQ9Ik0gNjgyIDI3OCBMIDU3OS4yIDI3OCIgY2xhc3M9ImEtZGVmYXVsdCIgc3Ryb2tlLXdpZHRoPSIxLjQiIHN0cm9rZS1kYXNoYXJyYXk9IjMsNSIgbWFya2VyLWVuZD0idXJsKCNhcnJvd2hlYWQpIiAvPgogICAgICAgIDxnPgogICAgICAgICAgPHJlY3QgeD0iNjEzLjYiIHk9IjI1OCIgd2lkdGg9IjM0IiBoZWlnaHQ9IjE2IiByeD0iMyIgY2xhc3M9ImMtbWFzayIgLz4KICAgICAgICAgIDx0ZXh0IHg9IjYzMC42IiB5PSIyNjgiIGNsYXNzPSJ0LW11dGVkIiBmb250LXNpemU9IjkiIHRleHQtYW5jaG9yPSJtaWRkbGUiPlJlc3BvbnNlPC90ZXh0PgogICAgICAgIDwvZz4KICAgICAgICA8L2c+CgogICAgICAgIDxnIGRhdGEtZWRnZS1mcm9tPSJzdXBwbGllciIgZGF0YS1lZGdlLXRvPSJyb3RhdG9yIiBkYXRhLWVkZ2UtbGFiZWw9Ik5lYXIgbGltaXQgwrcgdG9rZW4gQiIgZGF0YS1lZGdlLWtleT0iNSIgZGF0YS1lZGdlLWlkPSJuZXctdG9rZW4iPgogICAgICAgICAgPHBhdGggZGF0YS1jb21wb3NpdGlvbi1lZGdlLWZyb209InN1cHBsaWVyIiBkYXRhLWNvbXBvc2l0aW9uLWVkZ2UtdG89InJvdGF0b3IiIGRhdGEtY29tcG9zaXRpb24tZWRnZS1pZD0ibmV3LXRva2VuIiBkYXRhLWNvbXBvc2l0aW9uLXBvaW50cz0iMzMxLjYsMjc4OzExMiwyNzgiIGQ9Ik0gMzMxLjYgMjc4IEwgMTEyIDI3OCIgY2xhc3M9ImEtZGVmYXVsdCIgc3Ryb2tlLXdpZHRoPSIxLjQiIHN0cm9rZS1kYXNoYXJyYXk9IjMsNSIgbWFya2VyLWVuZD0idXJsKCNhcnJvd2hlYWQpIiAvPgogICAgICAgIDxnPgogICAgICAgICAgPHJlY3QgeD0iMTY5IiB5PSIyNTgiIHdpZHRoPSIxMDUuNjAwMDAwMDAwMDAwMDEiIGhlaWdodD0iMTYiIHJ4PSIzIiBjbGFzcz0iYy1tYXNrIiAvPgogICAgICAgICAgPHRleHQgeD0iMjIxLjgiIHk9IjI2OCIgY2xhc3M9InQtbXV0ZWQiIGZvbnQtc2l6ZT0iOSIgdGV4dC1hbmNob3I9Im1pZGRsZSI+TmVhciBsaW1pdCDCtyB0b2tlbiBCPC90ZXh0PgogICAgICAgIDwvZz4KICAgICAgICA8L2c+CgogICAgICAgIDxnIGRhdGEtZWRnZS1mcm9tPSJzZXNzaW9uIiBkYXRhLWVkZ2UtdG89ImFwaSIgZGF0YS1lZGdlLWxhYmVsPSJTdGlsbCB1c2luZyBBIiBkYXRhLWVkZ2Uta2V5PSI2IiBkYXRhLWVkZ2UtaWQ9InJlcXVlc3Qtc3RpbGwtYSI+CiAgICAgICAgICA8cGF0aCBkYXRhLWNvbXBvc2l0aW9uLWVkZ2UtZnJvbT0ic2Vzc2lvbiIgZGF0YS1jb21wb3NpdGlvbi1lZGdlLXRvPSJhcGkiIGRhdGEtY29tcG9zaXRpb24tZWRnZS1pZD0icmVxdWVzdC1zdGlsbC1hIiBkYXRhLWNvbXBvc2l0aW9uLXBvaW50cz0iNTc5LjIsMzA2OzY4MiwzMDYiIGQ9Ik0gNTc5LjIgMzA2IEwgNjgyIDMwNiIgY2xhc3M9ImEtZGVmYXVsdCIgc3Ryb2tlLXdpZHRoPSIxLjQiIG1hcmtlci1lbmQ9InVybCgjYXJyb3doZWFkKSIgLz4KICAgICAgICA8Zz4KICAgICAgICAgIDxyZWN0IHg9IjU4NS42IiB5PSIyODYiIHdpZHRoPSI5MCIgaGVpZ2h0PSIxNiIgcng9IjMiIGNsYXNzPSJjLW1hc2siIC8+CiAgICAgICAgICA8dGV4dCB4PSI2MzAuNiIgeT0iMjk2IiBjbGFzcz0idC1iYWNrZW5kIiBmb250LXNpemU9IjkiIHRleHQtYW5jaG9yPSJtaWRkbGUiPlN0aWxsIHVzaW5nIEE8L3RleHQ+CiAgICAgICAgPC9nPgogICAgICAgIDwvZz4KCiAgICAgICAgPGcgZGF0YS1lZGdlLWZyb209InJvdGF0b3IiIGRhdGEtZWRnZS10bz0ia2V5Y2hhaW4iIGRhdGEtZWRnZS1sYWJlbD0iU2F2ZSB0b2tlbiBCIiBkYXRhLWVkZ2Uta2V5PSI3IiBkYXRhLWVkZ2UtaWQ9ImNvcHktcmVwbGFjZWQiPgogICAgICAgICAgPHBhdGggZGF0YS1jb21wb3NpdGlvbi1lZGdlLWZyb209InJvdGF0b3IiIGRhdGEtY29tcG9zaXRpb24tZWRnZS10bz0ia2V5Y2hhaW4iIGRhdGEtY29tcG9zaXRpb24tZWRnZS1pZD0iY29weS1yZXBsYWNlZCIgZGF0YS1jb21wb3NpdGlvbi1wb2ludHM9IjExMiwzMDY7MjE0LjgsMzA2IiBkPSJNIDExMiAzMDYgTCAyMTQuOCAzMDYiIGNsYXNzPSJhLXNlY3VyaXR5IiBzdHJva2Utd2lkdGg9IjEuNCIgbWFya2VyLWVuZD0idXJsKCNhcnJvd2hlYWQtc2VjdXJpdHkpIiAvPgogICAgICAgIDxnPgogICAgICAgICAgPHJlY3QgeD0iMTE4LjQiIHk9IjI4NiIgd2lkdGg9IjkwIiBoZWlnaHQ9IjE2IiByeD0iMyIgY2xhc3M9ImMtbWFzayIgLz4KICAgICAgICAgIDx0ZXh0IHg9IjE2My40IiB5PSIyOTYiIGNsYXNzPSJ0LXNlY3VyaXR5IiBmb250LXNpemU9IjkiIHRleHQtYW5jaG9yPSJtaWRkbGUiPlNhdmUgdG9rZW4gQjwvdGV4dD4KICAgICAgICA8L2c+CiAgICAgICAgPC9nPgoKICAgICAgICA8ZyBkYXRhLWVkZ2UtZnJvbT0iYXBpIiBkYXRhLWVkZ2UtdG89InNlc3Npb24iIGRhdGEtZWRnZS1sYWJlbD0iTGltaXQgcmVhY2hlZCIgZGF0YS1lZGdlLWtleT0iOCIgZGF0YS1lZGdlLWlkPSJsaW1pdC1yZWFjaGVkIj4KICAgICAgICAgIDxwYXRoIGRhdGEtY29tcG9zaXRpb24tZWRnZS1mcm9tPSJhcGkiIGRhdGEtY29tcG9zaXRpb24tZWRnZS10bz0ic2Vzc2lvbiIgZGF0YS1jb21wb3NpdGlvbi1lZGdlLWlkPSJsaW1pdC1yZWFjaGVkIiBkYXRhLWNvbXBvc2l0aW9uLXBvaW50cz0iNjgyLDMzNDs1NzkuMiwzMzQiIGQ9Ik0gNjgyIDMzNCBMIDU3OS4yIDMzNCIgY2xhc3M9ImEtZGVmYXVsdCIgc3Ryb2tlLXdpZHRoPSIxLjQiIHN0cm9rZS1kYXNoYXJyYXk9IjMsNSIgbWFya2VyLWVuZD0idXJsKCNhcnJvd2hlYWQpIiAvPgogICAgICAgIDxnPgogICAgICAgICAgPHJlY3QgeD0iNjAxLjIiIHk9IjMxNCIgd2lkdGg9IjU4LjgwMDAwMDAwMDAwMDAwNCIgaGVpZ2h0PSIxNiIgcng9IjMiIGNsYXNzPSJjLW1hc2siIC8+CiAgICAgICAgICA8dGV4dCB4PSI2MzAuNiIgeT0iMzI0IiBjbGFzcz0idC1tdXRlZCIgZm9udC1zaXplPSI5IiB0ZXh0LWFuY2hvcj0ibWlkZGxlIj5MaW1pdCByZWFjaGVkPC90ZXh0PgogICAgICAgIDwvZz4KICAgICAgICA8L2c+CgogICAgICAgIDxnIGRhdGEtZWRnZS1mcm9tPSJsYXVuY2hlciIgZGF0YS1lZGdlLXRvPSJzdXBwbGllciIgZGF0YS1lZGdlLWxhYmVsPSJGZXRjaCBsYXRlc3QgdG9rZW4iIGRhdGEtZWRnZS1rZXk9IjkiIGRhdGEtZWRnZS1pZD0ic2Vjb25kLWZldGNoIj4KICAgICAgICAgIDxwYXRoIGRhdGEtY29tcG9zaXRpb24tZWRnZS1mcm9tPSJsYXVuY2hlciIgZGF0YS1jb21wb3NpdGlvbi1lZGdlLXRvPSJzdXBwbGllciIgZGF0YS1jb21wb3NpdGlvbi1lZGdlLWlkPSJzZWNvbmQtZmV0Y2giIGRhdGEtY29tcG9zaXRpb24tcG9pbnRzPSI0NDguNCwzODA7MzQ1LjYsMzgwIiBkPSJNIDQ0OC40IDM4MCBMIDM0NS42IDM4MCIgY2xhc3M9ImEtZGVmYXVsdCIgc3Ryb2tlLXdpZHRoPSIxLjQiIG1hcmtlci1lbmQ9InVybCgjYXJyb3doZWFkKSIgLz4KICAgICAgICA8Zz4KICAgICAgICAgIDxyZWN0IHg9IjM1NC42IiB5PSIzNjAiIHdpZHRoPSI4NC44IiBoZWlnaHQ9IjE2IiByeD0iMyIgY2xhc3M9ImMtbWFzayIgLz4KICAgICAgICAgIDx0ZXh0IHg9IjM5NyIgeT0iMzcwIiBjbGFzcz0idC1iYWNrZW5kIiBmb250LXNpemU9IjkiIHRleHQtYW5jaG9yPSJtaWRkbGUiPkZldGNoIGxhdGVzdCB0b2tlbjwvdGV4dD4KICAgICAgICA8L2c+CiAgICAgICAgPC9nPgoKICAgICAgICA8ZyBkYXRhLWVkZ2UtZnJvbT0ibGF1bmNoZXIiIGRhdGEtZWRnZS10bz0ic2Vzc2lvbiIgZGF0YS1lZGdlLWxhYmVsPSJOZXcgc2Vzc2lvbiB3aXRoIEIiIGRhdGEtZWRnZS1rZXk9IjEwIiBkYXRhLWVkZ2UtaWQ9InNlY29uZC1zdGFydCI+CiAgICAgICAgICA8cGF0aCBkYXRhLWNvbXBvc2l0aW9uLWVkZ2UtZnJvbT0ibGF1bmNoZXIiIGRhdGEtY29tcG9zaXRpb24tZWRnZS10bz0ic2Vzc2lvbiIgZGF0YS1jb21wb3NpdGlvbi1lZGdlLWlkPSJzZWNvbmQtc3RhcnQiIGRhdGEtY29tcG9zaXRpb24tcG9pbnRzPSI0NjIuNCw0MDg7NTY1LjIsNDA4IiBkPSJNIDQ2Mi40IDQwOCBMIDU2NS4yIDQwOCIgY2xhc3M9ImEtZW1waGFzaXMiIHN0cm9rZS13aWR0aD0iMS44IiBtYXJrZXItZW5kPSJ1cmwoI2Fycm93aGVhZC1lbXBoYXNpcykiIC8+CiAgICAgICAgPGc+CiAgICAgICAgICA8cmVjdCB4PSI0NjYuMTk5OTk5OTk5OTk5OTMiIHk9IjM4OCIgd2lkdGg9Ijk1LjIiIGhlaWdodD0iMTYiIHJ4PSIzIiBjbGFzcz0iYy1tYXNrIiAvPgogICAgICAgICAgPHRleHQgeD0iNTEzLjgiIHk9IjM5OCIgY2xhc3M9InQtYmFja2VuZCIgZm9udC1zaXplPSI5IiB0ZXh0LWFuY2hvcj0ibWlkZGxlIj5OZXcgc2Vzc2lvbiB3aXRoIEI8L3RleHQ+CiAgICAgICAgPC9nPgogICAgICAgIDwvZz4KCiAgICAgICAgCiAgICAgICAgPGcgZGF0YS1ncmFwaC1yb2xlPSJzZWdtZW50LWxhYmVsIiBkYXRhLXNlZ21lbnQtaWQ9IjAiPgogICAgICAgICAgPHJlY3QgeD0iNTYiIHk9IjE0NCIgd2lkdGg9IjQyIiBoZWlnaHQ9IjE4IiByeD0iMyIgY2xhc3M9ImMtbWFzayIgLz4KICAgICAgICAgIDx0ZXh0IHg9IjYyIiB5PSIxNTciIGNsYXNzPSJ0LWRpbSIgZm9udC1zaXplPSI5IiBmb250LXdlaWdodD0iNjAwIj5TdGFydDwvdGV4dD4KICAgICAgICA8L2c+CiAgICAgICAgPGcgZGF0YS1ncmFwaC1yb2xlPSJzZWdtZW50LWxhYmVsIiBkYXRhLXNlZ21lbnQtaWQ9IjEiPgogICAgICAgICAgPHJlY3QgeD0iNTYiIHk9IjIxNiIgd2lkdGg9Ijg2LjgiIGhlaWdodD0iMTgiIHJ4PSIzIiBjbGFzcz0iYy1tYXNrIiAvPgogICAgICAgICAgPHRleHQgeD0iNjIiIHk9IjIyOSIgY2xhc3M9InQtZGltIiBmb250LXNpemU9IjkiIGZvbnQtd2VpZ2h0PSI2MDAiPldvcmsgYW5kIHJvdGF0aW9uPC90ZXh0PgogICAgICAgIDwvZz4KICAgICAgICA8ZyBkYXRhLWdyYXBoLXJvbGU9InNlZ21lbnQtbGFiZWwiIGRhdGEtc2VnbWVudC1pZD0iMiI+CiAgICAgICAgICA8cmVjdCB4PSI1NiIgeT0iMzQ0IiB3aWR0aD0iNjAuODAwMDAwMDAwMDAwMDA0IiBoZWlnaHQ9IjE4IiByeD0iMyIgY2xhc3M9ImMtbWFzayIgLz4KICAgICAgICAgIDx0ZXh0IHg9IjYyIiB5PSIzNTciIGNsYXNzPSJ0LWRpbSIgZm9udC1zaXplPSI5IiBmb250LXdlaWdodD0iNjAwIj5SZXN0YXJ0PC90ZXh0PgogICAgICAgIDwvZz4KCiAgICAgICAgCiAgICAgICAgPGcgaWQ9Im5vZGUtcm90YXRvciIgZGF0YS1ub2RlLWlkPSJyb3RhdG9yIiBkYXRhLW5vZGUtbGFiZWw9IlJvdGF0aW9uIHdvcmtlciIgdGFiaW5kZXg9IjAiIHJvbGU9ImJ1dHRvbiIgYXJpYS1sYWJlbD0iRm9jdXMgUm90YXRpb24gd29ya2VyLCBCYWNrZ3JvdW5kLCBTZXF1ZW5jZSBwYXJ0aWNpcGFudCIgYXJpYS1wcmVzc2VkPSJmYWxzZSIgZGF0YS1ub2RlLWtpbmQ9ImJhY2tlbmQiIGRhdGEtbm9kZS1zdWJsYWJlbD0iQmFja2dyb3VuZCIgZGF0YS1ub2RlLWNvbnRleHQ9IlNlcXVlbmNlIHBhcnRpY2lwYW50Ij4KICAgICAgICAgIDx0aXRsZT5Sb3RhdGlvbiB3b3JrZXIgwrcgQmFja2dyb3VuZCDCtyBTZXF1ZW5jZSBwYXJ0aWNpcGFudDwvdGl0bGU+CiAgICAgICAgICA8cmVjdCB4PSI2MiIgeT0iNzIiIHdpZHRoPSI4NiIgaGVpZ2h0PSI1NCIgcng9IjYiIGNsYXNzPSJjLW1hc2siIC8+CiAgICAgICAgICA8cmVjdCB4PSI2MiIgeT0iNzIiIHdpZHRoPSI4NiIgaGVpZ2h0PSI1NCIgcng9IjYiIGNsYXNzPSJjLWJhY2tlbmQiIHN0cm9rZS13aWR0aD0iMS41IiAvPgogICAgICAgICAgPGcgYXJpYS1oaWRkZW49InRydWUiIGRhdGEtc2VtYW50aWMtc2lnaWw9ImJhY2tlbmQiIGNsYXNzPSJzZW1hbnRpYy1zaWdpbCBzLWJhY2tlbmQiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDY4IDc4KSBzY2FsZSgwLjY4NzUpIj4KICAgICAgICAgICAgPHBhdGggZD0iTTYgMyAzIDhsMyA1TTEwIDNsMyA1LTMgNSIgLz4KICAgICAgICAgIDwvZz4KICAgICAgICAgIDx0ZXh0IGRhdGEtbm9kZS1sYWJlbD0iIiB4PSIxMDUiIHk9Ijk0IiBjbGFzcz0idC1wcmltYXJ5IiBmb250LXNpemU9IjkuNSIgZm9udC13ZWlnaHQ9IjYwMCIgdGV4dC1hbmNob3I9Im1pZGRsZSI+Um90YXRpb24gd29ya2VyPC90ZXh0PgogICAgICAgICAgPHRleHQgeD0iMTA1IiB5PSIxMTEiIGNsYXNzPSJ0LW11dGVkIiBmb250LXNpemU9IjciIHRleHQtYW5jaG9yPSJtaWRkbGUiPkJhY2tncm91bmQ8L3RleHQ+CiAgICAgICAgPC9nPgoKICAgICAgICA8ZyBpZD0ibm9kZS1rZXljaGFpbiIgZGF0YS1ub2RlLWlkPSJrZXljaGFpbiIgZGF0YS1ub2RlLWxhYmVsPSJLZXljaGFpbiIgdGFiaW5kZXg9IjAiIHJvbGU9ImJ1dHRvbiIgYXJpYS1sYWJlbD0iRm9jdXMgS2V5Y2hhaW4sIENhY2hlZCB0b2tlbiwgU2VxdWVuY2UgcGFydGljaXBhbnQiIGFyaWEtcHJlc3NlZD0iZmFsc2UiIGRhdGEtbm9kZS1raW5kPSJzZWN1cml0eSIgZGF0YS1ub2RlLXN1YmxhYmVsPSJDYWNoZWQgdG9rZW4iIGRhdGEtbm9kZS1jb250ZXh0PSJTZXF1ZW5jZSBwYXJ0aWNpcGFudCI+CiAgICAgICAgICA8dGl0bGU+S2V5Y2hhaW4gwrcgQ2FjaGVkIHRva2VuIMK3IFNlcXVlbmNlIHBhcnRpY2lwYW50PC90aXRsZT4KICAgICAgICAgIDxyZWN0IHg9IjE3OC44IiB5PSI3MiIgd2lkdGg9Ijg2IiBoZWlnaHQ9IjU0IiByeD0iNiIgY2xhc3M9ImMtbWFzayIgLz4KICAgICAgICAgIDxyZWN0IHg9IjE3OC44IiB5PSI3MiIgd2lkdGg9Ijg2IiBoZWlnaHQ9IjU0IiByeD0iNiIgY2xhc3M9ImMtc2VjdXJpdHkiIHN0cm9rZS13aWR0aD0iMS41IiAvPgogICAgICAgICAgPGcgYXJpYS1oaWRkZW49InRydWUiIGRhdGEtc2VtYW50aWMtc2lnaWw9InNlY3VyaXR5IiBjbGFzcz0ic2VtYW50aWMtc2lnaWwgcy1zZWN1cml0eSIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTg0LjggNzgpIHNjYWxlKDAuNjg3NSkiPgogICAgICAgICAgICA8cGF0aCBkPSJNOCAyLjIgMTMgNHYzLjVjMCAzLjEtMS44IDUuNC01IDYuNS0zLjItMS4xLTUtMy40LTUtNi41VjRaIiAvPgogICAgICAgICAgICA8cGF0aCBkPSJtNS44IDggMS41IDEuNSAzLTMiIC8+CiAgICAgICAgICA8L2c+CiAgICAgICAgICA8dGV4dCBkYXRhLW5vZGUtbGFiZWw9IiIgeD0iMjIxLjgiIHk9Ijk0IiBjbGFzcz0idC1wcmltYXJ5IiBmb250LXNpemU9IjExIiBmb250LXdlaWdodD0iNjAwIiB0ZXh0LWFuY2hvcj0ibWlkZGxlIj5LZXljaGFpbjwvdGV4dD4KICAgICAgICAgIDx0ZXh0IHg9IjIyMS44IiB5PSIxMTEiIGNsYXNzPSJ0LW11dGVkIiBmb250LXNpemU9IjciIHRleHQtYW5jaG9yPSJtaWRkbGUiPkNhY2hlZCB0b2tlbjwvdGV4dD4KICAgICAgICA8L2c+CgogICAgICAgIDxnIGlkPSJub2RlLXN1cHBsaWVyIiBkYXRhLW5vZGUtaWQ9InN1cHBsaWVyIiBkYXRhLW5vZGUtbGFiZWw9IkNyZWRlbnRpYWwgc2VydmVyIiB0YWJpbmRleD0iMCIgcm9sZT0iYnV0dG9uIiBhcmlhLWxhYmVsPSJGb2N1cyBDcmVkZW50aWFsIHNlcnZlciwgUHJvdmlkZXMgdG9rZW5zLCBTZXF1ZW5jZSBwYXJ0aWNpcGFudCIgYXJpYS1wcmVzc2VkPSJmYWxzZSIgZGF0YS1ub2RlLWtpbmQ9ImV4dGVybmFsIiBkYXRhLW5vZGUtc3VibGFiZWw9IlByb3ZpZGVzIHRva2VucyIgZGF0YS1ub2RlLWNvbnRleHQ9IlNlcXVlbmNlIHBhcnRpY2lwYW50Ij4KICAgICAgICAgIDx0aXRsZT5DcmVkZW50aWFsIHNlcnZlciDCtyBQcm92aWRlcyB0b2tlbnMgwrcgU2VxdWVuY2UgcGFydGljaXBhbnQ8L3RpdGxlPgogICAgICAgICAgPHJlY3QgeD0iMjk1LjYiIHk9IjcyIiB3aWR0aD0iODYiIGhlaWdodD0iNTQiIHJ4PSI2IiBjbGFzcz0iYy1tYXNrIiAvPgogICAgICAgICAgPHJlY3QgeD0iMjk1LjYiIHk9IjcyIiB3aWR0aD0iODYiIGhlaWdodD0iNTQiIHJ4PSI2IiBjbGFzcz0iYy1leHRlcm5hbCIgc3Ryb2tlLXdpZHRoPSIxLjUiIC8+CiAgICAgICAgICA8ZyBhcmlhLWhpZGRlbj0idHJ1ZSIgZGF0YS1zZW1hbnRpYy1zaWdpbD0iZXh0ZXJuYWwiIGNsYXNzPSJzZW1hbnRpYy1zaWdpbCBzLWV4dGVybmFsIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgzMDEuNiA3OCkgc2NhbGUoMC42ODc1KSI+CiAgICAgICAgICAgIDxyZWN0IHg9IjIuNSIgeT0iNSIgd2lkdGg9IjguNSIgaGVpZ2h0PSI4IiByeD0iMS41IiAvPgogICAgICAgICAgICA8cGF0aCBkPSJNOCAyLjVoNS41VjhNMTMuNSAyLjUgNy41IDguNSIgLz4KICAgICAgICAgIDwvZz4KICAgICAgICAgIDx0ZXh0IGRhdGEtbm9kZS1sYWJlbD0iIiB4PSIzMzguNiIgeT0iOTQiIGNsYXNzPSJ0LXByaW1hcnkiIGZvbnQtc2l6ZT0iOS41IiBmb250LXdlaWdodD0iNjAwIiB0ZXh0LWFuY2hvcj0ibWlkZGxlIj5DcmVkZW50aWFsIHNlcnZlcjwvdGV4dD4KICAgICAgICAgIDx0ZXh0IHg9IjMzOC42IiB5PSIxMTEiIGNsYXNzPSJ0LW11dGVkIiBmb250LXNpemU9IjciIHRleHQtYW5jaG9yPSJtaWRkbGUiPlByb3ZpZGVzIHRva2VuczwvdGV4dD4KICAgICAgICA8L2c+CgogICAgICAgIDxnIGlkPSJub2RlLWxhdW5jaGVyIiBkYXRhLW5vZGUtaWQ9ImxhdW5jaGVyIiBkYXRhLW5vZGUtbGFiZWw9IkxhdW5jaGVyIiB0YWJpbmRleD0iMCIgcm9sZT0iYnV0dG9uIiBhcmlhLWxhYmVsPSJGb2N1cyBMYXVuY2hlciwgU3RhcnRlZCBieSB1c2VyLCBTZXF1ZW5jZSBwYXJ0aWNpcGFudCIgYXJpYS1wcmVzc2VkPSJmYWxzZSIgZGF0YS1ub2RlLWtpbmQ9ImJhY2tlbmQiIGRhdGEtbm9kZS1zdWJsYWJlbD0iU3RhcnRlZCBieSB1c2VyIiBkYXRhLW5vZGUtY29udGV4dD0iU2VxdWVuY2UgcGFydGljaXBhbnQiPgogICAgICAgICAgPHRpdGxlPkxhdW5jaGVyIMK3IFN0YXJ0ZWQgYnkgdXNlciDCtyBTZXF1ZW5jZSBwYXJ0aWNpcGFudDwvdGl0bGU+CiAgICAgICAgICA8cmVjdCB4PSI0MTIuNCIgeT0iNzIiIHdpZHRoPSI4NiIgaGVpZ2h0PSI1NCIgcng9IjYiIGNsYXNzPSJjLW1hc2siIC8+CiAgICAgICAgICA8cmVjdCB4PSI0MTIuNCIgeT0iNzIiIHdpZHRoPSI4NiIgaGVpZ2h0PSI1NCIgcng9IjYiIGNsYXNzPSJjLWJhY2tlbmQiIHN0cm9rZS13aWR0aD0iMS41IiAvPgogICAgICAgICAgPGcgYXJpYS1oaWRkZW49InRydWUiIGRhdGEtc2VtYW50aWMtc2lnaWw9ImJhY2tlbmQiIGNsYXNzPSJzZW1hbnRpYy1zaWdpbCBzLWJhY2tlbmQiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDQxOC40IDc4KSBzY2FsZSgwLjY4NzUpIj4KICAgICAgICAgICAgPHBhdGggZD0iTTYgMyAzIDhsMyA1TTEwIDNsMyA1LTMgNSIgLz4KICAgICAgICAgIDwvZz4KICAgICAgICAgIDx0ZXh0IGRhdGEtbm9kZS1sYWJlbD0iIiB4PSI0NTUuNCIgeT0iOTQiIGNsYXNzPSJ0LXByaW1hcnkiIGZvbnQtc2l6ZT0iMTEiIGZvbnQtd2VpZ2h0PSI2MDAiIHRleHQtYW5jaG9yPSJtaWRkbGUiPkxhdW5jaGVyPC90ZXh0PgogICAgICAgICAgPHRleHQgeD0iNDU1LjQiIHk9IjExMSIgY2xhc3M9InQtbXV0ZWQiIGZvbnQtc2l6ZT0iNyIgdGV4dC1hbmNob3I9Im1pZGRsZSI+U3RhcnRlZCBieSB1c2VyPC90ZXh0PgogICAgICAgIDwvZz4KCiAgICAgICAgPGcgaWQ9Im5vZGUtc2Vzc2lvbiIgZGF0YS1ub2RlLWlkPSJzZXNzaW9uIiBkYXRhLW5vZGUtbGFiZWw9IlNlc3Npb24iIHRhYmluZGV4PSIwIiByb2xlPSJidXR0b24iIGFyaWEtbGFiZWw9IkZvY3VzIFNlc3Npb24sIENsYXVkZSBDb2RlLCBTZXF1ZW5jZSBwYXJ0aWNpcGFudCIgYXJpYS1wcmVzc2VkPSJmYWxzZSIgZGF0YS1ub2RlLWtpbmQ9ImZyb250ZW5kIiBkYXRhLW5vZGUtc3VibGFiZWw9IkNsYXVkZSBDb2RlIiBkYXRhLW5vZGUtY29udGV4dD0iU2VxdWVuY2UgcGFydGljaXBhbnQiPgogICAgICAgICAgPHRpdGxlPlNlc3Npb24gwrcgQ2xhdWRlIENvZGUgwrcgU2VxdWVuY2UgcGFydGljaXBhbnQ8L3RpdGxlPgogICAgICAgICAgPHJlY3QgeD0iNTI5LjIiIHk9IjcyIiB3aWR0aD0iODYiIGhlaWdodD0iNTQiIHJ4PSI2IiBjbGFzcz0iYy1tYXNrIiAvPgogICAgICAgICAgPHJlY3QgeD0iNTI5LjIiIHk9IjcyIiB3aWR0aD0iODYiIGhlaWdodD0iNTQiIHJ4PSI2IiBjbGFzcz0iYy1mcm9udGVuZCIgc3Ryb2tlLXdpZHRoPSIxLjUiIC8+CiAgICAgICAgICA8ZyBhcmlhLWhpZGRlbj0idHJ1ZSIgZGF0YS1zZW1hbnRpYy1zaWdpbD0iZnJvbnRlbmQiIGNsYXNzPSJzZW1hbnRpYy1zaWdpbCBzLWZyb250ZW5kIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSg1MzUuMiA3OCkgc2NhbGUoMC42ODc1KSI+CiAgICAgICAgICAgIDxyZWN0IHg9IjIiIHk9IjMiIHdpZHRoPSIxMiIgaGVpZ2h0PSIxMCIgcng9IjIiIC8+CiAgICAgICAgICAgIDxwYXRoIGQ9Ik0yIDYuNWgxMiIgLz4KICAgICAgICAgICAgPGNpcmNsZSBjeD0iNC4xIiBjeT0iNC44IiByPSIuNyIgY2xhc3M9InNpZ2lsLWZpbGwiIC8+CiAgICAgICAgICAgIDxjaXJjbGUgY3g9IjYuMyIgY3k9IjQuOCIgcj0iLjciIGNsYXNzPSJzaWdpbC1maWxsIiAvPgogICAgICAgICAgPC9nPgogICAgICAgICAgPHRleHQgZGF0YS1ub2RlLWxhYmVsPSIiIHg9IjU3Mi4yIiB5PSI5NCIgY2xhc3M9InQtcHJpbWFyeSIgZm9udC1zaXplPSIxMSIgZm9udC13ZWlnaHQ9IjYwMCIgdGV4dC1hbmNob3I9Im1pZGRsZSI+U2Vzc2lvbjwvdGV4dD4KICAgICAgICAgIDx0ZXh0IHg9IjU3Mi4yIiB5PSIxMTEiIGNsYXNzPSJ0LW11dGVkIiBmb250LXNpemU9IjciIHRleHQtYW5jaG9yPSJtaWRkbGUiPkNsYXVkZSBDb2RlPC90ZXh0PgogICAgICAgIDwvZz4KCiAgICAgICAgPGcgaWQ9Im5vZGUtYXBpIiBkYXRhLW5vZGUtaWQ9ImFwaSIgZGF0YS1ub2RlLWxhYmVsPSJQcm92aWRlciBBUEkiIHRhYmluZGV4PSIwIiByb2xlPSJidXR0b24iIGFyaWEtbGFiZWw9IkZvY3VzIFByb3ZpZGVyIEFQSSwgQW50aHJvcGljLCBTZXF1ZW5jZSBwYXJ0aWNpcGFudCIgYXJpYS1wcmVzc2VkPSJmYWxzZSIgZGF0YS1ub2RlLWtpbmQ9ImV4dGVybmFsIiBkYXRhLW5vZGUtc3VibGFiZWw9IkFudGhyb3BpYyIgZGF0YS1ub2RlLWNvbnRleHQ9IlNlcXVlbmNlIHBhcnRpY2lwYW50Ij4KICAgICAgICAgIDx0aXRsZT5Qcm92aWRlciBBUEkgwrcgQW50aHJvcGljIMK3IFNlcXVlbmNlIHBhcnRpY2lwYW50PC90aXRsZT4KICAgICAgICAgIDxyZWN0IHg9IjY0NiIgeT0iNzIiIHdpZHRoPSI4NiIgaGVpZ2h0PSI1NCIgcng9IjYiIGNsYXNzPSJjLW1hc2siIC8+CiAgICAgICAgICA8cmVjdCB4PSI2NDYiIHk9IjcyIiB3aWR0aD0iODYiIGhlaWdodD0iNTQiIHJ4PSI2IiBjbGFzcz0iYy1leHRlcm5hbCIgc3Ryb2tlLXdpZHRoPSIxLjUiIC8+CiAgICAgICAgICA8ZyBhcmlhLWhpZGRlbj0idHJ1ZSIgZGF0YS1zZW1hbnRpYy1zaWdpbD0iZXh0ZXJuYWwiIGNsYXNzPSJzZW1hbnRpYy1zaWdpbCBzLWV4dGVybmFsIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSg2NTIgNzgpIHNjYWxlKDAuNjg3NSkiPgogICAgICAgICAgICA8cmVjdCB4PSIyLjUiIHk9IjUiIHdpZHRoPSI4LjUiIGhlaWdodD0iOCIgcng9IjEuNSIgLz4KICAgICAgICAgICAgPHBhdGggZD0iTTggMi41aDUuNVY4TTEzLjUgMi41IDcuNSA4LjUiIC8+CiAgICAgICAgICA8L2c+CiAgICAgICAgICA8dGV4dCBkYXRhLW5vZGUtbGFiZWw9IiIgeD0iNjg5IiB5PSI5NCIgY2xhc3M9InQtcHJpbWFyeSIgZm9udC1zaXplPSIxMSIgZm9udC13ZWlnaHQ9IjYwMCIgdGV4dC1hbmNob3I9Im1pZGRsZSI+UHJvdmlkZXIgQVBJPC90ZXh0PgogICAgICAgICAgPHRleHQgeD0iNjg5IiB5PSIxMTEiIGNsYXNzPSJ0LW11dGVkIiBmb250LXNpemU9IjciIHRleHQtYW5jaG9yPSJtaWRkbGUiPkFudGhyb3BpYzwvdGV4dD4KICAgICAgICA8L2c+CgogICAgICAgIAoKICAgICAgPC9zdmc+" loading="lazy"><img class="cc-diagram-dark" alt="When a replacement token takes effect. While the session uses token A, the rotation worker replaces the cached copy with B. The session continues using A until it reaches the limit. Restarting the session makes it read B." src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGRhdGEtdGhlbWU9ImRhcmsiIHZpZXdCb3g9IjAgMCA3NzIgNDk2IiByb2xlPSJpbWciIGxhbmc9ImVuIiB4bWw6bGFuZz0iZW4iIGFyaWEtbGFiZWxsZWRieT0iYXJjaGlmeS1kaWFncmFtLXRpdGxlIGFyY2hpZnktZGlhZ3JhbS1kZXNjcmlwdGlvbiIgZGF0YS1wcmVzZXQ9ImNsYXNzaWMiIGRhdGEtcXVhbGl0eS1wcm9maWxlPSJzaG93Y2FzZSIgd2lkdGg9Ijc3MiIgaGVpZ2h0PSI0OTYiPjxzdHlsZT5zdmcgeyBmb250LWZhbWlseTogLWFwcGxlLXN5c3RlbSwgQmxpbmtNYWNTeXN0ZW1Gb250LCAiQXBwbGUgU0QgR290aGljIE5lbyIsICJNYWxndW4gR290aGljIiwgIk5vdG8gU2FucyBLUiIsICJOb3RvIFNhbnMgQ0pLIEtSIiwgc2Fucy1zZXJpZjsgfQo6cm9vdCwgW2RhdGEtdGhlbWU9ImRhcmsiXSB7IC0tYmc6ICMwMjA2MTc7IC0tZ3JpZDogIzFlMjkzYjsgLS10ZXh0OiAjZmZmZmZmOyAtLXRleHQtbXV0ZWQ6ICM5NGEzYjg7IC0tdGV4dC1kaW06ICM0NzU1Njk7IC0tdGV4dC1mYWludDogIzdkOGRhMTsgLS1wYW5lbDogcmdiYSgxNSwgMjMsIDQyLCAwLjUpOyAtLXBhbmVsLWJvcmRlcjogIzFlMjkzYjsgLS1sYW5lLWZpbGw6IHJnYmEoMTUsIDIzLCA0MiwgMC4yMik7IC0tbGFuZS1zdHJva2U6ICMzMzQxNTU7IC0tYXJyb3c6ICM2NDc0OGI7IC0tYXJyb3ctZW1waGFzaXM6ICMzNGQzOTk7IC0tbWFzazogIzBmMTcyYTsgLS1mcm9udGVuZC1maWxsOiByZ2JhKDgsIDUxLCA2OCwgMC40KTsgLS1mcm9udGVuZC1zdHJva2U6ICMyMmQzZWU7IC0tYmFja2VuZC1maWxsOiByZ2JhKDYsIDc4LCA1OSwgMC40KTsgLS1iYWNrZW5kLXN0cm9rZTogIzM0ZDM5OTsgLS1kYXRhYmFzZS1maWxsOiByZ2JhKDc2LCAyOSwgMTQ5LCAwLjQpOyAtLWRhdGFiYXNlLXN0cm9rZTogI2E3OGJmYTsgLS1jbG91ZC1maWxsOiByZ2JhKDEyMCwgNTMsIDE1LCAwLjMpOyAtLWNsb3VkLXN0cm9rZTogI2ZiYmYyNDsgLS1zZWN1cml0eS1maWxsOiByZ2JhKDEzNiwgMTksIDU1LCAwLjQpOyAtLXNlY3VyaXR5LXN0cm9rZTogI2ZiNzE4NTsgLS1tZXNzYWdlYnVzLWZpbGw6IHJnYmEoMjUxLCAxNDYsIDYwLCAwLjMpOyAtLW1lc3NhZ2VidXMtc3Ryb2tlOiAjZmI5MjNjOyAtLWV4dGVybmFsLWZpbGw6IHJnYmEoMzAsIDQxLCA1OSwgMC41KTsgLS1leHRlcm5hbC1zdHJva2U6ICM5NGEzYjg7IC0tdG9vbGJhci1iZzogcmdiYSgxNSwgMjMsIDQyLCAwLjgpOyAtLXRvb2xiYXItYm9yZGVyOiAjMzM0MTU1OyAtLXRvb2xiYXItdGV4dDogI2UyZThmMDsgLS10b29sYmFyLWhvdmVyOiByZ2JhKDE1LCAyMywgNDIsIDAuOTUpOyAtLXRvb2xiYXItbWVudS1iZzogIzBmMTcyYTsgfQpzdmcgeyB3aWR0aDogMTAwJTsgbWluLXdpZHRoOiBtaW4oOTAwcHgsIDEwMCUpOyBkaXNwbGF5OiBibG9jazsgdHJhbnNmb3JtLW9yaWdpbjogMHB4IDBweDsgdHJhbnNpdGlvbjogdHJhbnNmb3JtIDAuMThzOyB9Ci5jLWdyaWQgeyBzdHJva2U6IHZhcigtLWdyaWQpOyBmaWxsOiBub25lOyB9Ci5jLW1hc2sgeyBmaWxsOiB2YXIoLS1tYXNrKTsgc3Ryb2tlOiBub25lOyB9Ci5jLWZyb250ZW5kIHsgZmlsbDogdmFyKC0tZnJvbnRlbmQtZmlsbCk7IHN0cm9rZTogdmFyKC0tZnJvbnRlbmQtc3Ryb2tlKTsgfQouYy1iYWNrZW5kIHsgZmlsbDogdmFyKC0tYmFja2VuZC1maWxsKTsgc3Ryb2tlOiB2YXIoLS1iYWNrZW5kLXN0cm9rZSk7IH0KLmMtc2VjdXJpdHkgeyBmaWxsOiB2YXIoLS1zZWN1cml0eS1maWxsKTsgc3Ryb2tlOiB2YXIoLS1zZWN1cml0eS1zdHJva2UpOyB9Ci5jLWV4dGVybmFsIHsgZmlsbDogdmFyKC0tZXh0ZXJuYWwtZmlsbCk7IHN0cm9rZTogdmFyKC0tZXh0ZXJuYWwtc3Ryb2tlKTsgfQoudC1wcmltYXJ5IHsgZmlsbDogdmFyKC0tdGV4dCk7IH0KLnQtbXV0ZWQgeyBmaWxsOiB2YXIoLS10ZXh0LW11dGVkKTsgfQoudC1kaW0geyBmaWxsOiB2YXIoLS10ZXh0LWRpbSk7IH0KLnQtYmFja2VuZCB7IGZpbGw6IHZhcigtLWJhY2tlbmQtc3Ryb2tlKTsgfQoudC1zZWN1cml0eSB7IGZpbGw6IHZhcigtLXNlY3VyaXR5LXN0cm9rZSk7IH0Kc3ZnIC5zZW1hbnRpYy1zaWdpbCB7IGZpbGw6IG5vbmU7IHN0cm9rZTogY3VycmVudGNvbG9yOyBzdHJva2Utd2lkdGg6IDEuMzU7IHN0cm9rZS1saW5lY2FwOiByb3VuZDsgc3Ryb2tlLWxpbmVqb2luOiByb3VuZDsgb3BhY2l0eTogMC43NjsgcG9pbnRlci1ldmVudHM6IG5vbmU7IH0Kc3ZnIC5zZW1hbnRpYy1zaWdpbCAmZ3Q7ICogeyB2ZWN0b3ItZWZmZWN0OiBub24tc2NhbGluZy1zdHJva2U7IH0Kc3ZnIC5zZW1hbnRpYy1zaWdpbCAuc2lnaWwtZmlsbCB7IGZpbGw6IGN1cnJlbnRjb2xvcjsgc3Ryb2tlOiBub25lOyB9CnN2ZyAucy1mcm9udGVuZCB7IGNvbG9yOiB2YXIoLS1mcm9udGVuZC1zdHJva2UpOyB9CnN2ZyAucy1iYWNrZW5kIHsgY29sb3I6IHZhcigtLWJhY2tlbmQtc3Ryb2tlKTsgfQpzdmcgLnMtc2VjdXJpdHkgeyBjb2xvcjogdmFyKC0tc2VjdXJpdHktc3Ryb2tlKTsgfQpzdmcgLnMtZXh0ZXJuYWwgeyBjb2xvcjogdmFyKC0tZXh0ZXJuYWwtc3Ryb2tlKTsgfQouYS1kZWZhdWx0IHsgc3Ryb2tlOiB2YXIoLS1hcnJvdyk7IGZpbGw6IG5vbmU7IH0KLmEtZW1waGFzaXMgeyBzdHJva2U6IHZhcigtLWFycm93LWVtcGhhc2lzKTsgZmlsbDogbm9uZTsgfQouYS1zZWN1cml0eSB7IHN0cm9rZTogdmFyKC0tc2VjdXJpdHktc3Ryb2tlKTsgZmlsbDogbm9uZTsgc3Ryb2tlLWRhc2hhcnJheTogNSwgNTsgfQoubS1kZWZhdWx0IHsgZmlsbDogdmFyKC0tYXJyb3cpOyB9Ci5tLWVtcGhhc2lzIHsgZmlsbDogdmFyKC0tYXJyb3ctZW1waGFzaXMpOyB9Ci5tLXNlY3VyaXR5IHsgZmlsbDogdmFyKC0tc2VjdXJpdHktc3Ryb2tlKTsgfQoubS1kYXNoZWQgeyBmaWxsOiB2YXIoLS1kYXRhYmFzZS1zdHJva2UpOyB9CnN2ZyBbZGF0YS1ub2RlLWlkXSB7IGN1cnNvcjogcG9pbnRlcjsgdHJhbnNpdGlvbjogb3BhY2l0eSAwLjE4cywgZmlsdGVyIDAuMThzOyB9CnN2ZyBbZGF0YS1lZGdlLWZyb21dIHsgdHJhbnNpdGlvbjogb3BhY2l0eSAwLjE4cywgZmlsdGVyIDAuMThzOyB9Ci5jLWxhbmUgeyBmaWxsOiB2YXIoLS1sYW5lLWZpbGwpOyBzdHJva2U6IHZhcigtLWxhbmUtc3Ryb2tlKTsgc3Ryb2tlLWRhc2hhcnJheTogNiwgNjsgfQo6cm9vdCwgc3ZnIHsgLS1iZzogIzAyMDYxNzsgLS1ncmlkOiAjMWUyOTNiOyAtLXRleHQ6ICNmZmZmZmY7IC0tdGV4dC1tdXRlZDogIzk0YTNiODsgLS10ZXh0LWRpbTogIzQ3NTU2OTsgLS10ZXh0LWZhaW50OiAjN2Q4ZGExOyAtLXBhbmVsOiByZ2JhKDE1LCAyMywgNDIsIDAuNSk7IC0tcGFuZWwtYm9yZGVyOiAjMWUyOTNiOyAtLWxhbmUtZmlsbDogcmdiYSgxNSwgMjMsIDQyLCAwLjIyKTsgLS1sYW5lLXN0cm9rZTogIzMzNDE1NTsgLS1hcnJvdzogIzY0NzQ4YjsgLS1hcnJvdy1lbXBoYXNpczogIzM0ZDM5OTsgLS1tYXNrOiAjMGYxNzJhOyAtLWZyb250ZW5kLWZpbGw6IHJnYmEoOCwgNTEsIDY4LCAwLjQpOyAtLWZyb250ZW5kLXN0cm9rZTogIzIyZDNlZTsgLS1iYWNrZW5kLWZpbGw6IHJnYmEoNiwgNzgsIDU5LCAwLjQpOyAtLWJhY2tlbmQtc3Ryb2tlOiAjMzRkMzk5OyAtLWRhdGFiYXNlLWZpbGw6IHJnYmEoNzYsIDI5LCAxNDksIDAuNCk7IC0tZGF0YWJhc2Utc3Ryb2tlOiAjYTc4YmZhOyAtLWNsb3VkLWZpbGw6IHJnYmEoMTIwLCA1MywgMTUsIDAuMyk7IC0tY2xvdWQtc3Ryb2tlOiAjZmJiZjI0OyAtLXNlY3VyaXR5LWZpbGw6IHJnYmEoMTM2LCAxOSwgNTUsIDAuNCk7IC0tc2VjdXJpdHktc3Ryb2tlOiAjZmI3MTg1OyAtLW1lc3NhZ2VidXMtZmlsbDogcmdiYSgyNTEsIDE0NiwgNjAsIDAuMyk7IC0tbWVzc2FnZWJ1cy1zdHJva2U6ICNmYjkyM2M7IC0tZXh0ZXJuYWwtZmlsbDogcmdiYSgzMCwgNDEsIDU5LCAwLjUpOyAtLWV4dGVybmFsLXN0cm9rZTogIzk0YTNiODsgLS10b29sYmFyLWJnOiByZ2JhKDE1LCAyMywgNDIsIDAuOCk7IC0tdG9vbGJhci1ib3JkZXI6ICMzMzQxNTU7IC0tdG9vbGJhci10ZXh0OiAjZTJlOGYwOyAtLXRvb2xiYXItaG92ZXI6IHJnYmEoMTUsIDIzLCA0MiwgMC45NSk7IC0tdG9vbGJhci1tZW51LWJnOiAjMGYxNzJhOyB9CnN2Z1tkYXRhLXRoZW1lPSJkYXJrIl0geyAtLWJnOiAjMDIwNjE3OyAtLWdyaWQ6ICMxZTI5M2I7IC0tdGV4dDogI2ZmZmZmZjsgLS10ZXh0LW11dGVkOiAjOTRhM2I4OyAtLXRleHQtZGltOiAjNDc1NTY5OyAtLXRleHQtZmFpbnQ6ICM3ZDhkYTE7IC0tcGFuZWw6IHJnYmEoMTUsIDIzLCA0MiwgMC41KTsgLS1wYW5lbC1ib3JkZXI6ICMxZTI5M2I7IC0tbGFuZS1maWxsOiByZ2JhKDE1LCAyMywgNDIsIDAuMjIpOyAtLWxhbmUtc3Ryb2tlOiAjMzM0MTU1OyAtLWFycm93OiAjNjQ3NDhiOyAtLWFycm93LWVtcGhhc2lzOiAjMzRkMzk5OyAtLW1hc2s6ICMwZjE3MmE7IC0tZnJvbnRlbmQtZmlsbDogcmdiYSg4LCA1MSwgNjgsIDAuNCk7IC0tZnJvbnRlbmQtc3Ryb2tlOiAjMjJkM2VlOyAtLWJhY2tlbmQtZmlsbDogcmdiYSg2LCA3OCwgNTksIDAuNCk7IC0tYmFja2VuZC1zdHJva2U6ICMzNGQzOTk7IC0tZGF0YWJhc2UtZmlsbDogcmdiYSg3NiwgMjksIDE0OSwgMC40KTsgLS1kYXRhYmFzZS1zdHJva2U6ICNhNzhiZmE7IC0tY2xvdWQtZmlsbDogcmdiYSgxMjAsIDUzLCAxNSwgMC4zKTsgLS1jbG91ZC1zdHJva2U6ICNmYmJmMjQ7IC0tc2VjdXJpdHktZmlsbDogcmdiYSgxMzYsIDE5LCA1NSwgMC40KTsgLS1zZWN1cml0eS1zdHJva2U6ICNmYjcxODU7IC0tbWVzc2FnZWJ1cy1maWxsOiByZ2JhKDI1MSwgMTQ2LCA2MCwgMC4zKTsgLS1tZXNzYWdlYnVzLXN0cm9rZTogI2ZiOTIzYzsgLS1leHRlcm5hbC1maWxsOiByZ2JhKDMwLCA0MSwgNTksIDAuNSk7IC0tZXh0ZXJuYWwtc3Ryb2tlOiAjOTRhM2I4OyAtLXRvb2xiYXItYmc6IHJnYmEoMTUsIDIzLCA0MiwgMC44KTsgLS10b29sYmFyLWJvcmRlcjogIzMzNDE1NTsgLS10b29sYmFyLXRleHQ6ICNlMmU4ZjA7IC0tdG9vbGJhci1ob3ZlcjogcmdiYSgxNSwgMjMsIDQyLCAwLjk1KTsgLS10b29sYmFyLW1lbnUtYmc6ICMwZjE3MmE7IH0KcmVjdC5jLWJnLXJlY3QgeyBmaWxsOiB2YXIoLS1iZyk7IH08L3N0eWxlPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGNsYXNzPSJjLWJnLXJlY3QiIC8+CiAgICAgICAgPHRpdGxlIGlkPSJhcmNoaWZ5LWRpYWdyYW0tdGl0bGUiPldoZW4gYSByZXBsYWNlbWVudCB0b2tlbiB0YWtlcyBlZmZlY3Q8L3RpdGxlPgogICAgICAgIDxkZXNjIGlkPSJhcmNoaWZ5LWRpYWdyYW0tZGVzY3JpcHRpb24iPkEgc2VxdWVuY2UgZGlhZ3JhbSBnZW5lcmF0ZWQgYnkgQXJjaGlmeS48L2Rlc2M+CiAgICAgICAgCiAgICAgICAgPGRlZnM+CiAgICAgICAgICA8bWFya2VyIGlkPSJhcnJvd2hlYWQiIG1hcmtlcldpZHRoPSIxMCIgbWFya2VySGVpZ2h0PSI3IiByZWZYPSI5IiByZWZZPSIzLjUiIG9yaWVudD0iYXV0byI+CiAgICAgICAgICAgIDxwb2x5Z29uIHBvaW50cz0iMCAwLCAxMCAzLjUsIDAgNyIgY2xhc3M9Im0tZGVmYXVsdCIgLz4KICAgICAgICAgIDwvbWFya2VyPgogICAgICAgICAgPG1hcmtlciBpZD0iYXJyb3doZWFkLWVtcGhhc2lzIiBtYXJrZXJXaWR0aD0iMTAiIG1hcmtlckhlaWdodD0iNyIgcmVmWD0iOSIgcmVmWT0iMy41IiBvcmllbnQ9ImF1dG8iPgogICAgICAgICAgICA8cG9seWdvbiBwb2ludHM9IjAgMCwgMTAgMy41LCAwIDciIGNsYXNzPSJtLWVtcGhhc2lzIiAvPgogICAgICAgICAgPC9tYXJrZXI+CiAgICAgICAgICA8bWFya2VyIGlkPSJhcnJvd2hlYWQtc2VjdXJpdHkiIG1hcmtlcldpZHRoPSIxMCIgbWFya2VySGVpZ2h0PSI3IiByZWZYPSI5IiByZWZZPSIzLjUiIG9yaWVudD0iYXV0byI+CiAgICAgICAgICAgIDxwb2x5Z29uIHBvaW50cz0iMCAwLCAxMCAzLjUsIDAgNyIgY2xhc3M9Im0tc2VjdXJpdHkiIC8+CiAgICAgICAgICA8L21hcmtlcj4KICAgICAgICAgIDxtYXJrZXIgaWQ9ImFycm93aGVhZC1kYXNoZWQiIG1hcmtlcldpZHRoPSIxMCIgbWFya2VySGVpZ2h0PSI3IiByZWZYPSI5IiByZWZZPSIzLjUiIG9yaWVudD0iYXV0byI+CiAgICAgICAgICAgIDxwb2x5Z29uIHBvaW50cz0iMCAwLCAxMCAzLjUsIDAgNyIgY2xhc3M9Im0tZGFzaGVkIiAvPgogICAgICAgICAgPC9tYXJrZXI+CiAgICAgICAgICA8cGF0dGVybiBpZD0iZ3JpZCIgd2lkdGg9IjQwIiBoZWlnaHQ9IjQwIiBwYXR0ZXJuVW5pdHM9InVzZXJTcGFjZU9uVXNlIj4KICAgICAgICAgICAgPHBhdGggZD0iTSA0MCAwIEwgMCAwIDAgNDAiIGNsYXNzPSJjLWdyaWQiIHN0cm9rZS13aWR0aD0iMC41IiAvPgogICAgICAgICAgPC9wYXR0ZXJuPgogICAgICAgIDwvZGVmcz4KCiAgICAgICAgCiAgICAgICAgPHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0idXJsKCNncmlkKSIgLz4KCiAgICAgICAgCiAgICAgICAgPHJlY3QgZGF0YS1ncmFwaC1yb2xlPSJzdHJ1Y3R1cmFsLWZyYW1lIiBkYXRhLWNvbXBvc2l0aW9uLWZyYW1lLWtpbmQ9InNlZ21lbnQiIGRhdGEtY29tcG9zaXRpb24tZnJhbWUtaWQ9IjAiIHg9IjQ4IiB5PSIxNjYiIHdpZHRoPSI2NzYiIGhlaWdodD0iNTYiIHJ4PSIxMCIgY2xhc3M9ImMtbGFuZSIgc3Ryb2tlLXdpZHRoPSIxIiAvPgoKICAgICAgICA8cmVjdCBkYXRhLWdyYXBoLXJvbGU9InN0cnVjdHVyYWwtZnJhbWUiIGRhdGEtY29tcG9zaXRpb24tZnJhbWUta2luZD0ic2VnbWVudCIgZGF0YS1jb21wb3NpdGlvbi1mcmFtZS1pZD0iMSIgeD0iNDgiIHk9IjIzOCIgd2lkdGg9IjY3NiIgaGVpZ2h0PSIxMTIiIHJ4PSIxMCIgY2xhc3M9ImMtbGFuZSIgc3Ryb2tlLXdpZHRoPSIxIiAvPgoKICAgICAgICA8cmVjdCBkYXRhLWdyYXBoLXJvbGU9InN0cnVjdHVyYWwtZnJhbWUiIGRhdGEtY29tcG9zaXRpb24tZnJhbWUta2luZD0ic2VnbWVudCIgZGF0YS1jb21wb3NpdGlvbi1mcmFtZS1pZD0iMiIgeD0iNDgiIHk9IjM2NiIgd2lkdGg9IjY3NiIgaGVpZ2h0PSI1OCIgcng9IjEwIiBjbGFzcz0iYy1sYW5lIiBzdHJva2Utd2lkdGg9IjEiIC8+CgogICAgICAgIAogICAgICAgIDxwYXRoIGQ9Ik0gMTA1IDE0MiBMIDEwNSA0MzEiIGNsYXNzPSJhLWRlZmF1bHQiIHN0cm9rZS13aWR0aD0iMC44IiBzdHJva2UtZGFzaGFycmF5PSIzLDciIC8+CiAgICAgICAgPHBhdGggZD0iTSAyMjEuOCAxNDIgTCAyMjEuOCA0MzEiIGNsYXNzPSJhLWRlZmF1bHQiIHN0cm9rZS13aWR0aD0iMC44IiBzdHJva2UtZGFzaGFycmF5PSIzLDciIC8+CiAgICAgICAgPHBhdGggZD0iTSAzMzguNiAxNDIgTCAzMzguNiA0MzEiIGNsYXNzPSJhLWRlZmF1bHQiIHN0cm9rZS13aWR0aD0iMC44IiBzdHJva2UtZGFzaGFycmF5PSIzLDciIC8+CiAgICAgICAgPHBhdGggZD0iTSA0NTUuNCAxNDIgTCA0NTUuNCA0MzEiIGNsYXNzPSJhLWRlZmF1bHQiIHN0cm9rZS13aWR0aD0iMC44IiBzdHJva2UtZGFzaGFycmF5PSIzLDciIC8+CiAgICAgICAgPHBhdGggZD0iTSA1NzIuMiAxNDIgTCA1NzIuMiA0MzEiIGNsYXNzPSJhLWRlZmF1bHQiIHN0cm9rZS13aWR0aD0iMC44IiBzdHJva2UtZGFzaGFycmF5PSIzLDciIC8+CiAgICAgICAgPHBhdGggZD0iTSA2ODkgMTQyIEwgNjg5IDQzMSIgY2xhc3M9ImEtZGVmYXVsdCIgc3Ryb2tlLXdpZHRoPSIwLjgiIHN0cm9rZS1kYXNoYXJyYXk9IjMsNyIgLz4KCiAgICAgICAgCgoKICAgICAgICAKICAgICAgICA8ZyBkYXRhLWVkZ2UtZnJvbT0ibGF1bmNoZXIiIGRhdGEtZWRnZS10bz0ic3VwcGxpZXIiIGRhdGEtZWRnZS1sYWJlbD0iRmV0Y2ggbGF0ZXN0IHRva2VuIiBkYXRhLWVkZ2Uta2V5PSIwIiBkYXRhLWVkZ2UtaWQ9ImZpcnN0LWZldGNoIj4KICAgICAgICAgIDxwYXRoIGRhdGEtY29tcG9zaXRpb24tZWRnZS1mcm9tPSJsYXVuY2hlciIgZGF0YS1jb21wb3NpdGlvbi1lZGdlLXRvPSJzdXBwbGllciIgZGF0YS1jb21wb3NpdGlvbi1lZGdlLWlkPSJmaXJzdC1mZXRjaCIgZGF0YS1jb21wb3NpdGlvbi1wb2ludHM9IjQ0OC40LDE3NjszNDUuNiwxNzYiIGQ9Ik0gNDQ4LjQgMTc2IEwgMzQ1LjYgMTc2IiBjbGFzcz0iYS1kZWZhdWx0IiBzdHJva2Utd2lkdGg9IjEuNCIgbWFya2VyLWVuZD0idXJsKCNhcnJvd2hlYWQpIiAvPgogICAgICAgIDxnPgogICAgICAgICAgPHJlY3QgeD0iMzU0LjYiIHk9IjE1NiIgd2lkdGg9Ijg0LjgiIGhlaWdodD0iMTYiIHJ4PSIzIiBjbGFzcz0iYy1tYXNrIiAvPgogICAgICAgICAgPHRleHQgeD0iMzk3IiB5PSIxNjYiIGNsYXNzPSJ0LWJhY2tlbmQiIGZvbnQtc2l6ZT0iOSIgdGV4dC1hbmNob3I9Im1pZGRsZSI+RmV0Y2ggbGF0ZXN0IHRva2VuPC90ZXh0PgogICAgICAgIDwvZz4KICAgICAgICA8L2c+CgogICAgICAgIDxnIGRhdGEtZWRnZS1mcm9tPSJsYXVuY2hlciIgZGF0YS1lZGdlLXRvPSJzZXNzaW9uIiBkYXRhLWVkZ2UtbGFiZWw9IlN0YXJ0IHdpdGggQSIgZGF0YS1lZGdlLWtleT0iMSIgZGF0YS1lZGdlLWlkPSJmaXJzdC1zdGFydCI+CiAgICAgICAgICA8cGF0aCBkYXRhLWNvbXBvc2l0aW9uLWVkZ2UtZnJvbT0ibGF1bmNoZXIiIGRhdGEtY29tcG9zaXRpb24tZWRnZS10bz0ic2Vzc2lvbiIgZGF0YS1jb21wb3NpdGlvbi1lZGdlLWlkPSJmaXJzdC1zdGFydCIgZGF0YS1jb21wb3NpdGlvbi1wb2ludHM9IjQ2Mi40LDIwNDs1NjUuMiwyMDQiIGQ9Ik0gNDYyLjQgMjA0IEwgNTY1LjIgMjA0IiBjbGFzcz0iYS1lbXBoYXNpcyIgc3Ryb2tlLXdpZHRoPSIxLjgiIG1hcmtlci1lbmQ9InVybCgjYXJyb3doZWFkLWVtcGhhc2lzKSIgLz4KICAgICAgICA8Zz4KICAgICAgICAgIDxyZWN0IHg9IjQ3My45OTk5OTk5OTk5OTk5NCIgeT0iMTg0IiB3aWR0aD0iNzkuNjAwMDAwMDAwMDAwMDEiIGhlaWdodD0iMTYiIHJ4PSIzIiBjbGFzcz0iYy1tYXNrIiAvPgogICAgICAgICAgPHRleHQgeD0iNTEzLjgiIHk9IjE5NCIgY2xhc3M9InQtYmFja2VuZCIgZm9udC1zaXplPSI5IiB0ZXh0LWFuY2hvcj0ibWlkZGxlIj5TdGFydCB3aXRoIEE8L3RleHQ+CiAgICAgICAgPC9nPgogICAgICAgIDwvZz4KCiAgICAgICAgPGcgZGF0YS1lZGdlLWZyb209InNlc3Npb24iIGRhdGEtZWRnZS10bz0iYXBpIiBkYXRhLWVkZ2UtbGFiZWw9IlJlcXVlc3Qgd2l0aCBBIiBkYXRhLWVkZ2Uta2V5PSIyIiBkYXRhLWVkZ2UtaWQ9InJlcXVlc3QtYSI+CiAgICAgICAgICA8cGF0aCBkYXRhLWNvbXBvc2l0aW9uLWVkZ2UtZnJvbT0ic2Vzc2lvbiIgZGF0YS1jb21wb3NpdGlvbi1lZGdlLXRvPSJhcGkiIGRhdGEtY29tcG9zaXRpb24tZWRnZS1pZD0icmVxdWVzdC1hIiBkYXRhLWNvbXBvc2l0aW9uLXBvaW50cz0iNTc5LjIsMjUwOzY4MiwyNTAiIGQ9Ik0gNTc5LjIgMjUwIEwgNjgyIDI1MCIgY2xhc3M9ImEtZGVmYXVsdCIgc3Ryb2tlLXdpZHRoPSIxLjQiIG1hcmtlci1lbmQ9InVybCgjYXJyb3doZWFkKSIgLz4KICAgICAgICA8Zz4KICAgICAgICAgIDxyZWN0IHg9IjU5MC44MDAwMDAwMDAwMDAxIiB5PSIyMzAiIHdpZHRoPSI3OS42MDAwMDAwMDAwMDAwMSIgaGVpZ2h0PSIxNiIgcng9IjMiIGNsYXNzPSJjLW1hc2siIC8+CiAgICAgICAgICA8dGV4dCB4PSI2MzAuNiIgeT0iMjQwIiBjbGFzcz0idC1iYWNrZW5kIiBmb250LXNpemU9IjkiIHRleHQtYW5jaG9yPSJtaWRkbGUiPlJlcXVlc3Qgd2l0aCBBPC90ZXh0PgogICAgICAgIDwvZz4KICAgICAgICA8L2c+CgogICAgICAgIDxnIGRhdGEtZWRnZS1mcm9tPSJyb3RhdG9yIiBkYXRhLWVkZ2UtdG89InN1cHBsaWVyIiBkYXRhLWVkZ2UtbGFiZWw9IkNoZWNrIHN0YXR1cyIgZGF0YS1lZGdlLWtleT0iMyIgZGF0YS1lZGdlLWlkPSJzdGF0dXMtY2hlY2siPgogICAgICAgICAgPHBhdGggZGF0YS1jb21wb3NpdGlvbi1lZGdlLWZyb209InJvdGF0b3IiIGRhdGEtY29tcG9zaXRpb24tZWRnZS10bz0ic3VwcGxpZXIiIGRhdGEtY29tcG9zaXRpb24tZWRnZS1pZD0ic3RhdHVzLWNoZWNrIiBkYXRhLWNvbXBvc2l0aW9uLXBvaW50cz0iMTEyLDI1MDszMzEuNiwyNTAiIGQ9Ik0gMTEyIDI1MCBMIDMzMS42IDI1MCIgY2xhc3M9ImEtZGVmYXVsdCIgc3Ryb2tlLXdpZHRoPSIxLjQiIG1hcmtlci1lbmQ9InVybCgjYXJyb3doZWFkKSIgLz4KICAgICAgICA8Zz4KICAgICAgICAgIDxyZWN0IHg9IjE5Mi40IiB5PSIyMzAiIHdpZHRoPSI1OC44MDAwMDAwMDAwMDAwMDQiIGhlaWdodD0iMTYiIHJ4PSIzIiBjbGFzcz0iYy1tYXNrIiAvPgogICAgICAgICAgPHRleHQgeD0iMjIxLjgiIHk9IjI0MCIgY2xhc3M9InQtYmFja2VuZCIgZm9udC1zaXplPSI5IiB0ZXh0LWFuY2hvcj0ibWlkZGxlIj5DaGVjayBzdGF0dXM8L3RleHQ+CiAgICAgICAgPC9nPgogICAgICAgIDwvZz4KCiAgICAgICAgPGcgZGF0YS1lZGdlLWZyb209ImFwaSIgZGF0YS1lZGdlLXRvPSJzZXNzaW9uIiBkYXRhLWVkZ2UtbGFiZWw9IlJlc3BvbnNlIiBkYXRhLWVkZ2Uta2V5PSI0IiBkYXRhLWVkZ2UtaWQ9InJlc3BvbnNlLWEiPgogICAgICAgICAgPHBhdGggZGF0YS1jb21wb3NpdGlvbi1lZGdlLWZyb209ImFwaSIgZGF0YS1jb21wb3NpdGlvbi1lZGdlLXRvPSJzZXNzaW9uIiBkYXRhLWNvbXBvc2l0aW9uLWVkZ2UtaWQ9InJlc3BvbnNlLWEiIGRhdGEtY29tcG9zaXRpb24tcG9pbnRzPSI2ODIsMjc4OzU3OS4yLDI3OCIgZD0iTSA2ODIgMjc4IEwgNTc5LjIgMjc4IiBjbGFzcz0iYS1kZWZhdWx0IiBzdHJva2Utd2lkdGg9IjEuNCIgc3Ryb2tlLWRhc2hhcnJheT0iMyw1IiBtYXJrZXItZW5kPSJ1cmwoI2Fycm93aGVhZCkiIC8+CiAgICAgICAgPGc+CiAgICAgICAgICA8cmVjdCB4PSI2MTMuNiIgeT0iMjU4IiB3aWR0aD0iMzQiIGhlaWdodD0iMTYiIHJ4PSIzIiBjbGFzcz0iYy1tYXNrIiAvPgogICAgICAgICAgPHRleHQgeD0iNjMwLjYiIHk9IjI2OCIgY2xhc3M9InQtbXV0ZWQiIGZvbnQtc2l6ZT0iOSIgdGV4dC1hbmNob3I9Im1pZGRsZSI+UmVzcG9uc2U8L3RleHQ+CiAgICAgICAgPC9nPgogICAgICAgIDwvZz4KCiAgICAgICAgPGcgZGF0YS1lZGdlLWZyb209InN1cHBsaWVyIiBkYXRhLWVkZ2UtdG89InJvdGF0b3IiIGRhdGEtZWRnZS1sYWJlbD0iTmVhciBsaW1pdCDCtyB0b2tlbiBCIiBkYXRhLWVkZ2Uta2V5PSI1IiBkYXRhLWVkZ2UtaWQ9Im5ldy10b2tlbiI+CiAgICAgICAgICA8cGF0aCBkYXRhLWNvbXBvc2l0aW9uLWVkZ2UtZnJvbT0ic3VwcGxpZXIiIGRhdGEtY29tcG9zaXRpb24tZWRnZS10bz0icm90YXRvciIgZGF0YS1jb21wb3NpdGlvbi1lZGdlLWlkPSJuZXctdG9rZW4iIGRhdGEtY29tcG9zaXRpb24tcG9pbnRzPSIzMzEuNiwyNzg7MTEyLDI3OCIgZD0iTSAzMzEuNiAyNzggTCAxMTIgMjc4IiBjbGFzcz0iYS1kZWZhdWx0IiBzdHJva2Utd2lkdGg9IjEuNCIgc3Ryb2tlLWRhc2hhcnJheT0iMyw1IiBtYXJrZXItZW5kPSJ1cmwoI2Fycm93aGVhZCkiIC8+CiAgICAgICAgPGc+CiAgICAgICAgICA8cmVjdCB4PSIxNjkiIHk9IjI1OCIgd2lkdGg9IjEwNS42MDAwMDAwMDAwMDAwMSIgaGVpZ2h0PSIxNiIgcng9IjMiIGNsYXNzPSJjLW1hc2siIC8+CiAgICAgICAgICA8dGV4dCB4PSIyMjEuOCIgeT0iMjY4IiBjbGFzcz0idC1tdXRlZCIgZm9udC1zaXplPSI5IiB0ZXh0LWFuY2hvcj0ibWlkZGxlIj5OZWFyIGxpbWl0IMK3IHRva2VuIEI8L3RleHQ+CiAgICAgICAgPC9nPgogICAgICAgIDwvZz4KCiAgICAgICAgPGcgZGF0YS1lZGdlLWZyb209InNlc3Npb24iIGRhdGEtZWRnZS10bz0iYXBpIiBkYXRhLWVkZ2UtbGFiZWw9IlN0aWxsIHVzaW5nIEEiIGRhdGEtZWRnZS1rZXk9IjYiIGRhdGEtZWRnZS1pZD0icmVxdWVzdC1zdGlsbC1hIj4KICAgICAgICAgIDxwYXRoIGRhdGEtY29tcG9zaXRpb24tZWRnZS1mcm9tPSJzZXNzaW9uIiBkYXRhLWNvbXBvc2l0aW9uLWVkZ2UtdG89ImFwaSIgZGF0YS1jb21wb3NpdGlvbi1lZGdlLWlkPSJyZXF1ZXN0LXN0aWxsLWEiIGRhdGEtY29tcG9zaXRpb24tcG9pbnRzPSI1NzkuMiwzMDY7NjgyLDMwNiIgZD0iTSA1NzkuMiAzMDYgTCA2ODIgMzA2IiBjbGFzcz0iYS1kZWZhdWx0IiBzdHJva2Utd2lkdGg9IjEuNCIgbWFya2VyLWVuZD0idXJsKCNhcnJvd2hlYWQpIiAvPgogICAgICAgIDxnPgogICAgICAgICAgPHJlY3QgeD0iNTg1LjYiIHk9IjI4NiIgd2lkdGg9IjkwIiBoZWlnaHQ9IjE2IiByeD0iMyIgY2xhc3M9ImMtbWFzayIgLz4KICAgICAgICAgIDx0ZXh0IHg9IjYzMC42IiB5PSIyOTYiIGNsYXNzPSJ0LWJhY2tlbmQiIGZvbnQtc2l6ZT0iOSIgdGV4dC1hbmNob3I9Im1pZGRsZSI+U3RpbGwgdXNpbmcgQTwvdGV4dD4KICAgICAgICA8L2c+CiAgICAgICAgPC9nPgoKICAgICAgICA8ZyBkYXRhLWVkZ2UtZnJvbT0icm90YXRvciIgZGF0YS1lZGdlLXRvPSJrZXljaGFpbiIgZGF0YS1lZGdlLWxhYmVsPSJTYXZlIHRva2VuIEIiIGRhdGEtZWRnZS1rZXk9IjciIGRhdGEtZWRnZS1pZD0iY29weS1yZXBsYWNlZCI+CiAgICAgICAgICA8cGF0aCBkYXRhLWNvbXBvc2l0aW9uLWVkZ2UtZnJvbT0icm90YXRvciIgZGF0YS1jb21wb3NpdGlvbi1lZGdlLXRvPSJrZXljaGFpbiIgZGF0YS1jb21wb3NpdGlvbi1lZGdlLWlkPSJjb3B5LXJlcGxhY2VkIiBkYXRhLWNvbXBvc2l0aW9uLXBvaW50cz0iMTEyLDMwNjsyMTQuOCwzMDYiIGQ9Ik0gMTEyIDMwNiBMIDIxNC44IDMwNiIgY2xhc3M9ImEtc2VjdXJpdHkiIHN0cm9rZS13aWR0aD0iMS40IiBtYXJrZXItZW5kPSJ1cmwoI2Fycm93aGVhZC1zZWN1cml0eSkiIC8+CiAgICAgICAgPGc+CiAgICAgICAgICA8cmVjdCB4PSIxMTguNCIgeT0iMjg2IiB3aWR0aD0iOTAiIGhlaWdodD0iMTYiIHJ4PSIzIiBjbGFzcz0iYy1tYXNrIiAvPgogICAgICAgICAgPHRleHQgeD0iMTYzLjQiIHk9IjI5NiIgY2xhc3M9InQtc2VjdXJpdHkiIGZvbnQtc2l6ZT0iOSIgdGV4dC1hbmNob3I9Im1pZGRsZSI+U2F2ZSB0b2tlbiBCPC90ZXh0PgogICAgICAgIDwvZz4KICAgICAgICA8L2c+CgogICAgICAgIDxnIGRhdGEtZWRnZS1mcm9tPSJhcGkiIGRhdGEtZWRnZS10bz0ic2Vzc2lvbiIgZGF0YS1lZGdlLWxhYmVsPSJMaW1pdCByZWFjaGVkIiBkYXRhLWVkZ2Uta2V5PSI4IiBkYXRhLWVkZ2UtaWQ9ImxpbWl0LXJlYWNoZWQiPgogICAgICAgICAgPHBhdGggZGF0YS1jb21wb3NpdGlvbi1lZGdlLWZyb209ImFwaSIgZGF0YS1jb21wb3NpdGlvbi1lZGdlLXRvPSJzZXNzaW9uIiBkYXRhLWNvbXBvc2l0aW9uLWVkZ2UtaWQ9ImxpbWl0LXJlYWNoZWQiIGRhdGEtY29tcG9zaXRpb24tcG9pbnRzPSI2ODIsMzM0OzU3OS4yLDMzNCIgZD0iTSA2ODIgMzM0IEwgNTc5LjIgMzM0IiBjbGFzcz0iYS1kZWZhdWx0IiBzdHJva2Utd2lkdGg9IjEuNCIgc3Ryb2tlLWRhc2hhcnJheT0iMyw1IiBtYXJrZXItZW5kPSJ1cmwoI2Fycm93aGVhZCkiIC8+CiAgICAgICAgPGc+CiAgICAgICAgICA8cmVjdCB4PSI2MDEuMiIgeT0iMzE0IiB3aWR0aD0iNTguODAwMDAwMDAwMDAwMDA0IiBoZWlnaHQ9IjE2IiByeD0iMyIgY2xhc3M9ImMtbWFzayIgLz4KICAgICAgICAgIDx0ZXh0IHg9IjYzMC42IiB5PSIzMjQiIGNsYXNzPSJ0LW11dGVkIiBmb250LXNpemU9IjkiIHRleHQtYW5jaG9yPSJtaWRkbGUiPkxpbWl0IHJlYWNoZWQ8L3RleHQ+CiAgICAgICAgPC9nPgogICAgICAgIDwvZz4KCiAgICAgICAgPGcgZGF0YS1lZGdlLWZyb209ImxhdW5jaGVyIiBkYXRhLWVkZ2UtdG89InN1cHBsaWVyIiBkYXRhLWVkZ2UtbGFiZWw9IkZldGNoIGxhdGVzdCB0b2tlbiIgZGF0YS1lZGdlLWtleT0iOSIgZGF0YS1lZGdlLWlkPSJzZWNvbmQtZmV0Y2giPgogICAgICAgICAgPHBhdGggZGF0YS1jb21wb3NpdGlvbi1lZGdlLWZyb209ImxhdW5jaGVyIiBkYXRhLWNvbXBvc2l0aW9uLWVkZ2UtdG89InN1cHBsaWVyIiBkYXRhLWNvbXBvc2l0aW9uLWVkZ2UtaWQ9InNlY29uZC1mZXRjaCIgZGF0YS1jb21wb3NpdGlvbi1wb2ludHM9IjQ0OC40LDM4MDszNDUuNiwzODAiIGQ9Ik0gNDQ4LjQgMzgwIEwgMzQ1LjYgMzgwIiBjbGFzcz0iYS1kZWZhdWx0IiBzdHJva2Utd2lkdGg9IjEuNCIgbWFya2VyLWVuZD0idXJsKCNhcnJvd2hlYWQpIiAvPgogICAgICAgIDxnPgogICAgICAgICAgPHJlY3QgeD0iMzU0LjYiIHk9IjM2MCIgd2lkdGg9Ijg0LjgiIGhlaWdodD0iMTYiIHJ4PSIzIiBjbGFzcz0iYy1tYXNrIiAvPgogICAgICAgICAgPHRleHQgeD0iMzk3IiB5PSIzNzAiIGNsYXNzPSJ0LWJhY2tlbmQiIGZvbnQtc2l6ZT0iOSIgdGV4dC1hbmNob3I9Im1pZGRsZSI+RmV0Y2ggbGF0ZXN0IHRva2VuPC90ZXh0PgogICAgICAgIDwvZz4KICAgICAgICA8L2c+CgogICAgICAgIDxnIGRhdGEtZWRnZS1mcm9tPSJsYXVuY2hlciIgZGF0YS1lZGdlLXRvPSJzZXNzaW9uIiBkYXRhLWVkZ2UtbGFiZWw9Ik5ldyBzZXNzaW9uIHdpdGggQiIgZGF0YS1lZGdlLWtleT0iMTAiIGRhdGEtZWRnZS1pZD0ic2Vjb25kLXN0YXJ0Ij4KICAgICAgICAgIDxwYXRoIGRhdGEtY29tcG9zaXRpb24tZWRnZS1mcm9tPSJsYXVuY2hlciIgZGF0YS1jb21wb3NpdGlvbi1lZGdlLXRvPSJzZXNzaW9uIiBkYXRhLWNvbXBvc2l0aW9uLWVkZ2UtaWQ9InNlY29uZC1zdGFydCIgZGF0YS1jb21wb3NpdGlvbi1wb2ludHM9IjQ2Mi40LDQwODs1NjUuMiw0MDgiIGQ9Ik0gNDYyLjQgNDA4IEwgNTY1LjIgNDA4IiBjbGFzcz0iYS1lbXBoYXNpcyIgc3Ryb2tlLXdpZHRoPSIxLjgiIG1hcmtlci1lbmQ9InVybCgjYXJyb3doZWFkLWVtcGhhc2lzKSIgLz4KICAgICAgICA8Zz4KICAgICAgICAgIDxyZWN0IHg9IjQ2Ni4xOTk5OTk5OTk5OTk5MyIgeT0iMzg4IiB3aWR0aD0iOTUuMiIgaGVpZ2h0PSIxNiIgcng9IjMiIGNsYXNzPSJjLW1hc2siIC8+CiAgICAgICAgICA8dGV4dCB4PSI1MTMuOCIgeT0iMzk4IiBjbGFzcz0idC1iYWNrZW5kIiBmb250LXNpemU9IjkiIHRleHQtYW5jaG9yPSJtaWRkbGUiPk5ldyBzZXNzaW9uIHdpdGggQjwvdGV4dD4KICAgICAgICA8L2c+CiAgICAgICAgPC9nPgoKICAgICAgICAKICAgICAgICA8ZyBkYXRhLWdyYXBoLXJvbGU9InNlZ21lbnQtbGFiZWwiIGRhdGEtc2VnbWVudC1pZD0iMCI+CiAgICAgICAgICA8cmVjdCB4PSI1NiIgeT0iMTQ0IiB3aWR0aD0iNDIiIGhlaWdodD0iMTgiIHJ4PSIzIiBjbGFzcz0iYy1tYXNrIiAvPgogICAgICAgICAgPHRleHQgeD0iNjIiIHk9IjE1NyIgY2xhc3M9InQtZGltIiBmb250LXNpemU9IjkiIGZvbnQtd2VpZ2h0PSI2MDAiPlN0YXJ0PC90ZXh0PgogICAgICAgIDwvZz4KICAgICAgICA8ZyBkYXRhLWdyYXBoLXJvbGU9InNlZ21lbnQtbGFiZWwiIGRhdGEtc2VnbWVudC1pZD0iMSI+CiAgICAgICAgICA8cmVjdCB4PSI1NiIgeT0iMjE2IiB3aWR0aD0iODYuOCIgaGVpZ2h0PSIxOCIgcng9IjMiIGNsYXNzPSJjLW1hc2siIC8+CiAgICAgICAgICA8dGV4dCB4PSI2MiIgeT0iMjI5IiBjbGFzcz0idC1kaW0iIGZvbnQtc2l6ZT0iOSIgZm9udC13ZWlnaHQ9IjYwMCI+V29yayBhbmQgcm90YXRpb248L3RleHQ+CiAgICAgICAgPC9nPgogICAgICAgIDxnIGRhdGEtZ3JhcGgtcm9sZT0ic2VnbWVudC1sYWJlbCIgZGF0YS1zZWdtZW50LWlkPSIyIj4KICAgICAgICAgIDxyZWN0IHg9IjU2IiB5PSIzNDQiIHdpZHRoPSI2MC44MDAwMDAwMDAwMDAwMDQiIGhlaWdodD0iMTgiIHJ4PSIzIiBjbGFzcz0iYy1tYXNrIiAvPgogICAgICAgICAgPHRleHQgeD0iNjIiIHk9IjM1NyIgY2xhc3M9InQtZGltIiBmb250LXNpemU9IjkiIGZvbnQtd2VpZ2h0PSI2MDAiPlJlc3RhcnQ8L3RleHQ+CiAgICAgICAgPC9nPgoKICAgICAgICAKICAgICAgICA8ZyBpZD0ibm9kZS1yb3RhdG9yIiBkYXRhLW5vZGUtaWQ9InJvdGF0b3IiIGRhdGEtbm9kZS1sYWJlbD0iUm90YXRpb24gd29ya2VyIiB0YWJpbmRleD0iMCIgcm9sZT0iYnV0dG9uIiBhcmlhLWxhYmVsPSJGb2N1cyBSb3RhdGlvbiB3b3JrZXIsIEJhY2tncm91bmQsIFNlcXVlbmNlIHBhcnRpY2lwYW50IiBhcmlhLXByZXNzZWQ9ImZhbHNlIiBkYXRhLW5vZGUta2luZD0iYmFja2VuZCIgZGF0YS1ub2RlLXN1YmxhYmVsPSJCYWNrZ3JvdW5kIiBkYXRhLW5vZGUtY29udGV4dD0iU2VxdWVuY2UgcGFydGljaXBhbnQiPgogICAgICAgICAgPHRpdGxlPlJvdGF0aW9uIHdvcmtlciDCtyBCYWNrZ3JvdW5kIMK3IFNlcXVlbmNlIHBhcnRpY2lwYW50PC90aXRsZT4KICAgICAgICAgIDxyZWN0IHg9IjYyIiB5PSI3MiIgd2lkdGg9Ijg2IiBoZWlnaHQ9IjU0IiByeD0iNiIgY2xhc3M9ImMtbWFzayIgLz4KICAgICAgICAgIDxyZWN0IHg9IjYyIiB5PSI3MiIgd2lkdGg9Ijg2IiBoZWlnaHQ9IjU0IiByeD0iNiIgY2xhc3M9ImMtYmFja2VuZCIgc3Ryb2tlLXdpZHRoPSIxLjUiIC8+CiAgICAgICAgICA8ZyBhcmlhLWhpZGRlbj0idHJ1ZSIgZGF0YS1zZW1hbnRpYy1zaWdpbD0iYmFja2VuZCIgY2xhc3M9InNlbWFudGljLXNpZ2lsIHMtYmFja2VuZCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNjggNzgpIHNjYWxlKDAuNjg3NSkiPgogICAgICAgICAgICA8cGF0aCBkPSJNNiAzIDMgOGwzIDVNMTAgM2wzIDUtMyA1IiAvPgogICAgICAgICAgPC9nPgogICAgICAgICAgPHRleHQgZGF0YS1ub2RlLWxhYmVsPSIiIHg9IjEwNSIgeT0iOTQiIGNsYXNzPSJ0LXByaW1hcnkiIGZvbnQtc2l6ZT0iOS41IiBmb250LXdlaWdodD0iNjAwIiB0ZXh0LWFuY2hvcj0ibWlkZGxlIj5Sb3RhdGlvbiB3b3JrZXI8L3RleHQ+CiAgICAgICAgICA8dGV4dCB4PSIxMDUiIHk9IjExMSIgY2xhc3M9InQtbXV0ZWQiIGZvbnQtc2l6ZT0iNyIgdGV4dC1hbmNob3I9Im1pZGRsZSI+QmFja2dyb3VuZDwvdGV4dD4KICAgICAgICA8L2c+CgogICAgICAgIDxnIGlkPSJub2RlLWtleWNoYWluIiBkYXRhLW5vZGUtaWQ9ImtleWNoYWluIiBkYXRhLW5vZGUtbGFiZWw9IktleWNoYWluIiB0YWJpbmRleD0iMCIgcm9sZT0iYnV0dG9uIiBhcmlhLWxhYmVsPSJGb2N1cyBLZXljaGFpbiwgQ2FjaGVkIHRva2VuLCBTZXF1ZW5jZSBwYXJ0aWNpcGFudCIgYXJpYS1wcmVzc2VkPSJmYWxzZSIgZGF0YS1ub2RlLWtpbmQ9InNlY3VyaXR5IiBkYXRhLW5vZGUtc3VibGFiZWw9IkNhY2hlZCB0b2tlbiIgZGF0YS1ub2RlLWNvbnRleHQ9IlNlcXVlbmNlIHBhcnRpY2lwYW50Ij4KICAgICAgICAgIDx0aXRsZT5LZXljaGFpbiDCtyBDYWNoZWQgdG9rZW4gwrcgU2VxdWVuY2UgcGFydGljaXBhbnQ8L3RpdGxlPgogICAgICAgICAgPHJlY3QgeD0iMTc4LjgiIHk9IjcyIiB3aWR0aD0iODYiIGhlaWdodD0iNTQiIHJ4PSI2IiBjbGFzcz0iYy1tYXNrIiAvPgogICAgICAgICAgPHJlY3QgeD0iMTc4LjgiIHk9IjcyIiB3aWR0aD0iODYiIGhlaWdodD0iNTQiIHJ4PSI2IiBjbGFzcz0iYy1zZWN1cml0eSIgc3Ryb2tlLXdpZHRoPSIxLjUiIC8+CiAgICAgICAgICA8ZyBhcmlhLWhpZGRlbj0idHJ1ZSIgZGF0YS1zZW1hbnRpYy1zaWdpbD0ic2VjdXJpdHkiIGNsYXNzPSJzZW1hbnRpYy1zaWdpbCBzLXNlY3VyaXR5IiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgxODQuOCA3OCkgc2NhbGUoMC42ODc1KSI+CiAgICAgICAgICAgIDxwYXRoIGQ9Ik04IDIuMiAxMyA0djMuNWMwIDMuMS0xLjggNS40LTUgNi41LTMuMi0xLjEtNS0zLjQtNS02LjVWNFoiIC8+CiAgICAgICAgICAgIDxwYXRoIGQ9Im01LjggOCAxLjUgMS41IDMtMyIgLz4KICAgICAgICAgIDwvZz4KICAgICAgICAgIDx0ZXh0IGRhdGEtbm9kZS1sYWJlbD0iIiB4PSIyMjEuOCIgeT0iOTQiIGNsYXNzPSJ0LXByaW1hcnkiIGZvbnQtc2l6ZT0iMTEiIGZvbnQtd2VpZ2h0PSI2MDAiIHRleHQtYW5jaG9yPSJtaWRkbGUiPktleWNoYWluPC90ZXh0PgogICAgICAgICAgPHRleHQgeD0iMjIxLjgiIHk9IjExMSIgY2xhc3M9InQtbXV0ZWQiIGZvbnQtc2l6ZT0iNyIgdGV4dC1hbmNob3I9Im1pZGRsZSI+Q2FjaGVkIHRva2VuPC90ZXh0PgogICAgICAgIDwvZz4KCiAgICAgICAgPGcgaWQ9Im5vZGUtc3VwcGxpZXIiIGRhdGEtbm9kZS1pZD0ic3VwcGxpZXIiIGRhdGEtbm9kZS1sYWJlbD0iQ3JlZGVudGlhbCBzZXJ2ZXIiIHRhYmluZGV4PSIwIiByb2xlPSJidXR0b24iIGFyaWEtbGFiZWw9IkZvY3VzIENyZWRlbnRpYWwgc2VydmVyLCBQcm92aWRlcyB0b2tlbnMsIFNlcXVlbmNlIHBhcnRpY2lwYW50IiBhcmlhLXByZXNzZWQ9ImZhbHNlIiBkYXRhLW5vZGUta2luZD0iZXh0ZXJuYWwiIGRhdGEtbm9kZS1zdWJsYWJlbD0iUHJvdmlkZXMgdG9rZW5zIiBkYXRhLW5vZGUtY29udGV4dD0iU2VxdWVuY2UgcGFydGljaXBhbnQiPgogICAgICAgICAgPHRpdGxlPkNyZWRlbnRpYWwgc2VydmVyIMK3IFByb3ZpZGVzIHRva2VucyDCtyBTZXF1ZW5jZSBwYXJ0aWNpcGFudDwvdGl0bGU+CiAgICAgICAgICA8cmVjdCB4PSIyOTUuNiIgeT0iNzIiIHdpZHRoPSI4NiIgaGVpZ2h0PSI1NCIgcng9IjYiIGNsYXNzPSJjLW1hc2siIC8+CiAgICAgICAgICA8cmVjdCB4PSIyOTUuNiIgeT0iNzIiIHdpZHRoPSI4NiIgaGVpZ2h0PSI1NCIgcng9IjYiIGNsYXNzPSJjLWV4dGVybmFsIiBzdHJva2Utd2lkdGg9IjEuNSIgLz4KICAgICAgICAgIDxnIGFyaWEtaGlkZGVuPSJ0cnVlIiBkYXRhLXNlbWFudGljLXNpZ2lsPSJleHRlcm5hbCIgY2xhc3M9InNlbWFudGljLXNpZ2lsIHMtZXh0ZXJuYWwiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDMwMS42IDc4KSBzY2FsZSgwLjY4NzUpIj4KICAgICAgICAgICAgPHJlY3QgeD0iMi41IiB5PSI1IiB3aWR0aD0iOC41IiBoZWlnaHQ9IjgiIHJ4PSIxLjUiIC8+CiAgICAgICAgICAgIDxwYXRoIGQ9Ik04IDIuNWg1LjVWOE0xMy41IDIuNSA3LjUgOC41IiAvPgogICAgICAgICAgPC9nPgogICAgICAgICAgPHRleHQgZGF0YS1ub2RlLWxhYmVsPSIiIHg9IjMzOC42IiB5PSI5NCIgY2xhc3M9InQtcHJpbWFyeSIgZm9udC1zaXplPSI5LjUiIGZvbnQtd2VpZ2h0PSI2MDAiIHRleHQtYW5jaG9yPSJtaWRkbGUiPkNyZWRlbnRpYWwgc2VydmVyPC90ZXh0PgogICAgICAgICAgPHRleHQgeD0iMzM4LjYiIHk9IjExMSIgY2xhc3M9InQtbXV0ZWQiIGZvbnQtc2l6ZT0iNyIgdGV4dC1hbmNob3I9Im1pZGRsZSI+UHJvdmlkZXMgdG9rZW5zPC90ZXh0PgogICAgICAgIDwvZz4KCiAgICAgICAgPGcgaWQ9Im5vZGUtbGF1bmNoZXIiIGRhdGEtbm9kZS1pZD0ibGF1bmNoZXIiIGRhdGEtbm9kZS1sYWJlbD0iTGF1bmNoZXIiIHRhYmluZGV4PSIwIiByb2xlPSJidXR0b24iIGFyaWEtbGFiZWw9IkZvY3VzIExhdW5jaGVyLCBTdGFydGVkIGJ5IHVzZXIsIFNlcXVlbmNlIHBhcnRpY2lwYW50IiBhcmlhLXByZXNzZWQ9ImZhbHNlIiBkYXRhLW5vZGUta2luZD0iYmFja2VuZCIgZGF0YS1ub2RlLXN1YmxhYmVsPSJTdGFydGVkIGJ5IHVzZXIiIGRhdGEtbm9kZS1jb250ZXh0PSJTZXF1ZW5jZSBwYXJ0aWNpcGFudCI+CiAgICAgICAgICA8dGl0bGU+TGF1bmNoZXIgwrcgU3RhcnRlZCBieSB1c2VyIMK3IFNlcXVlbmNlIHBhcnRpY2lwYW50PC90aXRsZT4KICAgICAgICAgIDxyZWN0IHg9IjQxMi40IiB5PSI3MiIgd2lkdGg9Ijg2IiBoZWlnaHQ9IjU0IiByeD0iNiIgY2xhc3M9ImMtbWFzayIgLz4KICAgICAgICAgIDxyZWN0IHg9IjQxMi40IiB5PSI3MiIgd2lkdGg9Ijg2IiBoZWlnaHQ9IjU0IiByeD0iNiIgY2xhc3M9ImMtYmFja2VuZCIgc3Ryb2tlLXdpZHRoPSIxLjUiIC8+CiAgICAgICAgICA8ZyBhcmlhLWhpZGRlbj0idHJ1ZSIgZGF0YS1zZW1hbnRpYy1zaWdpbD0iYmFja2VuZCIgY2xhc3M9InNlbWFudGljLXNpZ2lsIHMtYmFja2VuZCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNDE4LjQgNzgpIHNjYWxlKDAuNjg3NSkiPgogICAgICAgICAgICA8cGF0aCBkPSJNNiAzIDMgOGwzIDVNMTAgM2wzIDUtMyA1IiAvPgogICAgICAgICAgPC9nPgogICAgICAgICAgPHRleHQgZGF0YS1ub2RlLWxhYmVsPSIiIHg9IjQ1NS40IiB5PSI5NCIgY2xhc3M9InQtcHJpbWFyeSIgZm9udC1zaXplPSIxMSIgZm9udC13ZWlnaHQ9IjYwMCIgdGV4dC1hbmNob3I9Im1pZGRsZSI+TGF1bmNoZXI8L3RleHQ+CiAgICAgICAgICA8dGV4dCB4PSI0NTUuNCIgeT0iMTExIiBjbGFzcz0idC1tdXRlZCIgZm9udC1zaXplPSI3IiB0ZXh0LWFuY2hvcj0ibWlkZGxlIj5TdGFydGVkIGJ5IHVzZXI8L3RleHQ+CiAgICAgICAgPC9nPgoKICAgICAgICA8ZyBpZD0ibm9kZS1zZXNzaW9uIiBkYXRhLW5vZGUtaWQ9InNlc3Npb24iIGRhdGEtbm9kZS1sYWJlbD0iU2Vzc2lvbiIgdGFiaW5kZXg9IjAiIHJvbGU9ImJ1dHRvbiIgYXJpYS1sYWJlbD0iRm9jdXMgU2Vzc2lvbiwgQ2xhdWRlIENvZGUsIFNlcXVlbmNlIHBhcnRpY2lwYW50IiBhcmlhLXByZXNzZWQ9ImZhbHNlIiBkYXRhLW5vZGUta2luZD0iZnJvbnRlbmQiIGRhdGEtbm9kZS1zdWJsYWJlbD0iQ2xhdWRlIENvZGUiIGRhdGEtbm9kZS1jb250ZXh0PSJTZXF1ZW5jZSBwYXJ0aWNpcGFudCI+CiAgICAgICAgICA8dGl0bGU+U2Vzc2lvbiDCtyBDbGF1ZGUgQ29kZSDCtyBTZXF1ZW5jZSBwYXJ0aWNpcGFudDwvdGl0bGU+CiAgICAgICAgICA8cmVjdCB4PSI1MjkuMiIgeT0iNzIiIHdpZHRoPSI4NiIgaGVpZ2h0PSI1NCIgcng9IjYiIGNsYXNzPSJjLW1hc2siIC8+CiAgICAgICAgICA8cmVjdCB4PSI1MjkuMiIgeT0iNzIiIHdpZHRoPSI4NiIgaGVpZ2h0PSI1NCIgcng9IjYiIGNsYXNzPSJjLWZyb250ZW5kIiBzdHJva2Utd2lkdGg9IjEuNSIgLz4KICAgICAgICAgIDxnIGFyaWEtaGlkZGVuPSJ0cnVlIiBkYXRhLXNlbWFudGljLXNpZ2lsPSJmcm9udGVuZCIgY2xhc3M9InNlbWFudGljLXNpZ2lsIHMtZnJvbnRlbmQiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDUzNS4yIDc4KSBzY2FsZSgwLjY4NzUpIj4KICAgICAgICAgICAgPHJlY3QgeD0iMiIgeT0iMyIgd2lkdGg9IjEyIiBoZWlnaHQ9IjEwIiByeD0iMiIgLz4KICAgICAgICAgICAgPHBhdGggZD0iTTIgNi41aDEyIiAvPgogICAgICAgICAgICA8Y2lyY2xlIGN4PSI0LjEiIGN5PSI0LjgiIHI9Ii43IiBjbGFzcz0ic2lnaWwtZmlsbCIgLz4KICAgICAgICAgICAgPGNpcmNsZSBjeD0iNi4zIiBjeT0iNC44IiByPSIuNyIgY2xhc3M9InNpZ2lsLWZpbGwiIC8+CiAgICAgICAgICA8L2c+CiAgICAgICAgICA8dGV4dCBkYXRhLW5vZGUtbGFiZWw9IiIgeD0iNTcyLjIiIHk9Ijk0IiBjbGFzcz0idC1wcmltYXJ5IiBmb250LXNpemU9IjExIiBmb250LXdlaWdodD0iNjAwIiB0ZXh0LWFuY2hvcj0ibWlkZGxlIj5TZXNzaW9uPC90ZXh0PgogICAgICAgICAgPHRleHQgeD0iNTcyLjIiIHk9IjExMSIgY2xhc3M9InQtbXV0ZWQiIGZvbnQtc2l6ZT0iNyIgdGV4dC1hbmNob3I9Im1pZGRsZSI+Q2xhdWRlIENvZGU8L3RleHQ+CiAgICAgICAgPC9nPgoKICAgICAgICA8ZyBpZD0ibm9kZS1hcGkiIGRhdGEtbm9kZS1pZD0iYXBpIiBkYXRhLW5vZGUtbGFiZWw9IlByb3ZpZGVyIEFQSSIgdGFiaW5kZXg9IjAiIHJvbGU9ImJ1dHRvbiIgYXJpYS1sYWJlbD0iRm9jdXMgUHJvdmlkZXIgQVBJLCBBbnRocm9waWMsIFNlcXVlbmNlIHBhcnRpY2lwYW50IiBhcmlhLXByZXNzZWQ9ImZhbHNlIiBkYXRhLW5vZGUta2luZD0iZXh0ZXJuYWwiIGRhdGEtbm9kZS1zdWJsYWJlbD0iQW50aHJvcGljIiBkYXRhLW5vZGUtY29udGV4dD0iU2VxdWVuY2UgcGFydGljaXBhbnQiPgogICAgICAgICAgPHRpdGxlPlByb3ZpZGVyIEFQSSDCtyBBbnRocm9waWMgwrcgU2VxdWVuY2UgcGFydGljaXBhbnQ8L3RpdGxlPgogICAgICAgICAgPHJlY3QgeD0iNjQ2IiB5PSI3MiIgd2lkdGg9Ijg2IiBoZWlnaHQ9IjU0IiByeD0iNiIgY2xhc3M9ImMtbWFzayIgLz4KICAgICAgICAgIDxyZWN0IHg9IjY0NiIgeT0iNzIiIHdpZHRoPSI4NiIgaGVpZ2h0PSI1NCIgcng9IjYiIGNsYXNzPSJjLWV4dGVybmFsIiBzdHJva2Utd2lkdGg9IjEuNSIgLz4KICAgICAgICAgIDxnIGFyaWEtaGlkZGVuPSJ0cnVlIiBkYXRhLXNlbWFudGljLXNpZ2lsPSJleHRlcm5hbCIgY2xhc3M9InNlbWFudGljLXNpZ2lsIHMtZXh0ZXJuYWwiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDY1MiA3OCkgc2NhbGUoMC42ODc1KSI+CiAgICAgICAgICAgIDxyZWN0IHg9IjIuNSIgeT0iNSIgd2lkdGg9IjguNSIgaGVpZ2h0PSI4IiByeD0iMS41IiAvPgogICAgICAgICAgICA8cGF0aCBkPSJNOCAyLjVoNS41VjhNMTMuNSAyLjUgNy41IDguNSIgLz4KICAgICAgICAgIDwvZz4KICAgICAgICAgIDx0ZXh0IGRhdGEtbm9kZS1sYWJlbD0iIiB4PSI2ODkiIHk9Ijk0IiBjbGFzcz0idC1wcmltYXJ5IiBmb250LXNpemU9IjExIiBmb250LXdlaWdodD0iNjAwIiB0ZXh0LWFuY2hvcj0ibWlkZGxlIj5Qcm92aWRlciBBUEk8L3RleHQ+CiAgICAgICAgICA8dGV4dCB4PSI2ODkiIHk9IjExMSIgY2xhc3M9InQtbXV0ZWQiIGZvbnQtc2l6ZT0iNyIgdGV4dC1hbmNob3I9Im1pZGRsZSI+QW50aHJvcGljPC90ZXh0PgogICAgICAgIDwvZz4KCiAgICAgICAgCgogICAgICA8L3N2Zz4=" loading="lazy"></div><figcaption>When a replacement token takes effect. While the session uses token A, the rotation worker replaces the cached copy with B. The session continues using A until it reaches the limit. Restarting the session makes it read B.</figcaption></figure>
<p lang="en">Claude Code reads the environment variables once at startup, and Codex reads <code>auth.json</code> when its session starts. Codex renews the current account's tokens itself during use. In my observations, however, replacing the file externally with another account's credentials did not switch the running session. Even after the worker replaces the cached copy with B, the active session keeps making requests with A. Once A reaches its limit, that session cannot continue. Ending it and starting again through the launcher loads the latest credentials. Its history remains in the dedicated directory, so I can continue with <code>--resume</code>. Closing the Codex desktop app's window does not stop its process; it must be fully quit and reopened to read the new credentials.</p>
<p lang="en">For headless use in scripts, check the launch options too. With <code>--bare</code>, Claude Code does not read <code>CLAUDE_CODE_OAUTH_TOKEN</code>. I omit <code>--bare</code> when using the shared subscription token. Usage-based API keys have official mechanisms such as <code>apiKeyHelper</code> that can fetch keys again while running. This post uses subscription tokens passed through an environment variable, so I restart the session.</p>
<h2 lang="en" id="정리">Operating rules</h2>
<p lang="en">The setup rests on three principles. Separate accounts with configuration directories, and share working preferences through links. Pass credentials only to the process being launched. Store them in Keychain and run the rotation worker through launchd.</p>
<p lang="en">Five practical rules came out of operating it.</p>
<ol lang="en">
<li>Keep the shared account in a dedicated directory to avoid logging it out by accident.</li>
<li>Keep background workers alive on failure. A supervisor that immediately restarts them can turn failure into a restart loop.</li>
<li>Store credentials in Keychain and check their length after writing. To update an entry, delete it and create a new one.</li>
<li>Change the login password through the normal password-change flow instead of resetting it without the old password.</li>
<li>Automate preparing new credentials; restart sessions manually.</li>
</ol>]]></content:encoded>
    </item>
    <item>
      <title><![CDATA[OKF: I Couldn't Find a Reason to Adopt It Yet]]></title>
      <link>https://blog.funq.kr/en/blogs/okf-real-service-180runs-20260907-v01</link>
      <guid isPermaLink="true">https://blog.funq.kr/en/blogs/okf-real-service-180runs-20260907-v01</guid>
      <description><![CDATA[With ordinary Markdown already in use, I found no benefit substantial enough to justify switching to OKF. Adoption is on hold; I'll test its value as an index again when the text grows much longer.]]></description>
      <category><![CDATA[ai]]></category><category><![CDATA[agent]]></category><category><![CDATA[okf]]></category><category><![CDATA[knowledge-management]]></category><category><![CDATA[evaluation]]></category>
      <media:content url="https://api.funq.kr/blog/images/e26a59be-dced-4890-b202-a8d12280b79b.png" medium="image" type="image/png"/>
      <pubDate>Mon, 07 Sep 2026 07:16:38 GMT</pubDate>
      <author>funqdev@gmail.com (fundev)</author>
      <content:encoded><![CDATA[<style>
.post-body .okf-study { --ink:#24212c; --muted:#696171; --accent:#7B00D3; --line:#e4dde9; --paper:#fff; --wash:#f7f3fb; --track:#ede7f2; color:var(--ink); font-family:-apple-system,BlinkMacSystemFont,'Apple SD Gothic Neo','Malgun Gothic',sans-serif; line-height:1.95; font-size:17px; word-break:keep-all; overflow-wrap:anywhere; }
.dark .post-body .okf-study { --ink:#eeeaf3; --muted:#bab0c6; --accent:#ffdb4d; --line:#443b50; --paper:#211c29; --wash:#2b2434; --track:#43384f; }
.post-body .okf-study * { box-sizing:border-box; }
.post-body .okf-study p { margin:1.2em 0; }
.post-body .okf-study b,.post-body .okf-study strong { color:inherit; font-weight:750; }
.post-body .okf-study a { color:var(--accent); text-decoration:underline; text-decoration-thickness:1px; text-underline-offset:4px; }
.post-body .okf-study a:hover { text-decoration-thickness:2px; }
.post-body .okf-study a:focus-visible,.post-body .okf-study summary:focus-visible,.post-body .okf-study [tabindex]:focus-visible { outline:3px solid var(--accent); outline-offset:4px; }
.post-body .okf-study .okf-lead { font-size:1.14em; line-height:1.95; }
.post-body .okf-study h2 { border-top:1px solid var(--line); margin:3.3em 0 1em; padding-top:1.4em; font-size:1.6em; line-height:1.45; font-weight:800; letter-spacing:-.045em; scroll-margin-top:90px; color:var(--ink); }
.post-body .okf-study h3 { font-size:1.2em; line-height:1.5; margin:2em 0 .8em; letter-spacing:-.03em; color:var(--ink); }
.post-body .okf-study code { color:var(--accent); background:var(--wash); padding:2px 5px; border-radius:4px; font-size:.86em; font-family:ui-monospace,SFMono-Regular,Consolas,monospace; overflow-wrap:anywhere; }
.post-body .okf-study code::before,.post-body .okf-study code::after { content:none; }
.post-body .okf-study .okf-metrics { display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); gap:0; margin:32px 0; background:var(--wash); border-block:2px solid var(--accent); }
.post-body .okf-study .okf-metrics>div { padding:23px 18px; }
.post-body .okf-study .okf-metrics>div+div { border-left:1px solid var(--line); }
.post-body .okf-study .okf-metrics>div>span { display:block; font-size:12px; color:var(--muted); }
.post-body .okf-study .okf-metrics strong { display:block; color:var(--accent); font-size:35px; line-height:1.3; letter-spacing:-.04em; margin:10px 0; font-variant-numeric:tabular-nums; }
.post-body .okf-study .okf-metrics strong span { font-size:17px; margin-left:3px; }
.post-body .okf-study .okf-metrics strong.okf-word-metric { font-size:25px; padding-block:6px; }
.post-body .okf-study .okf-metrics small { display:block; font-size:11px; line-height:1.7; color:var(--muted); }
.post-body .okf-study .okf-note { border-left:3px solid var(--accent); padding:18px 22px; margin:28px 0; background:var(--wash); border-radius:0 9px 9px 0; font-size:.94em; }
.post-body .okf-study .okf-note>strong { color:var(--accent); display:block; font-size:13px; letter-spacing:.03em; }
.post-body .okf-study .okf-note p { margin:.5em 0 0; }
.post-body .okf-study .okf-caution { border-color:#bd7217; }
.post-body .okf-study .okf-caution>strong { color:#a25b00; }
.dark .post-body .okf-study .okf-caution>strong { color:#f4bd71; }
.post-body .okf-study .okf-figure { margin:28px 0; padding:24px; background:var(--paper); border:1px solid var(--line); border-radius:12px; }
.post-body .okf-study figcaption { color:var(--muted); margin:18px 0 0; font-size:12px; line-height:1.8; }
.post-body .okf-study .okf-flow-source { text-align:center; padding:12px; border:1px solid var(--line); background:var(--wash); border-radius:6px; margin-bottom:22px; font-size:14px; font-weight:700; }
.post-body .okf-study .okf-flow-arms { display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); gap:12px; }
.post-body .okf-study .okf-flow-arms>div { border:1px solid var(--line); padding:15px 13px; border-radius:8px; position:relative; }
.post-body .okf-study .okf-flow-arms>div::before { content:''; position:absolute; height:22px; width:1px; background:var(--line); top:-23px; left:50%; }
.post-body .okf-study .okf-arm-label { display:block; color:var(--muted); font-size:11px; margin-bottom:8px; }
.post-body .okf-study .okf-flow-arms b { display:block; font-size:16px; }
.post-body .okf-study .okf-flow-arms p { font-size:12px; line-height:1.8; color:var(--muted); margin:6px 0 0; }
.post-body .okf-study .okf-flow-arms .okf-arm-highlight { border-color:var(--accent); background:var(--wash); }
.post-body .okf-study .okf-flow-merge { text-align:center; color:var(--accent); font-size:24px; margin:6px 0; }
.post-body .okf-study .okf-flow-engine { text-align:center; background:var(--accent); color:#fff; border-radius:6px; font-size:13px; padding:13px; font-weight:650; }
.dark .post-body .okf-study .okf-flow-engine { color:#251e04; }
.post-body .okf-study .okf-figure-head span { display:block; color:var(--muted); font-size:11px; letter-spacing:.07em; }
.post-body .okf-study .okf-figure-head b { display:block; font-size:19px; letter-spacing:-.03em; margin:3px 0 20px; }
.post-body .okf-study .okf-diagram-scroll { overflow-x:auto; border-radius:6px; background:#fff; }
.post-body .okf-study .okf-diagram-scroll img { min-width:1040px; width:100%; max-width:none; height:auto; margin:0; display:block; }
.post-body .okf-study .okf-table-wrap { overflow-x:auto; margin:28px 0; border:1px solid var(--line); border-radius:9px; }
.post-body .okf-study table { width:100%; border-collapse:collapse; margin:0; font-size:13px; line-height:1.8; min-width:510px; }
.post-body .okf-study caption { text-align:left; font-size:12px; padding:12px 16px; color:var(--muted); background:var(--wash); }
.post-body .okf-study th,.post-body .okf-study td { padding:12px 15px; border:0; border-top:1px solid var(--line); vertical-align:top; text-align:left; color:var(--ink); }
.post-body .okf-study thead th { font-weight:750; background:var(--wash); }
.post-body .okf-study tbody th { min-width:140px; font-weight:650; }
.post-body .okf-study td { font-variant-numeric:tabular-nums; }
.post-body .okf-study .okf-details { margin:22px 0; border-block:1px solid var(--line); padding:14px 2px; font-size:14px; }
.post-body .okf-study summary { cursor:pointer; font-weight:650; color:var(--muted); }
.post-body .okf-study .okf-details p { margin-bottom:5px; }
.post-body .okf-study .okf-formula { background:var(--wash); text-align:center; padding:20px; font-size:15px; border-radius:8px; margin:25px 0; font-weight:650; }
.post-body .okf-study .okf-chart-legend { display:flex; flex-wrap:wrap; gap:20px; font-size:12px; color:var(--muted); margin:0 0 24px; }
.post-body .okf-study .okf-chart-legend i { display:inline-block; height:9px; width:9px; border-radius:2px; margin-right:6px; }
.post-body .okf-study .okf-md { background:#8c8497; }
.post-body .okf-study .okf-okf { background:var(--accent); }
.post-body .okf-study .okf-second { background:#187f88; }
.dark .post-body .okf-study .okf-second { background:#63c7ca; }
.post-body .okf-study .okf-chart-grid { display:grid; grid-template-columns:1fr 1fr; gap:24px 30px; }
.post-body .okf-study .okf-bar-group>b { font-size:13px; display:block; margin:0 0 6px; }
.post-body .okf-study .okf-bar { display:grid; grid-template-columns:36px 1fr 35px; gap:7px; align-items:center; font-size:10px; height:25px; color:var(--muted); }
.post-body .okf-study .okf-bar>div { height:11px; background:var(--track); border-radius:2px; overflow:hidden; }
.post-body .okf-study .okf-bar i { display:block; height:100%; border-radius:2px; }
.post-body .okf-study .okf-bar>span:last-child { text-align:right; font-size:11px; font-variant-numeric:tabular-nums; color:var(--ink); }
.post-body .okf-study .okf-bar-n { font-size:10px; color:var(--muted); margin-top:5px; }
.post-body .okf-study .okf-layer { margin:19px 0; }
.post-body .okf-study .okf-layer>div:first-child { display:flex; justify-content:space-between; gap:12px; font-size:14px; }
.post-body .okf-study .okf-layer b { color:var(--accent); font-variant-numeric:tabular-nums; }
.post-body .okf-study .okf-layer-track { height:12px; background:var(--track); border-radius:3px; margin:7px 0; overflow:hidden; }
.post-body .okf-study .okf-layer-track i { display:block; height:100%; background:var(--accent); }
.post-body .okf-study .okf-layer small { display:block; font-size:11px; color:var(--muted); }
.post-body .okf-study .okf-comparison { padding:22px; border:1px solid var(--line); border-radius:8px; margin:25px 0; }
.post-body .okf-study .okf-comparison>div+div { margin-top:22px; }
.post-body .okf-study .okf-comparison span { font-size:12px; color:var(--muted); display:block; }
.post-body .okf-study .okf-comparison b { font-size:20px; }
.post-body .okf-study .okf-size-track { height:10px; background:var(--track); border-radius:2px; margin-top:10px; }
.post-body .okf-study .okf-size-track i { display:block; height:100%; background:#8c8497; border-radius:2px; }
.post-body .okf-study .okf-comparison>div+div i { background:#b66a10; }
.post-body .okf-study .okf-next-list { padding-left:1.4em; margin:25px 0; }
.post-body .okf-study .okf-next-list li { padding-left:8px; margin:18px 0; }
.post-body .okf-study .okf-next-list li::marker { color:var(--accent); font-weight:750; }
.post-body .okf-study .okf-ending { margin:36px 0; border-left:3px solid var(--accent); padding-left:22px; font-size:1.13em; line-height:1.9; }
.post-body .okf-study .okf-sources { margin-top:45px; border-top:1px solid var(--line); padding-top:22px; color:var(--muted); font-size:12px; line-height:1.9; }
.post-body .okf-study .okf-sources strong { display:block; font-size:13px; }
@media(max-width:600px) {
 .post-body .okf-study { font-size:16px; line-height:1.95; }
 .post-body .okf-study h2 { font-size:1.4em; margin-top:2.5em; }
 .post-body .okf-study .okf-metrics { grid-template-columns:1fr; }
 .post-body .okf-study .okf-metrics>div { padding:16px 18px; display:grid; grid-template-columns:1fr 1fr; align-items:center; }
 .post-body .okf-study .okf-metrics>div+div { border-left:0; border-top:1px solid var(--line); }
 .post-body .okf-study .okf-metrics strong { text-align:right; margin:0; font-size:29px; grid-row:1 / 3; grid-column:2; }
 .post-body .okf-study .okf-metrics strong.okf-word-metric { font-size:21px; }
 .post-body .okf-study .okf-metrics small { font-size:10px; margin-top:5px; }
 .post-body .okf-study .okf-figure { padding:18px 14px; }
 .post-body .okf-study .okf-chart-grid { grid-template-columns:1fr; }
 .post-body .okf-study .okf-flow-arms { grid-template-columns:1fr; gap:10px; }
 .post-body .okf-study .okf-flow-arms>div::before { content:none; }
 .post-body .okf-study .okf-flow-arms>div { padding:12px; }
 .post-body .okf-study .okf-flow-arms p { font-size:12px; }
 .post-body .okf-study .okf-arm-label { margin-bottom:3px; }
 .post-body .okf-study .okf-formula { font-size:13px; padding:16px; }
 .post-body .okf-study .okf-note { padding:16px; }
}
</style>
<div lang="en" class="okf-study">
<p class="okf-lead">In my <a href="https://blog.funq.kr/blogs/okf-open-knowledge-format-20260729-v01">previous post</a>, I introduced a way to organize an organization's knowledge with OKF. This time, I tried it in an AI report for sellers that had been generated from ordinary Markdown. Across 180 runs, I compared three approaches: providing the original documents, summarizing them by concept, and automatically selecting the concepts needed.</p>
<p><strong>With the current volume of documents and the way we use them, I couldn't find a reason to adopt OKF right away in place of ordinary Markdown.</strong> Summarization sometimes reduced the input, but I found no advantage in the reports' judgments or preservation of necessary content substantial enough to justify changing the existing approach. Factoring in the operational burden of preprocessing and retrieval, I decided that keeping ordinary Markdown was the better choice for now.</p>
<p>The time to test it again is <strong>when the text becomes much longer than it is now</strong>. Once feeding in all the documents at once becomes difficult, OKF may help as an index that points to the knowledge needed. That possibility is the next question to test with more documents, rather than a result established by this experiment.</p>

<div class="okf-metrics" aria-label="Current decision and conditions for reconsideration">
 <div><span>Approach to use now</span><strong class="okf-word-metric">Ordinary Markdown</strong><small>Keep the existing documents and delivery method</small></div>
 <div><span>Decision on OKF adoption</span><strong class="okf-word-metric">On hold for now</strong><small>No practical gain found to justify the switch</small></div>
 <div><span>Condition for another test</span><strong class="okf-word-metric">More documents</strong><small>Check whether it becomes useful as an index</small></div>
</div>

<h2 id="system">01. Three Inputs, One Pipeline</h2>
<p>The report is HTML that reads a seller's product, social media, and marketplace status, then suggests changes in the form of cards. Since a generation pipeline already existed, there was no need to build three copies of it. I swapped only the input at two points: <code>page_sources</code>, which fetches the sources, and <code>page_payload</code>, which builds the request body.</p>

<figure class="okf-figure">
 <div class="okf-flow-source">Source Markdown documents</div>
 <div class="okf-flow-arms">
  <div><span class="okf-arm-label">Input method 1</span><b>Documents as they are</b><p>Concatenate the document list specified for each page.</p></div>
  <div class="okf-arm-highlight"><span class="okf-arm-label">Input method 2</span><b>Summaries by concept</b><p>Summarize for the generation prompt and cache the result.</p></div>
  <div><span class="okf-arm-label">Input method 3</span><b>Selected content</b><p>Select paths from the concept index in the DB and pass along their content.</p></div>
 </div>
 <div class="okf-flow-merge" aria-hidden="true">↓</div>
 <div class="okf-flow-engine">Shared request assembly → LLM generation → HTML validation → Storage</div>
 <figcaption>The reference knowledge is what changes. The generation engine's contract and downstream processing path are shared. </figcaption>
</figure>

<h3 id="summary-storage">Summary Bundles Went into the DB, Not a Folder of Files</h3>
<p>I sent the source documents and the generation prompt for the relevant area to a preprocessing LLM together. I parsed the returned result into a <code>{path, content}</code> array and stored it in a single DB row. Logically, these are multiple concept files, but the implementation does not create an actual file tree.</p>
<p>In one case, creating the initial summary took about 302 seconds. Shorter input therefore does not immediately mean a lower total cost. How much the cache is reused matters. The cache key included the source hash and preprocessing prompt identifier, but changes to the generation prompt were missing from the invalidation conditions. Since the summary is tailored to the generation objective, this also needs to be managed in production.</p>
<p>This implementation takes inspiration from OKF to create summaries by concept. I instructed it to use frontmatter, but did not attach a conformance validator. The experiment also did not include using trust metadata such as <code>verified</code> and <code>stale_after</code>.</p>

<h3 id="concept-retrieval">Concept Retrieval Became a Separate Subsystem</h3>
<p>The retrieval approach splits the source into concepts and loads them into the DB, then reads an index and selects the paths it needs. Documents that need verbatim preservation are stored without conversion. I also built a path for comparing the selections against reference paths and displaying metrics in an admin interface.</p>
<figure class="okf-figure okf-architecture">
 <div class="okf-figure-head"><span>The system I built</span><b>Concept retrieval → Report generation</b></div>
 <div class="okf-diagram-scroll" tabindex="0" role="region" aria-label="Detailed concept retrieval flowchart. Scroll horizontally on narrow screens."><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAACxQAAAUiCAIAAACh0cYJAAAQAElEQVR4nOz9B4Bc13kY+t/d2d5QFotGgCQIEiDYOykWiSqURJHqlixFLnEsuST2S+LkxfH7204cJ1Zs/xPHz7FjO5aLYskqlkxZnRIpUeydIEGiEr137GL7zsw7MxcYDrbP7AI7i/v7cbW6O3NnMHt25n73fOe759Rks9kIAAAAAAAAACCpqiMAAAAAAAAAgASrif9v14ETDXU1EaXrGxjSdGXIRtHAwFC9pitdNhsNDA3V12q6kmWz2YGhTH1tKqJEmq5smWx2aChTp+lKl8lkh9Karhy5pstk6mo0XcnSmWwmk6nVdKXLN122tkZZdsnS4T2XiTRdGdLhbRd6sylNV7IQXiNNVxZNV7bQdFVRlNJ0pRscylRXR6lqTVeyfNNVpaqrIko0OJSurq7WdGUYGErXhLbTdKUbGEyH8KrpypBruvC2q9J0JesfTNfVhJbTdCXTdGXrHxyqq6nRcmXoHxiqq6vRcmUwfl22U60Wmq9jXnNEicIw9pHj3Qs0XenCWOyRE70L5jZFlCgMThzr6m2fo+lKFsYnTnT1z5/TGFGikO7s6u6f16bpShZydt29/XNbNV3JQs6uu3dwbmtDRIlC9qS3f3BOi6YrWeiMhSxAW3N9RIlCZ2xwMN2q6UrX2z+UTqdbmjRdycKBLp3JtjTWRZSop28w9MWaNV3punsHQpq4qaE2okQnewZSqarGek1XspM9/alUqrFexrNkoQNbW5uSLC5DZ3d/fW3KxU5lOHGyLxzoXABQhuNdfc2NtarYy3C8q7c5vO2UYpfuWGdv6MAqii3D0RO9c1rrVXaW4ciJnnmtjQrFynD4eE/7nEYlO2U4fKy7fW6zliuDYxwAAAAAAAAAkGiKJwAAAAAAAACARFM8AQAAAAAAAAAkmuIJAAAAAAAAACDRFE8AAAAAAAAAAImmeAIAAAAAAAAASDTFEwAAAAAAAABAoimeAAAAAAAAAAASTfEEAAAAAAAAAJBoiicAAAAAAAAAgERTPAEAAAAAAAAAJJriCQAAAAAAAAAg0RRPAAAAAAAAAACJpngCAAAAAAAAAEg0xRMAAAAAAAAAQKIpngAAAAAAAAAAEk3xBAAAAAAAAACQaIonAAAAAAAAAIBEUzwBAAAAAAAAACSa4gkAAAAAAAAAINEUTwAAAAAAAAAAiaZ4AgAAAAAAAABINMUTAAAAAAAAAECiKZ4AAAAAAAAAABJN8QQAAAAAAAAAkGiKJwAAAAAAAACARFM8AQAAAAAAAAAkmuIJAAAAAAAAACDRFE8AAAAAAAAAAImmeAIAAAAAAAAASDTFEwAAAAAAAABAoimeAAAAAAAAAAASTfEEAAAAAAAAAJBoiicAAAAAAAAAgERTPAEAAAAAAAAAJJriCQAAAAAAAAAg0RRPAAAAAAAAAACJpngCAAAAAAAAAEg0xRMAAAAAAAAAQKIpngAAAAAAAAAAEk3xBAAAAAAAAACQaIonAAAAAAAAAIBEUzwBAAAAAAAAACSa4gkAAAAAAAAAINEUTwAAAAAAAAAAiaZ4AgAAAAAAAABINMUTAAAAAAAAAECiKZ4AAAAAAAAAABJN8QQAAAAAAAAAkGiKJwAAAAAAAACARFM8AQAAAAAAAAAkmuIJAAAAAAAAACDRFE8AAAAAAAAAAImmeAIAAAAAAAAASDTFEwAAAAAAAABAoimeAAAAAAAAAAASTfEEAAAAAAAAAJBoiicAAAAAAAAAgERTPAEAAAAAAAAAJJriCQAAAAAAAAAg0RRPAAAAAAAAAACJpngCAAAAAAAAAEg0xRMAAAAAAAAAQKIpngAAAAAAAAAAEk3xBAAAAAAAAACQaIonAAAAAAAAAIBEUzwBAAAAAAAAACSa4gkAAAAAAAAAINEUTwAAAAAAAAAAiaZ4AgAAAAAAAABINMUTAAAAAAAAAECiKZ4AAAAAAAAAABJN8QQAAAAAAAAAkGiKJwAAAAAAAACARFM8AQAAAAAAAAAkmuIJAAAAAAAAACDRFE8AAAAAAAAAAImmeAIAAAAAAAAASDTFEwAAAAAAAABAoimeAAAAAAAAAAASTfEEAAAAAAAAAJBoiicAAAAAAAAAgERTPAEAAAAAAAAAJJriCQAAAAAAAAAg0RRPAAAAAAAAAACJpngCAAAAAAAAAEg0xRMAAAAAAAAAQKIpngAAAAAAAAAAEk3xBAAAAAAAAACQaIonAAAAAAAAAIBEUzwBAAAAAAAAACSa4gkAAAAAAAAAINEUTwAAAAAAAAAAiaZ4AgAAAAAAAABINMUTAAAAAAAAAECiKZ4AAAAAAAAAABJN8QQAAAAAAAAAkGiKJwAAAAAAAACARFM8AQAAAAAA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" width="2160" height="1000" alt="Source documents are converted or preserved verbatim in a concept corpus, then pass through concept selection and shared report generation before the HTML is stored. The dashed path comparing reference paths with selection records was not executed during the experiment." loading="lazy" /></div>
 <figcaption>A public-facing reconstruction of the supplied Archify flowchart. Dashed lines mark the scoring path that was not executed in this experiment. On small screens, scroll horizontally to read the diagram.</figcaption>
</figure>
<p>The retrieval approach <strong>reused the selection cache in all 60 runs</strong>. What this experiment repeatedly measured was therefore <strong>reports generated from six inputs that had each been selected once</strong>, rather than a system performing a fresh search on every run. Because the implementation skips comparison with the reference answers when reusing the cache, retrieval precision and recall were empty in all 60 runs. I measured output repeatability, but could not directly score retrieval at its core job.</p>

<h2 id="design">02. What the Number 180 Contains</h2>
<p>The experiment ran on September 2–3, 2026. I generated each of six requests—products, social media, and marketplaces for two sellers—ten times using each of three input methods. Below, I call a group with the same request and input method a “cell.” There are 18 cells in total.</p>
<p>I fixed the model and reasoning effort and turned off additional refinement and candidate screening. For the original-versus-summary comparison, I alternated the methods within each page. Concept retrieval was added in a separate run, so this was not an experiment in which all three methods were randomly assigned together. In 3 of the 6 retrieval cells, the source documents came from a different point in time than those used by the other methods.</p>
<div class="okf-table-wrap"><table>
 <caption>Distinguishing the Plan, Stored Records, and Analysis Sample</caption>
 <thead><tr><th scope="col">Category</th><th scope="col">Count</th><th scope="col">Meaning</th></tr></thead>
 <tbody>
 <tr><th scope="row">Planned repeated runs</th><td>180 runs</td><td>2 × 3 × 3 × 10</td></tr>
 <tr><th scope="row">Stored execution records</th><td>182 records</td><td>Includes a recovered extra result and a timeout record</td></tr>
 <tr><th scope="row">Records containing HTML</th><td>181 records</td><td>Original 61 · Summary 60 · Retrieval 60</td></tr>
 <tr><th scope="row">Used to calculate stability</th><td>173 records</td><td>Excludes 1 extra result, 6 source changes, and 1 collapsed output</td></tr>
 </tbody>
</table></div>
<p>Thus, “180 runs” in this post refers to the size of the experimental design. The stability table uses 173 records; the search for suspicious text uses the 181 records with surviving HTML. One timeout had been omitted by the filename pattern used in the earlier aggregation. That must not be read as evidence that there were no failures.</p>
<details class="okf-details"><summary>What the Request Byte Comparison Verified</summary><p>I reassembled requests using the same rules as the actual pipeline and compared them against the call logs. There are 118 stored verification records. Of these, 97 had untruncated logs with both system and user messages matching, 19 had truncated user logs, and 2 had mismatched user messages. This check helped assess the comparison conditions, but did not fully prove input control across all 180 runs.</p></details>

<h2 id="metrics">03. Similar Words, or the Same Content?</h2>
<p>The first metric is a mechanically calculated <strong>Jaccard similarity of word sets</strong>. After stripping HTML tags, I formed word sets, compared every pair of outputs within each cell, and took the average. A cell with ten outputs remaining has 45 pairs. Those pairs share outputs, so they do not represent 45 independent experiments.</p>
<div class="okf-formula">Jaccard similarity = Number of shared words ÷ Total number of unique words × 100</div>
<p>I compared sets of card titles in the same way. Word similarity reflects vocabulary repetition, while title similarity reflects part of the layout. Neither directly measures factual accuracy or content completeness. <strong>Similarity can be high even if the exact same item is omitted every time.</strong></p>
<p>The second measure is a content review. I removed tags, classes, and image URLs, leaving a “skeleton” of titles, judgments, evidence, checks, and table rows for an LLM reviewer to read. It evaluated five dimensions—judgments, suggestions, numbers, checks, and structure—and extracted items that stayed the same and items that changed. In a separate verification pass, the resulting 134 discrepancies were classified as 96 confirmed, 37 partially confirmed, and 1 unconfirmed. These results were not exhaustively verified against human-authored reference answers.</p>
<details class="okf-details"><summary>Limitations of the Calculation</summary><p>The tokenizer splits only on whitespace and some punctuation. Commas and periods are also separators, so numbers can be split apart, and word order and frequency are ignored. Titles are extracted using a particular HTML tag pattern, so markup changes can affect the result even when the meaning stays the same. The figures in this post were recalculated from the original HTML using the existing rules.</p></details>

<h2 id="results">04. Summaries Led to More Repeated Words</h2>
<p>For word similarity, the summary bundle scored higher than the original in all six requests. The cell averages were <strong>62.4</strong> for the original, <strong>72.6</strong> for summaries, and <strong>59.1</strong> for concept retrieval. The average difference between summaries and the original was <strong>10.2 percentage points</strong>.</p>
<figure class="okf-figure"><div class="okf-figure-head"><span>0–100 · Higher means more similar word choices</span><b>Word similarity for the same request with three input methods</b></div><div class="okf-chart-legend">
<span><i class="okf-md" aria-hidden="true"></i>Original</span> <span><i class="okf-okf" aria-hidden="true"></i>Summary</span> <span><i class="okf-second" aria-hidden="true"></i>Retrieval</span>
</div><div class="okf-chart-grid">
<div class="okf-bar-group"><b>Seller A · Products</b>
<div class="okf-bar"><span>Original</span><div aria-hidden="true"><i class="okf-md" style="width:85.3%"></i></div><span>85.3</span></div>
<div class="okf-bar"><span>Summary</span><div aria-hidden="true"><i class="okf-okf" style="width:92.1%"></i></div><span>92.1</span></div>
<div class="okf-bar"><span>Retrieval</span><div aria-hidden="true"><i class="okf-second" style="width:82.7%"></i></div><span>82.7</span></div>
<div class="okf-bar-n">Analysis sample · Original/Summary/Retrieval: 8 / 8 / 10</div></div>
<div class="okf-bar-group"><b>Seller B · Products</b>
<div class="okf-bar"><span>Original</span><div aria-hidden="true"><i class="okf-md" style="width:85.4%"></i></div><span>85.4</span></div>
<div class="okf-bar"><span>Summary</span><div aria-hidden="true"><i class="okf-okf" style="width:87.1%"></i></div><span>87.1</span></div>
<div class="okf-bar"><span>Retrieval</span><div aria-hidden="true"><i class="okf-second" style="width:87.7%"></i></div><span>87.7</span></div>
<div class="okf-bar-n">Analysis sample · Original/Summary/Retrieval: 9 / 9 / 10</div></div>
<div class="okf-bar-group"><b>Seller A · Social media</b>
<div class="okf-bar"><span>Original</span><div aria-hidden="true"><i class="okf-md" style="width:63.5%"></i></div><span>63.5</span></div>
<div class="okf-bar"><span>Summary</span><div aria-hidden="true"><i class="okf-okf" style="width:73.5%"></i></div><span>73.5</span></div>
<div class="okf-bar"><span>Retrieval</span><div aria-hidden="true"><i class="okf-second" style="width:50.7%"></i></div><span>50.7</span></div>
<div class="okf-bar-n">Analysis sample · Original/Summary/Retrieval: 10 / 10 / 10</div></div>
<div class="okf-bar-group"><b>Seller B · Social media</b>
<div class="okf-bar"><span>Original</span><div aria-hidden="true"><i class="okf-md" style="width:58.1%"></i></div><span>58.1</span></div>
<div class="okf-bar"><span>Summary</span><div aria-hidden="true"><i class="okf-okf" style="width:68.7%"></i></div><span>68.7</span></div>
<div class="okf-bar"><span>Retrieval</span><div aria-hidden="true"><i class="okf-second" style="width:46.3%"></i></div><span>46.3</span></div>
<div class="okf-bar-n">Analysis sample · Original/Summary/Retrieval: 10 / 9 / 10</div></div>
<div class="okf-bar-group"><b>Seller A · Marketplace</b>
<div class="okf-bar"><span>Original</span><div aria-hidden="true"><i class="okf-md" style="width:38.7%"></i></div><span>38.7</span></div>
<div class="okf-bar"><span>Summary</span><div aria-hidden="true"><i class="okf-okf" style="width:57.2%"></i></div><span>57.2</span></div>
<div class="okf-bar"><span>Retrieval</span><div aria-hidden="true"><i class="okf-second" style="width:43.3%"></i></div><span>43.3</span></div>
<div class="okf-bar-n">Analysis sample · Original/Summary/Retrieval: 10 / 10 / 10</div></div>
<div class="okf-bar-group"><b>Seller B · Marketplace</b>
<div class="okf-bar"><span>Original</span><div aria-hidden="true"><i class="okf-md" style="width:43.1%"></i></div><span>43.1</span></div>
<div class="okf-bar"><span>Summary</span><div aria-hidden="true"><i class="okf-okf" style="width:56.8%"></i></div><span>56.8</span></div>
<div class="okf-bar"><span>Retrieval</span><div aria-hidden="true"><i class="okf-second" style="width:43.9%"></i></div><span>43.9</span></div>
<div class="okf-bar-n">Analysis sample · Original/Summary/Retrieval: 10 / 10 / 10</div></div>
</div><figcaption>The average Jaccard similarity of word sets across output pairs in each cell, calculated from 173 analysis samples. Some retrieval cells use a different generation of source documents, so the ranking of the three methods cannot be interpreted as a causal effect.</figcaption></figure>
<p>These results mix the effects of input length, summarization, and more uniform phrasing. Since this was not an experiment that changed only the format while keeping the content identical, the increase cannot be isolated as an effect of OKF syntax itself.</p>
<p>Differences between request areas were also substantial. The average was 86.7 for products, 60.1 for social media, and 47.2 for marketplaces. The spread between area averages was <strong>39.6 percentage points</strong>, compared with <strong>13.5 percentage points</strong> between input-method averages. Across these six requests, the observed differences between request areas were larger than those between input methods. </p>

<h3 id="word-overlap-and-content">Similar Words Do Not Guarantee the Content</h3>
<p>Average judgment-consistency scores were similar: 90.0 for the original, 89.8 for summaries, and 89.2 for retrieval. Score variation during reevaluation was greater than these differences, making it difficult to use these scores to determine which method produced more stable judgments. The effects I could confirm were <strong>less input and more similar wording</strong>. Those alone were not enough to conclude that the output had improved sufficiently to justify replacing the current Markdown approach with OKF.</p>

<h2 id="layers">05. Evidence and Structure Changed More Often than the Direction of Recommendations</h2>
<p>The item lists extracted by the reviewer were more interesting than the scores. I regrouped by content the 312 items classified as “always the same” and the 216 classified as “different across runs.”</p>
<figure class="okf-figure"><div class="okf-figure-head"><span>Composition of the item lists extracted by the reviewer</span><b>Fixed items ÷ (Fixed items + Variable items)</b></div>
<div class="okf-layer"><div><span>Judgments and suggestions</span><b>79.1%</b></div><div class="okf-layer-track" aria-hidden="true"><i style="width:79.1%"></i></div><small>144 fixed · 38 variable · 182 total</small></div>
<div class="okf-layer"><div><span>Checks and items to retain</span><b>64.3%</b></div><div class="okf-layer-track" aria-hidden="true"><i style="width:64.3%"></i></div><small>83 fixed · 46 variable · 129 total</small></div>
<div class="okf-layer"><div><span>Supporting figures</span><b>43.5%</b></div><div class="okf-layer-track" aria-hidden="true"><i style="width:43.5%"></i></div><small>64 fixed · 83 variable · 147 total</small></div>
<div class="okf-layer"><div><span>Layout structure</span><b>30.0%</b></div><div class="okf-layer-track" aria-hidden="true"><i style="width:30.0%"></i></div><small>21 fixed · 49 variable · 70 total</small></div>
<figcaption>These are neither repeated-run success rates nor accuracy scores. The four categories contain different numbers of items, and the results depend on how the LLM reviewer divides the content into items.</figcaption></figure>
<p>The share classified as fixed was 79.1% for judgments and suggestions, and 43.5% for figures. <strong>This does not mean that “the judgment was reproduced eight times out of ten.”</strong> These are proportions within item lists with different denominators. The values also change depending on how finely an item is divided.</p>
<p>Even so, this showed where to pay attention when reading the reports. Recommendations could point in a similar direction while the supporting numbers and additional checks changed. Actions were not always fixed either: 4 of the 14 severe discrepancies concerned judgments and suggestions.</p>


<h2 id="failures">06. Other Problems Found During Repeated Runs</h2>
<p>Apart from the input-method comparison, defects in generation also stood out. One output had <strong>753 characters and 0 cards</strong>, yet was saved as a success. The other nine outputs in the same cell averaged 10,371 characters and all had 6 cards.</p>
<div class="okf-comparison"><div><span>Average of the other 9 outputs in the same cell</span><b>10,371 characters · 6 cards</b><div class="okf-size-track"><i style="width:100%"></i></div></div><div><span>Collapsed output saved as a success</span><b>753 characters · 0 cards</b><div class="okf-size-track"><i style="width:7.3%"></i></div></div></div>
<p>Some results also contained strings such as <code>ennials</code> or traces of intermediate work in the middle of sentences. Searching all 181 HTML outputs for infrequent Latin-script tokens produced 23 candidates for another review. All three methods had them: original 5/61, summary 10/60, and retrieval 8/60. Since rare but legitimate words could also be included, I did not treat these figures directly as contamination rates.</p>
<p>These defects need to be addressed separately in the generation pipeline. I changed the way knowledge was delivered in this experiment, but that change alone did not make the output dependable enough to use without concern.</p>

<h2 id="decision">07. Keep Ordinary Markdown for Now</h2>
<p>Summarization clearly had a compression effect. In the product area, it reduced input by about 60–65%. But for inputs that were already short, such as social media, it made them larger.</p>
<div class="okf-table-wrap"><table><caption>Original → Summary bundle · Comparison of stored input character counts</caption><thead><tr><th scope="col">Request</th><th scope="col">Original</th><th scope="col">Summary</th><th scope="col">Change</th></tr></thead><tbody>
<tr><th scope="row">Seller A · Products</th><td>81,782 characters</td><td>32,699 characters</td><td>-60.0%</td></tr>
<tr><th scope="row">Seller B · Products</th><td>87,888 characters</td><td>30,563 characters</td><td>-65.2%</td></tr>
<tr><th scope="row">Seller A · Social media</th><td>24,567 characters</td><td>27,155 characters</td><td>+10.5%</td></tr>
<tr><th scope="row">Seller B · Social media</th><td>24,237 characters</td><td>25,483 characters</td><td>+5.1%</td></tr>
</tbody></table></div>
<p>The table shows input <strong>character counts</strong>. Summarization adds LLM calls and cache management, so fewer characters do not immediately mean lower total cost. Above all, being able to compress the input is not by itself a reason to adopt OKF. The criterion should be whether the benefit is substantial enough to change what is currently done with Markdown.</p>
<p>Concept retrieval did not support making the switch either. Compared against key output items from the other methods, only 18/46 or 25/51 items were preserved, and 12 of the 14 severe discrepancies came from retrieval. These figures are comparisons between output items, rather than recall measured against a retrieval answer set.</p>
<p>The retrieval implementation I built comprised 5,820 lines across production code, tests, and DDL, with 20 routes and 4 tables. That scale reflects the maintenance burden of my implementation, rather than a requirement of the OKF specification itself. Without a clear advantage over the existing approach, there was no reason to add that burden to production.</p>
<p><strong>The conclusion of this experiment is therefore to keep ordinary Markdown in the current service and put OKF adoption on hold.</strong> Whether summarization or conceptualization is possible and whether it needs to be adopted now turned out to be separate judgments.</p>

<h2 id="next">08. Test It Again as an Index When the Documents Get Longer</h2>
<p>Currently, I specify and pass along the list of Markdown documents needed for each request. If the number and length of the documents keep growing, managing that list or supplying all the source text at once may become burdensome. At that point, an index that locates the necessary concepts and their source passages could become useful.</p>
<p>What I want to establish is <strong>whether an index built with OKF can find all the knowledge needed with less input</strong>. As document volume increases in stages, I need to compare passing ordinary Markdown directly, adding a simple table of contents to Markdown, and using an OKF concept index. This would distinguish the effect of having an index from the effect of choosing OKF.</p>
<p>If that comparison shows practical gains in preserving necessary items, input volume, processing time, and cost, adoption can be reconsidered. Because this retrieval experiment repeatedly reused cached selections, it did not establish how well the index could find the right material as the number of documents grows.</p>
<aside class="okf-note okf-conclusion"><strong>Conclusion</strong><p><b>In the current situation, where we use ordinary Markdown, OKF did not help much. There is no reason to adopt it immediately. But when the text becomes much longer than it is now, its potential value as an index needs to be tested again.</b></p></aside>
</div>
]]></content:encoded>
    </item>
    <item>
      <title><![CDATA[Using Codex Alongside Claude Code: Plugin Comparison and Three Use Cases]]></title>
      <link>https://blog.funq.kr/en/blogs/codex-in-claude-code-20260906-v01</link>
      <guid isPermaLink="true">https://blog.funq.kr/en/blogs/codex-in-claude-code-20260906-v01</guid>
      <description><![CDATA[Compare the official Codex plugin with direct CLI calls, then explore how to divide work between two agents through one measured image-generation run and designs for adversarial review and A/B evaluation.]]></description>
      <category><![CDATA[AI]]></category><category><![CDATA[Claude Code]]></category><category><![CDATA[Codex]]></category><category><![CDATA[Agent Skills]]></category><category><![CDATA[개발도구]]></category>
      <media:content url="https://api.funq.kr/blog/images/87093405-7f3d-43b7-a20f-951995ad9f14.png" medium="image" type="image/png"/>
      <pubDate>Sun, 06 Sep 2026 16:09:40 GMT</pubDate>
      <author>funqdev@gmail.com (fundev)</author>
      <content:encoded><![CDATA[<style>
.post-body .cc-table { max-width:100%; overflow-x:auto; margin:1.6rem 0; border:1px solid #ddd4e7; border-radius:.7rem; }
.post-body .cc-table table { width:100%; min-width:35rem; border-collapse:collapse; margin:0; font-size:.9em; }
.post-body .cc-table th,.post-body .cc-table td { padding:.85rem 1rem; text-align:left; vertical-align:top; border-bottom:1px solid #e9e2ef; line-height:1.75; }
.post-body .cc-table th { background:#f5eefb; color:#4e1574; font-weight:700; }
.post-body .cc-table tr:last-child td { border-bottom:0; }
.post-body pre.code-block { max-width:100%; overflow-x:auto; background:#24292e; color:#e1e4e8; padding:1.2rem; border-radius:.7rem; line-height:1.8; font-size:.85em; margin:1.75rem 0; }
.post-body pre.code-block code { color:inherit; background:transparent; padding:0; white-space:pre; font-size:inherit; }
.post-body .cc-proof { margin:2rem 0; }
.post-body .cc-proof img { display:block; width:100%; max-width:100%; height:auto; border-radius:.75rem; margin:0; }
.post-body .cc-proof figcaption { margin-top:.8rem; font-size:.83em; color:#695f72; line-height:1.8; }
.post-body .cc-scope-note { margin-top:3rem; border-left:.2rem solid #7B00D3; padding:1rem 1.2rem; background:#f8f4fc; color:#5d4e68; font-size:.87em; }
.post-body .cc-scope-note em { font-style:normal; }
.dark .post-body .cc-table { border-color:#4d4737; }
.dark .post-body .cc-table th { background:#302a19; color:#ffdb4d; }
.dark .post-body .cc-table td { border-color:#423e32; }
.dark .post-body .cc-proof figcaption { color:#beb5c8; }
.dark .post-body .cc-scope-note { border-color:#ffdb4d; background:#28251d; color:#dbd2bd; }
</style>

<p lang="en">When working with Claude Code, there are moments when another agent's help would be useful: creating a blog cover image, checking newly written code for gaps from a different perspective, or deciding which of two outputs to adopt. You can open Codex separately, or connect the process so that Claude Code passes it the work and receives the results.</p>
<p lang="en">The starting point was a question: “Is there a skill for using Codex from Claude Code?” After investigating seven candidates, including the official plugin, I found that the first distinction to make was less about how many plugins exist and more about <strong>how Codex is invoked and how its results are verified</strong>.</p>
<p lang="en">This post has two parts. First, I compare the official plugin with direct CLI calls. Then I look at their application to image generation, adversarial review, and A/B evaluation. For image generation, I checked the saved execution logs and PNG. The other two use cases remain designs that have not yet been run.</p>
<h2 lang="en" id="무엇을-실제로-확인했나">What Did I Actually Verify?</h2>
<p lang="en">The initial research took place on September 6, 2026, in a Windows 10 environment. The report records Claude Code 2.1.263 and Codex CLI 0.153.4. While preparing this post, I checked the findings again against the official documentation and plugin source as of September 7. The distinctions below are the basis for reading this post.</p>
<div lang="en" aria-label="Horizontally scrollable comparison table" class="cc-table" role="region" tabindex="0"><table>
<thead>
<tr>
<th>Subject</th>
<th>Verified</th>
<th>Not Yet Verified</th>
</tr>
</thead>
<tbody>
<tr>
<td>Official plugin</td>
<td>Documentation and source structure; environment checks recorded as passing in the source material</td>
<td>Whether an actual review succeeds after installation</td>
</tr>
<tr>
<td>Image generation through direct CLI calls</td>
<td>One run, the resulting PNG, timing, and event logs</td>
<td>Repeated-run success rate and average latency</td>
</tr>
<tr>
<td>Adversarial review</td>
<td>Review of draft prompts, schemas, and wrappers</td>
<td>A verification loop against actual code</td>
</tr>
<tr>
<td>A/B evaluation</td>
<td>Designs for comparing outputs, comparing implementations, and visitor experiments</td>
<td>Comparative experimental results or conversion-rate improvements</td>
</tr>
</tbody>
</table></div>
<p lang="en">I did not install the official plugin. The <code>ready: true</code> in the source material records a check of the environment and authentication required to run it; it must not be read as meaning that every plugin feature worked correctly on this PC.</p>
<h2 lang="en" id="공식-플러그인은-무엇을-연결하나">What Does the Official Plugin Connect?</h2>
<p lang="en">OpenAI provides <a href="https://github.com/openai/codex-plugin-cc" rel="noopener noreferrer">openai/codex-plugin-cc</a> for Claude Code. The plugin I investigated was v1.0.6, and the code I inspected was at commit <code>db52e28</code>.</p>
<p lang="en">The invocation flow is as follows.</p>
<pre class="code-block"><code>Claude Code의 /codex:* 명령
  → 플러그인의 Node 스크립트
  → 로컬 codex app-server
  → Codex 작업·리뷰
  → Claude Code로 결과 반환
</code></pre>
<p lang="en">Internally, the plugin starts a <code>codex app-server</code> process and exchanges requests through JSON-RPC. It is closer to <strong>a connection layer through which Claude Code invokes separate Codex tasks</strong> than a setting that adds a Codex model inside the Claude model. <a href="https://github.com/openai/codex-plugin-cc/blob/db52e28f4d9ded852ab3942cea316258ae4ef346/plugins/codex/scripts/lib/app-server.mjs#L190" rel="noopener noreferrer">Execution code</a></p>
<p lang="en">The main commands are divided by role.</p>
<div lang="en" aria-label="Horizontally scrollable comparison table" class="cc-table" role="region" tabindex="0"><table>
<thead>
<tr>
<th>Command</th>
<th>Purpose</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>/codex:review</code></td>
<td>General code review of changes</td>
</tr>
<tr>
<td><code>/codex:adversarial-review</code></td>
<td>Review focused on assumptions, design, and failure conditions</td>
</tr>
<tr>
<td><code>/codex:rescue</code></td>
<td>Delegate investigation or fixes to Codex</td>
</tr>
<tr>
<td><code>/codex:transfer</code></td>
<td>Transfer a Claude Code session to Codex</td>
</tr>
<tr>
<td><code>/codex:status</code>, <code>/codex:result</code>, <code>/codex:cancel</code></td>
<td>Check progress, retrieve results, and request cancellation</td>
</tr>
</tbody>
</table></div>
<p lang="en">Reviews and delegated fixes have different permissions. In particular, <code>rescue</code> may lead to work that can modify files, so it should not be treated like a read-only review. Receiving review results and verifying the changes are also separate things.</p>
<h2 lang="en" id="설치-절차와-windows에서-확인할-점">Installation and Things to Check on Windows</h2>
<p lang="en">The official README gives the following installation procedure inside Claude Code. <strong>This reproduces the official installation instructions; it is not a record of installation completed during this investigation.</strong></p>
<pre class="code-block"><code>/plugin marketplace add openai/codex-plugin-cc
/plugin install codex@openai-codex
/reload-plugins
/codex:setup
</code></pre>
<p lang="en">Node and the Codex CLI must be available, along with authentication for Codex. It is best to check the <a href="https://github.com/openai/codex-plugin-cc#install" rel="noopener noreferrer">official installation guide</a> at installation time for detailed requirements and commands.</p>
<p lang="en">On Windows, installation is not the only thing to check; termination, cancellation, and background-process behavior also matter. For example, <a href="https://github.com/openai/codex-plugin-cc/issues/530" rel="noopener noreferrer">issue #530</a> reports that the Stop hook waited for a long time in a particular Windows 11 environment even with the review gate disabled. <a href="https://github.com/openai/codex-plugin-cc/issues/525" rel="noopener noreferrer">Issue #525</a> describes a failure to force-terminate a process because of Git Bash path conversion. The latter report also describes conditions under which a graceful cancellation request is possible, so summarizing it as “cancellation does not work at all” would overstate the report.</p>
<p lang="en">These are reports from other users, not results I reproduced on this PC. The fact that related issues were open when I checked is a reason to investigate before adoption, but does not establish that the same problems occur in every Windows environment.</p>
<p lang="en">When I checked on September 7, 2026, the most recent commit on the default branch was from July 8. The interval between commits alone cannot establish that maintenance has stopped. When adopting it, look at the issues and change history for the features you need.</p>
<h2 lang="en" id="직접-cli-호출과는-무엇이-다른가">How Does a Direct CLI Call Differ?</h2>
<p lang="en">Another approach is to run <code>codex exec</code> from a Claude Code tool or project skill without going through the plugin. You define the recurring command, input prompt, and location for the results yourself.</p>
<div lang="en" aria-label="Horizontally scrollable comparison table" class="cc-table" role="region" tabindex="0"><table>
<thead>
<tr>
<th>Comparison</th>
<th>Official plugin</th>
<th>Direct <code>codex exec</code> call</th>
</tr>
</thead>
<tbody>
<tr>
<td>Execution path</td>
<td>Node script → <code>app-server</code></td>
<td>Run a single CLI task</td>
</tr>
<tr>
<td>Starting point</td>
<td><code>/codex:*</code> commands and provided workflows</td>
<td>Your own commands, skills, and scripts</td>
</tr>
<tr>
<td>Task management</td>
<td>Status, result, and cancellation commands provided</td>
<td>Manage termination and result files yourself</td>
</tr>
<tr>
<td>Result format</td>
<td>Follow the output format of the provided command</td>
<td>Choose text, JSONL, or JSON Schema output</td>
</tr>
<tr>
<td>What you maintain</td>
<td>Plugin version, hook behavior, and broker behavior</td>
<td>Paths, exit codes, timeouts, and result validation</td>
</tr>
<tr>
<td>Good fit</td>
<td>Use the supplied review and delegation flows directly</td>
<td>Customize a narrow, repeatable task</td>
</tr>
</tbody>
</table></div>
<p lang="en">The choice comes down to whether you need the conveniences of the official plugin or prefer simpler inputs and outputs while accepting more things to manage yourself. Direct calls bypass this plugin's hooks and broker, but do not eliminate compatibility issues in the Codex CLI itself.</p>
<p lang="en">The community alternatives I investigated also need to be distinguished by their execution paths. For example, <a href="https://github.com/briandconnelly/codex-in-claude" rel="noopener noreferrer">codex-in-claude</a> wraps <code>codex exec</code> in its own MCP server. By contrast, <a href="https://github.com/ching-kuo/claude-codex" rel="noopener noreferrer">claude-codex</a> registers Codex's built-in <code>mcp-server</code>. Both descriptions mention MCP, but they are different implementations.</p>
<p lang="en">What the current official documentation marks as deprecated is <strong>the <code>codex mcp-server</code> command</strong>. This must not be interpreted as meaning that every community wrapper using MCP has been discontinued. The official documentation points to app-server and the official Claude Code plugin as alternatives. <a href="https://learn.chatgpt.com/docs/mcp-server" rel="noopener noreferrer">MCP server documentation</a></p>
<h3 lang="en" id="직접-호출의-기본-모양">The Basic Shape of a Direct Call</h3>
<p lang="en">The following configuration example reads changes in a repository from Git Bash and writes the result to a file. It is not an exact copy of the command used for the image-generation probe.</p>
<pre class="code-block"><code>mkdir<span style="color: #E1E4E8"> </span>-p<span style="color: #E1E4E8"> </span>review-output
codex<span style="color: #E1E4E8"> </span><span style="color: #B392F0">exec</span><span style="color: #E1E4E8"> </span>--sandbox<span style="color: #E1E4E8"> </span>read-only<span style="color: #E1E4E8"> </span>--json<span style="color: #E1E4E8"> </span><span style="color: #9ECBFF">\</span>
<span style="color: #E1E4E8"> </span>--output-last-message<span style="color: #E1E4E8"> </span>review-output/result.md<span style="color: #E1E4E8"> </span><span style="color: #9ECBFF">\</span>
<span style="color: #E1E4E8"> </span><span style="color: #9ECBFF">"변경 사항을 읽고 실패 가능성이 있는 지점을 근거와 함께 정리해줘. 파일은 수정하지 마."</span><span style="color: #E1E4E8"> </span><span style="color: #9ECBFF">\</span>
<span style="color: #E1E4E8"> </span>&gt;<span style="color: #E1E4E8"> </span>review-output/events.jsonl
</code></pre>
<p lang="en">Here, <code>--json</code> records runtime events as JSONL, while <code>--output-last-message</code> saves the final response in a separate file. For automated processing, <code>--output-schema</code> can specify the structure of the final response. Check the exit code, failure events, and whether the result file exists together. Valid JSON alone does not guarantee accurate content. <a href="https://learn.chatgpt.com/docs/non-interactive-mode" rel="noopener noreferrer">Non-interactive execution documentation</a></p>
<h2 lang="en" id="활용-1-이미지-생성은-로그와-파일까지-확인했다">Use Case 1: Image Generation, Verified Through Logs and Files</h2>
<p lang="en">The task I actually ran called the Codex CLI from Claude Code to create a blog cover image. The prompt requested Codex's built-in image-generation tool and asked it to save the result as <code>sample-a.png</code>. The subject measured was a cover concept for last30days.</p>
<p lang="en">The verified path used <code>codex exec</code> to invoke the built-in <code>image_gen</code>. This was not an experiment using a separately obtained Images API key and an API script. The built-in tool's availability still needs to be checked for the environment and account being used. <a href="https://learn.chatgpt.com/docs/image-generation" rel="noopener noreferrer">Codex image-generation guide</a></p>
<figure lang="en" class="cc-proof"><img alt="A robot researcher and reference cards from the image-generation probe" height="887" loading="lazy" src="https://api.funq.kr/blog/images/d2e6fdf7-c79a-4299-ab00-fc4108eccb4e.png" width="1774"/><figcaption>The actual PNG from the September 6, 2026 probe. The measurements below apply to this sample; the cover at the top of this post was created separately.</figcaption></figure>

<div lang="en" aria-label="Horizontally scrollable comparison table" class="cc-table" role="region" tabindex="0"><table>
<thead>
<tr>
<th>Measurement</th>
<th>Result</th>
</tr>
</thead>
<tbody>
<tr>
<td>Number of runs</td>
<td>1 run</td>
</tr>
<tr>
<td>Wrapper start to finish</td>
<td>77 seconds</td>
</tr>
<tr>
<td>Time reported by the image-generation tool</td>
<td>38.4 seconds</td>
</tr>
<tr>
<td>Saved image</td>
<td>1774 × 887 PNG, 2:1 aspect ratio</td>
</tr>
<tr>
<td>File size</td>
<td>2,221,382 bytes</td>
</tr>
<tr>
<td>File verification</td>
<td>Matching SHA-256 hashes for the original saved by Codex and its copy</td>
</tr>
</tbody>
</table></div>
<p lang="en">The 77 seconds is not just image-model inference time. It is the full execution time, including task startup, context reading, image generation, file verification, and copying. It should not be compared with the tool's reported 38.4 seconds as though they were the same metric. Nor does one successful run establish average speed or repeatability.</p>
<p lang="en">The final usage event recorded 110,853 input tokens, of which 90,496 were cached, and 693 output tokens. These values describe that agent run; <strong>they are not a token cost for one image or a figure converted into money.</strong> Both the image-generation tool call and the agent's context processing need to be considered.</p>
<p lang="en">The practical lesson is that work remains after “an image was generated.” In the sample, marks resembling numbers remained around the calendar border despite a request for no text. Successful file creation and fulfillment of the visual requirements must be checked separately. Identify the result path for the current run, verify that the file really exists, check its dimensions and aspect ratio, and inspect whether its visual content is appropriate. Since several sessions may run simultaneously, it is also better to avoid simply picking whichever image file was created most recently.</p>
<h2 lang="en" id="활용-2-적대적-검증은-반박-가능한-결과를-받도록-설계한다">Use Case 2: Design Adversarial Review to Produce Falsifiable Findings</h2>
<p lang="en">Adversarial review is not a request to find as many defects as possible. It is a process of examining assumptions and failure conditions that the implementer may overlook from another perspective, and making the findings verifiable through code or tests.</p>
<p lang="en">A draft skill was written for this use case, but the actual verification loop was not run. The original draft also has two CLI-option locations that need to be fixed and rechecked on this PC. It is therefore not yet ready to install as a finished tool or to present as a method that “caught bugs.”</p>
<p lang="en">The core of the design is to separate the roles.</p>
<pre class="code-block"><code>Claude Code: 요구사항과 변경 범위 정리
  → Codex: 읽기 전용으로 실패 조건·근거 검토
  → Claude Code: 지적을 코드·테스트로 재확인
  → 확인된 지적 수정
  → 테스트와 필요한 재검토
</code></pre>
<p lang="en">Give the reviewer a baseline for the changes, the target files, and the behavior that must be preserved. It is important that the reviewer read the actual changes and requirements, rather than relying only on the author's conclusion that the implementation is good.</p>
<p lang="en">The result should contain at least the following information.</p>
<ul lang="en">
<li>What could go wrong?</li>
<li>What input or execution sequence triggers it?</li>
<li>Which file, function, or code supports the finding?</li>
<li>What check could reproduce or disprove it?</li>
<li>What remains unverified?</li>
</ul>
<p lang="en">For example, “There is a branch that changes the state to completed even when the save request fails, which can be checked with a test returning a failure response” is more useful for follow-up work than “Exception handling is insufficient.”</p>
<p lang="en">In operation, content judgments and execution status also need separate records. A completed review that found no issues must not be treated as the same approval as a timeout that produced no result. The design keeps failed runs marked as failures, caps the number of iterations, and accepts actual fixes only after checking their test results.</p>
<p lang="en">You can use the official plugin's <code>/codex:adversarial-review</code>, or create your own result format directly with <code>codex exec --output-schema</code>. That plugin feature and the custom draft skill examined here are separate implementations.</p>
<h2 lang="en" id="활용-3-ab-평가는-먼저-무엇을-비교할지-정한다">Use Case 3: Decide What You Are Comparing Before an A/B Evaluation</h2>
<p lang="en">While organizing the report, I also found that the term “A/B testing” was being used for several different activities.</p>
<div lang="en" aria-label="Horizontally scrollable comparison table" class="cc-table" role="region" tabindex="0"><table>
<thead>
<tr>
<th>What to compare</th>
<th>Required setup</th>
<th>What the result can tell you</th>
</tr>
</thead>
<tbody>
<tr>
<td>Choose between two images or drafts</td>
<td>The same evaluation criteria; two options with their authors hidden</td>
<td>The model's preference and reasoning under those criteria</td>
</tr>
<tr>
<td>Compare Claude and Codex implementations</td>
<td>The same requirements and baseline code, separate workspaces, and identical tests</td>
<td>A comparison of implementation and verification results under defined conditions</td>
</tr>
<tr>
<td>Compare actual visitor responses</td>
<td>Random assignment, metric collection, and a sufficient sample</td>
<td>Experimental results about user behavior</td>
</tr>
</tbody>
</table></div>
<p lang="en">The first approach uses an LLM as a judge. For two blog covers, you can first define criteria such as “Does it convey the topic?”, “Is it readable on a small screen?”, and “Is there room for an overlaid title?” Hide the author or generation-model names, swap A and B, and present them again to check whether position affects the judgment. If the judgments conflict, leave the choice for human review instead of forcing a winner.</p>
<p lang="en">Models can be influenced by response order or length. This calls for pairwise comparisons with clear criteria, rather than just a single score, along with a process to check agreement with human judgment. <a href="https://developers.openai.com/api/docs/guides/evaluation-best-practices" rel="noopener noreferrer">OpenAI evaluation best practices</a></p>
<p lang="en">The second approach is an implementation experiment. Since changes can become mixed if two agents work in the same repository at once, the idea is to use separate workspaces created from the same baseline commit. Judging correctness should involve the same tests, build, and requirements checks, as well as the persuasiveness of the explanation. If the conditions differ, model differences become difficult to separate from workspace differences.</p>
<p lang="en">The third approach, a visitor experiment, requires separate instrumentation. A model preferring cover B is not evidence that readers clicked B more often. Improvements in click-through or conversion rates cannot be claimed before assigning actual traffic and measuring behavior. This investigation reviewed only the designs for all three types.</p>
<h2 lang="en" id="비용과-권한도-작업마다-나눈다">Separate Costs and Permissions by Task, Too</h2>
<p lang="en">Using a plugin does not make Codex free. It uses the local Codex authentication: a ChatGPT login draws on that plan's Codex allowance, while an API-key login follows separate OpenAI Platform API billing. This image probe ran in the ChatGPT-login environment recorded in the report. <a href="https://learn.chatgpt.com/docs/auth" rel="noopener noreferrer">Codex authentication</a>, <a href="https://learn.chatgpt.com/docs/pricing" rel="noopener noreferrer">Pricing and usage guide</a></p>
<p lang="en">Claude usage is also incurred when Claude Code prepares the work and reads the results. The comparison therefore needs to cover the whole task: output quality, number of calls, context size, waiting time, and time spent on human verification.</p>
<p lang="en">Set permissions according to the role. Start code review in read-only mode, and grant the necessary scope of write access to tasks that need to create files. Read-only restricts file-writing permissions for tools run by Codex; it does not mean that the material is not sent to an external model. Which files and context are transmitted needs to be checked separately.</p>
<h2 lang="en" id="이-블로그에-적용한다면">Applying This to This Blog</h2>
<p lang="en">The operating approach I have outlined at this stage is clear. For image generation, start with the one verified path and check the result file, dimensions, and content every time. For code verification, narrow the scope in read-only mode and request findings whose evidence can be checked with tests. To choose between two outputs, define evaluation criteria first and retain a final human decision.</p>
<p lang="en">The official plugin is a candidate when you want to use the supplied review and task-management commands. Direct CLI calls are worth considering when, as with this blog, you want to begin with narrow tasks that have defined inputs and result files. With either path, <strong>separating responsibility for creation, review, and adoption, and retaining execution results</strong> is the starting point for using the two agents together.</p>
<p lang="en" class="cc-scope-note"><em>This post was written from research material and an image-generation probe dated September 6, 2026, and checked against official documentation and plugin source on September 7. Image generation was measured once; this post does not claim completion of the official plugin installation, actual reviews, or A/B experiments. Local environment information in the original logs has been excluded from the public text, leaving only the relevant measurements.</em></p>
]]></content:encoded>
    </item>
    <item>
      <title><![CDATA[last30days: Reading the Past Month's Community Discussions with an AI Agent]]></title>
      <link>https://blog.funq.kr/en/blogs/last30days-skill-20260906-v01</link>
      <guid isPermaLink="true">https://blog.funq.kr/en/blogs/last30days-skill-20260906-v01</guid>
      <description><![CDATA[Explore how last30days conducts research, its strengths and limitations, and how an actual INTENT.md investigation connects a summary of recent trends to verification against original sources.]]></description>
      <category><![CDATA[AI]]></category><category><![CDATA[Agent Skills]]></category><category><![CDATA[last30days]]></category><category><![CDATA[리서치]]></category>
      <media:content url="https://api.funq.kr/blog/images/ac2ba5bd-19a4-449e-af7b-ca9e4d9205b2.png" medium="image" type="image/png"/>
      <pubDate>Sun, 06 Sep 2026 12:40:28 GMT</pubDate>
      <author>funqdev@gmail.com (fundev)</author>
      <content:encoded><![CDATA[<style>
.post-body .l30-flow { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: .65rem; max-width: 100%; margin: 1.75rem 0 2.5rem; padding: 0; list-style: none; }
.post-body .l30-flow li { min-width: 0; margin: 0; padding: 1rem .75rem; border: 1px solid #e5d9ee; border-radius: .75rem; background: #faf7fc; color: #302537; }
.post-body .l30-flow .l30-step { display: block; margin-bottom: .5rem; color: #7B00D3; font-size: .8em; font-weight: 700; line-height: 1.3; }
.post-body .l30-flow .l30-label { display: block; font-size: .9em; font-weight: 600; line-height: 1.6; word-break: keep-all; overflow-wrap: anywhere; }
.post-body .l30-scope-note { margin-top: 2.5rem; padding: 1rem 1.25rem; border-left: .2rem solid #7B00D3; border-radius: 0 .5rem .5rem 0; background: #faf7fc; color: #55485e; font-size: .9em; line-height: 1.8; }
.post-body .l30-scope-note em { font-style: normal; }
.dark .post-body .l30-flow li { border-color: #4d4834; background: #27251f; color: #f4efe1; }
.dark .post-body .l30-flow .l30-step { color: #ffdb4d; }
.dark .post-body .l30-scope-note { border-color: #ffdb4d; background: #27251f; color: #e2dbc6; }
@media (max-width: 480px) {
  .post-body .l30-flow { grid-template-columns: 1fr; gap: .5rem; }
  .post-body .l30-flow li { display: flex; align-items: baseline; gap: .85rem; padding: .75rem 1rem; }
  .post-body .l30-flow .l30-step { flex: 0 0 auto; margin: 0; }
}
</style>

<p lang="en">When I discover a new development tool, I start with the official documentation. It is relatively easy to find out what it does and how to get started. But before deciding to adopt it, I have a different question: what do people who have tried it recently like, and where are they getting stuck?</p>

<p lang="en">Answering that question means moving between several places: reading user experiences on Reddit, finding developers' posts on X, watching demonstrations on YouTube, and checking related issues on GitHub. Checking publication dates and grouping similar discussions takes time, just as finding the material does.</p>

<p lang="en"><strong>last30days is an open-source skill for delegating this kind of research into recent trends to an AI agent.</strong> This post looks at what the tool does, how its research proceeds, where it is useful, and what its limitations are.</p>

<h2 lang="en" id="last30days는-무엇인가">What Is last30days?</h2>

<p lang="en">last30days researches a given topic with a focus on the past 30 days and summarizes material found across multiple platforms. It supports Reddit, X, YouTube, Hacker News, GitHub, and others, though the sources actually queried depend on the environment and configuration. The <a href="https://github.com/mvanhorn/last30days-skill">official GitHub repository</a> describes the project and its support scope.</p>

<p lang="en">Here, a skill means a package of instructions for an AI agent and tools it can run. last30days includes both a <code>SKILL.md</code> describing the research procedure and Python code that performs the actual collection. The agent you use reads the instructions, runs the tools, and carries out the research. <a href="https://github.com/mvanhorn/last30days-skill/blob/main/CONCEPTS.md">The project's explanation of its concepts</a></p>

<p lang="en">The focus is on recent community reactions. While official documentation is needed to verify features and conditions of use, the material gathered with last30days helps reveal what people experience when using those features.</p>

<p lang="en">For example, when considering a new library, you might read the release notes to understand the changes, then look for recurring frustrations or use cases in related discussions. Reading both can surface evaluation questions that a feature list alone does not reveal.</p>

<h2 lang="en" id="어떻게-조사하는가">How Does It Conduct Research?</h2>

<p lang="en">The workflow becomes easier to understand when you separate <strong>the AI agent's judgments from the Python engine's collection and organization work</strong>. On a host with web search and reasoning capabilities, the agent creates a search plan, passes it to the engine, and reads the collected results to write the final explanation. Depending on the environment, it may also use the engine's internal planning and ranking path. <a href="https://github.com/mvanhorn/last30days-skill/blob/main/skills/last30days/SKILL.md">Skill execution instructions</a></p>

<ol lang="en" class="l30-flow" aria-label="The last30days research process">
  <li><span class="l30-step" aria-hidden="true">01</span><span class="l30-label">Search plan</span></li>
  <li><span class="l30-step" aria-hidden="true">02</span><span class="l30-label">Collect material</span></li>
  <li><span class="l30-step" aria-hidden="true">03</span><span class="l30-label">Assess relevance, recency, and engagement</span></li>
  <li><span class="l30-step" aria-hidden="true">04</span><span class="l30-label">Deduplicate and organize evidence</span></li>
  <li><span class="l30-step" aria-hidden="true">05</span><span class="l30-label">Summarize results</span></li>
</ol>

<h3 lang="en" id="1-질문을-검색-계획으로-바꾼다">1. Turn the Question into a Search Plan</h3>

<p lang="en">First, clarify what is being investigated. Products or people may share a name, so the agent checks relevant accounts, repositories, and communities to narrow the target. It then divides the question into several subqueries and decides which sources each search should query.</p>

<p lang="en">For example, when asking how a particular library is regarded, user reviews and migration experiences are different search perspectives. Splitting the question is more effective at finding the necessary evidence than repeatedly searching the product name alone.</p>

<p lang="en">This step involves the model's interpretation of the user's question. That is why it helps to provide the type of product and what you want to know, rather than just a topic name.</p>

<h3 lang="en" id="2-소스별로-자료를-수집한다">2. Collect Material from Each Source</h3>

<p lang="en">The Python engine queries multiple sources in parallel according to the plan. The material available differs by platform. It has to handle different forms of information, such as discussion posts and comments, video details and subtitles, and repository activity.</p>

<p lang="en">Installed CLIs, API keys, and authentication status affect the scope of the investigation. YouTube or X appearing on a support list and that source being successfully queried in the current run are separate facts. This is why the actual collection scope matters when reading the results.</p>

<h3 lang="en" id="3-관련도최신성참여도를-함께-살핀다">3. Consider Relevance, Recency, and Engagement Together</h3>

<p lang="en">Collected material is ranked using its relevance to the topic, how recent it is, and the level of response it has received. The information used for engagement also varies by platform: upvotes, comment counts, likes, and views are handled in a way suited to each source.</p>

<p lang="en">The initial ranking code in v3.23.0 that I checked locally assigns weights of 65% to relevance, 25% to recency, and 10% to engagement. These weights apply to <strong>the initial ranking within each search-result list</strong>. Results are subsequently merged and ranked again, so this should be distinguished from the scoring formula for the final report. <a href="https://github.com/mvanhorn/last30days-skill/blob/v3.23.0/skills/last30days/scripts/lib/signals.py">Initial ranking code</a></p>

<p lang="en">The point is that engagement is one of several criteria. Even a highly viewed item is unlikely to help if it is unrelated to the question.</p>

<h3 lang="en" id="4-중복을-합치고-근거를-정리한다">4. Merge Duplicates and Organize the Evidence</h3>

<p lang="en">Different queries may find the same post. The engine merges duplicates by URL and combines their ranks across the search results. The technique used here is RRF, or Reciprocal Rank Fusion. Think of it as combining the ranking information for material that appears near the top of multiple lists. <a href="https://github.com/mvanhorn/last30days-skill/blob/v3.23.0/skills/last30days/scripts/lib/fusion.py">Result-merging code</a></p>

<p lang="en">Depending on the research type, it also clusters related material. Grouping posts about the same event or controversy, for example, makes it easier for the agent to identify the issues than reading scattered links one by one. <a href="https://github.com/mvanhorn/last30days-skill/blob/v3.23.0/skills/last30days/scripts/lib/cluster.py">Clustering code</a></p>

<p lang="en">However, similar posts across multiple platforms do not necessarily mean a fact has been independently confirmed. They may all be quoting the same announcement or original source. How widely material has spread and how independent the supporting evidence is must be judged separately.</p>

<h3 lang="en" id="5-에이전트가-결과를-읽고-요약한다">5. The Agent Reads and Summarizes the Results</h3>

<p lang="en">Finally, the agent reads the collected and organized evidence, then explains the main points and recurring patterns. How much was gathered from each source and which sources were inaccessible also affect how the results should be interpreted.</p>

<p lang="en">In addition to general research, it supports workflows such as comparisons and topic discovery. You can change the request to specify two tools to compare, for example, or ask it to find worthwhile research topics in a field of interest. Supported modes and options are described in the <a href="https://github.com/mvanhorn/last30days-skill/blob/main/CONFIGURATION.md">configuration documentation</a>.</p>

<h2 lang="en" id="어떤-점이-유용한가">What Makes It Useful?</h2>

<p lang="en"><strong>First, it can reduce the repetitive work of starting an investigation.</strong> Searching multiple sites, checking dates, and deduplicating links recur with every new topic. Once that process is delegated, the user can spend time selecting and reading the most relevant original sources.</p>

<p lang="en"><strong>Second, it lets you examine different kinds of material together.</strong> You can see whether a frustration from a user review is also being discussed in GitHub issues, or whether users have actually responded to a feature introduced in a video. The value of gathering multiple sources lies less in their number than in the opportunity to compare perspectives.</p>

<p lang="en"><strong>Third, it helps generate the next questions to investigate.</strong> Recurring installation problems, expectations for a feature, or conflicting assessments can give the research a clearer direction. When writing a blog post, this material can also help identify issues readers care about and check them again against official documentation.</p>

<p lang="en">It fits tasks that need recent context: researching a tool's reputation before adoption, checking reactions after a product update, or exploring discussion points before writing. It is especially useful as a starting point when you know the topic but do not know which original sources to read first.</p>

<h2 lang="en" id="결과를-읽을-때-알아둘-한계">Limitations to Keep in Mind</h2>

<h3 lang="en" id="인기와-정확성은-다른-기준이다">Popularity and Accuracy Are Different Criteria</h3>

<p lang="en">Engagement shows which discussions attract attention. But many likes do not make a technical explanation accurate. Provocative claims and promotional content can also draw strong reactions.</p>

<p lang="en">Before citing a feature, performance figure, or support claim from a summary in your writing, check the official documentation or original source again. Community reactions are better read as material for understanding experiences and opinions.</p>

<h3 lang="en" id="조회하지-못한-소스는-조용한-소스가-아니다">An Inaccessible Source Is Not a Quiet Source</h3>

<p lang="en">Expired authentication, rate limits, or network problems can prevent collection. If the result is zero items under those conditions, you cannot conclude that no relevant discussion exists.</p>

<p lang="en">The project instructs the agent to distinguish a successful query with no results from authentication failures, timeouts, and partial collection. <code>doctor</code> diagnoses configuration and source status, while <code>doctor --postmortem</code> helps identify problems using the last run's results. <code>--preflight</code> shows configuration origins and planned access and storage operations before the actual investigation. <a href="https://github.com/mvanhorn/last30days-skill/blob/main/skills/last30days/SKILL.md">Diagnostics and execution instructions</a></p>

<p lang="en">Readers should check not only which claims a report makes, but also what material was available when those claims were formed.</p>

<h3 lang="en" id="주제와-언어에-따라-얻는-자료가-달라진다">The Material Varies by Topic and Language</h3>

<p lang="en">There may not be enough useful material if the supported platforms have few relevant posts or the product name is ambiguous. Even when you ask in Korean and receive a Korean summary, the collected originals may lean toward English-language platforms.</p>

<p lang="en">If reactions from Korean-language communities matter, those communities also need to be investigated directly. A result written in Korean should not be interpreted as representative of Korean users' opinions merely because of its language.</p>

<p lang="en">The same caution applies to the 30-day window. Longstanding frustrations and long-term assessments are hard to capture in a short research window alone. Check the period setting alongside the actual dates of the material, and compare older sources separately if you want to understand longer-term changes.</p>

<h3 lang="en" id="설정과-실행-비용이-따른다">Setup and Execution Have Costs</h3>

<p lang="en">The cost of installing the skill and the cost of conducting research should be considered separately. Some access paths are free, but particular sources may require separate API keys or a logged-in session, and may incur usage-based charges. The agent also consumes the host's allowance when creating search plans and summaries. <a href="https://github.com/mvanhorn/last30days-skill/blob/main/CONFIGURATION.md">Source-specific configuration guide</a></p>

<p lang="en">If you choose a path that uses a browser session, you also need to understand which account it accesses. Starting with only the sources you need, checking the actual collection scope, and expanding from there is easier to manage.</p>

<p lang="en">Waiting time grows as more sources, comments, and subtitles are examined. For a question about a single fact, going straight to official documentation may be faster. The tool becomes more useful for questions that require gathering and reading many people's recent reactions.</p>

<h2 lang="en" id="설치와-간단한-사용-예시">Installation and Simple Usage Examples</h2>

<p lang="en">For installation by host and runtime requirements, see the <a href="https://github.com/mvanhorn/last30days-skill#install">installation guide in the official README</a>. Source-specific API keys and output options are covered in <a href="https://github.com/mvanhorn/last30days-skill/blob/main/CONFIGURATION.md">CONFIGURATION.md</a>.</p>

<p lang="en">After installation, select the skill and provide both the research topic and its purpose. The following are examples of how to frame a question, not quotations from actual research results.</p>

<blockquote lang="en">
  <p>Use last30days to research recent migration experiences with Next.js App Router. Separate recurring problems from examples of solutions, and tell me which sources could not be checked.</p>
</blockquote>

<blockquote lang="en">
  <p>Use last30days to compare recent user reactions to two development tools. Focus on team collaboration and extensions.</p>
</blockquote>

<p lang="en">Naming the products precisely and narrowing the comparison criteria also clarifies which original sources you should verify once the results arrive.</p>

<h2 lang="en" id="실제-조사-사례-intent-md">An Actual Research Example: INTENT.md</h2>
<p lang="en">A supplementary research report on <code>INTENT.md</code> saved on September 6, 2026, illustrates how last30days can be used. It examined how the practice of documenting intent and constraints connects to actual code changes, verification, and agent handoffs. Recent trends were scoped to August 7 through September 6, with older material classified as background.</p>
<p lang="en">Checking the original sources found in the results revealed different ways of applying the idea.</p>
<ul lang="en">
<li><strong>X-GIS:</strong> A PR merged on September 5 added a new GitHub issue template for recording intent, constraints, rejected approaches, and completion checks, and linked it to the existing guidance. This captures intent when an issue is created instead of adding a separate document. <a href="https://github.com/X-GIS/X-GIS/pull/2562/files" rel="noopener noreferrer">PR and changed files</a></li>
<li><strong>callback:</strong> PR #81 replaced the collector based on requirements in <code>INTENT.md</code> and added verification material. It had not yet been merged when checked on September 6. The before-and-after collector performance figures need to be distinguished from measurements of the intent document's own effects. <a href="https://github.com/thedandano/callback/pull/81" rel="noopener noreferrer">Original PR</a></li>
<li><strong>Pathmode:</strong> On August 30, it announced a fix for handoff records not reaching the next agent; on September 3, it announced a review feature tied to PR commits and intent-document versions. This review is advisory and does not block merging. <a href="https://pathmode.io/changelog" rel="noopener noreferrer">Changelog</a></li>
</ul>
<p lang="en">These three cases are evidence of attempts to connect intent documents to development workflows. However, <strong>finding adoption examples and proving productivity gains are different things.</strong> Effects described by a PR author or product vendor must not be read as results from an independent comparative experiment.</p>
<p lang="en">In this process, the last30days results provided a starting point for finding original sources to read and issues to compare. To turn them into evidence usable in an article, the actual changed files, merge status, and scope of the feature still need to be checked in those sources. Collection counts likewise describe the volume of search results, not the number of independent adoption cases.</p>

<h2 lang="en" id="블로그-자료-조사에-활용한다면">Using It for Blog Research</h2>

<p lang="en">For blog research, a suitable workflow is to find discussion points in the summary, then verify key claims against the linked originals and official documentation. Used that way, it reduces the effort of starting research while allowing you to examine the article's evidence yourself.</p>

<p lang="en" class="l30-scope-note"><em>The feature descriptions are based on official documentation checked on September 6, 2026, and local v3.23.0 code. The case study was prepared by comparing an INTENT.md research report saved that day with its linked original sources. While supplementing this post, I did not rerun the research engine or measure productivity improvements. Supported sources and configuration methods may vary by version.</em></p>
]]></content:encoded>
    </item>
    <item>
      <title><![CDATA[An ADE, Not an IDE: Orca, a Command Center for Multiple Agents]]></title>
      <link>https://blog.funq.kr/en/blogs/orca-ade-20260729-v01</link>
      <guid isPermaLink="true">https://blog.funq.kr/en/blogs/orca-ade-20260729-v01</guid>
      <description><![CDATA[Stably AI's Orca is an Agent Development Environment (ADE) that runs coding agents such as Claude Code and Codex in parallel on git worktrees. A look at its core mechanics, costs, differences from Cursor, and competing tools.]]></description>
      <category><![CDATA[ai]]></category><category><![CDATA[agent]]></category><category><![CDATA[orca]]></category><category><![CDATA[ade]]></category><category><![CDATA[dev-tools]]></category>
      <media:content url="https://blog.funq.kr/blog-cover/orca-ade-20260729-v01.jpg" medium="image" type="image/jpeg"/>
      <pubDate>Mon, 03 Aug 2026 04:28:07 GMT</pubDate>
      <author>funqdev@gmail.com (fundev)</author>
      <content:encoded><![CDATA[<style>
.post-body .metric-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 12px; margin: 28px 0; }
.post-body .metric-card { border: 1px solid #7B00D3; border-radius: 12px; padding: 18px 14px; text-align: center; line-height: 1.4; }
.post-body .metric-card strong { display: block; font-size: 1.7em; font-weight: 800; color: #7B00D3; margin-bottom: 4px; }
.post-body .metric-card span { font-size: 0.85em; opacity: 0.75; }
.dark .post-body .metric-card { border-color: #ffdb4d; }
.dark .post-body .metric-card strong { color: #ffdb4d; }

.post-body .flow-box { border: 1px solid rgba(123, 0, 211, 0.35); border-radius: 14px; padding: 22px 20px; margin: 28px 0; background: rgba(123, 0, 211, 0.04); }
.dark .post-body .flow-box { border-color: rgba(255, 219, 77, 0.35); background: rgba(255, 219, 77, 0.06); }
.post-body .flow-title { font-size: 0.8em; letter-spacing: 0.08em; text-transform: uppercase; font-weight: 700; color: #7B00D3; margin-bottom: 16px; }
.dark .post-body .flow-title { color: #ffdb4d; }
.post-body .flow-steps { display: flex; flex-wrap: wrap; gap: 10px; align-items: stretch; margin: 0; padding: 0; list-style: none; }
.post-body .flow-steps li { flex: 1 1 130px; border: 1px solid rgba(123, 0, 211, 0.3); border-radius: 10px; padding: 12px 10px; text-align: center; font-size: 0.9em; line-height: 1.5; }
.dark .post-body .flow-steps li { border-color: rgba(255, 219, 77, 0.3); }
.post-body .flow-steps li b { display: block; font-size: 0.75em; opacity: 0.6; margin-bottom: 4px; font-weight: 700; }

.post-body .layer-stack { margin: 28px 0; }
.post-body .layer { border-radius: 12px; padding: 16px 18px; margin-bottom: 8px; border: 1px solid rgba(123, 0, 211, 0.3); }
.dark .post-body .layer { border-color: rgba(255, 219, 77, 0.3); }
.post-body .layer-top { background: rgba(123, 0, 211, 0.1); }
.dark .post-body .layer-top { background: rgba(255, 219, 77, 0.12); }
.post-body .layer-label { font-size: 0.75em; letter-spacing: 0.06em; text-transform: uppercase; font-weight: 700; opacity: 0.65; margin-bottom: 6px; }
.post-body .layer-name { font-weight: 700; font-size: 1.05em; margin-bottom: 4px; }
.post-body .layer-desc { font-size: 0.9em; opacity: 0.8; line-height: 1.6; }

.post-body .table-scroll { overflow-x: auto; margin: 24px 0; -webkit-overflow-scrolling: touch; }
.post-body .table-scroll table { width: 100%; min-width: 640px; border-collapse: collapse; font-size: 0.92em; }
.post-body .table-scroll th, .post-body .table-scroll td { border: 1px solid rgba(128, 128, 128, 0.3); padding: 10px 12px; text-align: left; vertical-align: top; line-height: 1.6; }
.post-body .table-scroll th { background: rgba(123, 0, 211, 0.08); font-weight: 700; }
.dark .post-body .table-scroll th { background: rgba(255, 219, 77, 0.1); }

.post-body .callout { border-left: 4px solid #7B00D3; border-radius: 0 10px 10px 0; padding: 14px 18px; margin: 24px 0; background: rgba(123, 0, 211, 0.05); font-size: 0.95em; line-height: 1.7; }
.dark .post-body .callout { border-left-color: #ffdb4d; background: rgba(255, 219, 77, 0.07); }
.post-body .callout strong:first-child { color: #7B00D3; }
.dark .post-body .callout strong:first-child { color: #ffdb4d; }

.post-body .code-block { background: #24292e; color: #e1e4e8; border-radius: 10px; padding: 16px 18px; overflow-x: auto; margin: 20px 0; font-size: 0.88em; line-height: 1.7; }
.post-body .code-block code { background: none; padding: 0; color: inherit; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }

.post-body .verdict-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); gap: 14px; margin: 28px 0; }
.post-body .verdict-card { border: 1px solid rgba(128, 128, 128, 0.35); border-radius: 12px; padding: 18px; line-height: 1.65; }
.post-body .verdict-card h4 { margin: 0 0 8px; font-size: 1em; font-weight: 700; }
.post-body .verdict-yes { border-color: #7B00D3; }
.dark .post-body .verdict-yes { border-color: #ffdb4d; }
.post-body .verdict-card p { margin: 0; font-size: 0.92em; opacity: 0.85; }
</style>

<p lang="en">In one terminal window, Claude Code is refactoring the payment module. In another, Codex is writing tests for the feature deployed yesterday. The agent in the third tab has been quiet for ten minutes. You cannot tell whether it has finished, gotten stuck, or is waiting for your approval.</p>

<p lang="en">It is a scene many developers in 2026 have encountered. AI coding agents have improved rapidly, but the way we manage <em>several of them</em> still amounts to manually switching between terminal tabs. <strong>Orca</strong>, which has recently seen explosive growth on GitHub, targets exactly this problem. It calls itself a new category: an <strong>ADE (Agent Development Environment)</strong>, rather than an IDE.</p>

<div lang="en" class="metric-grid">
  <div class="metric-card"><strong>32.1K</strong><span>GitHub stars (2026-07-29)</span></div>
  <div class="metric-card"><strong>MIT</strong><span>Completely free and open source</span></div>
  <div class="metric-card"><strong>25+</strong><span>Supported CLI agents</span></div>
  <div class="metric-card"><strong>2026.03</strong><span>First public release</span></div>
</div>

<h2 lang="en" id="ade란-무엇인가">An ADE Instead of an IDE? Start with the Terminology</h2>

<p lang="en">Orca was created by <strong>Stably AI</strong> (legal name: Lovecast Inc.). Founded by Jinjing Liang and Neil Parker in 2022, the San Francisco company went through Y Combinator's winter batch that same year. The team originally built QA automation products. Orca is effectively its next big bet.</p>

<p lang="en">The official site puts it this way: <em>“IDEs were built for you. ADEs are built for you and your agents.”</em> The opening page of the documentation makes an even more direct statement: <strong>“Not a model. Orca runs agents you already use.”</strong></p>

<p lang="en">That sentence is the key to understanding Orca. Cursor, Windsurf, and GitHub Copilot are <em>integrated products</em> that bundle model access into a subscription. Orca, by contrast, does not sell models. It does not even have its own inline autocomplete. Instead, it runs and manages several agents you <em>already subscribe to</em>, such as Claude Code, Codex, and Cursor CLI, at the same time, serving only as their <strong>command center</strong>.</p>

<div lang="en" class="callout">
<strong>Watch the name.</strong> “Orca” is a common name. The OrcaSlicer 3D-printing slicer, GNOME's Orca screen reader, Microsoft's Orca LLM, and MacStadium's macOS virtualization product Orka are all unrelated to the Orca in this post. This post covers the product at <code>github.com/stablyai/orca</code> and its official site, <code>onorca.dev</code>.
</div>

<h2 lang="en" id="worktree-fanout">The Core Mechanism: Spread Agents Across git Worktrees</h2>

<p lang="en">What happens when several agents are let loose in one repository at once? They edit the same files, overwrite each other's changes, and tangle the branches. That is the fundamental multi-agent problem.</p>

<p lang="en">Orca's solution is almost elegantly simple: <strong>give every task its own git worktree.</strong> A worktree is a native git feature that creates another working copy of the same repository on disk. Instead of repeatedly stashing changes and switching branches, you give each task a physically separate workspace from the start. As the official documentation puts it, <em>“This is what makes parallel agents safe—they never step on each other's files.”</em></p>

<p lang="en">Each worktree also gets its own agent terminal and browser tab. Orca's full workflow can therefore be summarized as follows.</p>

<div lang="en" class="flow-box">
  <div class="flow-title">All of Orca: The Six Steps Listed in the Official Documentation</div>
  <ul class="flow-steps">
    <li><b>1. add</b>Add a task</li>
    <li><b>2. worktree</b>Create an isolated copy</li>
    <li><b>3. agent</b>Attach an agent</li>
    <li><b>4. split</b>Watch in split panes</li>
    <li><b>5. diff</b>Compare results</li>
    <li><b>6. ship</b>Merge the winner</li>
  </ul>
</div>

<p lang="en">The most striking part is steps 4–6: <strong>fan-out and winner selection</strong>. Give the same prompt to 3–5 different agents simultaneously, let each solve the problem on its own branch, then compare the diffs side by side and merge only the best result. The official tutorial captures the experience well: <em>“Three branches. Three diffs. Same prompt.”</em></p>

<p lang="en">A review video posted in July showed an interesting side effect. The reviewer said they gave five agents different tasks, but told <em>only one</em> about a bug. Three ended up finding it on their own. Running multiple perspectives in parallel lets one catch what another misses—an effect that may sound obvious, but is hard to appreciate until you actually try it.</p>

<h2 lang="en" id="지원-에이전트와-비용">Supported Agents and the Real Cost Structure</h2>

<p lang="en">Orca's principle is clear: <strong>“If it runs in a terminal, it runs in Orca.”</strong> The official support list includes 25–30 or more agents, including Claude Code, Codex, Cursor CLI, Gemini, GitHub Copilot CLI, OpenCode, Grok, and Pi. You can also add custom CLI agents that are not listed.</p>

<p lang="en">Authentication is BYO—bring your own. Orca itself has no login and no per-seat fee. The app passes your existing subscription credentials to each agent CLI, which calls its provider's API directly from your computer. Orca takes no margin in between.</p>

<p lang="en">That produces the following pricing comparison.</p>

<div lang="en" class="table-scroll">
<table>
  <thead>
    <tr><th>Item</th><th>Orca</th><th>Cursor</th><th>Windsurf</th></tr>
  </thead>
  <tbody>
    <tr><td>Cost of the product itself</td><td>Free (MIT open source)</td><td>From $20/month</td><td>From $15/month</td></tr>
    <tr><td>Model access</td><td>None (connect your own subscriptions)</td><td>Bundled with the subscription</td><td>Bundled with the subscription</td></tr>
    <tr><td>Where your money actually goes</td><td>My agent subscriptions (Claude Max, etc.)</td><td>Cursor subscription</td><td>Windsurf subscription</td></tr>
    <tr><td>Source availability</td><td>Fully open</td><td>Closed source</td><td>Closed source</td></tr>
  </tbody>
</table>
</div>

<div lang="en" class="callout">
<strong>The “free” trap.</strong> A free app does not make the total cost low. It can do the opposite. Run 5 agents in parallel, and your subscription tokens burn 5 times as fast. A Reddit thread discussed when a parallel fleet is <em>“just burning 5 times the tokens”</em> and advised setting guardrails first. An installation-guide video likewise warned that <em>“you need to actively monitor cumulative API token spending across multiple providers.”</em> Orca's features showing per-account usage and rate-limit reset times serve the same purpose.
</div>

<h2 lang="en" id="cursor와의-차이">How It Differs from Cursor and Windsurf: A Layer Above Them</h2>

<p lang="en">You may see headlines calling Orca a “free Cursor killer.” That may attract traffic, but it does not accurately describe the product's structure. Orca is closer to a layer <em>above</em> Cursor than a <em>replacement</em> for it. In fact, Cursor CLI is one of Orca's supported agents.</p>

<div lang="en" class="layer-stack">
  <div class="layer layer-top">
    <div class="layer-label">Orchestration layer</div>
    <div class="layer-name">Orca (ADE)</div>
    <div class="layer-desc">Isolate agents in worktrees, run them in parallel, compare diffs, and merge the winner · No model</div>
  </div>
  <div class="layer">
    <div class="layer-label">Agent layer</div>
    <div class="layer-name">Claude Code · Codex · Cursor CLI · Gemini · OpenCode …</div>
    <div class="layer-desc">The agents that actually read and write code, each with its own subscription and model</div>
  </div>
  <div class="layer">
    <div class="layer-label">Model layer</div>
    <div class="layer-name">Anthropic · OpenAI · Google · xAI …</div>
    <div class="layer-desc">Where token costs are actually incurred</div>
  </div>
</div>

<p lang="en">Independent reviewers' conclusions support this picture. Their point is: <strong>“Orca wins when you want to use two or more agents. If you use only one agent and like its UI, stay there.”</strong></p>

<p lang="en">In other words, if Cursor's inline autocomplete or chat panel is central to your work, Orca is not the answer. Even the official documentation explicitly says it is not a fit if all you need is simple autocomplete assistance. Orca targets <em>“engineers running 10–100 coding agents at the same time.”</em></p>

<h2 lang="en" id="오케스트레이터-비교">Competitors in the Same Space: Conductor, cmux, and Herdr</h2>

<p lang="en">Interestingly, the “agent orchestrator” category itself has become crowded very quickly in 2026. In July alone, reviews comparing orchestrators appeared in such numbers that they seemed to form a genre of their own.</p>

<div lang="en" class="table-scroll">
<table>
  <thead>
    <tr><th>Item</th><th>Orca</th><th>Conductor</th><th>cmux</th></tr>
  </thead>
  <tbody>
    <tr><td>Creator</td><td>Stably AI (YC W22)</td><td>Melty Labs</td><td>Open-source community</td></tr>
    <tr><td>Platforms</td><td>macOS · Windows · Linux + Mobile · VPS</td><td>macOS only</td><td>Terminal (cross-platform)</td></tr>
    <tr><td>License</td><td>MIT open source</td><td>Closed source</td><td>Open source</td></tr>
    <tr><td>UI style</td><td>Editor-style (file editor, diff viewer, PR review, and unlimited terminal splits)</td><td>Focused on workspace snapshots, rollback, and merge reviews</td><td>Terminal-first, lightweight, and keyboard-focused</td></tr>
    <tr><td>Supported agents</td><td>25 or more</td><td>Claude Code · Codex</td><td>Multiple agents supported</td></tr>
    <tr><td>Choose it when</td><td>You want open source, Linux/Windows support, and a broad agent selection</td><td>Checkpoints and merge reviews are central to your workflow</td><td>You want a lightweight, keyboard-only experience without a GUI</td></tr>
  </tbody>
</table>
</div>

<p lang="en">Emerging tools such as Herdr have joined the field too. A “Herdr vs Orca” comparison video posted in mid-July drew nearly 5,000 views. Its central question captures the category well: <em>“How do you give developers visibility and control over a fleet of agents without turning them into full-time managers?”</em></p>

<p lang="en">Three features are often cited as Orca's differentiators. First, a <strong>mobile companion</strong>: receive a notification when an agent finishes and send follow-up instructions from your phone. A Japanese-language podcast singled out the fact that <em>“the agents keep running even after you close your computer”</em> as the most impressive part. Second, <strong>Design Mode</strong>: click a UI element in an actual Chromium window, and its HTML, CSS, and a cropped screenshot go straight into the agent's prompt. Third, <strong>remote worktrees over SSH</strong> let you run work on a remote machine.</p>

<h2 lang="en" id="설치와-첫-세션">Install It and Run Three Agents in Five Minutes</h2>

<p lang="en">Installation is straightforward. However, <strong>there is one trap for Mac users.</strong></p>

<pre class="code-block"><code><span style="color:#6a737d"># macOS - 반드시 tap 경로를 명시할 것</span>
<span style="color:#f97583">brew</span> install --cask stablyai/orca/orca

<span style="color:#6a737d"># Arch Linux (AUR)</span>
<span style="color:#f97583">yay</span> -S stably-orca-bin</code></pre>

<div lang="en" class="callout">
<strong>The brew trap.</strong> A short command without the tap, such as <code>brew install --cask orca</code>, installs the <code>orca</code> cask from homebrew-cask: <strong>the entirely unrelated and discontinued Plotly Orca</strong>. This has also been reported as a repository issue. Use the full <code>stablyai/orca/orca</code> path, or download the macOS (Apple Silicon/Intel), Windows (.exe), or Linux (AppImage) build directly from the official site.
</div>

<p lang="en">After installation, the first guide in the official documentation is the “first three-agent session” tutorial. It says going from an empty app to three agents running in parallel takes less than five minutes. The flow is as follows.</p>

<ol lang="en">
  <li>Create a worktree. A terminal opens in the new worktree. Choose Claude Code, Codex, Cursor CLI, or another agent in the selector, and Orca launches the CLI in the correct working directory and passes it your subscription credentials.</li>
  <li>Repeat the process twice to create three worktrees in total.</li>
  <li>Paste <strong>the same prompt</strong> into all three agents.</li>
  <li>Drag the worktree tabs to the edges of the screen to split the view and watch all three agents at once.</li>
  <li>When they finish, open each worktree's diff view and compare. You can also add inline comments with Annotate AI Diff and send them back to the agent whose result is closest to what you want.</li>
  <li>Commit and push directly from Orca, then clean up the other two worktrees and their branches with one click.</li>
</ol>

<h2 lang="en" id="커뮤니티-반응과-한계">What the Community Says, and What to Watch Out For</h2>

<p lang="en">The speed of growth is itself the project's biggest news. A review video from early July said it had “passed 11,100 stars” at recording time. By late July, the repository's live count was <strong>32,135 stars</strong>—nearly three times as many in a month. Releases arrive almost daily, and open issues have passed 2,500. Reviewers praise the team for triaging feature requests posted on Discord within a day.</p>

<p lang="en">First impressions are generally positive too. Review quotes such as “I didn't expect it to be this good” and “Everything gets so much easier” stand out, while video comments include reactions like <em>“That explanation was really concise. I'm going to try Orca.”</em></p>

<p lang="en">For balance, however, most signals currently available come from <strong>YouTube reviewers, personal blogs, and small Reddit threads</strong>. There is still little accumulated critical scrutiny of the kind found in major Hacker News discussions or large-scale usage reports. As always with a young tool, early reviewers' enthusiasm needs to be taken with a grain of salt.</p>

<p lang="en">Three limitations have been raised repeatedly so far.</p>

<ul lang="en">
  <li><strong>It is overkill for single-agent users.</strong> If you use only one agent, Orca offers little benefit.</li>
  <li><strong>Costs scale with the number of parallel agents.</strong> That applies to token consumption and disk usage per worktree alike.</li>
  <li><strong>Without agent subscriptions, it is an empty shell.</strong> Orca itself cannot generate anything.</li>
</ul>

<p lang="en">One amusing footnote: a Reddit commenter noted the name's similarity to MacStadium's macOS virtualization product <em>Orka</em> and quipped, <em>“I don't think that name is going to last.”</em> Naming collisions are indeed a practical problem for search traffic in this category.</p>

<h2 lang="en" id="결론">Conclusion: Is It the Right Tool for You?</h2>

<p lang="en">Orca in one sentence: <strong>a free, open-source command center that sells no model, but lets you run the agents you already have simultaneously in isolated worktrees.</strong></p>

<div lang="en" class="verdict-grid">
  <div class="verdict-card verdict-yes">
    <h4>A good fit for</h4>
    <p>People who want to run two or more agents in parallel, compare several models on the same problem, avoid vendor lock-in while using Linux or Windows, or monitor agents while on the move.</p>
  </div>
  <div class="verdict-card">
    <h4>Still too early for</h4>
    <p>People who use one agent and like their current UI, rely mainly on inline autocomplete, have a tight token budget, or use only mature tools with proven stability.</p>
  </div>
</div>

<p lang="en">If the competitive focus in AI coding tools is shifting from “Which model is smarter?” to “How do we manage multiple agents?”, Orca is one of the clearest examples. If it interests you, I recommend spending five minutes on the official three-agent tutorial. That is enough to get a feel for it.</p>

<div lang="en" class="callout">
<strong>Note.</strong> The figures in this post are as of July 29, 2026. Orca is a very young project with daily releases, so star counts, versions, and the supported-agent list change quickly. Check <code>onorca.dev</code> and <code>github.com/stablyai/orca</code> for the latest information.
</div>
]]></content:encoded>
    </item>
    <item>
      <title><![CDATA[Google's OKF Knowledge Standard for AI Agents: Checking the Original Specification]]></title>
      <link>https://blog.funq.kr/en/blogs/okf-open-knowledge-format-20260729-v01</link>
      <guid isPermaLink="true">https://blog.funq.kr/en/blogs/okf-open-knowledge-format-20260729-v01</guid>
      <description><![CDATA[A source-checked introduction to Google's Open Knowledge Format (OKF): the trust and provenance fields in v0.2, its relationship to AGENTS.md, and the gap between marketing language and the actual specification.]]></description>
      <category><![CDATA[ai]]></category><category><![CDATA[agent]]></category><category><![CDATA[okf]]></category><category><![CDATA[knowledge-management]]></category>
      <media:content url="https://blog.funq.kr/blog-cover/okf-open-knowledge-format-20260729-v01.jpg" medium="image" type="image/jpeg"/>
      <pubDate>Mon, 03 Aug 2026 04:26:23 GMT</pubDate>
      <author>funqdev@gmail.com (fundev)</author>
      <content:encoded><![CDATA[<style>
.post-body .okf-callout { border-left: 4px solid #7B00D3; border-radius: 0 12px 12px 0; padding: 14px 18px; margin: 24px 0; background: rgba(123, 0, 211, 0.05); }
.post-body .okf-callout p { margin: 6px 0; }
.post-body .okf-callout .callout-label { display: inline-block; font-weight: 700; font-size: 0.85em; letter-spacing: 0.02em; color: #7B00D3; }
.post-body .okf-callout.ok { border-left-color: #14803c; background: rgba(20, 128, 60, 0.06); }
.post-body .okf-callout.ok .callout-label { color: #14803c; }
.post-body .okf-callout.warn { border-left-color: #b45309; background: rgba(180, 83, 9, 0.07); }
.post-body .okf-callout.warn .callout-label { color: #b45309; }
.post-body .okf-callout.no { border-left-color: #b91c1c; background: rgba(185, 28, 28, 0.06); }
.post-body .okf-callout.no .callout-label { color: #b91c1c; }
.dark .post-body .okf-callout { border-left-color: #ffdb4d; background: rgba(255, 219, 77, 0.07); }
.dark .post-body .okf-callout .callout-label { color: #ffdb4d; }
.dark .post-body .okf-callout.ok { border-left-color: #4ade80; background: rgba(74, 222, 128, 0.08); }
.dark .post-body .okf-callout.ok .callout-label { color: #4ade80; }
.dark .post-body .okf-callout.warn { border-left-color: #fbbf24; background: rgba(251, 191, 36, 0.08); }
.dark .post-body .okf-callout.warn .callout-label { color: #fbbf24; }
.dark .post-body .okf-callout.no { border-left-color: #f87171; background: rgba(248, 113, 113, 0.08); }
.dark .post-body .okf-callout.no .callout-label { color: #f87171; }
.post-body .metric-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 12px; margin: 24px 0; }
.post-body .metric-card { border: 1px solid #7B00D3; border-radius: 12px; padding: 16px 12px; text-align: center; font-size: 0.9em; }
.post-body .metric-card strong { display: block; font-size: 1.7em; color: #7B00D3; margin-bottom: 4px; }
.dark .post-body .metric-card { border-color: #ffdb4d; }
.dark .post-body .metric-card strong { color: #ffdb4d; }
.post-body .tier-ladder { display: flex; gap: 10px; flex-wrap: wrap; margin: 24px 0; align-items: stretch; }
.post-body .tier-step { flex: 1 1 160px; border: 1px solid rgba(27, 27, 27, 0.25); border-radius: 12px; padding: 14px; text-align: center; font-size: 0.88em; }
.post-body .tier-step strong { display: block; margin-bottom: 6px; font-size: 1.05em; }
.post-body .tier-step .tier-cond { display: block; font-size: 0.85em; opacity: 0.75; }
.post-body .tier-step.tier-top { border: 2px solid #7B00D3; }
.post-body .tier-step.tier-top strong { color: #7B00D3; }
.dark .post-body .tier-step { border-color: rgba(255, 255, 255, 0.3); }
.dark .post-body .tier-step.tier-top { border-color: #ffdb4d; }
.dark .post-body .tier-step.tier-top strong { color: #ffdb4d; }
.post-body .code-block { background: #24292e; color: #e1e4e8; padding: 16px 20px; border-radius: 12px; overflow-x: auto; font-size: 0.88em; line-height: 1.65; margin: 20px 0; }
.post-body .code-block code { background: none; padding: 0; color: inherit; }
.post-body .table-scroll { overflow-x: auto; margin: 24px 0; }
.post-body .src-note { font-size: 0.85em; border-top: 1px solid rgba(27, 27, 27, 0.2); margin-top: 40px; padding-top: 16px; opacity: 0.85; }
.dark .post-body .src-note { border-top-color: rgba(255, 255, 255, 0.25); }
</style>

<p lang="en">On June 12, Google quietly released a specification called <strong>Open Knowledge Format (OKF)</strong>, introducing it as a “vendor-neutral standard for delivering organizational knowledge to AI agents.” Six weeks later, on July 25, v0.2 arrived, directly addressing the question: “Can an agent trust this knowledge?”</p>

<p lang="en">A new standard usually attracts plenty of introductions, but marketing language and the actual specification often differ subtly. OKF was no exception. So this post takes a slightly different approach: <strong>cross-checking every claim against the original specification (SPEC.md) and official announcements, and labeling what is verified and what is not</strong>. An article introducing a standard about the provenance and trustworthiness of knowledge should probably be written that way too.</p>

<div lang="en" class="okf-callout">
  <span class="callout-label">How This Post Was Verified</span>
  <p>As of July 29, 2026, I cross-checked SPEC.md in the specification repository (<a href="https://github.com/GoogleCloudPlatform/knowledge-catalog" target="_blank" rel="noopener">GoogleCloudPlatform/knowledge-catalog</a>), two official Google Cloud blog posts, and related community sources. The <strong>✅ Verified / ⚠️ Caution / ❌ Unverified</strong> labels in each section reflect that process. OKF is still a draft (v0.2), so individual fields may change.</p>
</div>

<h2 lang="en" id="okf는-무엇인가">What Is OKF?</h2>

<p lang="en">OKF is an open specification that represents organizational knowledge—metric definitions, table schemas, APIs, and runbooks—as <strong>a directory of Markdown files with YAML frontmatter</strong>. Its structural rules can be summarized in three lines.</p>

<ul lang="en">
  <li><strong>One file = one concept</strong></li>
  <li><strong>The file path, minus .md, is the concept's ID</strong></li>
  <li><strong>Relationships between concepts are ordinary Markdown links</strong>—as links accumulate, the whole directory becomes a relationship graph</li>
</ul>

<p lang="en">The most striking feature is the specification's minimalism. In numbers:</p>

<div lang="en" class="metric-grid">
  <div class="metric-card"><strong>1</strong>Required frontmatter field (type)</div>
  <div class="metric-card"><strong>0</strong>Schema registries or central authorities</div>
  <div class="metric-card"><strong>0</strong>Required tools</div>
  <div class="metric-card"><strong>6 weeks</strong>From v0.1 to v0.2</div>
</div>

<p lang="en">In other words, the file below is a fully conformant OKF concept on its own.</p>

<pre class="code-block"><code><span style="color:#6a737d"># runbooks/deploy-rollback.md</span>
<span style="color:#e1e4e8">---</span>
<span style="color:#79b8ff">type</span><span style="color:#e1e4e8">: </span><span style="color:#9ecbff">Runbook</span>
<span style="color:#e1e4e8">---</span>
<span style="color:#e1e4e8">배포 롤백 절차는 ...</span></code></pre>

<div lang="en" class="okf-callout ok">
  <span class="callout-label">✅ Verified</span>
  <p>“type is the only required field” is exactly what the specification says. SPEC.md states: <em>“There is no schema registry, no central authority, and no required tooling.”</em> Section 11 likewise has just two conformance requirements: (1) parseable YAML frontmatter and (2) a nonempty <code>type</code>. <code>title</code>, <code>description</code>, and <code>tags</code> are merely recommended, and even type values are not registered centrally.</p>
</div>

<h2 lang="en" id="구글도-새롭지-않다는-걸-안다">Google Knows It Is “Nothing New”</h2>

<p lang="en">“What is new about adding frontmatter to Markdown? How is this different from Obsidian?” These were among the most common community reactions after OKF's announcement. Interestingly, <strong>Google itself raises this point in the launch blog post</strong>.</p>

<blockquote lang="en">
  <p>“Similar knowledge-as-Wiki pattern keeps reappearing under different names: Obsidian vaults wired to coding agents, the AGENTS.md / CLAUDE.md family of convention files ... each instance is bespoke. <strong>OKF formalizes the small set of conventions needed to make these patterns interoperable.</strong>”<br />— Google Cloud launch blog post</p>
</blockquote>

<p lang="en">The launch post links directly to Karpathy's LLM Wiki idea and defines OKF as a formalization of the “LLM-wiki” pattern. Its value proposition was therefore <strong>standardized interoperability, rather than technical novelty</strong>, from the outset: put common conventions around the bespoke “Markdown knowledge folders” built for each agent and company, making them portable.</p>

<div lang="en" class="okf-callout ok">
  <span class="callout-label">✅ Verified</span>
  <p>The framing of OKF as “Google's answer to Karpathy's LLM wiki” came from <strong>Google's own launch messaging</strong>—the blog and official tweets—rather than the community. The real debate is therefore more accurately understood as <strong>“Do we need a common standard backed by Google?”</strong> than “Is the format new?”</p>
</div>

<h2 lang="en" id="v02-이-지식을-믿어도-되는가">v0.2: “Can This Knowledge Be Trusted?”</h2>

<p lang="en">If v0.1 defined how to <em>represent</em> knowledge, v0.2 is about <strong>trust</strong>. The Google blog organizes what an agent should assess when reading a concept file into five questions: What was it derived from (provenance)? How much can it be trusted (trust)? Is it still valid (freshness)? Is it the latest version (lifecycle)? Was it computed as promised (attestation)?</p>

<div lang="en" class="okf-callout warn">
  <span class="callout-label">⚠️ Caution: The Gap Between Marketing and the Specification</span>
  <p>Many introductions repeat these “five questions” as though five signal families were added to the specification, but <strong>the actual specification has four families</strong>: trust, lifecycle, provenance, and computation. Freshness is a single lifecycle field, <code>stale_after</code>, rather than its own family. Attestation is implemented as a separate concept type (<code>Attested Computation</code>, §10), rather than a frontmatter family. The blog's “five questions” are a consumer-facing framing; the specification's “four families” are the actual data structure. Keeping that distinction in mind makes the specification much clearer.</p>
</div>

<h3 lang="en" id="실제-필드-구조">The Actual Field Structure</h3>

<p lang="en">Reconstructing v0.2 frontmatter from the specification's examples gives the following.</p>

<pre class="code-block"><code><span style="color:#e1e4e8">---</span>
<span style="color:#79b8ff">type</span><span style="color:#e1e4e8">: </span><span style="color:#9ecbff">Metric</span>
<span style="color:#79b8ff">title</span><span style="color:#e1e4e8">: </span><span style="color:#9ecbff">Quarterly Revenue</span>
<span style="color:#79b8ff">generated</span><span style="color:#e1e4e8">:            </span><span style="color:#6a737d"># 누가·언제 만들었나</span>
<span style="color:#79b8ff">  by</span><span style="color:#e1e4e8">: </span><span style="color:#9ecbff">reference_agent/gemini-2.5-pro</span>
<span style="color:#79b8ff">  at</span><span style="color:#e1e4e8">: </span><span style="color:#9ecbff">2026-06-20T22:53:05Z</span>
<span style="color:#79b8ff">verified</span><span style="color:#e1e4e8">:             </span><span style="color:#6a737d"># 누가·언제 검증했나 (작성자와 별개)</span>
<span style="color:#e1e4e8">  - </span><span style="color:#79b8ff">by</span><span style="color:#e1e4e8">: </span><span style="color:#9ecbff">human:ahormati</span>
<span style="color:#79b8ff">    at</span><span style="color:#e1e4e8">: </span><span style="color:#9ecbff">2026-06-25T09:00:00Z</span>
<span style="color:#79b8ff">sources</span><span style="color:#e1e4e8">:              </span><span style="color:#6a737d"># 출처 — 엔트리당 필수는 resource 하나</span>
<span style="color:#e1e4e8">  - </span><span style="color:#79b8ff">id</span><span style="color:#e1e4e8">: </span><span style="color:#9ecbff">rev-policy</span>
<span style="color:#79b8ff">    resource</span><span style="color:#e1e4e8">: </span><span style="color:#9ecbff">https://wiki.acme/finance/revenue-recognition</span>
<span style="color:#79b8ff">status</span><span style="color:#e1e4e8">: </span><span style="color:#9ecbff">stable</span><span style="color:#e1e4e8">        </span><span style="color:#6a737d"># draft | stable | deprecated (생략 시 stable)</span>
<span style="color:#79b8ff">stale_after</span><span style="color:#e1e4e8">: </span><span style="color:#9ecbff">2026-12-31</span><span style="color:#e1e4e8">  </span><span style="color:#6a737d"># 이 날짜가 지나면 stale</span>
<span style="color:#e1e4e8">---</span></code></pre>

<p lang="en">Two details in the design stand out:</p>

<ul lang="en">
  <li><strong>The deliberate separation of <code>generated</code> and <code>verified</code></strong>—in the official blog's words, because “who wrote it and who checked it do not have to be the same actor.” The field structure directly reflects a pipeline in which AI creates the content and a human verifies it.</li>
  <li><strong>Actor notation (§7)</strong>—agents and tools use producer/version, such as <code>reference_agent/gemini-2.5-pro</code>, while humans use a value such as <code>human:ahormati</code> with the <code>human:</code> prefix. That prefix is the key to deriving the trust tiers below.</li>
</ul>

<h3 lang="en" id="신뢰-3단계">Three Levels of Trust: Machine-Readable Trust Tiers</h3>

<p lang="en">A consumer, usually another agent, derives one of three trust tiers for a concept from the <code>verified</code> field alone.</p>

<div lang="en" class="tier-ladder">
  <div class="tier-step"><strong>unverified</strong><span class="tier-cond">No verified key</span></div>
  <div class="tier-step"><strong>machine-confirmed</strong><span class="tier-cond">Verified only by machine actors</span></div>
  <div class="tier-step tier-top"><strong>human-reviewed</strong><span class="tier-cond">Verification by a human: actor exists</span></div>
</div>

<div lang="en" class="okf-callout ok">
  <span class="callout-label">✅ Verified</span>
  <p>These are the exact rules in SPEC.md §5.3. Since the determination depends on the presence of the <code>human:</code> prefix in the actor string, it is fully machine-readable from a parser's perspective. The repository README summary appears to imply that generated is considered too, but the normative text—the specification—uses <code>verified</code> alone.</p>
</div>

<h3 lang="en" id="하위-호환">Backward Compatibility: Exactly Two Renames</h3>

<p lang="en">v0.2 is an additive minor bump with exactly two intentional renames: v0.1's <code>timestamp</code> → <code>generated.at</code>, and the body’s <code># Citations</code> list → <code>sources</code> frontmatter. In the specification's words, <em>“A v0.1 bundle drops in unchanged.”</em> Existing bundles work without modification.</p>

<h2 lang="en" id="agentsmd-claudemd와의-관계">How Does It Relate to AGENTS.md and CLAUDE.md?</h2>

<p lang="en">If you use Claude Code or another coding agent, you are probably already supplying context through convention files such as CLAUDE.md and AGENTS.md. Does OKF replace those? A community FAQ offers a clear distinction.</p>

<blockquote lang="en">
  <p>"AGENTS.md tells an agent <strong>how to behave</strong>. OKF tells an agent <strong>what exists</strong> in the world. They're friends, not competitors."</p>
</blockquote>

<div lang="en" class="table-scroll">
  <table>
    <thead>
      <tr><th></th><th>AGENTS.md / CLAUDE.md</th><th>OKF bundle</th></tr>
    </thead>
    <tbody>
      <tr><td>Role</td><td>“Behave this way” (instructions)</td><td>“This is what we know” (knowledge)</td></tr>
      <tr><td>Scope</td><td>One repository or agent</td><td>Organization-wide knowledge, portable across repositories and tools</td></tr>
      <tr><td>Composition pattern</td><td colspan="2">AGENTS.md instructs: “For domain context, refer to the OKF bundle in /knowledge”</td></tr>
    </tbody>
  </table>
</div>

<div lang="en" class="okf-callout warn">
  <span class="callout-label">⚠️ Source Caution</span>
  <p>The okf.md FAQ behind this comparison is <strong>an independently run community site, not an official Google source</strong>. It is also still based on v0.1 and does not reflect the trust fields. The framing is useful, but must not be cited as an “official position.” Google's launch post identifying AGENTS.md and CLAUDE.md as “instances of the same pattern,” discussed above, is an official primary source.</p>
</div>

<h2 lang="en" id="벡터db를-버려라">“Throw Away Your Vector DB”? The Specification Does Not Say That</h2>

<p lang="en">A community narrative has grown around OKF: “Abandon vector DBs and RAG, and return to a Markdown link graph.” The claim is that following explicit links instead of approximate similarity search removes both embedding costs and hallucination risk.</p>

<div lang="en" class="okf-callout no">
  <span class="callout-label">❌ Unverified: A Community Narrative</span>
  <p>In this verification, <strong>I found no statement in either the official specification or the Google blog posts positioning OKF against vector DBs or RAG.</strong> The “OKF vs RAG” framing is entirely a community interpretation. Even on a practical level, stable organizational facts such as metric definitions and runbooks suit direct reading, while exploratory queries over large corpora still need search, so the two are not mutually exclusive. Treat “the end of vector DBs” headlines with distance.</p>
</div>

<h2 lang="en" id="생태계와-반응">The Ecosystem and Its Reception</h2>

<p lang="en">For an ecosystem only six weeks past launch, things are moving quickly. The specification repository has gathered about 8K stars, and both the launch and v0.2 announcement have HN threads with active debate. One notable third-party tool is the Ruby ecosystem's <a href="https://github.com/serradura/okf-gem" target="_blank" rel="noopener">serradura/okf-gem</a>. It packs authoring through an agent skill, specification validation, linting with CI exit codes, full-text search, knowledge-graph visualization, and even a Claude Code plugin into one gem, running 100% locally.</p>

<div lang="en" class="okf-callout warn">
  <span class="callout-label">⚠️ Check the Community Mood Yourself</span>
  <p>Many secondary sources summarize the reception as “mostly cynical,” but this verification did not establish individual quotations or the distribution of sentiment. The verifiable point is that the debate centers on the need for a Google-led common standard rather than the format's novelty. I recommend reading the original threads directly: <a href="https://news.ycombinator.com/item?id=48517735" target="_blank" rel="noopener">launch</a> and <a href="https://news.ycombinator.com/item?id=48643541" target="_blank" rel="noopener">v0.2</a>.</p>
</div>

<h2 lang="en" id="정리-지금-써볼-만한가">Wrapping Up: Is It Worth Trying Now?</h2>

<p lang="en">Here is my assessment.</p>

<ul lang="en">
  <li><strong>The adoption cost is effectively zero.</strong> If you already write documentation in Markdown, you can start by adding a single <code>type</code> line to the frontmatter, with everything versioned in git. Even if the specification is abandoned, what remains is a well-organized Markdown folder. There is nothing to lose.</li>
  <li><strong>v0.2's real contribution is standardizing trust metadata.</strong> Making the pipeline “AI generates → human verifies → validity period specified” machine-readable through three fields (<code>generated</code>/<code>verified</code>/<code>stale_after</code>) is a practical design for an era in which agents consume one another's outputs.</li>
  <li><strong>But it is still a draft.</strong> Fields may change further, just as renames occurred between v0.1 and v0.2. At this stage, applying it experimentally to one knowledge folder read by an agent seems more appropriate than enforcing it as an organizational standard.</li>
</ul>

<p lang="en">Personally, since I already use Claude Code's memory directory and CLAUDE.md, I plan to try the division “CLAUDE.md for behavior, OKF bundles for knowledge” in a side project. I will share a follow-up once results accumulate.</p>

<div lang="en" class="src-note">
  <p><strong>Verification note</strong>—Fact-checking for this post is as of 2026-07-29. It reflects a deep-research pipeline that extracted claims from 21 sources and cross-checked the top 25 through a three-vote process, confirming 22 and rejecting 3. Primary sources: <a href="https://github.com/GoogleCloudPlatform/knowledge-catalog/blob/main/okf/SPEC.md" target="_blank" rel="noopener">OKF SPEC.md</a> · <a href="https://cloud.google.com/blog/products/data-analytics/how-the-open-knowledge-format-can-improve-data-sharing" target="_blank" rel="noopener">Launch blog post</a> · <a href="https://cloud.google.com/blog/products/data-analytics/okf-v0-2-adds-trust-signals" target="_blank" rel="noopener">v0.2 blog post</a>. Since OKF remains a draft, check the original specification again before adopting it.</p>
</div>
]]></content:encoded>
    </item>
    <item>
      <title><![CDATA[Building a Jira-Driven Development Harness: From One Issue Key to Verified Code]]></title>
      <link>https://blog.funq.kr/en/blogs/jira-workflow-automation-20260401-v01</link>
      <guid isPermaLink="true">https://blog.funq.kr/en/blogs/jira-workflow-automation-20260401-v01</guid>
      <description><![CDATA[Harness engineering in practice: implementing an 11-stage automation pipeline with Claude Code skills, from a single Jira issue key through specifications and development to five-stage verification.]]></description>
      <category><![CDATA[ai]]></category><category><![CDATA[claude-code]]></category><category><![CDATA[automation]]></category><category><![CDATA[jira]]></category><category><![CDATA[workflow]]></category>
      <media:content url="https://blog.funq.kr/blog-cover/jira-workflow-automation-20260401-v01.jpg" medium="image" type="image/jpeg"/>
      <pubDate>Wed, 01 Apr 2026 01:00:00 GMT</pubDate>
      <author>funqdev@gmail.com (fundev)</author>
      <content:encoded><![CDATA[<h2 lang="en" id="지라-이슈-키-하나로-개발을-자동화하다">Automating Development with a Single Jira Issue Key</h2>
<p lang="en">Enter a Jira issue key. The agent calls the Jira API to automatically collect the issue body, comments, attachments, linked issues, and change history. It analyzes the collected data and source code to write a development specification, then cross-checks it itself. From that specification, it creates a file-by-file development plan and verifies the plan too. It creates an isolated environment with a Git worktree, writes the code, and takes it through five stages of verification: functionality, types, security, performance, and code quality. Finally, it generates a completion report covering the entire process.</p>
<p lang="en">All the human did was enter the issue key.</p>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="text" data-theme="github-dark"><code data-language="text" data-theme="github-dark" style="display: grid;"><span data-line=""><span>지라 이슈 키 입력</span></span>
<span data-line=""><span>  │</span></span>
<span data-line=""><span>  ├─ 1. 이슈 컨텍스트 수집 ─── 지라 API → context.json + report.md</span></span>
<span data-line=""><span>  │</span></span>
<span data-line=""><span>  ├─ 2. 스펙 생성 ──────────── 컨텍스트 + 코드 분석 → spec.md</span></span>
<span data-line=""><span>  ├─ 3. 스펙 리뷰 ──────────── 팩트 체크 + 범위 검증 → APPROVED / REGENERATE</span></span>
<span data-line=""><span>  │</span></span>
<span data-line=""><span>  ├─ 4. BDD 시나리오 ────────── Given-When-Then 도출 (조건부)</span></span>
<span data-line=""><span>  ├─ 5. 개발 계획 ──────────── 파일 단위 태스크 + 코드 스니펫 → dev-plan.md</span></span>
<span data-line=""><span>  ├─ 6. 계획 리뷰 ──────────── 스펙 커버리지 + 경로 검증 → APPROVED / REGENERATE</span></span>
<span data-line=""><span>  │</span></span>
<span data-line=""><span>  ├─ 7. 격리 환경 구성 ─────── Git worktree + 의존성 설치</span></span>
<span data-line=""><span>  ├─ 8. 자동 개발 ──────────── TDD 사이클, 태스크 단위 커밋</span></span>
<span data-line=""><span>  │</span></span>
<span data-line=""><span>  ├─ 9. 5단계 검증 ─────────── 기능 → 타입 → 보안 → 성능 → 품질</span></span>
<span data-line=""><span>  ├─ 10. E2E 테스트 ─────────── 브라우저 자동화 (조건부)</span></span>
<span data-line=""><span>  │</span></span>
<span data-line=""><span>  └─ 11. 완료 보고서 ────────── 전체 산출물 종합 + HTML 시각화</span></span></code></pre></figure>
<p lang="en">This post shares the <strong>four design principles</strong> behind the pipeline and how to implement it using the <strong>Claude Code skill system</strong>.</p>
<hr lang="en">
<h2 lang="en" id="하네스-엔지니어링-그래서-구체적으로-뭘-만들었나">Harness Engineering: What Did I Actually Build?</h2>
<p lang="en">My earlier post, <a href="/blogs/harness-engineer-20260309-v01">Harness Engineering: The Developer's New Role in the Age of AI Agents</a>, introduced the concept. The model is the CPU; the harness is the OS. Harness engineering means designing everything outside the agent: CI, linters, tests, observability, and AGENTS.md.</p>
<p lang="en">The concept makes sense, but a question remains: **“What should I actually build?”** This post answers that question. It documents a practical implementation of harness-engineering principles in Jira-driven development automation.</p>
<h3 lang="en" id="풀고-싶었던-문제">The Problem I Wanted to Solve</h3>
<p lang="en">Most AI coding workflows currently look like this.</p>
<pre><code>"이 지라 이슈 보고 코드 만들어줘" → 코드 생성 → 끝
</code></pre>
<p lang="en">There are two problems.</p>
<p lang="en"><strong>First, the context bottleneck.</strong> Open a Jira issue. Requirements are in the body, additional explanations from the product planner are in the comments, screen designs are in attachments, related bug reports are in linked issues, and revised acceptance criteria are in the change history. Even developers do not read all of this carefully. For AI, they copy and paste only the issue body. However sophisticated the prompt, incomplete input produces incomplete output.</p>
<p lang="en"><strong>Second, the limits of one-shot generation.</strong> Generate code without a specification, and requirements get missed. Generate tests without one, and coverage falls short. Human developers go through analysis → design → implementation → verification, yet we ask AI to “do everything in one go.”</p>
<p lang="en">Apply the core principles of harness engineering—structured context, architectural constraints, and feedback loops—to this problem, and you end up designing a <strong>stage-by-stage pipeline</strong> rather than a single prompt. Define each stage's inputs and outputs, put verification gates between stages, and create retry loops for failures. The harness defines the agent's sequence of work, quality standards, and input/output framework.</p>
<hr lang="en">
<h2 lang="en" id="4대-설계-원칙">Four Design Principles</h2>
<p lang="en">Here are the four principles I repeatedly relied on when applying general harness-engineering principles to this pipeline.</p>
<h3 lang="en" id="원칙-1-컨텍스트-자동화">Principle 1: Automate Context Collection</h3>
<blockquote lang="en">
<p><strong>Let the agent gather the context it needs itself.</strong></p>
</blockquote>
<p lang="en">Having humans organize and hand over context creates two problems. First, information loss: people pass along only what they consider important, leaving things out. Second, a bottleneck: however fast the agent is, it is bound by the speed of the human preparing its context.</p>
<p lang="en">The solution is to let the agent <strong>collect it directly</strong>. It automatically gathers the full issue data through the Jira API, reads the source code to identify the scope of impact, and analyzes DB schemas to understand the data structures.</p>
<p lang="en">The key principle is **“Collect broadly, refine in the next stage.”** Filtering during collection can omit important information because of a mistaken judgment. It is safer to fetch everything first and extract the relevant information during specification generation.</p>
<p lang="en">This principle extends beyond the explicit collection stage. An agent reading existing code to learn its patterns while planning development, or finding and referencing related files during automated development, is also automating context collection.</p>
<h3 lang="en" id="원칙-2-단계적-정제">Principle 2: Refine in Stages</h3>
<blockquote lang="en">
<p><strong>Build a pipeline in which each stage's output becomes the next stage's input.</strong></p>
</blockquote>
<p lang="en">Instead of generating code in one shot, progressively refine raw data → specification → plan → code. Each stage produces a clearly defined format, such as Markdown or JSON. A fixed format ensures the quality of the next stage's input.</p>
<p lang="en">This structure has another benefit: <strong>every intermediate artifact remains as a file.</strong> Specifications, plans, development logs, and verification results each have their own files, making it possible to trace where a decision was made afterward. When an agent generates incorrect code, you can trace back whether the specification, plan, or implementation was wrong.</p>
<p lang="en">Retaining intermediate artifacts also creates <strong>review points where a human can intervene</strong>. The entire process can run automatically, but a semi-automated mode is also possible, with a human checking the specification before proceeding.</p>
<h3 lang="en" id="원칙-3-자기-검증-루프">Principle 3: Self-Verification Loops</h3>
<blockquote lang="en">
<p><strong>Always include a stage that independently cross-checks the generated result.</strong></p>
</blockquote>
<p lang="en">AI gets things wrong with confidence. If generation and verification happen in the same context, <strong>the same bias lets the result pass.</strong> Even if it asks itself, “Is this specification correct?”, it answers yes because it wrote it.</p>
<p lang="en">The solution is to <strong>separate generation from verification</strong>.</p>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="text" data-theme="github-dark"><code data-language="text" data-theme="github-dark" style="display: grid;"><span data-line=""><span>스펙 생성 (Step 2) ──→ 스펙 리뷰 (Step 3)</span></span>
<span data-line=""><span>                        │</span></span>
<span data-line=""><span>                        ├─ APPROVED → 다음 단계로</span></span>
<span data-line=""><span>                        ├─ REVISE → 리뷰어가 직접 수정</span></span>
<span data-line=""><span>                        └─ REGENERATE → Step 2 재실행 (최대 2회)</span></span>
<span data-line=""> </span>
<span data-line=""><span>개발 계획 (Step 5) ──→ 계획 리뷰 (Step 6)</span></span>
<span data-line=""><span>                        └─ 동일한 판정 루프</span></span>
<span data-line=""> </span>
<span data-line=""><span>자동 개발 (Step 8) ──→ 5단계 검증 (Step 9)</span></span>
<span data-line=""><span>                        │</span></span>
<span data-line=""><span>                        ├─ PASS → 다음 단계로</span></span>
<span data-line=""><span>                        └─ FAIL → 수정 후 재검증 (최대 5회)</span></span></code></pre></figure>
<p lang="en">Verification evaluates the result from a different perspective than generation. Specification review focuses on fact checks such as “Does this file path actually exist?” and “Does this table actually exist?” It checks <strong>verifiable facts</strong>, rather than abstract judgments.</p>
<p lang="en">Capping retries matters. Without a cap, the system can loop forever or keep consuming tokens while repeating the same mistake. At the cap, <strong>escalate to a human</strong>. Explicitly saying “This stage cannot be resolved automatically” is better than proceeding on guesses.</p>
<h3 lang="en" id="원칙-4-환경-격리">Principle 4: Isolate the Environment</h3>
<blockquote lang="en">
<p><strong>Isolate the automation environment from the original.</strong></p>
</blockquote>
<p lang="en">In a system where AI agents edit code directly, the greatest risk is contaminating the original. If incorrect code is applied directly to the original repository, it becomes difficult to undo.</p>
<p lang="en">Use <strong>Git worktrees</strong> to create an independent environment for each issue. Keep the original repository untouched and work in a separate branch and directory for every issue. If it fails, delete the worktree. If it succeeds, merge through a PR.</p>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="text" data-theme="github-dark"><code data-language="text" data-theme="github-dark" style="display: grid;"><span data-line=""><span>repos/                          ← 원본 (절대 수정하지 않음)</span></span>
<span data-line=""><span>├── frontend/</span></span>
<span data-line=""><span>├── backend/</span></span>
<span data-line=""><span>└── ...</span></span>
<span data-line=""> </span>
<span data-line=""><span>worktrees/                      ← 이슈별 독립 환경</span></span>
<span data-line=""><span>├── PROJ-1234/</span></span>
<span data-line=""><span>│   ├── reports/                ← 산출물 (스펙, 계획, 검증 결과)</span></span>
<span data-line=""><span>│   ├── frontend/               ← git worktree (독립 브랜치)</span></span>
<span data-line=""><span>│   └── backend/                ← git worktree (독립 브랜치)</span></span>
<span data-line=""><span>└── PROJ-5678/</span></span>
<span data-line=""><span>    └── ...</span></span></code></pre></figure>
<p lang="en">Even several issues can be worked on simultaneously without conflicts because each worktree has its own branch, dependencies, and directory.</p>
<h3 lang="en" id="원칙--파이프라인-매핑">Mapping Principles to the Pipeline</h3>
<p lang="en">Here is where the four principles apply in the 11-stage pipeline. ● marks a stage's core principle; ○ marks a supporting principle.</p>

























































































<table lang="en"><thead><tr><th>Pipeline stage</th><th align="center">1: Context automation</th><th align="center">2: Staged refinement</th><th align="center">3: Self-verification</th><th align="center">4: Environment isolation</th></tr></thead><tbody><tr><td>Issue context collection</td><td align="center">●</td><td align="center">○</td><td align="center"></td><td align="center"></td></tr><tr><td>Specification generation</td><td align="center">●</td><td align="center">●</td><td align="center"></td><td align="center"></td></tr><tr><td>Specification review</td><td align="center"></td><td align="center"></td><td align="center">●</td><td align="center"></td></tr><tr><td>BDD scenarios</td><td align="center"></td><td align="center">●</td><td align="center"></td><td align="center"></td></tr><tr><td>Development plan</td><td align="center">○</td><td align="center">●</td><td align="center"></td><td align="center"></td></tr><tr><td>Plan review</td><td align="center"></td><td align="center"></td><td align="center">●</td><td align="center"></td></tr><tr><td>Isolated environment setup</td><td align="center"></td><td align="center"></td><td align="center"></td><td align="center">●</td></tr><tr><td>Automated development</td><td align="center">○</td><td align="center">●</td><td align="center"></td><td align="center">●</td></tr><tr><td>Five-stage verification</td><td align="center"></td><td align="center"></td><td align="center">●</td><td align="center">●</td></tr><tr><td>E2E tests</td><td align="center"></td><td align="center"></td><td align="center">●</td><td align="center">●</td></tr><tr><td>Report generation</td><td align="center"></td><td align="center">○</td><td align="center"></td><td align="center"></td></tr></tbody></table>
<hr lang="en">
<h2 lang="en" id="파이프라인-설계-상세">Pipeline Design in Detail</h2>
<p lang="en">The following describes each stage's purpose, specific behavior, and input/output formats.</p>
<h3 lang="en" id="이슈-컨텍스트-수집">Issue context collection</h3>
<p lang="en">This is the pipeline's starting point. It calls the Jira REST API v2 to automatically collect all data related to the issue.</p>
<p lang="en"><strong>Collection scope:</strong></p>








































<table lang="en"><thead><tr><th>Data</th><th>Collection method</th><th>Notes</th></tr></thead><tbody><tr><td>Issue body</td><td><code>GET /rest/api/2/issue/{key}</code></td><td>description, summary, and custom fields</td></tr><tr><td>Comments</td><td>The comments field in the same API</td><td>Includes author and timestamp</td></tr><tr><td>Attachments</td><td>Download attachment URLs</td><td>Extract the full contents of text files</td></tr><tr><td>Linked issues</td><td>issuelinks field → recursive collection to a depth of 1</td><td>Includes linked issue bodies and comments</td></tr><tr><td>Change history</td><td><code>expand=changelog</code></td><td>Which fields changed, when, and how</td></tr><tr><td>External URLs</td><td>Crawl URLs in the body and comments</td><td>Reference documents, design links, etc.</td></tr></tbody></table>
<p lang="en"><strong>The output is generated in two formats</strong>.</p>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="json" data-theme="github-dark"><code data-language="json" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#E1E4E8">{</span></span>
<span data-line=""><span style="color:#79B8FF">  "issue"</span><span style="color:#E1E4E8">: {</span></span>
<span data-line=""><span style="color:#79B8FF">    "key"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"PROJ-1234"</span><span style="color:#E1E4E8">,</span></span>
<span data-line=""><span style="color:#79B8FF">    "summary"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"주문 상세 페이지에 배송 추적 기능 추가"</span><span style="color:#E1E4E8">,</span></span>
<span data-line=""><span style="color:#79B8FF">    "description"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"..."</span><span style="color:#E1E4E8">,</span></span>
<span data-line=""><span style="color:#79B8FF">    "type"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"feature"</span></span>
<span data-line=""><span style="color:#E1E4E8">  },</span></span>
<span data-line=""><span style="color:#79B8FF">  "comments"</span><span style="color:#E1E4E8">: [</span></span>
<span data-line=""><span style="color:#E1E4E8">    { </span><span style="color:#79B8FF">"author"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"PM Kim"</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">"body"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"배송사 API 연동 필요"</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">"created"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"2026-03-28"</span><span style="color:#E1E4E8"> }</span></span>
<span data-line=""><span style="color:#E1E4E8">  ],</span></span>
<span data-line=""><span style="color:#79B8FF">  "attachments"</span><span style="color:#E1E4E8">: [</span></span>
<span data-line=""><span style="color:#E1E4E8">    { </span><span style="color:#79B8FF">"filename"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"shipping-ui-design.html"</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">"content"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"..."</span><span style="color:#E1E4E8"> }</span></span>
<span data-line=""><span style="color:#E1E4E8">  ],</span></span>
<span data-line=""><span style="color:#79B8FF">  "linked_issues"</span><span style="color:#E1E4E8">: [</span></span>
<span data-line=""><span style="color:#E1E4E8">    { </span><span style="color:#79B8FF">"key"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"PROJ-1100"</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">"summary"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"배송사 API 인증 구현"</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">"relation"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"is blocked by"</span><span style="color:#E1E4E8"> }</span></span>
<span data-line=""><span style="color:#E1E4E8">  ],</span></span>
<span data-line=""><span style="color:#79B8FF">  "changelog"</span><span style="color:#E1E4E8">: [</span></span>
<span data-line=""><span style="color:#E1E4E8">    { </span><span style="color:#79B8FF">"field"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"description"</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">"from"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"..."</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">"to"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"..."</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">"date"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"2026-03-30"</span><span style="color:#E1E4E8"> }</span></span>
<span data-line=""><span style="color:#E1E4E8">  ]</span></span>
<span data-line=""><span style="color:#E1E4E8">}</span></span></code></pre></figure>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="markdown" data-theme="github-dark"><code data-language="markdown" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#79B8FF;font-weight:bold">## PROJ-1234: 주문 상세 페이지에 배송 추적 기능 추가</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#79B8FF;font-weight:bold">### 이슈 요약</span></span>
<span data-line=""><span style="color:#FFAB70">-</span><span style="color:#E1E4E8"> 유형: feature</span></span>
<span data-line=""><span style="color:#FFAB70">-</span><span style="color:#E1E4E8"> 보고자: PM Kim</span></span>
<span data-line=""><span style="color:#FFAB70">-</span><span style="color:#E1E4E8"> 생성일: 2026-03-25</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#79B8FF;font-weight:bold">### 댓글 (3건)</span></span>
<span data-line=""><span style="color:#FFAB70">1.</span><span style="color:#E1E4E8"> PM Kim (03-28): 배송사 API 연동 필요</span></span>
<span data-line=""><span style="color:#E1E4E8">...</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#79B8FF;font-weight:bold">### 연결 이슈 (1건)</span></span>
<span data-line=""><span style="color:#FFAB70">-</span><span style="color:#E1E4E8"> PROJ-1100: 배송사 API 인증 구현 (is blocked by)</span></span></code></pre></figure>
<p lang="en">The design principle is **“Collect broadly, refine in the next stage.”** This stage does not judge which information is important. That judgment belongs to specification generation.</p>
<h3 lang="en" id="스펙-생성--스펙-리뷰">Specification Generation + Specification Review</h3>
<p lang="en">This is the most important stage of the pipeline. It turns the collected context into <strong>a specification ready for development</strong>.</p>
<p lang="en"><strong>Inputs to specification generation:</strong></p>
<ul lang="en">
<li>context.json (output of the previous stage)</li>
<li>Source-code analysis (the agent reads the codebase directly to identify the impact scope)</li>
<li>DB schema (inspect the database structure if needed)</li>
</ul>
<p lang="en"><strong>Specification-generation output (spec.md):</strong></p>

































<table lang="en"><thead><tr><th>Section</th><th>Contents</th></tr></thead><tbody><tr><td>Issue classification</td><td>One of feature / bug / enhancement / refactor</td></tr><tr><td>Requirements</td><td>Functional requirements extracted from Jira data</td></tr><tr><td>Acceptance criteria</td><td>List of completion conditions</td></tr><tr><td>Impact scope</td><td>Files, modules, and APIs that need changes</td></tr><tr><td>Technical decisions</td><td>Design choices and their rationale</td></tr><tr><td>TDD/BDD/E2E flags</td><td>Test strategy based on issue type</td></tr></tbody></table>
<p lang="en"><strong>Test strategy by issue type:</strong></p>



































<table lang="en"><thead><tr><th>Issue type</th><th align="center">TDD</th><th align="center">BDD</th><th align="center">E2E</th></tr></thead><tbody><tr><td>feature</td><td align="center">O</td><td align="center">O</td><td align="center">O</td></tr><tr><td>enhancement</td><td align="center">O</td><td align="center">Conditional</td><td align="center">O</td></tr><tr><td>bug</td><td align="center">X</td><td align="center">X</td><td align="center">Conditional</td></tr><tr><td>refactor</td><td align="center">O</td><td align="center">X</td><td align="center">X</td></tr></tbody></table>
<p lang="en">This classification determines whether BDD scenario generation and E2E tests run conditionally later.</p>
<p lang="en"><strong>Specification review</strong> cross-checks the generated specification. It uses a verifiable checklist rather than abstract judgment.</p>





























<table lang="en"><thead><tr><th>Check</th><th>Verification method</th></tr></thead><tbody><tr><td>Whether file paths exist</td><td>Check the actual file system</td></tr><tr><td>Whether tables and columns exist</td><td>Query the DB schema</td></tr><tr><td>Whether API endpoints exist</td><td>Check the router code</td></tr><tr><td>Requirements completeness</td><td>Whether every item in context.json is reflected in the specification</td></tr><tr><td>Accuracy of issue classification</td><td>Compare against the original Jira data</td></tr></tbody></table>
<p lang="en">There are three decisions: <strong>APPROVED</strong> proceeds to the next stage, <strong>REVISE</strong> lets the reviewer edit directly, and <strong>REGENERATE</strong> regenerates the specification from scratch. REGENERATE is allowed at most 2 times; if it still does not pass, the issue is escalated to a human.</p>
<h3 lang="en" id="bdd--개발-계획--계획-리뷰">BDD + Development Plan + Plan Review</h3>
<p lang="en">Once finalized, the specification is converted into <strong>an executable plan</strong>.</p>
<p lang="en"><strong>BDD scenarios</strong> are generated only for feature and enhancement issues. The specification's acceptance criteria are converted into Given-When-Then form.</p>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="gherkin" data-theme="github-dark"><code data-language="gherkin" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#F97583">Feature</span><span style="color:#E1E4E8">:</span><span style="color:#9ECBFF"> 주문 상세 페이지 배송 추적</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F97583">  Scenario</span><span style="color:#E1E4E8">:</span><span style="color:#9ECBFF"> 배송 중인 주문의 추적 정보 표시</span></span>
<span data-line=""><span style="color:#F97583">    Given </span><span style="color:#E1E4E8">주문 번호 </span><span style="color:#9ECBFF">"ORD-001"</span><span style="color:#E1E4E8">이 배송 중 상태이다</span></span>
<span data-line=""><span style="color:#F97583">    When </span><span style="color:#E1E4E8">사용자가 주문 상세 페이지를 연다</span></span>
<span data-line=""><span style="color:#F97583">    Then </span><span style="color:#E1E4E8">배송 추적 정보가 표시된다</span></span>
<span data-line=""><span style="color:#F97583">    And </span><span style="color:#E1E4E8">현재 배송 위치가 지도에 표시된다</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F97583">  Scenario</span><span style="color:#E1E4E8">:</span><span style="color:#9ECBFF"> 배송 전 주문은 추적 정보 없음</span></span>
<span data-line=""><span style="color:#F97583">    Given </span><span style="color:#E1E4E8">주문 번호 </span><span style="color:#9ECBFF">"ORD-002"</span><span style="color:#E1E4E8">가 결제 완료 상태이다</span></span>
<span data-line=""><span style="color:#F97583">    When </span><span style="color:#E1E4E8">사용자가 주문 상세 페이지를 연다</span></span>
<span data-line=""><span style="color:#F97583">    Then </span><span style="color:#9ECBFF">"배송 준비 중"</span><span style="color:#E1E4E8"> 메시지가 표시된다</span></span>
<span data-line=""><span style="color:#F97583">    And </span><span style="color:#E1E4E8">배송 추적 섹션은 비활성화된다</span></span></code></pre></figure>
<p lang="en">The **development plan (dev-plan.md)** breaks the specification into file-level tasks. Each task includes the files to change, the changes required, and runnable code snippets. The standard is clear: <strong>“Detailed enough that an outside developer could implement it from this plan alone.”</strong></p>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="markdown" data-theme="github-dark"><code data-language="markdown" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#79B8FF;font-weight:bold">### Task 3: 배송 추적 API 엔드포인트 추가</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#E1E4E8;font-weight:bold">**파일:**</span></span>
<span data-line=""><span style="color:#FFAB70">-</span><span style="color:#E1E4E8"> Create: </span><span style="color:#79B8FF">`app/routers/shipping.py`</span></span>
<span data-line=""><span style="color:#FFAB70">-</span><span style="color:#E1E4E8"> Modify: </span><span style="color:#79B8FF">`app/main.py`</span><span style="color:#E1E4E8"> (라우터 등록)</span></span>
<span data-line=""><span style="color:#FFAB70">-</span><span style="color:#E1E4E8"> Test: </span><span style="color:#79B8FF">`tests/routers/test_shipping.py`</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#E1E4E8;font-weight:bold">**단계:**</span></span>
<span data-line=""><span style="color:#FFAB70">1.</span><span style="color:#E1E4E8"> Pydantic 스키마 정의</span></span>
<span data-line=""><span style="color:#FFAB70">   -</span><span style="color:#E1E4E8"> ShippingTrackingResponse: tracking_id, status, location, updated_at</span></span>
<span data-line=""><span style="color:#FFAB70">2.</span><span style="color:#E1E4E8"> 라우터 함수 구현</span></span>
<span data-line=""><span style="color:#FFAB70">   -</span><span style="color:#E1E4E8"> GET /api/orders/{order_id}/shipping</span></span>
<span data-line=""><span style="color:#FFAB70">   -</span><span style="color:#E1E4E8"> 배송사 API 호출 → 응답 변환 → 반환</span></span>
<span data-line=""><span style="color:#FFAB70">3.</span><span style="color:#E1E4E8"> main.py에 라우터 등록</span></span>
<span data-line=""><span style="color:#FFAB70">4.</span><span style="color:#E1E4E8"> 테스트 작성 및 실행</span></span></code></pre></figure>
<p lang="en"><strong>Plan review</strong> checks two things: whether every requirement in the specification has been broken into tasks (coverage), and whether the file paths in those tasks actually exist (fact-checking). It uses the same APPROVED / REVISE / REGENERATE decisions as specification review.</p>
<h3 lang="en" id="격리-환경--자동-개발">Isolated Environment + Automated Development</h3>
<p lang="en">Once the plan is finalized, prepare the environment in which the code will be written.</p>
<p lang="en"><strong>Isolated environment setup:</strong></p>
<ol lang="en">
<li>Create Git worktrees only for projects within the specification's impact scope</li>
<li>Create a branch based on the issue key (for example, <code>developer/PROJ-1234</code>)</li>
<li>Install dependencies (npm install, pip install, etc.)</li>
<li>Provision environment variables</li>
</ol>
<p lang="en">For an issue that changes the frontend, create worktrees for both the frontend and backend, because API tests need the backend.</p>
<p lang="en"><strong>Automated development</strong> executes the plan's tasks in sequence.</p>





















<table lang="en"><thead><tr><th>Issue type</th><th>Development approach</th></tr></thead><tbody><tr><td>feature (subject to TDD)</td><td>RED → GREEN cycle: write a failing test → write code that passes it</td></tr><tr><td>bug</td><td>Modify code → verify</td></tr><tr><td>Other</td><td>Write code → verify</td></tr></tbody></table>
<p lang="en">Create a commit for each task and include the issue key in the commit message.</p>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="bash" data-theme="github-dark"><code data-language="bash" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#B392F0">feat(shipping</span><span style="color:#E1E4E8">): 배송 추적 API 엔드포인트 추가</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#B392F0">JIRA:</span><span style="color:#9ECBFF"> PROJ-1234</span></span></code></pre></figure>
<h3 lang="en" id="5단계-검증--e2e-테스트">Five-Stage Verification + E2E Tests</h3>
<p lang="en">Once the code is complete, run five consecutive verification stages. Run all of them in the order 1 → 2 → 3 → 4 → 5, without stopping midway.</p>



































<table lang="en"><thead><tr><th>Stage</th><th>What is checked</th><th>Method</th></tr></thead><tbody><tr><td>1. Functionality</td><td>Whether all specification requirements are met</td><td>Compare against the acceptance-criteria checklist</td></tr><tr><td>2. Types</td><td>Type safety</td><td><code>tsc --noEmit</code> (TS), <code>mypy</code> (Python)</td></tr><tr><td>3. Security</td><td>OWASP Top 10 vulnerabilities</td><td>Check for SQL injection, XSS, missing authentication, etc.</td></tr><tr><td>4. Performance</td><td>Performance antipatterns</td><td>N+1 queries, infinite loops, memory leaks, etc.</td></tr><tr><td>5. Code quality</td><td>Adherence to project patterns</td><td>Function length, duplication, and naming conventions</td></tr></tbody></table>
<p lang="en">If even one stage fails, fix the code and <strong>rerun all five stages from the beginning</strong>. There is no partial revalidation: a security fix can break functionality, and a performance improvement can break types.</p>
<p lang="en">Retries are capped at 5. After 5 failures, mark the work “unable to complete development automatically” and escalate to a human. Record where it failed and what was attempted in the report.</p>
<p lang="en"><strong>E2E tests</strong> use browser automation. Start the server, open it in a browser, check for console and network errors, and capture screenshots of the main screens. This stage applies only to feature/enhancement issues with a UI.</p>
<h3 lang="en" id="보고서-생성">Report generation</h3>
<p lang="en">This is the pipeline's final stage. It consolidates every artifact from the preceding 10 stages.</p>
<p lang="en"><strong>The completion report includes:</strong></p>
<ul lang="en">
<li>Issue summary and classification</li>
<li>Key specification details</li>
<li>Changed-file list and git commit log</li>
<li>Five-stage verification results (passes and failures)</li>
<li>E2E test screenshots, where applicable</li>
<li>Retry and escalation history</li>
</ul>
<p lang="en">It also generates a visual HTML report. This is a single HTML file with images and screenshots embedded inline, so you can open it directly in a browser to review the full work history.</p>
<h3 lang="en" id="에러-처리-전략">Error-Handling Strategy</h3>
<p lang="en">Error handling is central to a system that runs 11 stages automatically. A unified strategy applies throughout the pipeline.</p>






























<table lang="en"><thead><tr><th>Error type</th><th>Example</th><th>Response</th></tr></thead><tbody><tr><td>External system failure</td><td>Jira API unavailable, DB connection failure</td><td>Stop immediately and escalate to a human</td></tr><tr><td>Insufficient quality</td><td>REGENERATE in specification review, verification failure</td><td>Regeneration loop with a per-stage retry limit</td></tr><tr><td>Unable to execute</td><td>File-path mismatch, dependency installation failure</td><td>Stop immediately and escalate with the error message</td></tr><tr><td>Noncritical failure</td><td>Report visualization error</td><td>Log a warning and continue</td></tr></tbody></table>
<p lang="en">The core principle is **“Do not guess.”** If a file path does not exist, stop instead of guessing a similar path. If a test fails unexpectedly, stop instead of guessing the cause. Code an agent creates by proceeding on guesses causes bigger problems later.</p>
<hr lang="en">
<h2 lang="en" id="구현-가이드-claude-code-스킬-시스템">Implementation Guide: The Claude Code Skill System</h2>
<p lang="en">Here is how to implement this pipeline using <strong>Claude Code's custom slash-command (skill)</strong> system.</p>
<h3 lang="en" id="스킬-아키텍처-개요">Skill Architecture Overview</h3>
<p lang="en">Claude Code skills are Markdown files in the <code>.claude/skills/</code> directory. They can be invoked with <code>/슬래시 커맨드</code> and serve as <strong>instructions</strong> for the agent to read and follow.</p>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="text" data-theme="github-dark"><code data-language="text" data-theme="github-dark" style="display: grid;"><span data-line=""><span>.claude/</span></span>
<span data-line=""><span>└── skills/</span></span>
<span data-line=""><span>    ├── orchestrator/            # 마스터 스킬 (유일한 진입점)</span></span>
<span data-line=""><span>    │   └── SKILL.md</span></span>
<span data-line=""><span>    ├── jira-collector/          # 이슈 컨텍스트 수집</span></span>
<span data-line=""><span>    │   ├── SKILL.md</span></span>
<span data-line=""><span>    │   └── scripts/</span></span>
<span data-line=""><span>    │       ├── collect.py       # 지라 API 수집 스크립트</span></span>
<span data-line=""><span>    │       └── api_client.py    # HTTP 클라이언트</span></span>
<span data-line=""><span>    ├── spec-generator/          # 스펙 생성</span></span>
<span data-line=""><span>    │   └── SKILL.md</span></span>
<span data-line=""><span>    ├── spec-reviewer/           # 스펙 리뷰</span></span>
<span data-line=""><span>    │   └── SKILL.md</span></span>
<span data-line=""><span>    ├── bdd-explorer/            # BDD 시나리오 (조건부)</span></span>
<span data-line=""><span>    │   └── SKILL.md</span></span>
<span data-line=""><span>    ├── dev-planner/             # 개발 계획</span></span>
<span data-line=""><span>    │   └── SKILL.md</span></span>
<span data-line=""><span>    ├── plan-reviewer/           # 계획 리뷰</span></span>
<span data-line=""><span>    │   └── SKILL.md</span></span>
<span data-line=""><span>    ├── worktree-creator/        # 격리 환경 구성</span></span>
<span data-line=""><span>    │   ├── SKILL.md</span></span>
<span data-line=""><span>    │   └── scripts/</span></span>
<span data-line=""><span>    │       └── create-worktree.sh</span></span>
<span data-line=""><span>    ├── developer/               # 자동 개발</span></span>
<span data-line=""><span>    │   ├── SKILL.md</span></span>
<span data-line=""><span>    │   └── references/          # 프로젝트별 코딩 가이드</span></span>
<span data-line=""><span>    │       ├── backend.md</span></span>
<span data-line=""><span>    │       └── frontend.md</span></span>
<span data-line=""><span>    ├── verifier/                # 5단계 검증</span></span>
<span data-line=""><span>    │   ├── SKILL.md</span></span>
<span data-line=""><span>    │   └── references/</span></span>
<span data-line=""><span>    │       ├── security-checklist.md</span></span>
<span data-line=""><span>    │       └── quality-checklist.md</span></span>
<span data-line=""><span>    ├── e2e-tester/              # E2E 테스트 (조건부)</span></span>
<span data-line=""><span>    │   └── SKILL.md</span></span>
<span data-line=""><span>    └── reporter/                # 보고서 생성</span></span>
<span data-line=""><span>        └── SKILL.md</span></span></code></pre></figure>
<p lang="en">The core structure is that <strong>each skill is independent</strong>. Skills pass data through the file system: spec-generator reads the <code>context.json</code> created by jira-collector, and dev-planner reads the <code>spec.md</code> created by spec-generator.</p>
<h3 lang="en" id="skillmd-파일-구조">The Structure of a SKILL.md File</h3>
<p lang="en">SKILL.md is <strong>an instruction document</strong>, rather than code. It is Markdown that the agent reads and follows, defining procedures, output formats, and error conditions in natural language.</p>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="markdown" data-theme="github-dark"><code data-language="markdown" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#E1E4E8">---</span></span>
<span data-line=""><span style="color:#85E89D">name</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">spec-generator</span></span>
<span data-line=""><span style="color:#85E89D">description</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">수집된 컨텍스트를 기반으로 개발 명세서를 생성한다</span></span>
<span data-line=""><span style="color:#E1E4E8">---</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#79B8FF;font-weight:bold">## 입력</span></span>
<span data-line=""><span style="color:#FFAB70">-</span><span style="color:#79B8FF"> `{REPORTS_DIR}/research/jira/context.json`</span><span style="color:#E1E4E8"> — 지라 수집 데이터</span></span>
<span data-line=""><span style="color:#FFAB70">-</span><span style="color:#E1E4E8"> 소스 코드 — 에이전트가 직접 코드베이스를 읽어 분석</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#79B8FF;font-weight:bold">## 실행 절차</span></span>
<span data-line=""><span style="color:#FFAB70">1.</span><span style="color:#E1E4E8"> context.json을 읽고 이슈 유형을 분류한다 (feature/bug/enhancement/refactor)</span></span>
<span data-line=""><span style="color:#FFAB70">2.</span><span style="color:#E1E4E8"> 이슈 유형에 따라 TDD/BDD/E2E 플래그를 결정한다</span></span>
<span data-line=""><span style="color:#FFAB70">3.</span><span style="color:#E1E4E8"> 영향 범위에 해당하는 소스 코드를 읽어 현재 구조를 파악한다</span></span>
<span data-line=""><span style="color:#FFAB70">4.</span><span style="color:#E1E4E8"> 아래 템플릿에 맞춰 spec.md를 작성한다</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#79B8FF;font-weight:bold">## 출력</span></span>
<span data-line=""><span style="color:#FFAB70">-</span><span style="color:#79B8FF"> `{REPORTS_DIR}/spec/spec.md`</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#79B8FF;font-weight:bold">## 출력 포맷</span></span>
<span data-line=""><span style="color:#E1E4E8">(마크다운 템플릿 정의)</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#79B8FF;font-weight:bold">## 에러 처리</span></span>
<span data-line=""><span style="color:#FFAB70">-</span><span style="color:#E1E4E8"> 소스 코드 경로를 찾을 수 없으면 즉시 중단하고 보고한다</span></span>
<span data-line=""><span style="color:#FFAB70">-</span><span style="color:#E1E4E8"> context.json이 비어있으면 이전 단계 실패로 판단하고 중단한다</span></span></code></pre></figure>
<p lang="en">The same structure applies to all 11 skills: four sections for inputs, procedure, outputs, and errors.</p>
<h3 lang="en" id="오케스트레이터-패턴">The Orchestrator Pattern</h3>
<p lang="en">The orchestrator is a <strong>master skill</strong> that invokes the 11 skills in sequence. When the user enters <code>/orchestrator PROJ-1234</code>, it runs the remaining 10 skills in order.</p>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="text" data-theme="github-dark"><code data-language="text" data-theme="github-dark" style="display: grid;"><span data-line=""><span>Step 1:  /jira-collector      → context.json 생성</span></span>
<span data-line=""><span>Step 2:  /spec-generator       → spec.md 생성</span></span>
<span data-line=""><span>Step 3:  /spec-reviewer        → APPROVED?</span></span>
<span data-line=""><span>         └─ NO → Step 2 재실행 (max 2)</span></span>
<span data-line=""><span>Step 4:  if spec.BDD == true</span></span>
<span data-line=""><span>         └─ /bdd-explorer      → bdd-scenarios.md</span></span>
<span data-line=""><span>Step 5:  /dev-planner          → dev-plan.md</span></span>
<span data-line=""><span>Step 6:  /plan-reviewer        → APPROVED?</span></span>
<span data-line=""><span>         └─ NO → Step 5 재실행 (max 2)</span></span>
<span data-line=""><span>Step 7:  /worktree-creator     → 격리 환경 준비</span></span>
<span data-line=""><span>Step 8:  /developer            → 코드 작성 + 커밋</span></span>
<span data-line=""><span>Step 9:  /verifier             → 5단계 검증</span></span>
<span data-line=""><span>         └─ FAIL → 수정 후 재검증 (max 5)</span></span>
<span data-line=""><span>Step 10: if spec.E2E == true</span></span>
<span data-line=""><span>         └─ /e2e-tester        → 테스트 보고서</span></span>
<span data-line=""><span>Step 11: /reporter             → 완료 보고서</span></span></code></pre></figure>
<p lang="en">Conditional execution (Steps 4 and 10) and retry loops (Steps 3, 6, and 9) are the heart of the orchestrator. It checks each stage's output and decides the next stage according to the conditions. If a blocker occurs, it stops immediately and reports to a human.</p>
<h3 lang="en" id="지라-api-연동-스킬">The Jira API Integration Skill</h3>
<p lang="en">Jira data collection is implemented as a <strong>Python script</strong> and executed through Bash from the skill. It uses only the Python standard library—<code>urllib</code> and <code>json</code>—with no external dependencies, because it needs to run in the deployment environment without installing packages.</p>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="python" data-theme="github-dark"><code data-language="python" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#F97583">import</span><span style="color:#E1E4E8"> urllib.request</span></span>
<span data-line=""><span style="color:#F97583">import</span><span style="color:#E1E4E8"> json</span></span>
<span data-line=""><span style="color:#F97583">import</span><span style="color:#E1E4E8"> base64</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F97583">class</span><span style="color:#B392F0"> JiraClient</span><span style="color:#E1E4E8">:</span></span>
<span data-line=""><span style="color:#F97583">    def</span><span style="color:#79B8FF"> __init__</span><span style="color:#E1E4E8">(self, server_url, username, password):</span></span>
<span data-line=""><span style="color:#79B8FF">        self</span><span style="color:#E1E4E8">.server_url </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> server_url</span></span>
<span data-line=""><span style="color:#E1E4E8">        credentials </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> base64.b64encode(</span></span>
<span data-line=""><span style="color:#F97583">            f</span><span style="color:#9ECBFF">"</span><span style="color:#79B8FF">{</span><span style="color:#E1E4E8">username</span><span style="color:#79B8FF">}</span><span style="color:#9ECBFF">:</span><span style="color:#79B8FF">{</span><span style="color:#E1E4E8">password</span><span style="color:#79B8FF">}</span><span style="color:#9ECBFF">"</span><span style="color:#E1E4E8">.encode()</span></span>
<span data-line=""><span style="color:#E1E4E8">        ).decode()</span></span>
<span data-line=""><span style="color:#79B8FF">        self</span><span style="color:#E1E4E8">.headers </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> {</span></span>
<span data-line=""><span style="color:#9ECBFF">            "Authorization"</span><span style="color:#E1E4E8">: </span><span style="color:#F97583">f</span><span style="color:#9ECBFF">"Basic </span><span style="color:#79B8FF">{</span><span style="color:#E1E4E8">credentials</span><span style="color:#79B8FF">}</span><span style="color:#9ECBFF">"</span><span style="color:#E1E4E8">,</span></span>
<span data-line=""><span style="color:#9ECBFF">            "Content-Type"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"application/json"</span></span>
<span data-line=""><span style="color:#E1E4E8">        }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F97583">    def</span><span style="color:#B392F0"> get_issue</span><span style="color:#E1E4E8">(self, key, expand</span><span style="color:#F97583">=</span><span style="color:#9ECBFF">"renderedFields,changelog"</span><span style="color:#E1E4E8">):</span></span>
<span data-line=""><span style="color:#E1E4E8">        url </span><span style="color:#F97583">=</span><span style="color:#F97583"> f</span><span style="color:#9ECBFF">"</span><span style="color:#79B8FF">{self</span><span style="color:#E1E4E8">.server_url</span><span style="color:#79B8FF">}</span><span style="color:#9ECBFF">/rest/api/2/issue/</span><span style="color:#79B8FF">{</span><span style="color:#E1E4E8">key</span><span style="color:#79B8FF">}</span><span style="color:#9ECBFF">?expand=</span><span style="color:#79B8FF">{</span><span style="color:#E1E4E8">expand</span><span style="color:#79B8FF">}</span><span style="color:#9ECBFF">"</span></span>
<span data-line=""><span style="color:#E1E4E8">        req </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> urllib.request.Request(url, </span><span style="color:#FFAB70">headers</span><span style="color:#F97583">=</span><span style="color:#79B8FF">self</span><span style="color:#E1E4E8">.headers)</span></span>
<span data-line=""><span style="color:#F97583">        with</span><span style="color:#E1E4E8"> urllib.request.urlopen(req) </span><span style="color:#F97583">as</span><span style="color:#E1E4E8"> resp:</span></span>
<span data-line=""><span style="color:#F97583">            return</span><span style="color:#E1E4E8"> json.loads(resp.read())</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F97583">    def</span><span style="color:#B392F0"> search_issues</span><span style="color:#E1E4E8">(self, jql, fields</span><span style="color:#F97583">=</span><span style="color:#79B8FF">None</span><span style="color:#E1E4E8">, max_results</span><span style="color:#F97583">=</span><span style="color:#79B8FF">50</span><span style="color:#E1E4E8">):</span></span>
<span data-line=""><span style="color:#E1E4E8">        url </span><span style="color:#F97583">=</span><span style="color:#F97583"> f</span><span style="color:#9ECBFF">"</span><span style="color:#79B8FF">{self</span><span style="color:#E1E4E8">.server_url</span><span style="color:#79B8FF">}</span><span style="color:#9ECBFF">/rest/api/2/search"</span></span>
<span data-line=""><span style="color:#E1E4E8">        data </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> json.dumps({</span><span style="color:#9ECBFF">"jql"</span><span style="color:#E1E4E8">: jql, </span><span style="color:#9ECBFF">"maxResults"</span><span style="color:#E1E4E8">: max_results})</span></span>
<span data-line=""><span style="color:#E1E4E8">        req </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> urllib.request.Request(</span></span>
<span data-line=""><span style="color:#E1E4E8">            url, </span><span style="color:#FFAB70">data</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">data.encode(), </span><span style="color:#FFAB70">headers</span><span style="color:#F97583">=</span><span style="color:#79B8FF">self</span><span style="color:#E1E4E8">.headers</span></span>
<span data-line=""><span style="color:#E1E4E8">        )</span></span>
<span data-line=""><span style="color:#F97583">        with</span><span style="color:#E1E4E8"> urllib.request.urlopen(req) </span><span style="color:#F97583">as</span><span style="color:#E1E4E8"> resp:</span></span>
<span data-line=""><span style="color:#F97583">            return</span><span style="color:#E1E4E8"> json.loads(resp.read())</span></span></code></pre></figure>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="python" data-theme="github-dark"><code data-language="python" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#F97583">def</span><span style="color:#B392F0"> collect</span><span style="color:#E1E4E8">(jira_key, config, output_dir):</span></span>
<span data-line=""><span style="color:#E1E4E8">    client </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> JiraClient(</span><span style="color:#F97583">**</span><span style="color:#E1E4E8">config[</span><span style="color:#9ECBFF">"jira"</span><span style="color:#E1E4E8">])</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#6A737D">    # 1. 메인 이슈 수집</span></span>
<span data-line=""><span style="color:#E1E4E8">    issue </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> client.get_issue(jira_key)</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#6A737D">    # 2. 댓글 추출</span></span>
<span data-line=""><span style="color:#E1E4E8">    comments </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> extract_comments(issue)</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#6A737D">    # 3. 첨부파일 다운로드 + 텍스트 추출</span></span>
<span data-line=""><span style="color:#E1E4E8">    attachments </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> download_attachments(issue, output_dir)</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#6A737D">    # 4. 연결 이슈 1-depth 수집</span></span>
<span data-line=""><span style="color:#E1E4E8">    linked </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> collect_linked_issues(client, issue)</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#6A737D">    # 5. 변경 이력 추출</span></span>
<span data-line=""><span style="color:#E1E4E8">    changelog </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> extract_changelog(issue)</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#6A737D">    # 6. 외부 URL 크롤링</span></span>
<span data-line=""><span style="color:#E1E4E8">    urls </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> extract_and_fetch_urls(issue, comments)</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#6A737D">    # 7. context.json 저장</span></span>
<span data-line=""><span style="color:#E1E4E8">    context </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> {</span></span>
<span data-line=""><span style="color:#9ECBFF">        "issue"</span><span style="color:#E1E4E8">: normalize_issue(issue),</span></span>
<span data-line=""><span style="color:#9ECBFF">        "comments"</span><span style="color:#E1E4E8">: comments,</span></span>
<span data-line=""><span style="color:#9ECBFF">        "attachments"</span><span style="color:#E1E4E8">: attachments,</span></span>
<span data-line=""><span style="color:#9ECBFF">        "linked_issues"</span><span style="color:#E1E4E8">: linked,</span></span>
<span data-line=""><span style="color:#9ECBFF">        "changelog"</span><span style="color:#E1E4E8">: changelog,</span></span>
<span data-line=""><span style="color:#9ECBFF">        "external_urls"</span><span style="color:#E1E4E8">: urls</span></span>
<span data-line=""><span style="color:#E1E4E8">    }</span></span>
<span data-line=""><span style="color:#E1E4E8">    save_json(context, </span><span style="color:#F97583">f</span><span style="color:#9ECBFF">"</span><span style="color:#79B8FF">{</span><span style="color:#E1E4E8">output_dir</span><span style="color:#79B8FF">}</span><span style="color:#9ECBFF">/context.json"</span><span style="color:#E1E4E8">)</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#6A737D">    # 8. report.md 생성</span></span>
<span data-line=""><span style="color:#E1E4E8">    generate_report(context, </span><span style="color:#F97583">f</span><span style="color:#9ECBFF">"</span><span style="color:#79B8FF">{</span><span style="color:#E1E4E8">output_dir</span><span style="color:#79B8FF">}</span><span style="color:#9ECBFF">/report.md"</span><span style="color:#E1E4E8">)</span></span></code></pre></figure>
<p lang="en">The skill's SKILL.md invokes the script as follows:</p>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="markdown" data-theme="github-dark"><code data-language="markdown" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#79B8FF;font-weight:bold">## 실행 절차</span></span>
<span data-line=""><span style="color:#FFAB70">1.</span><span style="color:#E1E4E8"> config.json에서 지라 서버 URL과 인증 정보를 읽는다</span></span>
<span data-line=""><span style="color:#FFAB70">2.</span><span style="color:#E1E4E8"> 다음 명령으로 수집 스크립트를 실행한다:</span></span>
<span data-line=""><span style="color:#79B8FF">   `python scripts/collect.py --key {JIRA_KEY} --output {REPORTS_DIR}/research/jira`</span></span>
<span data-line=""><span style="color:#FFAB70">3.</span><span style="color:#E1E4E8"> 실행 결과를 확인한다:</span></span>
<span data-line=""><span style="color:#FFAB70">   -</span><span style="color:#E1E4E8"> context.json이 생성되었는지 확인</span></span>
<span data-line=""><span style="color:#FFAB70">   -</span><span style="color:#E1E4E8"> 이슈 본문이 비어있지 않은지 확인</span></span>
<span data-line=""><span style="color:#FFAB70">4.</span><span style="color:#E1E4E8"> 에러 시 즉시 중단하고 에러 메시지를 보고한다</span></span></code></pre></figure>
<h3 lang="en" id="스펙-생성-스킬">The Specification-Generation Skill</h3>
<p lang="en">This skill synthesizes context.json and code-analysis results to create spec.md. The key is to <strong>enforce the output format with a template</strong>.</p>
<p lang="en">Define a Markdown template for spec.md in SKILL.md, and the agent writes to that structure. Include section order, required items, and flag formats in the template.</p>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="markdown" data-theme="github-dark"><code data-language="markdown" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#79B8FF;font-weight:bold">## 이슈 분류</span></span>
<span data-line=""><span style="color:#FFAB70">-</span><span style="color:#E1E4E8"> 유형: {feature | bug | enhancement | refactor}</span></span>
<span data-line=""><span style="color:#FFAB70">-</span><span style="color:#E1E4E8"> TDD: {O | X}</span></span>
<span data-line=""><span style="color:#FFAB70">-</span><span style="color:#E1E4E8"> BDD: {O | X}</span></span>
<span data-line=""><span style="color:#FFAB70">-</span><span style="color:#E1E4E8"> E2E: {O | X}</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#79B8FF;font-weight:bold">## 요구사항</span></span>
<span data-line=""><span style="color:#E1E4E8">(context.json에서 추출한 기능 요구사항을 번호 목록으로)</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#79B8FF;font-weight:bold">## 수용 기준</span></span>
<span data-line=""><span style="color:#E1E4E8">(완료 조건을 체크리스트 형태로)</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#79B8FF;font-weight:bold">## 영향 범위</span></span>
<span data-line=""><span style="color:#E1E4E8">| 프로젝트 | 파일 | 변경 내용 |</span></span>
<span data-line=""><span style="color:#E1E4E8">|----------|------|-----------|</span></span>
<span data-line=""><span style="color:#E1E4E8">(에이전트가 코드 분석 후 채움)</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#79B8FF;font-weight:bold">## 기술 결정사항</span></span>
<span data-line=""><span style="color:#E1E4E8">(설계상 선택과 근거)</span></span></code></pre></figure>
<p lang="en">Issue-classification logic is also defined in natural language in SKILL.md:</p>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="markdown" data-theme="github-dark"><code data-language="markdown" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#79B8FF;font-weight:bold">## 이슈 분류 기준</span></span>
<span data-line=""><span style="color:#FFAB70">-</span><span style="color:#E1E4E8;font-weight:bold"> **feature**</span><span style="color:#E1E4E8">: 새로운 기능 추가 (기존에 없던 화면, API, 로직)</span></span>
<span data-line=""><span style="color:#FFAB70">-</span><span style="color:#E1E4E8;font-weight:bold"> **bug**</span><span style="color:#E1E4E8">: 기존 기능의 오작동 수정</span></span>
<span data-line=""><span style="color:#FFAB70">-</span><span style="color:#E1E4E8;font-weight:bold"> **enhancement**</span><span style="color:#E1E4E8">: 기존 기능의 개선 (UI 변경, 성능 개선, UX 개선)</span></span>
<span data-line=""><span style="color:#FFAB70">-</span><span style="color:#E1E4E8;font-weight:bold"> **refactor**</span><span style="color:#E1E4E8">: 동작 변경 없이 코드 구조 개선</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#E1E4E8">분류에 따른 플래그:</span></span>
<span data-line=""><span style="color:#FFAB70">-</span><span style="color:#E1E4E8"> feature → TDD: O, BDD: O, E2E: O</span></span>
<span data-line=""><span style="color:#FFAB70">-</span><span style="color:#E1E4E8"> bug → TDD: X, BDD: X, E2E: UI 관련이면 O</span></span>
<span data-line=""><span style="color:#FFAB70">-</span><span style="color:#E1E4E8"> enhancement → TDD: O, BDD: 사용자 시나리오 변경이 있으면 O, E2E: O</span></span>
<span data-line=""><span style="color:#FFAB70">-</span><span style="color:#E1E4E8"> refactor → TDD: O, BDD: X, E2E: X</span></span></code></pre></figure>
<h3 lang="en" id="검증-스킬">The Verification Skill</h3>
<p lang="en">The verification skill <strong>defines the checklist for each of the five stages in SKILL.md</strong>, and the agent executes them in sequence.</p>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="markdown" data-theme="github-dark"><code data-language="markdown" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#79B8FF;font-weight:bold">### Stage 3: 보안 검증 (OWASP Top 10)</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#E1E4E8">다음 체크리스트를 변경된 코드에 적용한다:</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#FFAB70">-</span><span style="color:#E1E4E8"> [ ] SQL 쿼리에 파라미터 바인딩 사용 여부</span></span>
<span data-line=""><span style="color:#FFAB70">-</span><span style="color:#E1E4E8"> [ ] 사용자 입력의 XSS 이스케이핑 여부</span></span>
<span data-line=""><span style="color:#FFAB70">-</span><span style="color:#E1E4E8"> [ ] 인증/인가 게이트 존재 여부 (API 엔드포인트)</span></span>
<span data-line=""><span style="color:#FFAB70">-</span><span style="color:#E1E4E8"> [ ] CSRF 토큰 검증 여부 (상태 변경 요청)</span></span>
<span data-line=""><span style="color:#FFAB70">-</span><span style="color:#E1E4E8"> [ ] 민감 정보(비밀번호, 토큰) 로깅 여부</span></span>
<span data-line=""><span style="color:#FFAB70">-</span><span style="color:#E1E4E8"> [ ] 파일 업로드 검증 여부 (타입, 크기)</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#E1E4E8">위반 항목이 있으면:</span></span>
<span data-line=""><span style="color:#FFAB70">1.</span><span style="color:#E1E4E8"> 위반 내용과 파일:라인 번호를 기록한다</span></span>
<span data-line=""><span style="color:#FFAB70">2.</span><span style="color:#E1E4E8"> 코드를 수정한다</span></span>
<span data-line=""><span style="color:#FFAB70">3.</span><span style="color:#E1E4E8"> FAIL로 판정한다 (전체 5단계 재실행 트리거)</span></span></code></pre></figure>
<p lang="en">The orchestrator controls the retry loop. The verification skill returns only PASS or FAIL; on FAIL, the orchestrator runs the fix → reverify loop.</p>
<h3 lang="en" id="나머지-스킬과-변형-포인트">The Remaining Skills and Customization Points</h3>
<p lang="en">The three skills covered above—collection, specification generation, and verification—represent the pipeline's main patterns. The remaining skills follow the same structure: <strong>define inputs, procedure, outputs, and errors in SKILL.md</strong>, read the previous stage's output files, and execute the next stage.</p>
<p lang="en">Points readers can adapt to their own projects:</p>

































<table lang="en"><thead><tr><th>What to customize</th><th>Example</th></tr></thead><tbody><tr><td>Verification checklists</td><td>Add security and quality checks suited to the project</td></tr><tr><td>Conditions for BDD</td><td>Adjust which issue types use BDD</td></tr><tr><td>Commit message format</td><td>Adapt to the team's commit conventions</td></tr><tr><td>Issue-classification criteria</td><td>Map to the organization's issue types</td></tr><tr><td>Review decision criteria</td><td>Adjust REGENERATE thresholds and retry counts</td></tr><tr><td>Coding guide</td><td>Add project-specific rules to the <code>references/</code> directory</td></tr></tbody></table>
<h3 lang="en" id="비용과-토큰-관리">Managing Cost and Tokens</h3>
<p lang="en">One run of the 11-stage pipeline makes numerous LLM calls. Without attention to cost, a single issue can consume tens of dollars.</p>
<p lang="en"><strong>Cost-optimization strategies:</strong></p>






























<table lang="en"><thead><tr><th>Strategy</th><th>Application</th><th>Effect</th></tr></thead><tbody><tr><td>Run tools first</td><td>Use <code>tsc --noEmit</code> for type checking and <code>eslint</code> for linting</td><td>Handle 2 of the 5 verification stages without an LLM</td></tr><tr><td>Separate context collection</td><td>Collect Jira data with a Python script and no LLM calls</td><td>Zero token cost for collection</td></tr><tr><td>Conditional execution</td><td>Skip BDD/E2E according to issue type</td><td>Save 2 stages for bug/refactor issues</td></tr><tr><td>Retry caps</td><td>Fix the maximum retry count per stage</td><td>Prevent runaway costs from infinite loops</td></tr></tbody></table>
<p lang="en">Actual costs vary greatly with project size, issue complexity, and model choice. I recommend starting with simple issues, measuring token consumption by stage, and expanding gradually.</p>
<h3 lang="en" id="성공-지표">Success Metrics</h3>
<p lang="en">Four metrics for judging whether the workflow is working:</p>






























<table lang="en"><thead><tr><th>Metric</th><th>Meaning</th><th>Signal for improvement</th></tr></thead><tbody><tr><td><strong>Pipeline completion rate</strong></td><td>Share of runs completing all 11 stages without human intervention</td><td>Low → improve skills in stages that fail frequently</td></tr><tr><td><strong>First-pass verification rate</strong></td><td>Share passing all five verification stages without a retry</td><td>Low → strengthen the development skill's coding guide</td></tr><tr><td><strong>Human escalation frequency</strong></td><td>Where the pipeline most often stops</td><td>Concentrated in one stage → improve that skill's instructions</td></tr><tr><td><strong>PR merge rate</strong></td><td>Share of automatically generated changes that pass review and are merged</td><td>Low → improve quality in the specification and planning stages</td></tr></tbody></table>
<p lang="en">Accumulating these metrics in reports provides evidence for improving the workflow. “The first-pass verification rate rose from 40% to 70%” quantifies the effect of strengthening the development skill's coding guide.</p>
<hr lang="en">
<h2 lang="en" id="마무리">Wrapping Up</h2>
<p lang="en">This 11-stage pipeline is the result of applying harness-engineering principles—structured context, architectural constraints, and feedback loops—to Jira-driven development automation.</p>
<blockquote lang="en">
<p><strong>Instead of asking the agent once to “write the code,” design an analysis → generation → verification pipeline as its harness.</strong></p>
</blockquote>
<p lang="en">Automatically collect context, refine it in stages, verify at every stage, and execute in an isolated environment. Each stage's output becomes the next stage's input. Retry on failure, and hand it to a human if retries cannot resolve it.</p>
<p lang="en">Ultimately, building a harness means defining **“what a good development process is”** in code. It turns the analysis → design → implementation → verification flow that human developers followed implicitly into a pipeline an agent can follow explicitly. The essence of software engineering does not change in the AI era. What changes is who carries it out.</p>
<hr lang="en">
<h3 lang="en" id="참고-자료">References</h3>
<ul lang="en">
<li><a href="https://developer.atlassian.com/cloud/jira/platform/rest/v2/">Atlassian Jira REST API v2</a></li>
<li><a href="https://git-scm.com/docs/git-worktree">Official Git Worktree documentation</a></li>
<li><a href="https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents">Anthropic — Effective Harnesses for Long-Running Agents</a></li>
<li><a href="/blogs/harness-engineer-20260309-v01">Harness Engineering: The Developer's New Role in the Age of AI Agents</a></li>
</ul>]]></content:encoded>
    </item>
    <item>
      <title><![CDATA[Harness Engineering: The Developer's New Role in the Age of AI Agents]]></title>
      <link>https://blog.funq.kr/en/blogs/harness-engineer-20260309-v01</link>
      <guid isPermaLink="true">https://blog.funq.kr/en/blogs/harness-engineer-20260309-v01</guid>
      <description><![CDATA[From writing code yourself to designing an environment where AI agents can work correctly. Tracing the evolution from Prompt to Context to Harness Engineering.]]></description>
      <category><![CDATA[ai]]></category><category><![CDATA[claude-code]]></category><category><![CDATA[automation]]></category><category><![CDATA[devops]]></category>
      <media:content url="https://blog.funq.kr/blog-cover/harness-engineer-20260309-v01.jpg" medium="image" type="image/jpeg"/>
      <pubDate>Mon, 09 Mar 2026 05:00:00 GMT</pubDate>
      <author>funqdev@gmail.com (fundev)</author>
      <content:encoded><![CDATA[<h2 lang="en" id="코드를-짜는-사람에서-에이전트를-일하게-만드는-사람으로">From Writing Code to Enabling Agents to Work</h2>
<p lang="en">OpenAI produced a million lines of production code in five months. The amount of application code written directly by a human was <strong>zero lines</strong>. Three engineers, later seven, built an internal beta product using only Codex agents and merged roughly 1,500 PRs.</p>
<p lang="en">What did the human engineers do in this experiment? They did not implement functions or fix bugs. Instead, they designed the repository structure, defined architectural rules, built linters and tests, and, when an agent got stuck, <strong>added new tools and constraints</strong> rather than rewriting the prompt.</p>
<p lang="en">This is **harness engineering**.</p>
<h2 lang="en" id="prompt--context--harness-세-단계의-진화">Prompt → Context → Harness: Three Stages of Evolution</h2>
<p lang="en">To understand harness engineering, first look at how our use of AI has evolved.</p>





























<table lang="en"><thead><tr><th>Stage</th><th>Period</th><th>Core question</th><th>Analogy</th></tr></thead><tbody><tr><td><strong>Prompt Engineering</strong></td><td>2022~2024</td><td>“How should I phrase it so it understands?”</td><td>Giving a horse voice commands</td></tr><tr><td><strong>Context Engineering</strong></td><td>2025</td><td>“What should I show it so it works well?”</td><td>Giving a horse a map and signposts</td></tr><tr><td><strong>Harness Engineering</strong></td><td>2026</td><td>“What environment lets it work on its own?”</td><td>Putting reins, saddles, fences, and roads in place to run ten horses at once</td></tr></tbody></table>
<p lang="en"><strong>Prompt engineering</strong> optimizes the text instructions sent in a single LLM call. It operates at the level of “Phrasing it this way gets a better answer.”</p>
<p lang="en"><strong>Context engineering</strong> goes one step further. It designs everything inside the context window: documents retrieved through RAG, tool definitions, message history, and memory. The question is how to curate the tokens the model sees.</p>
<p lang="en"><strong>Harness engineering</strong> designs everything <strong>outside</strong> the agent, from CI pipelines, evaluation loops, and linting rules to observability infrastructure and lifecycle management. It includes prompts and context, but starts from the recognition that those alone are insufficient.</p>
<p lang="en">Phil Schmid of Google DeepMind put it this way.</p>
<blockquote lang="en">
<p>The model is the CPU, and the harness is the operating system. However powerful the CPU, a poorly designed OS limits its performance.</p>
</blockquote>
<p lang="en">Mitchell Hashimoto, the creator of Terraform, established the term in his February 2026 post, “<a href="https://mitchellh.com/writing/my-ai-adoption-journey">My AI Adoption Journey</a>.” The central definition is: <strong>when AI makes a mistake, go beyond simply fixing it and design guidelines and tools that systematically prevent the same mistake from happening again.</strong></p>
<h2 lang="en" id="하네스의-세-가지-축">The Three Pillars of a Harness</h2>
<p lang="en">OpenAI's experiment revealed three core components of a harness.</p>
<h3 lang="en" id="1-컨텍스트-엔지니어링">1. Context Engineering</h3>
<p lang="en">Structure knowledge inside the repository so the agent can find and read the context it needs for its work.</p>
<p lang="en">OpenAI initially created one enormous AGENTS.md file. It failed because it wasted the context window. The team ultimately placed <strong>88 separate AGENTS.md files</strong> throughout the repository.</p>
<p lang="en">The key principle is to treat AGENTS.md as <strong>a table of contents rather than an encyclopedia</strong>. Keep it to around 100 lines, and put detailed design documents in <code>docs/</code>. The agent reads the contents and follows them to the documents it needs.</p>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="text" data-theme="github-dark"><code data-language="text" data-theme="github-dark" style="display: grid;"><span data-line=""><span># AGENTS.md (목차 역할, ~100줄)</span></span>
<span data-line=""><span>├── 프로젝트 개요</span></span>
<span data-line=""><span>├── 아키텍처 계층 규칙 → docs/architecture.md</span></span>
<span data-line=""><span>├── 코딩 컨벤션 → docs/conventions.md</span></span>
<span data-line=""><span>├── 테스트 가이드 → docs/testing.md</span></span>
<span data-line=""><span>├── 품질 지표 → docs/quality-scores.md</span></span>
<span data-line=""><span>└── 기술 부채 목록 → docs/tech-debt.md</span></span></code></pre></figure>
<p lang="en">Knowledge outside the repository—in Slack, Notion, or verbal agreements—is also invisible to the agent. Every important decision and pattern needs to be incorporated into the repository as code or documentation. <strong>The repository must become the single source of truth.</strong></p>
<h3 lang="en" id="2-아키텍처-제약">2. Architectural Constraints</h3>
<p lang="en">Mechanically enforce “You must not do this,” rather than merely telling the agent, “Do it this way.”</p>
<p lang="en">OpenAI defined strict dependency layers for each business domain.</p>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="text" data-theme="github-dark"><code data-language="text" data-theme="github-dark" style="display: grid;"><span data-line=""><span>Types → Config → Repo → Service → Runtime → UI</span></span>
<span data-line=""> </span>
<span data-line=""><span>규칙: 상위 레이어는 하위 레이어만 참조할 수 있다.</span></span>
<span data-line=""><span>       UI가 Types를 직접 참조하는 것은 허용되지만,</span></span>
<span data-line=""><span>       Types가 Service를 참조하는 것은 금지된다.</span></span></code></pre></figure>
<p lang="en"><strong>Custom linters and structural tests</strong> enforce these rules in CI. PRs that violate them are blocked automatically.</p>
<p lang="en">There is another key design choice: <strong>the linter's error messages themselves act as repair instructions</strong>. The messages are designed so that when an agent encounters a violation, it can read the error and fix it independently.</p>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="text" data-theme="github-dark"><code data-language="text" data-theme="github-dark" style="display: grid;"><span data-line=""><span>❌ 기존: "Layer violation detected"</span></span>
<span data-line=""><span>✅ 개선: "Layer violation: Runtime cannot import from UI.</span></span>
<span data-line=""><span>         Move the shared type to Types layer (src/types/).</span></span>
<span data-line=""><span>         See docs/architecture.md#layer-rules for details."</span></span></code></pre></figure>
<h3 lang="en" id="3-가비지-컬렉션">3. Garbage Collection</h3>
<p lang="en">As agents generate code, they copy bad patterns too, gradually eroding the architecture. OpenAI called this **“AI slop.”**</p>
<p lang="en">The solution is not manual cleanup. It is to <strong>encode “golden principles” in the repository and let background Codex tasks detect drift and automatically create refactoring PRs</strong>. Instead of a human spending every Friday cleaning up technical debt left by AI, another AI cleans it up automatically every day.</p>
<p lang="en">A harness is not a one-time setup. It is closer to <strong>an operating system</strong> that codifies rules and applies them automatically every day.</p>
<h2 lang="en" id="하네스-엔지니어가-실제로-하는-일">What a Harness Engineer Actually Does</h2>
<p lang="en">There are no official job postings with the title “Harness Engineer” yet, but functionally equivalent roles already exist. Here is what the work involves.</p>
<h3 lang="en" id="환경-설계">Environment Design</h3>
<p lang="en">Make the repository agent-friendly. Use AGENTS.md as a table of contents and structured <code>docs/</code> as the source of truth. Version execution plans, design documents, technical debt, and even quality scores inside the repository. The goal is to let the agent <strong>work from the repository alone</strong>, rather than from knowledge in people's heads.</p>
<h3 lang="en" id="도구-설계">Tool Design</h3>
<p lang="en">Carefully design the number, names, and return values of the tools an agent will use. Anthropic outlined the following principles for good agent tools.</p>

























<table lang="en"><thead><tr><th>Principle</th><th>Description</th></tr></thead><tbody><tr><td>An appropriate number of tools</td><td>Too many cause choice paralysis; too few limit capability</td></tr><tr><td>Clear naming</td><td>A tool's name alone should reveal its purpose</td></tr><tr><td>High-signal return values</td><td>Return meaningful results</td></tr><tr><td>Token-efficient responses</td><td>Reduce unnecessary information to conserve context</td></tr></tbody></table>
<p lang="en">A Vercel experiment supports this. After <strong>removing 80% of the tools</strong> from a text-to-SQL agent and leaving only a single bash execution tool, its success rate rose from 80% <strong>to 100%</strong>. The number of steps, token use, and response time all fell too.</p>
<h3 lang="en" id="제약-설계">Constraint Design</h3>
<p lang="en">Define architectural layers, dependency rules, and type, test, and lint rules, then enforce them through tools. The key is to <strong>enforce invariants without micromanaging the implementation</strong>. Set the boundaries of what is forbidden and let the agent decide how to implement the rest.</p>
<h3 lang="en" id="피드백-루프-설계">Feedback-Loop Design</h3>
<p lang="en">Design how the agent responds to test failures, lint errors, and deteriorating observability metrics. Treat every agent failure as <strong>a signal to improve the harness</strong>. If the agent repeats the same mistake, add a lint rule or test rather than rewriting the prompt.</p>
<h3 lang="en" id="장기-실행-에이전트를-위한-메모리와-핸드오프">Memory and Handoffs for Long-Running Agents</h3>
<p lang="en">Anthropic introduced an <strong>initializer-agent pattern</strong> for long-running agents.</p>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="text" data-theme="github-dark"><code data-language="text" data-theme="github-dark" style="display: grid;"><span data-line=""><span>1. 첫 컨텍스트 윈도우: 초기화 전용 프롬프트로 환경 셋업</span></span>
<span data-line=""><span>2. init.sh 실행, 200개+ 기능 목록 생성 (모두 "failing" 표시)</span></span>
<span data-line=""><span>3. progress 파일(JSON) 생성</span></span>
<span data-line=""><span>4. 새 컨텍스트 윈도우가 열려도 progress 파일을 읽어 작업 이어감</span></span>
<span data-line=""><span>5. git commit 로그로 진행 상황 추적</span></span></code></pre></figure>
<p lang="en">A key finding: <strong>JSON works better than Markdown</strong> for feature tracking because agents are less likely to make arbitrary edits to structured data. A harness engineer is effectively building **“an onboarding system for an AI team.”**</p>
<h3 lang="en" id="엔트로피-감소-루프">Entropy-Reduction Loops</h3>
<p lang="en">Encode golden principles in the repository and let background agents automatically open quality-check and refactoring PRs. A harness is <strong>a continuously operated system</strong>, not something configured once and forgotten.</p>
<h2 lang="en" id="실무-사례-하네스가-만든-차이">Practical Examples: The Difference a Harness Makes</h2>
<h3 lang="en" id="openai--100만-줄-수동-코드-0줄">OpenAI: One Million Lines, Zero Handwritten Code</h3>

































<table lang="en"><thead><tr><th>Metric</th><th>Value</th></tr></thead><tbody><tr><td>Duration</td><td>5 months</td></tr><tr><td>Code volume</td><td>1 million+ lines</td></tr><tr><td>PR count</td><td>~1,500</td></tr><tr><td>Team size</td><td>3 → 7 engineers</td></tr><tr><td>PRs per engineer per day</td><td>3.5 on average</td></tr><tr><td>Code written directly by humans</td><td>0 lines</td></tr></tbody></table>
<p lang="en">The central slogan: <strong>“Humans steer. Agents execute.”</strong> Humans set the direction; agents carry out the work.</p>
<h3 lang="en" id="stripe--minions-시스템">Stripe: The Minions System</h3>
<p lang="en">Stripe's Minions system produces <strong>more than 1,000 merged PRs per week</strong>. A developer posts a task in Slack, and an agent writes code in an isolated development environment (devbox), gets it through CI, and opens a PR for human review.</p>
<p lang="en">It can access more than 400 internal tools through an MCP server, but <strong>curates only about 15 tools per session</strong> to avoid “token paralysis.” The essence of a harness is providing the right tools, rather than simply providing many.</p>
<h3 lang="en" id="krafton--모델이-아닌-하네스의-차이">KRAFTON: The Difference Comes from the Harness</h3>
<p lang="en">Korea's KRAFTON used its own “Terminus-KIRA” harness to achieve <strong>second place worldwide</strong> on TerminalBench 2.0 with 74.8% accuracy, just 0.3 percentage points behind OpenAI at 75.1%. It demonstrates that <strong>the same model can produce different results depending on the harness</strong>.</p>
<h3 lang="en" id="langchain--벤치마크로-증명하다">LangChain: Demonstrating It with a Benchmark</h3>
<p lang="en">LangChain raised its benchmark score through harness improvements alone, without changing the model.</p>






























<table lang="en"><thead><tr><th>Change</th><th>Before</th><th>After</th></tr></thead><tbody><tr><td>Model</td><td>Same</td><td>Same</td></tr><tr><td>Harness</td><td>Default</td><td>Improved</td></tr><tr><td>Score</td><td>52.8%</td><td><strong>66.5%</strong></td></tr><tr><td>Rank</td><td>Outside the top 30</td><td><strong>5th</strong></td></tr></tbody></table>
<p lang="en">The key technique is the <strong>“reasoning sandwich”</strong> pattern: allocate more reasoning compute to planning and verification, and less to implementation. Think deeply; write code quickly.</p>
<h3 lang="en" id="anthropic--claude-code-하네스">Anthropic: The Claude Code Harness</h3>
<p lang="en">It introduced a pattern separating the initializer agent from the coding agent. Even when a fresh context window opens during a long task, progress files and git logs allow the work to continue.</p>
<h2 lang="en" id="핵심-원칙-정리">Core Principles</h2>
<p lang="en">These harness-engineering principles appear across the examples.</p>
<h3 lang="en" id="모델이-아니라-하네스가-경쟁력이다">The Harness Is the Competitive Advantage</h3>
<p lang="en">Models are becoming more general-purpose and cheaper. The real differentiation is in the environment where the agent works. The KRAFTON and LangChain examples demonstrate this.</p>
<h3 lang="en" id="agentsmd는-매뉴얼이-아니라-지도다">AGENTS.md Is a Map, Not a Manual</h3>
<p lang="en">Do not try to explain everything in detail. Focus on its role as <strong>a table of contents</strong> that tells the agent where to find things. OpenAI used 88 separate files instead of one giant AGENTS.md.</p>
<h3 lang="en" id="불변식을-강제하되-구현은-맡겨라">Enforce Invariants and Leave the Implementation to the Agent</h3>
<p lang="en">Linters and tests define only what must not be done. Leave the specific implementation to the agent's discretion. Micromanagement is inefficient whether it happens in the prompt or the harness.</p>
<h3 lang="en" id="실패를-자산화하라">Turn Failures into Assets</h3>
<p lang="en">An agent's failure is a defect in the harness. Feed failures back into documentation, tests, and tool improvements so the same problem does not recur. <strong>Add a new constraint instead of “prompting harder.”</strong></p>
<h3 lang="en" id="레포지토리를-유일한-진실의-원천으로-만들어라">Make the Repository the Single Source of Truth</h3>
<p lang="en">Agreements in Slack, conventions in Notion, decisions conveyed verbally—the agent cannot see any of them. Everything important must be incorporated into the repository.</p>
<h2 lang="en" id="시장-동향">Market Trends</h2>
<h3 lang="en" id="빅테크-ai-코드-생성-비율">AI-Generated Code at Big Tech Companies</h3>

























<table lang="en"><thead><tr><th>Company</th><th>Share of AI-generated code</th><th>Source</th></tr></thead><tbody><tr><td>Google</td><td>30%+ of new code</td><td>Sundar Pichai</td></tr><tr><td>Microsoft</td><td>20~30%</td><td>Satya Nadella</td></tr><tr><td>Meta</td><td>Targeting 50% within one year</td><td>Mark Zuckerberg</td></tr></tbody></table>
<h3 lang="en" id="채용-시장">The Hiring Market</h3>
<p lang="en">As of March 2026, there are no job postings titled “Harness Engineer” yet. Functionally equivalent roles are being recruited under titles such as Agentic AI Specialist and AI Infrastructure Engineer. AI engineering positions are growing <strong>300% faster</strong> than traditional software engineering roles.</p>
<h3 lang="en" id="국내-동향">Trends in Korea</h3>
<p lang="en">**Toss** introduced harness engineering on its technical blog as a mechanism for raising the floor of organizational productivity. Its argument is that LLM use “cannot remain a matter of individual intuition; it must become a system that the team designs and deploys.”</p>
<p lang="en">At Korea's first <strong>Ralph-ton hackathon</strong>, 13 elite developers set up harnesses on the first day, let their agents run autonomously overnight, and reviewed the results the next day. The winning team generated <strong>100,000 lines of code</strong> with agents, of which 70,000 were tests.</p>
<h2 lang="en" id="한계와-열린-질문들">Limitations and Open Questions</h2>
<p lang="en">Harness engineering is an interesting direction, but it has its critics.</p>





























<table lang="en"><thead><tr><th>Criticism</th><th>Details</th></tr></thead><tbody><tr><td><strong>Insufficient functional verification</strong></td><td>Thoughtworks' Birgitta Böckeler noted that OpenAI's example handles structural constraints well but does not discuss behavioral verification sufficiently</td></tr><tr><td><strong>Limits of the metaphor</strong></td><td>The “harness” metaphor can frame AI solely as something to control and underestimate the cognitive and social aspects of human-agent collaboration</td></tr><tr><td><strong>Long-term maintainability</strong></td><td>It remains unknown whether an entirely agent-generated codebase can maintain architectural consistency over several years</td></tr><tr><td><strong>Difficulty applying it to legacy systems</strong></td><td>Building a harness for a new repository differs in difficulty from retrofitting one to an old codebase</td></tr><tr><td><strong>Self-reporting bias</strong></td><td>OpenAI's case is a self-report of its own experiment, with limited independent verification</td></tr></tbody></table>
<p lang="en">Thoughtworks described it as a term that was “two weeks old.” It is not yet a standardized official job title. The direction, however, is clear.</p>
<h2 lang="en" id="하네스-도구-및-프레임워크">Harness Tools and Frameworks</h2>



































<table lang="en"><thead><tr><th>Tool</th><th>Features</th><th>Role in a harness</th></tr></thead><tbody><tr><td><strong>AGENTS.md</strong></td><td>Adopted by 60,000+ GitHub repositories; under the Linux Foundation</td><td>Standardizes agent instructions</td></tr><tr><td><strong>CLAUDE.md</strong></td><td>The agent harness behind Anthropic's Claude Code</td><td>Manages long-running agents</td></tr><tr><td><strong>LangGraph</strong></td><td>Middleware-based agent orchestration</td><td>Harness middleware such as LoopDetection and ReasoningSandwich</td></tr><tr><td><strong>CrewAI</strong></td><td>Role-based multi-agent orchestration</td><td>Dual structure: Crews for dynamic collaboration and Flows for deterministic tasks</td></tr><tr><td><strong>OpenAI Codex</strong></td><td>Agent-based coding + App Server</td><td>Long-running autonomy (6+ hours) + Reusable harness protocol</td></tr></tbody></table>
<p lang="en">OpenAI designed a JSON-RPC server called <strong>Codex App Server</strong> to reuse the Codex harness across products. The CLI, VS Code extension, web app, and macOS app all run on the same harness. This shows that harness engineering goes beyond writing new prompts for each app to encompass <strong>designing a reusable agent execution environment</strong>.</p>
<h2 lang="en" id="지금-할-수-있는-것">What You Can Do Now</h2>
<p lang="en">An article on Martin Fowler's site asked whether harnesses will become **“the new service templates.”** Just as teams spin up new services through golden paths today, tomorrow they may choose from a harness catalog bundling custom linters, structural tests, AGENTS.md, and cleanup agents.</p>
<p lang="en">You do not need to build a grand system all at once. Here are things you can start doing now.</p>
<ol lang="en">
<li><strong>Write AGENTS.md (or CLAUDE.md)</strong>—summarize the repository's structure, rules, and main patterns in no more than 100 lines</li>
<li><strong>Make linter errors agent-friendly</strong>—include how to fix the problem, rather than just saying there is a violation</li>
<li><strong>Break work into verifiable units</strong>—split tasks into small units whose success or failure can be determined through tests</li>
<li><strong>Document agent failures</strong>—when a failure repeats, add a constraint instead of another prompt</li>
<li><strong>Move knowledge into the repository</strong>—document conventions and decisions that exist only in Slack or Notion under <code>docs/</code></li>
</ol>
<h2 lang="en" id="마무리">Wrapping Up</h2>
<p lang="en">The central thesis of harness engineering is simple.</p>
<blockquote lang="en">
<p><strong>As models become increasingly general-purpose, the harness determines whether an agent succeeds or fails.</strong></p>
</blockquote>
<p lang="en">The ability to <strong>design an environment where agents produce correct code</strong> is becoming more important than writing clean code yourself. From writing code to enabling agents to work: that shift in the developer's role has already begun.</p>
<hr lang="en">
<h3 lang="en" id="참고-자료">References</h3>
<ul lang="en">
<li><a href="https://openai.com/index/harness-engineering/">OpenAI - Harness Engineering</a></li>
<li><a href="https://mitchellh.com/writing/my-ai-adoption-journey">Mitchell Hashimoto - My AI Adoption Journey</a></li>
<li><a href="https://martinfowler.com/articles/exploring-gen-ai/harness-engineering.html">Martin Fowler - Harness Engineering</a></li>
<li><a href="https://blog.langchain.com/improving-deep-agents-with-harness-engineering/">LangChain - Improving Deep Agents with Harness Engineering</a></li>
<li><a href="https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents">Anthropic - Effective Harnesses for Long-Running Agents</a></li>
<li><a href="https://toss.tech/article/harness-for-team-productivity">Toss technical blog: Improving team productivity through harnesses</a></li>
</ul>]]></content:encoded>
    </item>
    <item>
      <title><![CDATA[AIForge: An Unattended Automation Workflow System for Claude Code]]></title>
      <link>https://blog.funq.kr/en/blogs/aiforge-20260309-v01</link>
      <guid isPermaLink="true">https://blog.funq.kr/en/blogs/aiforge-20260309-v01</guid>
      <description><![CDATA[I built a web dashboard that runs Claude Code CLI unattended through cron scheduling and JIRA issue polling.]]></description>
      <category><![CDATA[claude-code]]></category><category><![CDATA[python]]></category><category><![CDATA[fastapi]]></category><category><![CDATA[automation]]></category><category><![CDATA[ai]]></category>
      <media:content url="https://blog.funq.kr/blog-cover/aiforge-20260309-v01.jpg" medium="image" type="image/jpeg"/>
      <pubDate>Mon, 09 Mar 2026 01:00:00 GMT</pubDate>
      <author>funqdev@gmail.com (fundev)</author>
      <content:encoded><![CDATA[<h2 lang="en" id="claude-code-사람-없이-돌릴-수-없을까">Could Claude Code Run Without a Human?</h2>
<p lang="en">Claude Code CLI is powerful. Code reviews, document generation, and even bug fixes take just one terminal command. The drawback is that a person still has to enter that command every time.</p>
<p lang="en">What if it could check JIRA issues at 8 every morning, ask Claude to analyze any new ones, and stop on its own when token usage gets too high?</p>
<p lang="en"><strong>AIForge</strong> is the system I built to solve that problem: a web dashboard for running Claude Code CLI unattended through cron scheduling and JIRA issue polling.</p>
<blockquote lang="en">
<p>GitHub: <a href="https://github.com/nasodev/claude-aiforge">nasodev/claude-aiforge</a></p>
</blockquote>
<h2 lang="en" id="어떤-시스템인가">What Kind of System Is It?</h2>
<p lang="en">AIForge is a FastAPI-based web application with three core features.</p>





















<table lang="en"><thead><tr><th>Feature</th><th>Description</th></tr></thead><tbody><tr><td><strong>Cron scheduling</strong></td><td>Run Claude Code automatically at specified times using APScheduler</td></tr><tr><td><strong>JIRA issue polling</strong></td><td>Find matching JIRA issues and process each automatically</td></tr><tr><td><strong>Token management</strong></td><td>Monitor claude.ai usage and pause automatically when limits are exceeded</td></tr></tbody></table>
<h3 lang="en" id="기술-스택">Technology Stack</h3>

































<table lang="en"><thead><tr><th>Area</th><th>Technology</th></tr></thead><tbody><tr><td>Backend</td><td>Python 3.12+, FastAPI, Uvicorn</td></tr><tr><td>Database</td><td>SQLite (WAL mode), aiosqlite</td></tr><tr><td>Scheduler</td><td>APScheduler 3 (AsyncIO)</td></tr><tr><td>Frontend</td><td>Jinja2 SSR, Tailwind CSS (Dark theme)</td></tr><tr><td>Process</td><td>psutil (PID monitoring)</td></tr><tr><td>Port</td><td>8010</td></tr></tbody></table>
<h2 lang="en" id="아키텍처">Architecture</h2>
<p lang="en">The system consists of four layers.</p>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="text" data-theme="github-dark"><code data-language="text" data-theme="github-dark" style="display: grid;"><span data-line=""><span>┌─ FastAPI Web Server (:8010)</span></span>
<span data-line=""><span>│  ├─ Routes (Dashboard, Projects, Schedules, Executions, Settings, Logs)</span></span>
<span data-line=""><span>│  ├─ Jinja2 Templates (SSR 다크 테마 UI)</span></span>
<span data-line=""><span>│  └─ Static Assets (CSS, Icons)</span></span>
<span data-line=""><span>│</span></span>
<span data-line=""><span>├─ APScheduler (AsyncIO)</span></span>
<span data-line=""><span>│  ├─ System Jobs (토큰 체크, 로그 모니터링)</span></span>
<span data-line=""><span>│  └─ Project Schedules (크론 기반 실행)</span></span>
<span data-line=""><span>│</span></span>
<span data-line=""><span>├─ Services Layer</span></span>
<span data-line=""><span>│  ├─ Executor (async/sync 모드 Claude CLI 실행)</span></span>
<span data-line=""><span>│  ├─ Log Checker (PID 모니터링, 로그 파싱)</span></span>
<span data-line=""><span>│  └─ Scheduler (Job 등록 및 생명주기 관리)</span></span>
<span data-line=""><span>│</span></span>
<span data-line=""><span>└─ SQLite Database (WAL mode)</span></span>
<span data-line=""><span>   ├─ projects</span></span>
<span data-line=""><span>   ├─ schedules</span></span>
<span data-line=""><span>   ├─ executions</span></span>
<span data-line=""><span>   └─ settings</span></span></code></pre></figure>
<p lang="en">The key design principle is <strong>using Claude CLI as a general-purpose executor</strong>. Instead of building a separate JIRA API client, it handles JIRA searches and tasks through Claude CLI and browser automation with <code>--chrome</code>.</p>
<h2 lang="en" id="데이터-모델">Data Model</h2>
<p lang="en">Four tables manage the entire system's state.</p>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="sql" data-theme="github-dark"><code data-language="sql" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#F97583">CREATE</span><span style="color:#F97583"> TABLE</span><span style="color:#B392F0"> IF</span><span style="color:#F97583"> NOT</span><span style="color:#F97583"> EXISTS</span><span style="color:#E1E4E8"> projects (</span></span>
<span data-line=""><span style="color:#E1E4E8">    id </span><span style="color:#F97583">INTEGER</span><span style="color:#F97583"> PRIMARY KEY</span><span style="color:#E1E4E8"> AUTOINCREMENT,</span></span>
<span data-line=""><span style="color:#F97583">    name</span><span style="color:#F97583"> TEXT</span><span style="color:#F97583"> NOT NULL</span><span style="color:#E1E4E8">,</span></span>
<span data-line=""><span style="color:#F97583">    type</span><span style="color:#F97583"> TEXT</span><span style="color:#F97583"> NOT NULL</span><span style="color:#F97583"> CHECK</span><span style="color:#E1E4E8"> (</span><span style="color:#F97583">type</span><span style="color:#F97583"> IN</span><span style="color:#E1E4E8"> (</span><span style="color:#9ECBFF">'jira'</span><span style="color:#E1E4E8">, </span><span style="color:#9ECBFF">'schedule'</span><span style="color:#E1E4E8">)),</span></span>
<span data-line=""><span style="color:#F97583">    description</span><span style="color:#F97583"> TEXT</span><span style="color:#E1E4E8">,</span></span>
<span data-line=""><span style="color:#E1E4E8">    jira_search_conditions </span><span style="color:#F97583">TEXT</span><span style="color:#E1E4E8">,  </span><span style="color:#6A737D">-- JIRA 타입일 때 검색 조건</span></span>
<span data-line=""><span style="color:#F97583">    enabled</span><span style="color:#F97583"> INTEGER</span><span style="color:#F97583"> DEFAULT</span><span style="color:#79B8FF"> 1</span><span style="color:#E1E4E8">,</span></span>
<span data-line=""><span style="color:#E1E4E8">    created_at </span><span style="color:#F97583">TIMESTAMP</span><span style="color:#F97583"> DEFAULT</span><span style="color:#E1E4E8"> CURRENT_TIMESTAMP,</span></span>
<span data-line=""><span style="color:#E1E4E8">    updated_at </span><span style="color:#F97583">TIMESTAMP</span><span style="color:#F97583"> DEFAULT</span><span style="color:#E1E4E8"> CURRENT_TIMESTAMP</span></span>
<span data-line=""><span style="color:#E1E4E8">);</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F97583">CREATE</span><span style="color:#F97583"> TABLE</span><span style="color:#B392F0"> IF</span><span style="color:#F97583"> NOT</span><span style="color:#F97583"> EXISTS</span><span style="color:#E1E4E8"> schedules (</span></span>
<span data-line=""><span style="color:#E1E4E8">    id </span><span style="color:#F97583">INTEGER</span><span style="color:#F97583"> PRIMARY KEY</span><span style="color:#E1E4E8"> AUTOINCREMENT,</span></span>
<span data-line=""><span style="color:#E1E4E8">    project_id </span><span style="color:#F97583">INTEGER</span><span style="color:#F97583"> NOT NULL</span><span style="color:#E1E4E8">,</span></span>
<span data-line=""><span style="color:#F97583">    name</span><span style="color:#F97583"> TEXT</span><span style="color:#F97583"> NOT NULL</span><span style="color:#E1E4E8">,</span></span>
<span data-line=""><span style="color:#E1E4E8">    prompt_template </span><span style="color:#F97583">TEXT</span><span style="color:#F97583"> NOT NULL</span><span style="color:#E1E4E8">,  </span><span style="color:#6A737D">-- Claude에게 보낼 프롬프트</span></span>
<span data-line=""><span style="color:#E1E4E8">    work_dir </span><span style="color:#F97583">TEXT</span><span style="color:#F97583"> NOT NULL</span><span style="color:#E1E4E8">,         </span><span style="color:#6A737D">-- Claude CLI 작업 디렉토리</span></span>
<span data-line=""><span style="color:#E1E4E8">    cron_expr </span><span style="color:#F97583">TEXT</span><span style="color:#F97583"> NOT NULL</span><span style="color:#E1E4E8">,        </span><span style="color:#6A737D">-- 5-field 크론 표현식</span></span>
<span data-line=""><span style="color:#F97583">    status</span><span style="color:#F97583"> TEXT</span><span style="color:#F97583"> DEFAULT</span><span style="color:#9ECBFF"> 'idle'</span></span>
<span data-line=""><span style="color:#F97583">        CHECK</span><span style="color:#E1E4E8"> (</span><span style="color:#F97583">status</span><span style="color:#F97583"> IN</span><span style="color:#E1E4E8"> (</span><span style="color:#9ECBFF">'idle'</span><span style="color:#E1E4E8">, </span><span style="color:#9ECBFF">'running'</span><span style="color:#E1E4E8">, </span><span style="color:#9ECBFF">'paused'</span><span style="color:#E1E4E8">, </span><span style="color:#9ECBFF">'error'</span><span style="color:#E1E4E8">)),</span></span>
<span data-line=""><span style="color:#F97583">    FOREIGN KEY</span><span style="color:#E1E4E8"> (project_id) </span><span style="color:#F97583">REFERENCES</span><span style="color:#E1E4E8"> projects(id) </span><span style="color:#F97583">ON DELETE CASCADE</span></span>
<span data-line=""><span style="color:#E1E4E8">);</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F97583">CREATE</span><span style="color:#F97583"> TABLE</span><span style="color:#B392F0"> IF</span><span style="color:#F97583"> NOT</span><span style="color:#F97583"> EXISTS</span><span style="color:#E1E4E8"> executions (</span></span>
<span data-line=""><span style="color:#E1E4E8">    id </span><span style="color:#F97583">INTEGER</span><span style="color:#F97583"> PRIMARY KEY</span><span style="color:#E1E4E8"> AUTOINCREMENT,</span></span>
<span data-line=""><span style="color:#E1E4E8">    schedule_id </span><span style="color:#F97583">INTEGER</span><span style="color:#F97583"> NOT NULL</span><span style="color:#E1E4E8">,</span></span>
<span data-line=""><span style="color:#E1E4E8">    pid </span><span style="color:#F97583">INTEGER</span><span style="color:#E1E4E8">,               </span><span style="color:#6A737D">-- OS 프로세스 ID</span></span>
<span data-line=""><span style="color:#F97583">    status</span><span style="color:#F97583"> TEXT</span><span style="color:#F97583"> DEFAULT</span><span style="color:#9ECBFF"> 'running'</span></span>
<span data-line=""><span style="color:#F97583">        CHECK</span><span style="color:#E1E4E8"> (</span><span style="color:#F97583">status</span><span style="color:#F97583"> IN</span><span style="color:#E1E4E8"> (</span><span style="color:#9ECBFF">'running'</span><span style="color:#E1E4E8">, </span><span style="color:#9ECBFF">'success'</span><span style="color:#E1E4E8">, </span><span style="color:#9ECBFF">'error'</span><span style="color:#E1E4E8">, </span><span style="color:#9ECBFF">'timeout'</span><span style="color:#E1E4E8">, </span><span style="color:#9ECBFF">'killed'</span><span style="color:#E1E4E8">)),</span></span>
<span data-line=""><span style="color:#E1E4E8">    command </span><span style="color:#F97583">TEXT</span><span style="color:#E1E4E8">,               </span><span style="color:#6A737D">-- 실행된 CLI 명령어</span></span>
<span data-line=""><span style="color:#E1E4E8">    issue_key </span><span style="color:#F97583">TEXT</span><span style="color:#E1E4E8">,             </span><span style="color:#6A737D">-- JIRA 이슈 키 (있는 경우)</span></span>
<span data-line=""><span style="color:#E1E4E8">    log_path </span><span style="color:#F97583">TEXT</span><span style="color:#E1E4E8">,              </span><span style="color:#6A737D">-- Claude 로컬 로그 경로</span></span>
<span data-line=""><span style="color:#E1E4E8">    duration_seconds </span><span style="color:#F97583">INTEGER</span><span style="color:#E1E4E8">,</span></span>
<span data-line=""><span style="color:#F97583">    FOREIGN KEY</span><span style="color:#E1E4E8"> (schedule_id) </span><span style="color:#F97583">REFERENCES</span><span style="color:#E1E4E8"> schedules(id) </span><span style="color:#F97583">ON DELETE CASCADE</span></span>
<span data-line=""><span style="color:#E1E4E8">);</span></span></code></pre></figure>
<p lang="en">The relationships are simple: <strong>Project → Schedules → Executions</strong>. CASCADE DELETE cleans up all child data when a project is deleted.</p>
<h2 lang="en" id="두-가지-실행-모드">Two Execution Modes</h2>
<p lang="en">AIForge runs Claude CLI in two modes, depending on the task.</p>
<h3 lang="en" id="fire-and-forget-비동기-모드">Fire-and-Forget (Asynchronous Mode)</h3>
<p lang="en">This is for project work. It launches Claude CLI as a subprocess, records only the PID, and returns immediately.</p>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="python" data-theme="github-dark"><code data-language="python" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#F97583">async</span><span style="color:#F97583"> def</span><span style="color:#B392F0"> execute_async</span><span style="color:#E1E4E8">(</span></span>
<span data-line=""><span style="color:#E1E4E8">    prompt: </span><span style="color:#79B8FF">str</span><span style="color:#E1E4E8">,</span></span>
<span data-line=""><span style="color:#E1E4E8">    work_dir: </span><span style="color:#79B8FF">str</span><span style="color:#E1E4E8">,</span></span>
<span data-line=""><span style="color:#E1E4E8">    issue_key: </span><span style="color:#79B8FF">str</span><span style="color:#F97583"> |</span><span style="color:#79B8FF"> None</span><span style="color:#F97583"> =</span><span style="color:#79B8FF"> None</span></span>
<span data-line=""><span style="color:#E1E4E8">) -> </span><span style="color:#79B8FF">dict</span><span style="color:#E1E4E8">:</span></span>
<span data-line=""><span style="color:#9ECBFF">    """Fire-and-forget 실행. PID 기록 후 즉시 반환."""</span></span>
<span data-line=""><span style="color:#E1E4E8">    env </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> os.environ.copy()</span></span>
<span data-line=""><span style="color:#E1E4E8">    env.pop(</span><span style="color:#9ECBFF">"CLAUDECODE"</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">None</span><span style="color:#E1E4E8">)  </span><span style="color:#6A737D"># 재귀 실행 방지</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#E1E4E8">    cmd </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> [</span></span>
<span data-line=""><span style="color:#9ECBFF">        "claude"</span><span style="color:#E1E4E8">, </span><span style="color:#9ECBFF">"--chrome"</span><span style="color:#E1E4E8">,</span></span>
<span data-line=""><span style="color:#9ECBFF">        "--dangerously-skip-permissions"</span><span style="color:#E1E4E8">,</span></span>
<span data-line=""><span style="color:#9ECBFF">        "-p"</span><span style="color:#E1E4E8">, prompt</span></span>
<span data-line=""><span style="color:#E1E4E8">    ]</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#E1E4E8">    process </span><span style="color:#F97583">=</span><span style="color:#F97583"> await</span><span style="color:#E1E4E8"> asyncio.create_subprocess_exec(</span></span>
<span data-line=""><span style="color:#F97583">        *</span><span style="color:#E1E4E8">cmd,</span></span>
<span data-line=""><span style="color:#FFAB70">        cwd</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">work_dir,</span></span>
<span data-line=""><span style="color:#FFAB70">        env</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">env,</span></span>
<span data-line=""><span style="color:#FFAB70">        stdout</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">asyncio.subprocess.</span><span style="color:#79B8FF">PIPE</span><span style="color:#E1E4E8">,</span></span>
<span data-line=""><span style="color:#FFAB70">        stderr</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">asyncio.subprocess.</span><span style="color:#79B8FF">PIPE</span><span style="color:#E1E4E8">,</span></span>
<span data-line=""><span style="color:#E1E4E8">    )</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F97583">    return</span><span style="color:#E1E4E8"> {</span><span style="color:#9ECBFF">"pid"</span><span style="color:#E1E4E8">: process.pid, </span><span style="color:#9ECBFF">"status"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"running"</span><span style="color:#E1E4E8">}</span></span></code></pre></figure>
<p lang="en">Removing the <code>CLAUDECODE</code> environment variable is essential. Otherwise, Claude CLI recognizes that it has been invoked inside itself, causing a recursive-execution problem.</p>
<h3 lang="en" id="synchronous-동기-모드">Synchronous Mode</h3>
<p lang="en">This is for system tasks whose results need immediate parsing, such as checking token usage and polling JIRA issues.</p>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="python" data-theme="github-dark"><code data-language="python" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#F97583">async</span><span style="color:#F97583"> def</span><span style="color:#B392F0"> execute_sync</span><span style="color:#E1E4E8">(</span></span>
<span data-line=""><span style="color:#E1E4E8">    prompt: </span><span style="color:#79B8FF">str</span><span style="color:#E1E4E8">,</span></span>
<span data-line=""><span style="color:#E1E4E8">    work_dir: </span><span style="color:#79B8FF">str</span><span style="color:#E1E4E8">,</span></span>
<span data-line=""><span style="color:#E1E4E8">    timeout: </span><span style="color:#79B8FF">int</span><span style="color:#F97583"> =</span><span style="color:#79B8FF"> 180</span></span>
<span data-line=""><span style="color:#E1E4E8">) -> </span><span style="color:#79B8FF">dict</span><span style="color:#E1E4E8">:</span></span>
<span data-line=""><span style="color:#9ECBFF">    """동기 실행. 결과를 JSON으로 파싱하여 반환."""</span></span>
<span data-line=""><span style="color:#6A737D">    # ... subprocess 실행 ...</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#E1E4E8">    stdout, stderr </span><span style="color:#F97583">=</span><span style="color:#F97583"> await</span><span style="color:#E1E4E8"> asyncio.wait_for(</span></span>
<span data-line=""><span style="color:#E1E4E8">        process.communicate(), </span><span style="color:#FFAB70">timeout</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">timeout</span></span>
<span data-line=""><span style="color:#E1E4E8">    )</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#6A737D">    # 3단계 JSON 파싱 폴백</span></span>
<span data-line=""><span style="color:#E1E4E8">    result </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> stdout.decode()</span></span>
<span data-line=""><span style="color:#F97583">    try</span><span style="color:#E1E4E8">:</span></span>
<span data-line=""><span style="color:#F97583">        return</span><span style="color:#E1E4E8"> json.loads(result)           </span><span style="color:#6A737D"># 1. 순수 JSON</span></span>
<span data-line=""><span style="color:#F97583">    except</span><span style="color:#E1E4E8"> json.JSONDecodeError:</span></span>
<span data-line=""><span style="color:#6A737D">        # 2. ```json ... ``` 마크다운 블록에서 추출</span></span>
<span data-line=""><span style="color:#E1E4E8">        match </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> re.search(</span><span style="color:#F97583">r</span><span style="color:#9ECBFF">'</span><span style="color:#DBEDFF">```json</span><span style="color:#79B8FF">\s</span><span style="color:#F97583">*</span><span style="color:#79B8FF">(.</span><span style="color:#F97583">*?</span><span style="color:#79B8FF">)\s</span><span style="color:#F97583">*</span><span style="color:#DBEDFF">```</span><span style="color:#9ECBFF">'</span><span style="color:#E1E4E8">, result, re.</span><span style="color:#79B8FF">DOTALL</span><span style="color:#E1E4E8">)</span></span>
<span data-line=""><span style="color:#F97583">        if</span><span style="color:#E1E4E8"> match:</span></span>
<span data-line=""><span style="color:#F97583">            return</span><span style="color:#E1E4E8"> json.loads(match.group(</span><span style="color:#79B8FF">1</span><span style="color:#E1E4E8">))</span></span>
<span data-line=""><span style="color:#6A737D">        # 3. { ... } 객체 리터럴에서 추출</span></span>
<span data-line=""><span style="color:#E1E4E8">        match </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> re.search(</span><span style="color:#F97583">r</span><span style="color:#9ECBFF">'</span><span style="color:#85E89D;font-weight:bold">\{</span><span style="color:#79B8FF">.</span><span style="color:#F97583">*</span><span style="color:#85E89D;font-weight:bold">\}</span><span style="color:#9ECBFF">'</span><span style="color:#E1E4E8">, result, re.</span><span style="color:#79B8FF">DOTALL</span><span style="color:#E1E4E8">)</span></span>
<span data-line=""><span style="color:#F97583">        if</span><span style="color:#E1E4E8"> match:</span></span>
<span data-line=""><span style="color:#F97583">            return</span><span style="color:#E1E4E8"> json.loads(match.group(</span><span style="color:#79B8FF">0</span><span style="color:#E1E4E8">))</span></span></code></pre></figure>
<p lang="en">Claude's output is not always clean JSON, so extraction uses three fallbacks: plain JSON, a Markdown code block, and then an object literal.</p>
<h2 lang="en" id="jira-통합-api-없이-브라우저로">JIRA Integration: Through the Browser, Without the API</h2>
<p lang="en">JIRA integrations usually use its REST API. But authentication for the company's JIRA is complex, and configuring API permissions is cumbersome.</p>
<p lang="en">AIForge takes a different approach: manipulating JIRA directly through <strong>Claude CLI and Chrome browser automation</strong>.</p>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="text" data-theme="github-dark"><code data-language="text" data-theme="github-dark" style="display: grid;"><span data-line=""><span>1. 크론 트리거 → 스케줄 실행</span></span>
<span data-line=""><span>2. 토큰 한도 체크</span></span>
<span data-line=""><span>3. Claude 동기 실행: "다음 조건에 해당하는 JIRA 검색 해줘 {conditions}"</span></span>
<span data-line=""><span>4. JSON 파싱 → 이슈 키 목록 추출</span></span>
<span data-line=""><span>5. 이슈별로:</span></span>
<span data-line=""><span>   - 이미 실행 중인지 확인</span></span>
<span data-line=""><span>   - prompt_template에서 {issue_key} 치환</span></span>
<span data-line=""><span>   - Claude CLI 비동기 실행</span></span>
<span data-line=""><span>   - PID 기록</span></span>
<span data-line=""><span>6. 백그라운드 로그 체커가 완료 모니터링</span></span></code></pre></figure>
<p lang="en">The JIRA search prompt is structured as follows:</p>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="python" data-theme="github-dark"><code data-language="python" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#E1E4E8">poll_prompt </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> (</span></span>
<span data-line=""><span style="color:#F97583">    f</span><span style="color:#9ECBFF">"다음 조건에 해당하는 JIRA 검색 해줘 </span><span style="color:#79B8FF">{</span><span style="color:#E1E4E8">conditions</span><span style="color:#79B8FF">}</span><span style="color:#9ECBFF"> "</span></span>
<span data-line=""><span style="color:#9ECBFF">    "조회해서 json 으로 알려줘 출력에 다른값은 넣지 않고 json 만 넣어줘"</span></span>
<span data-line=""><span style="color:#E1E4E8">)</span></span></code></pre></figure>
<p lang="en">The format Claude returns:</p>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="json" data-theme="github-dark"><code data-language="json" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#E1E4E8">[{</span><span style="color:#79B8FF">"key"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"FAS-130"</span><span style="color:#E1E4E8">}, {</span><span style="color:#79B8FF">"key"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"FAS-131"</span><span style="color:#E1E4E8">}]</span></span></code></pre></figure>
<p lang="en">The advantage is that JIRA API authentication does not need to be configured. If the browser is already logged in, that session is used as it is.</p>
<h2 lang="en" id="토큰-관리">Token Management</h2>
<p lang="en">Claude Code has usage limits. AIForge checks token usage periodically and automatically pauses work when it approaches a limit.</p>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="text" data-theme="github-dark"><code data-language="text" data-theme="github-dark" style="display: grid;"><span data-line=""><span>1. 시스템 잡 (기본 60분 간격)</span></span>
<span data-line=""><span>2. Claude 동기 실행: "claude.ai/settings/usage 접속해서 사용량 확인"</span></span>
<span data-line=""><span>3. 세션 사용률 / 주간 사용률 파싱</span></span>
<span data-line=""><span>4. settings 테이블에 상태 저장</span></span>
<span data-line=""><span>5. 한도 초과 시 → 모든 프로젝트 자동 일시정지</span></span></code></pre></figure>
<p lang="en">These settings are easy to change in the UI.</p>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="python" data-theme="github-dark"><code data-language="python" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#E1E4E8">token_check_config </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> {</span></span>
<span data-line=""><span style="color:#9ECBFF">    "enabled"</span><span style="color:#E1E4E8">: </span><span style="color:#79B8FF">True</span><span style="color:#E1E4E8">,</span></span>
<span data-line=""><span style="color:#9ECBFF">    "interval_minutes"</span><span style="color:#E1E4E8">: </span><span style="color:#79B8FF">60</span><span style="color:#E1E4E8">,</span></span>
<span data-line=""><span style="color:#9ECBFF">    "session_limit_percent"</span><span style="color:#E1E4E8">: </span><span style="color:#79B8FF">80</span><span style="color:#E1E4E8">,   </span><span style="color:#6A737D"># 세션 사용량 80% 이상이면 정지</span></span>
<span data-line=""><span style="color:#9ECBFF">    "weekly_limit_percent"</span><span style="color:#E1E4E8">: </span><span style="color:#79B8FF">90</span><span style="color:#6A737D">     # 주간 사용량 90% 이상이면 정지</span></span>
<span data-line=""><span style="color:#E1E4E8">}</span></span></code></pre></figure>
<h2 lang="en" id="백그라운드-로그-모니터링">Background Log Monitoring</h2>
<p lang="en">How do we know when a fire-and-forget Claude CLI process has finished? A background log checker checks it periodically.</p>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="python" data-theme="github-dark"><code data-language="python" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#F97583">async</span><span style="color:#F97583"> def</span><span style="color:#B392F0"> check_running_executions</span><span style="color:#E1E4E8">():</span></span>
<span data-line=""><span style="color:#9ECBFF">    """실행 중인 모든 작업의 상태를 확인한다."""</span></span>
<span data-line=""><span style="color:#E1E4E8">    running </span><span style="color:#F97583">=</span><span style="color:#F97583"> await</span><span style="color:#E1E4E8"> get_running_executions()</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F97583">    for</span><span style="color:#E1E4E8"> execution </span><span style="color:#F97583">in</span><span style="color:#E1E4E8"> running:</span></span>
<span data-line=""><span style="color:#E1E4E8">        pid </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> execution[</span><span style="color:#9ECBFF">"pid"</span><span style="color:#E1E4E8">]</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#6A737D">        # PID가 아직 살아있는지 확인</span></span>
<span data-line=""><span style="color:#F97583">        if</span><span style="color:#F97583"> not</span><span style="color:#E1E4E8"> psutil.pid_exists(pid):</span></span>
<span data-line=""><span style="color:#6A737D">            # 프로세스가 종료됨 → 로그 파일 분석</span></span>
<span data-line=""><span style="color:#E1E4E8">            log_content </span><span style="color:#F97583">=</span><span style="color:#F97583"> await</span><span style="color:#E1E4E8"> read_log_file(execution[</span><span style="color:#9ECBFF">"log_path"</span><span style="color:#E1E4E8">])</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F97583">            if</span><span style="color:#E1E4E8"> has_error_pattern(log_content):</span></span>
<span data-line=""><span style="color:#F97583">                await</span><span style="color:#E1E4E8"> update_execution_status(execution[</span><span style="color:#9ECBFF">"id"</span><span style="color:#E1E4E8">], </span><span style="color:#9ECBFF">"error"</span><span style="color:#E1E4E8">)</span></span>
<span data-line=""><span style="color:#F97583">            else</span><span style="color:#E1E4E8">:</span></span>
<span data-line=""><span style="color:#F97583">                await</span><span style="color:#E1E4E8"> update_execution_status(execution[</span><span style="color:#9ECBFF">"id"</span><span style="color:#E1E4E8">], </span><span style="color:#9ECBFF">"success"</span><span style="color:#E1E4E8">)</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#6A737D">            # 실행 시간 계산 및 스케줄 상태 업데이트</span></span>
<span data-line=""><span style="color:#F97583">            await</span><span style="color:#E1E4E8"> update_schedule_status(execution[</span><span style="color:#9ECBFF">"schedule_id"</span><span style="color:#E1E4E8">], </span><span style="color:#9ECBFF">"idle"</span><span style="color:#E1E4E8">)</span></span></code></pre></figure>
<p lang="en">It uses psutil to check whether the PID exists. This is lighter than continuously polling stdout and can monitor hundreds of concurrent runs without difficulty.</p>
<h2 lang="en" id="설정-시스템-config-vs-status-분리">The Settings System: Separating Config from Status</h2>
<p lang="en">A key design choice is to divide settings data into two categories.</p>




















<table lang="en"><thead><tr><th>Category</th><th>Description</th><th>Example</th></tr></thead><tbody><tr><td><strong>Config</strong> (user intent)</td><td>Settings editable in the UI</td><td><code>token_check_config</code>, <code>log_monitor_config</code>, <code>global</code></td></tr><tr><td><strong>Status</strong> (runtime state)</td><td>State values updated automatically by the system</td><td><code>token_check_status</code>, <code>log_monitor_status</code></td></tr></tbody></table>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="python" data-theme="github-dark"><code data-language="python" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#6A737D"># Config 키 - 사용자가 변경</span></span>
<span data-line=""><span style="color:#79B8FF">CONFIG_KEYS</span><span style="color:#F97583"> =</span><span style="color:#E1E4E8"> [</span><span style="color:#9ECBFF">"token_check_config"</span><span style="color:#E1E4E8">, </span><span style="color:#9ECBFF">"log_monitor_config"</span><span style="color:#E1E4E8">, </span><span style="color:#9ECBFF">"global"</span><span style="color:#E1E4E8">]</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#6A737D"># Status 키 - 시스템이 업데이트</span></span>
<span data-line=""><span style="color:#79B8FF">STATUS_KEYS</span><span style="color:#F97583"> =</span><span style="color:#E1E4E8"> [</span><span style="color:#9ECBFF">"token_check_status"</span><span style="color:#E1E4E8">, </span><span style="color:#9ECBFF">"log_monitor_status"</span><span style="color:#E1E4E8">]</span></span></code></pre></figure>
<p lang="en">Changing Config automatically reloads APScheduler. For example, changing the token-check interval from 60 minutes to 30 removes the existing job and registers it again with the new interval.</p>
<h2 lang="en" id="웹-대시보드-ui">Web Dashboard UI</h2>
<p lang="en">The whole system is managed through a dark-themed, server-rendered web UI.</p>

































<table lang="en"><thead><tr><th>Page</th><th>Feature</th></tr></thead><tbody><tr><td><strong>Dashboard</strong></td><td>KPI statistics, token usage, and currently running tasks</td></tr><tr><td><strong>Projects</strong></td><td>Create, edit, and toggle projects (JIRA or schedule type)</td></tr><tr><td><strong>Schedules</strong></td><td>Manage cron schedules and trigger manual runs</td></tr><tr><td><strong>Executions</strong></td><td>View execution history and filter by status</td></tr><tr><td><strong>Settings</strong></td><td>Token checks, log monitoring, and global settings</td></tr><tr><td><strong>Logs</strong></td><td>Generate claude-code-log HTML reports</td></tr></tbody></table>
<p lang="en">Information visible at a glance on the dashboard:</p>
<ul lang="en">
<li>Total project count and active status</li>
<li>Number of running tasks</li>
<li>Recent execution results (success/failure)</li>
<li>Token utilization (session/weekly)</li>
</ul>
<h2 lang="en" id="시작부터-실행까지-전체-흐름">From Startup to Execution: The Full Flow</h2>
<p lang="en">When the server starts, initialization proceeds in this order.</p>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="text" data-theme="github-dark"><code data-language="text" data-theme="github-dark" style="display: grid;"><span data-line=""><span>run.py (uvicorn 엔트리)</span></span>
<span data-line=""><span>  ↓</span></span>
<span data-line=""><span>app.main:lifespan (FastAPI lifespan)</span></span>
<span data-line=""><span>  ├─ init_db()</span></span>
<span data-line=""><span>  │  ├─ schema.sql 읽기</span></span>
<span data-line=""><span>  │  ├─ 테이블 생성 (없으면)</span></span>
<span data-line=""><span>  │  └─ 기본 설정값 삽입</span></span>
<span data-line=""><span>  │</span></span>
<span data-line=""><span>  └─ init_scheduler()</span></span>
<span data-line=""><span>     ├─ _register_system_jobs()</span></span>
<span data-line=""><span>     │  ├─ 로그 모니터 (interval)</span></span>
<span data-line=""><span>     │  └─ 토큰 체크 (interval)</span></span>
<span data-line=""><span>     │</span></span>
<span data-line=""><span>     └─ _register_project_schedules()</span></span>
<span data-line=""><span>        └─ DB에서 활성 스케줄 로드</span></span>
<span data-line=""><span>           └─ APScheduler 크론 잡으로 등록</span></span></code></pre></figure>
<h3 lang="en" id="실제-사용-시나리오-매일-코드-리뷰-자동화">A Practical Scenario: Automated Daily Code Reviews</h3>
<ol lang="en">
<li><strong>Create a project</strong>: type=schedule, name="daily-review"</li>
<li><strong>Set the schedule</strong>: cron=<code>0 8 * * 1-5</code> (8 a.m. on weekdays)</li>
<li><strong>Write the prompt</strong>: “Review recent code changes and organize the findings by team”</li>
<li><strong>Working directory</strong>: <code>/home/dev/project-alpha</code></li>
</ol>
<p lang="en">At 8 a.m. on Monday:</p>
<ol lang="en">
<li>APScheduler triggers the job</li>
<li>Token usage is checked synchronously</li>
<li>Proceed if session usage is below 80% of the limit</li>
<li>Run <code>claude --chrome -p "최근 코드 변경사항을..."</code> asynchronously</li>
<li>Record the PID in the executions table</li>
<li>Schedule status → running</li>
<li>The log checker checks the PID every 10 minutes</li>
<li>On completion → execution status=success; the schedule returns to idle</li>
</ol>
<h3 lang="en" id="jira-이슈-자동-처리-시나리오">Automated JIRA Issue Processing</h3>
<ol lang="en">
<li><strong>Create a project</strong>: type=jira, search criteria="Project=FAS AND Status=Pending"</li>
<li><strong>Set the schedule</strong>: cron=<code>*/30 * * * *</code> (every 30 minutes)</li>
<li><strong>Prompt template</strong>: “Analyze issue <code>{issue_key}</code> and write a proposed solution as a comment”</li>
</ol>
<p lang="en">Every 30 minutes:</p>
<ol lang="en">
<li>Claude searches JIRA for matching issues</li>
<li>Return <code>[{"key": "FAS-130"}, {"key": "FAS-131"}]</code></li>
<li>Substitute each issue key into the prompt and run asynchronously</li>
<li>Skip issues already being processed</li>
</ol>
<h2 lang="en" id="프로젝트-구조">Project Structure</h2>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="text" data-theme="github-dark"><code data-language="text" data-theme="github-dark" style="display: grid;"><span data-line=""><span>aiforge/</span></span>
<span data-line=""><span>├── run.py                    # uvicorn 엔트리포인트</span></span>
<span data-line=""><span>├── schema.sql                # DB 스키마</span></span>
<span data-line=""><span>├── requirements.txt          # Python 의존성</span></span>
<span data-line=""><span>│</span></span>
<span data-line=""><span>├── app/</span></span>
<span data-line=""><span>│   ├── main.py               # FastAPI 앱, lifespan, 라우트 등록</span></span>
<span data-line=""><span>│   ├── database.py           # SQLite 헬퍼, 설정 관리</span></span>
<span data-line=""><span>│   │</span></span>
<span data-line=""><span>│   ├── services/</span></span>
<span data-line=""><span>│   │   ├── scheduler.py      # APScheduler, 실행 로직 (352 LOC)</span></span>
<span data-line=""><span>│   │   ├── executor.py       # Claude CLI 서브프로세스 관리 (200 LOC)</span></span>
<span data-line=""><span>│   │   └── log_checker.py    # PID 모니터링, 로그 파싱 (124 LOC)</span></span>
<span data-line=""><span>│   │</span></span>
<span data-line=""><span>│   ├── routes/</span></span>
<span data-line=""><span>│   │   ├── dashboard.py      # 대시보드 통계</span></span>
<span data-line=""><span>│   │   ├── projects.py       # 프로젝트 CRUD</span></span>
<span data-line=""><span>│   │   ├── schedules.py      # 스케줄 CRUD + 수동 실행</span></span>
<span data-line=""><span>│   │   ├── executions.py     # 실행 이력</span></span>
<span data-line=""><span>│   │   ├── settings.py       # 설정 관리</span></span>
<span data-line=""><span>│   │   ├── logs.py           # 로그 리포트</span></span>
<span data-line=""><span>│   │   └── templates.py      # 워크스페이스 템플릿</span></span>
<span data-line=""><span>│   │</span></span>
<span data-line=""><span>│   └── templates/            # Jinja2 HTML 템플릿 (11개)</span></span>
<span data-line=""><span>│</span></span>
<span data-line=""><span>└── docs/</span></span>
<span data-line=""><span>    ├── ARCHITECTURE.md</span></span>
<span data-line=""><span>    └── PROJECT_ANALYSIS.md</span></span></code></pre></figure>
<p lang="en">The total codebase is about 1,600 LOC. The whole service fits into that amount of code because it <strong>uses Claude CLI as a general-purpose executor</strong>. There is no need to implement a separate JIRA client, web-scraping logic, and so on.</p>
<h2 lang="en" id="설계-결정과-트레이드오프">Design Decisions and Trade-offs</h2>
<h3 lang="en" id="claude-cli를-범용-실행기로-사용한-이유">Why Use Claude CLI as a General-Purpose Executor?</h3>
<p lang="en">Calling the JIRA REST API directly requires authentication setup (OAuth or API tokens) and response-parsing logic. Claude CLI with the <code>--chrome</code> flag can reuse the browser's existing logged-in session.</p>
<p lang="en">There are downsides. It is slower than an API, and Claude's responses are not always predictable. But <strong>the core goal of this automation system is unattended operation, not speed</strong>. An extra 10 seconds compared with an API is not a problem for a poll that runs once every 30 minutes.</p>
<h3 lang="en" id="pid-기반-상태-추적">PID-Based Status Tracking</h3>
<p lang="en">Instead of watching stdout continuously, check only whether the PID exists.</p>
<ul lang="en">
<li>Advantage: low resource consumption, even with hundreds of concurrent runs.</li>
<li>Disadvantage: no real-time progress information. Success or failure is determined only after completion.</li>
</ul>
<h3 lang="en" id="sqlite-선택">Choosing SQLite</h3>
<p lang="en">I chose SQLite instead of an external database such as PostgreSQL or Redis. WAL mode supports concurrent reads and writes, and there is no separate DB server to manage. SQLite is sufficient for a system running on one machine.</p>
<h2 lang="en" id="실행-방법">How to Run It</h2>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="bash" data-theme="github-dark"><code data-language="bash" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#6A737D"># 의존성 설치</span></span>
<span data-line=""><span style="color:#B392F0">pip</span><span style="color:#9ECBFF"> install</span><span style="color:#79B8FF"> -r</span><span style="color:#9ECBFF"> requirements.txt</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#6A737D"># 실행</span></span>
<span data-line=""><span style="color:#B392F0">python</span><span style="color:#9ECBFF"> run.py</span></span>
<span data-line=""><span style="color:#6A737D"># 또는</span></span>
<span data-line=""><span style="color:#B392F0">uvicorn</span><span style="color:#9ECBFF"> app.main:app</span><span style="color:#79B8FF"> --host</span><span style="color:#79B8FF"> 0.0.0.0</span><span style="color:#79B8FF"> --port</span><span style="color:#79B8FF"> 8010</span><span style="color:#79B8FF"> --reload</span></span></code></pre></figure>
<p lang="en">Open <code>http://localhost:8010</code> in a browser to access the dashboard.</p>
<blockquote lang="en">
<p><strong>Cautions</strong>: Claude Code CLI must be installed on the system. Chrome is also required because the system uses the <code>--chrome</code> flag. It includes <code>--dangerously-skip-permissions</code>, so it should be used only on a trusted network.</p>
</blockquote>
<h2 lang="en" id="한계와-향후-계획">Limitations and Future Plans</h2>
<p lang="en">The current version is v0.1.0 and has several limitations.</p>





























<table lang="en"><thead><tr><th>Limitation</th><th>Description</th></tr></thead><tbody><tr><td>No authentication</td><td>The UI has no login (a trusted network is assumed)</td></tr><tr><td>Single machine</td><td>No distributed scheduling</td></tr><tr><td>Log parsing</td><td>Error-pattern matching is basic</td></tr><tr><td>No API</td><td>Only HTML forms; no REST API endpoints</td></tr><tr><td>No webhooks</td><td>No external event triggers</td></tr></tbody></table>
<p lang="en">These limitations are intentional, however. The core goal is <strong>unattended Claude Code automation on a single machine</strong>, and the current structure is sufficient for that purpose.</p>
<h2 lang="en" id="마무리">Wrapping Up</h2>
<p lang="en">AIForge's core idea is simple: <strong>schedule Claude Code CLI like cron</strong>. I added JIRA issue polling, token management, and execution monitoring to create an unattended automation system.</p>
<p lang="en">All of this fits into 1,600 lines of Python because Claude CLI is already a powerful executor. JIRA searches, token-usage checks, and code reviews can all be delegated to Claude through prompts. AIForge is the wrapper that makes those instructions run automatically, repeatedly, and without a human present.</p>
<p lang="en">Automation in the AI era is less about writing complex code and more about <strong>sending the right instructions to AI at the right time</strong>. AIForge is my first attempt at that.</p>]]></content:encoded>
    </item>
    <item>
      <title><![CDATA[Automatically Handling Tens of Thousands of SonarQube Issues with AI Agents]]></title>
      <link>https://blog.funq.kr/en/blogs/sonar-claude-workflow-20260214-v01</link>
      <guid isPermaLink="true">https://blog.funq.kr/en/blogs/sonar-claude-workflow-20260214-v01</guid>
      <description><![CDATA[Designing and implementing a system that automatically analyzes, fixes, and verifies SonarQube issues using a Claude Code workflow plugin.]]></description>
      <category><![CDATA[sonarqube]]></category><category><![CDATA[claude-code]]></category><category><![CDATA[automation]]></category><category><![CDATA[ai-agent]]></category><category><![CDATA[devops]]></category>
      <media:content url="https://blog.funq.kr/blog-cover/sonar-claude-workflow-20260214-v01.jpg" medium="image" type="image/jpeg"/>
      <pubDate>Sat, 14 Feb 2026 01:00:00 GMT</pubDate>
      <author>funqdev@gmail.com (fundev)</author>
      <content:encoded><![CDATA[<p lang="en">Applying SonarQube to multiple projects produces thousands or tens of thousands of issues.</p>
<p lang="en">Ten is manageable, a hundred is difficult, and tens of thousands is impossible. The previous manual process looked like this.</p>
<pre><code>대시보드 확인 → 이슈 열람 → 분석 → 수정 → 테스트 → PR → 리뷰
</code></pre>
<p lang="en">It was hard to tell who was handling which issue, the depth of analysis and quality of fixes varied by person, and two people sometimes worked on the same issue simultaneously. Neither the reasoning behind a fix nor the analysis process was recorded.</p>
<p lang="en">So I built a system in which AI agents take one issue at a time and run the entire pipeline.</p>
<blockquote lang="en">
<p>Source code: <a href="https://github.com/nasodev/claude-sonarcube-workflow">github.com/nasodev/claude-sonarcube-workflow</a></p>
</blockquote>
<hr lang="en">
<h2 lang="en" id="핵심-아이디어">The Core Idea</h2>
<blockquote lang="en">
<p><strong>Use Google Sheets as a work queue, with Claude Code subagents taking issues one by one and running the entire pipeline.</strong></p>
</blockquote>
<pre><code>SonarQube API → Google Sheets(작업 대기열) → Claude Code 서브에이전트 × 5 병렬
                     ↕                              ↓
              여러 담당자가 배정           분석 → 리뷰 → 개발 → 리뷰 → 테스트 → 완료
                                                    ↓
                                          8단계 리포트 + SQLite 추적 DB
</code></pre>
<p lang="en">During the design, I borrowed patterns from tools I was already using.</p>

























<table lang="en"><thead><tr><th>Borrowed from</th><th>Applied to</th><th>Description</th></tr></thead><tbody><tr><td><strong>FAS-BackEnd FAS-MASTER SKILL</strong></td><td>Code modification rules</td><td>Inject project-specific coding conventions and architectural rules through <code>CODING_RULES_PATH</code></td></tr><tr><td><strong>SuperSkills</strong></td><td>SQLite log storage</td><td>Record execution traces, state transitions, and accumulated errors in SQLite</td></tr><tr><td><strong>SuperPowers</strong></td><td>TDD development approach</td><td>The characterization-test pattern and TDD gating decisions</td></tr></tbody></table>
<hr lang="en">
<h2 lang="en" id="플러그인-아키텍처">Plugin Architecture</h2>
<h3 lang="en" id="디렉토리-구조">Directory Structure</h3>
<pre><code>claude-plugin-sonar/
├── .claude-plugin/plugin.json      # 플러그인 매니페스트
├── settings.json                   # 보안 정책 + 이벤트 훅
├── .env.example                    # 환경 설정 템플릿
├── guides/                         # 에이전트 행동 규칙
│   ├── bash-execution-rules.md     # Bash 실행 9대 원칙
│   └── red-flags.md                # 에이전트 일탈 방지 9대 규칙
├── hooks/
│   └── save-history.sh             # 디버그 모드 대화 기록 저장
├── skills/                         # 7개 스킬 (마이크로서비스 구조)
│   ├── sonar/                      # 오케스트레이터 (유일한 진입점)
│   ├── sonar-intake/               # 이슈 수집
│   ├── sonar-analyze/              # 분석 + Jira/리포트 생성
│   ├── sonar-develop/              # 코드 수정 + TDD
│   ├── sonar-review/               # 독립 검증 에이전트
│   ├── sonar-common/               # 공유 라이브러리
│   └── sonar-dashboard/            # HTML 대시보드 생성
└── data/                           # SonarQube JSON 캐시
</code></pre>
<h3 lang="en" id="7개-스킬의-역할">The Roles of the Seven Skills</h3>













































<table lang="en"><thead><tr><th>Skill</th><th>Role</th><th>How it is invoked</th></tr></thead><tbody><tr><td><code>sonar</code></td><td>Orchestrator: controls the entire flow</td><td><code>/sonar</code> (the only entry point)</td></tr><tr><td><code>sonar-intake</code></td><td>SonarQube API → Upload to Google Sheets</td><td>Invoked by the orchestrator</td></tr><tr><td><code>sonar-analyze</code></td><td>Root-cause analysis, solution proposals, and TDD decisions</td><td>Invoked by the orchestrator</td></tr><tr><td><code>sonar-review</code></td><td>Independent verification of both analysis and fixes</td><td>Invoked by the orchestrator</td></tr><tr><td><code>sonar-develop</code></td><td>Code changes, tests, and final deliverables</td><td>Invoked by the orchestrator</td></tr><tr><td><code>sonar-common</code></td><td>Shared utilities (Sheets, SQLite, etc.)</td><td>Imported by other skills</td></tr><tr><td><code>sonar-dashboard</code></td><td>Generate an HTML KPI dashboard</td><td><code>/sonar-dashboard</code></td></tr></tbody></table>
<hr lang="en">
<h2 lang="en" id="워크플로우-이슈-하나의-여정">Workflow: The Journey of One Issue</h2>
<h3 lang="en" id="명령어-흐름">Command Flow</h3>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="bash" data-theme="github-dark"><code data-language="bash" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#6A737D"># 1단계: 이슈 수집</span></span>
<span data-line=""><span style="color:#B392F0">/sonar</span><span style="color:#9ECBFF"> intake</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#6A737D"># 2단계: 이슈 배정</span></span>
<span data-line=""><span style="color:#B392F0">/sonar</span><span style="color:#9ECBFF"> claim</span><span style="color:#79B8FF"> 5</span><span style="color:#6A737D">                  # 개인에게 N개 배정</span></span>
<span data-line=""><span style="color:#B392F0">/sonar</span><span style="color:#9ECBFF"> claim</span><span style="color:#79B8FF"> --distribute</span><span style="color:#6A737D">       # 팀 멤버에게 균등 분배</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#6A737D"># 3단계: 자동 처리 (담당 이슈 전체 병렬, MAX_CONCURRENT_AGENTS 제어)</span></span>
<span data-line=""><span style="color:#B392F0">/sonar</span><span style="color:#9ECBFF"> run</span></span></code></pre></figure>
<h3 lang="en" id="상태-머신">State Machine</h3>
<pre><code>대기 → CLAIMED → ANALYZING ⟷ REVIEW_ANALYSIS → JIRA_CREATED
                    ↓ (4회 실패)
                 BLOCKED

JIRA_CREATED → DEVELOPING ⟷ REVIEW_FIX → TESTING → DONE
                   ↓ (4회 실패)
                BLOCKED
</code></pre>
<p lang="en">Every transition generates a report, updates the spreadsheet status in real time, and leaves an execution record in the SQLite tracking database.</p>
<h3 lang="en" id="스킬-간-데이터-흐름">Data Flow Between Skills</h3>
<pre><code>sonar (오케스트레이터)
  │
  ├─ sonar-intake → SonarQube API 이슈 수집 → Google Sheets 업로드
  │
  ├─ sonar-analyze → 01_analysis_report.md / 03_jira_created.md
  │
  ├─ sonar-review("analysis") → 02_analysis_review.md (PASS/FAIL)
  │
  ├─ sonar-develop → 04_fix_report.md / 06_test_report.md / 07_final_deliverable.md / 08_cto_approval.md
  │
  └─ sonar-review("fix") → 05_fix_review.md (PASS/FAIL)
</code></pre>
<hr lang="en">
<h2 lang="en" id="핵심-설계-ai-에이전트-제어">Core Design: Controlling AI Agents</h2>
<h3 lang="en" id="독립-리뷰-에이전트">An Independent Review Agent</h3>
<p lang="en"><strong>The analyzing agent ≠ the verifying agent</strong></p>
<pre><code>sonar-analyze → 01_analysis_report.md → sonar-review (별개의 서브에이전트)
                                              ↓
                                        PASS → 다음 단계
                                        FAIL → 피드백 반영 후 재시도 (최대 3회)
                                        4회 실패 → BLOCKED (사람 개입)
</code></pre>
<p lang="en">Code changes follow the same structure. When <code>sonar-develop</code> makes a change, <code>sonar-review</code> verifies it independently.</p>
<h3 lang="en" id="기술적-차단-settingsjson">Technical Blocks (settings.json)</h3>
<p lang="en">Block dangerous agent actions at the <strong>system level</strong>.</p>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="json" data-theme="github-dark"><code data-language="json" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#E1E4E8">{</span></span>
<span data-line=""><span style="color:#79B8FF">  "permissions"</span><span style="color:#E1E4E8">: {</span></span>
<span data-line=""><span style="color:#79B8FF">    "deny"</span><span style="color:#E1E4E8">: [</span></span>
<span data-line=""><span style="color:#9ECBFF">      "Bash(git commit*)"</span><span style="color:#E1E4E8">,</span></span>
<span data-line=""><span style="color:#9ECBFF">      "Bash(git push*)"</span><span style="color:#E1E4E8">,</span></span>
<span data-line=""><span style="color:#9ECBFF">      "Bash(gh pr create*)"</span><span style="color:#E1E4E8">,</span></span>
<span data-line=""><span style="color:#9ECBFF">      "Bash(git worktree remove*)"</span><span style="color:#E1E4E8">,</span></span>
<span data-line=""><span style="color:#9ECBFF">      "Bash(rm -rf *)"</span><span style="color:#E1E4E8">,</span></span>
<span data-line=""><span style="color:#9ECBFF">      "Bash(git reset --hard*)"</span></span>
<span data-line=""><span style="color:#E1E4E8">    ]</span></span>
<span data-line=""><span style="color:#E1E4E8">  }</span></span>
<span data-line=""><span style="color:#E1E4E8">}</span></span></code></pre></figure>
<p lang="en"><strong>AI prepares the deliverables; a human executes them.</strong></p>
<h3 lang="en" id="red-flags--에이전트-일탈-방지-9대-규칙">Red Flags: Nine Rules to Prevent Agent Deviations</h3>

































































<table lang="en"><thead><tr><th>#</th><th>Rule</th><th>The AI's excuse</th><th>Reality</th></tr></thead><tbody><tr><td>1</td><td>Do not group issues</td><td>“They are in the same file, so grouping them is more efficient”</td><td>The context, impact scope, and fix differ</td></tr><tr><td>2</td><td>Do not skip states</td><td>“It is a simple fix, so review can be skipped”</td><td>Simple fixes have the most side effects</td></tr><tr><td>3</td><td>No automatic commits or PRs</td><td>“Verification is complete, so committing is safe”</td><td>Agent verification ≠ user verification</td></tr><tr><td>4</td><td>Do not end the workflow early</td><td>—</td><td>—</td></tr><tr><td>5</td><td>Do not ignore review results</td><td>—</td><td>—</td></tr><tr><td>6</td><td>Do not skip checking spreadsheet status</td><td>—</td><td>—</td></tr><tr><td>7</td><td>Do not omit reports</td><td>—</td><td>—</td></tr><tr><td>8</td><td>Do not delete worktrees</td><td>—</td><td>—</td></tr><tr><td>9</td><td>Prevent concurrent-work conflicts</td><td>—</td><td>—</td></tr></tbody></table>
<p lang="en">Every rule includes a “plausible excuse versus reality” comparison.</p>





















<table lang="en"><thead><tr><th>Excuse</th><th>Reality</th></tr></thead><tbody><tr><td>“Just this once, as an exception”</td><td>No exceptions. Once sets a precedent</td></tr><tr><td>“To save time”</td><td>Undoing an incorrect result takes longer</td></tr><tr><td>“The user would want this”</td><td>Do not guess the user's intent</td></tr></tbody></table>
<hr lang="en">
<h2 lang="en" id="tdd-게이팅">TDD Gating</h2>
<p lang="en">Writing tests for every issue is inefficient. The analysis stage decides whether TDD is needed.</p>





























<table lang="en"><thead><tr><th>TDD_REQUIRED (tests required)</th><th>TDD_SKIP (tests skipped)</th></tr></thead><tbody><tr><td>BUG or VULNERABILITY</td><td>CODE_SMELL + MINOR/INFO</td></tr><tr><td>SECURITY or RELIABILITY</td><td>Variable or method renames only</td></tr><tr><td>Function signature or return-value changes</td><td>Comment, documentation, or logging changes</td></tr><tr><td>Refactoring affecting 3 or more functions</td><td>Removing unused imports</td></tr><tr><td>Areas with no existing tests</td><td>Code formatting</td></tr></tbody></table>
<h3 lang="en" id="특성화-테스트-패턴-characterization-test">The Characterization-Test Pattern</h3>
<pre><code>1. 수정 전 동작을 캡처하는 테스트 작성
2. BEFORE 실행 → GREEN 확인 (현재 동작 보존 검증)
3. 코드 수정
4. AFTER 실행 → GREEN 확인 (기존 동작 미파괴 검증)
</code></pre>
<p lang="en"><strong>The key</strong>: test against “current behavior,” rather than “ideal behavior.” The goal is to improve code quality, not change existing logic.</p>
<hr lang="en">
<h2 lang="en" id="8단계-리포트-체인">The Eight-Stage Report Chain</h2>
<p lang="en">Processing one issue creates eight report files.</p>
<pre><code>reports/{issue_key}/
├── 01_analysis_report.md     ← 근본 원인 분석, 해결 방안, TDD 판정
├── 02_analysis_review.md     ← 독립 에이전트의 분석 검증 (PASS/FAIL)
├── 03_jira_created.md        ← Jira 티켓 생성 확인
├── 04_fix_report.md          ← 수정 내역, 변경 파일, 사이드이펙트
├── 05_fix_review.md          ← 독립 에이전트의 수정 검증 (PASS/FAIL)
├── 06_test_report.md         ← 테스트 실행 결과
├── 07_final_deliverable.md   ← 커밋 메시지 + PR 설명 (복사용)
└── 08_cto_approval.md        ← 전체 과정 종합, 승인 요청
</code></pre>
<p lang="en"><code>07_final_deliverable.md</code> looks like this.</p>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="markdown" data-theme="github-dark"><code data-language="markdown" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#79B8FF;font-weight:bold">## 커밋 메시지 (복사용)</span></span>
<span data-line=""><span style="color:#E1E4E8">fix(UserService): Remove unused parameter 'name' from validateEmail</span></span>
<span data-line=""><span style="color:#FFAB70">-</span><span style="color:#E1E4E8"> Removed unused parameter flagged by SonarQube rule java:S1172</span></span>
<span data-line=""><span style="color:#FFAB70">-</span><span style="color:#E1E4E8"> No behavioral changes, verified by characterization tests</span></span>
<span data-line=""><span style="color:#E1E4E8">Issue: ODIN-123</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#79B8FF;font-weight:bold">## PR 설명 (복사용)</span></span>
<span data-line=""><span style="color:#E1E4E8">...</span></span></code></pre></figure>
<p lang="en">A human reviews the report and, if they agree, copies the commit message and creates the commit and PR.</p>
<hr lang="en">
<h2 lang="en" id="협업-구조-google-sheets-기반">Collaboration Through Google Sheets</h2>
<h3 lang="en" id="다인-동시-작업">Concurrent Work by Multiple People</h3>
<p lang="en"><strong>Method 1: Individual claims</strong>—each assignee takes as many issues as they want.</p>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="bash" data-theme="github-dark"><code data-language="bash" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#B392F0">담당자</span><span style="color:#9ECBFF"> A:</span><span style="color:#9ECBFF"> /sonar</span><span style="color:#9ECBFF"> claim</span><span style="color:#79B8FF"> 10</span><span style="color:#9ECBFF">  →</span><span style="color:#9ECBFF"> 10개</span><span style="color:#9ECBFF"> 이슈</span><span style="color:#9ECBFF"> 배정</span><span style="color:#9ECBFF"> →</span><span style="color:#9ECBFF"> /sonar</span><span style="color:#9ECBFF"> run</span></span>
<span data-line=""><span style="color:#B392F0">담당자</span><span style="color:#9ECBFF"> B:</span><span style="color:#9ECBFF"> /sonar</span><span style="color:#9ECBFF"> claim</span><span style="color:#79B8FF"> 5</span><span style="color:#9ECBFF">   →</span><span style="color:#9ECBFF">  5개</span><span style="color:#9ECBFF"> 이슈</span><span style="color:#9ECBFF"> 배정</span><span style="color:#9ECBFF"> →</span><span style="color:#9ECBFF"> /sonar</span><span style="color:#9ECBFF"> run</span></span>
<span data-line=""><span style="color:#B392F0">담당자</span><span style="color:#9ECBFF"> C:</span><span style="color:#9ECBFF"> /sonar</span><span style="color:#9ECBFF"> claim</span><span style="color:#79B8FF"> 20</span><span style="color:#9ECBFF">  →</span><span style="color:#9ECBFF"> 20개</span><span style="color:#9ECBFF"> 이슈</span><span style="color:#9ECBFF"> 배정</span><span style="color:#9ECBFF"> →</span><span style="color:#9ECBFF"> /sonar</span><span style="color:#9ECBFF"> run</span></span></code></pre></figure>
<p lang="en"><strong>Method 2: Even distribution</strong>—assign all pending issues to the team at once.</p>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="bash" data-theme="github-dark"><code data-language="bash" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#B392F0">/sonar</span><span style="color:#9ECBFF"> claim</span><span style="color:#79B8FF"> --distribute</span><span style="color:#6A737D">                          # TEAM_MEMBERS 환경변수 사용</span></span>
<span data-line=""><span style="color:#B392F0">/sonar</span><span style="color:#9ECBFF"> claim</span><span style="color:#79B8FF"> --distribute</span><span style="color:#79B8FF"> --members</span><span style="color:#9ECBFF"> "A,B,C"</span><span style="color:#6A737D">        # 멤버 직접 지정</span></span></code></pre></figure>
<p lang="en">Sort by severity (BLOCKER → HIGH → MEDIUM → LOW → INFO), then assign round-robin. With 10 issues and 3 people, A gets 4, B gets 3, and C gets 3.</p>
<h3 lang="en" id="리더-분배--팀원-실행-흐름">Leader Assigns, Team Members Execute</h3>
<pre><code>리더 (1회)
  1. /sonar intake              ← SonarQube → Sheets 수집
  2. /sonar claim --distribute  ← 심각도 순 라운드로빈 분배
         ↓
  Google Sheets (SSOT)
  3,000개 이슈 / 상태: CLAIMED / 담당자 자동 배정 완료
         ↓
  A: 1,000개 / B: 1,000개 / C: 1,000개

팀원 각자 (동시)
  팀원 A: /sonar run → 서브에이전트 ×5 병렬 → 분석 → 리뷰 → 개발 → 테스트
  팀원 B: /sonar run → 서브에이전트 ×5 병렬 → 분석 → 리뷰 → 개발 → 테스트
  팀원 C: /sonar run → 서브에이전트 ×5 병렬 → 분석 → 리뷰 → 개발 → 테스트
         ↓
  3,000개 이슈 처리 완료 → reports/ + worktrees/
</code></pre>
<blockquote lang="en">
<p><strong>The leader prepares things with two commands; each team member needs only <code>/sonar run</code>.</strong></p>
</blockquote>
<ul lang="en">
<li><strong>Google Sheets = Single Source of Truth</strong> (central state store)</li>
<li>See who is working on which issue in real time</li>
<li>Each assignee runs independently in their local environment</li>
<li>Process with up to 5 subagents in parallel</li>
</ul>
<h3 lang="en" id="git-worktree-격리">Git Worktree Isolation</h3>
<pre><code>worktrees/
├── ODIN-123/    ← 이슈 1 (담당자 A) — fix/ODIN-123 브랜치
├── ODIN-124/    ← 이슈 2 (담당자 A) — fix/ODIN-124 브랜치
├── ODIN-125/    ← 이슈 3 (담당자 B) — fix/ODIN-125 브랜치
</code></pre>
<p lang="en">Each issue is worked on in an independent branch, so they do not interfere with one another.</p>
<hr lang="en">
<h2 lang="en" id="추적과-분석-sqlite--대시보드">Tracking and Analysis: SQLite + Dashboard</h2>
<h3 lang="en" id="sqlite-추적-db">SQLite Tracking Database</h3>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="sql" data-theme="github-dark"><code data-language="sql" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#6A737D">-- 이슈별 실행 추적</span></span>
<span data-line=""><span style="color:#E1E4E8">issue_executions: execution_id, issue_key, phase, </span><span style="color:#F97583">status</span><span style="color:#E1E4E8">, attempt_number, duration_ms, error_message</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#6A737D">-- 상태 전이 감사 로그</span></span>
<span data-line=""><span style="color:#E1E4E8">state_transitions: issue_key, from_status, to_status, triggered_by, notes</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#6A737D">-- 에러 누적 기록</span></span>
<span data-line=""><span style="color:#E1E4E8">error_log: issue_key, phase, error_type, error_message, review_suggestions</span></span></code></pre></figure>
<h3 lang="en" id="자동-상태-추적">Automatic State Tracking</h3>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="python" data-theme="github-dark"><code data-language="python" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#79B8FF">_STATUS_AUTO_TRACKING</span><span style="color:#F97583"> =</span><span style="color:#E1E4E8"> {</span></span>
<span data-line=""><span style="color:#9ECBFF">    'ANALYZING'</span><span style="color:#E1E4E8">:      (</span><span style="color:#9ECBFF">'start'</span><span style="color:#E1E4E8">, </span><span style="color:#9ECBFF">'analyze'</span><span style="color:#E1E4E8">),</span></span>
<span data-line=""><span style="color:#9ECBFF">    'JIRA_CREATED'</span><span style="color:#E1E4E8">:   (</span><span style="color:#9ECBFF">'complete'</span><span style="color:#E1E4E8">, </span><span style="color:#9ECBFF">'analyze'</span><span style="color:#E1E4E8">, </span><span style="color:#9ECBFF">'success'</span><span style="color:#E1E4E8">),</span></span>
<span data-line=""><span style="color:#9ECBFF">    'DEVELOPING'</span><span style="color:#E1E4E8">:     (</span><span style="color:#9ECBFF">'start'</span><span style="color:#E1E4E8">, </span><span style="color:#9ECBFF">'develop'</span><span style="color:#E1E4E8">),</span></span>
<span data-line=""><span style="color:#9ECBFF">    'DONE'</span><span style="color:#E1E4E8">:           (</span><span style="color:#9ECBFF">'complete'</span><span style="color:#E1E4E8">, </span><span style="color:#9ECBFF">'develop'</span><span style="color:#E1E4E8">, </span><span style="color:#9ECBFF">'success'</span><span style="color:#E1E4E8">),</span></span>
<span data-line=""><span style="color:#9ECBFF">    'BLOCKED'</span><span style="color:#E1E4E8">:        (</span><span style="color:#9ECBFF">'complete_all'</span><span style="color:#E1E4E8">, </span><span style="color:#9ECBFF">'blocked'</span><span style="color:#E1E4E8">),</span></span>
<span data-line=""><span style="color:#E1E4E8">}</span></span></code></pre></figure>
<p lang="en">Spreadsheet status changes are recorded automatically in SQLite.</p>
<h3 lang="en" id="html-대시보드">HTML Dashboard</h3>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="bash" data-theme="github-dark"><code data-language="bash" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#B392F0">/sonar-dashboard</span></span></code></pre></figure>
<p lang="en">Visualize KPIs such as success rates, average time per stage, error patterns, and issue timelines.</p>
<hr lang="en">
<h2 lang="en" id="프로젝트별-전문화">Project-Specific Specialization</h2>
<h3 lang="en" id="coding-rules-주입">Injecting Coding Rules</h3>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="bash" data-theme="github-dark"><code data-language="bash" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#E1E4E8">CODING_RULES_PATH</span><span style="color:#F97583">=</span><span style="color:#9ECBFF">/path/to/project-rules</span></span></code></pre></figure>
<pre><code>project-rules/
├── SKILL.md              ← 핵심 규칙 개요
├── references/            ← 상세 가이드 (architecture.md, patterns.md, testing.md)
├── templates/             ← 코드 패턴 예시 (service.java, controller.java)
└── scripts/               ← 유틸리티 (linter.sh, formatter.sh)
</code></pre>
<p lang="en">Project A uses Spring Boot conventions; project B uses Node.js patterns. <code>sonar-develop</code> loads and follows the rules before modifying code.</p>
<h3 lang="en" id="jira-모드--리포트-모드">Jira Mode / Report Mode</h3>




















<table lang="en"><thead><tr><th>Mode</th><th>Setting</th><th>Behavior</th></tr></thead><tbody><tr><td><strong>Jira mode</strong></td><td><code>JIRA_ENABLED=true</code></td><td>Automatically create a Jira ticket after analysis</td></tr><tr><td><strong>Report mode</strong></td><td><code>JIRA_ENABLED=false</code></td><td>Generate reports without Jira (convert to tickets later if needed)</td></tr></tbody></table>
<hr lang="en">
<h2 lang="en" id="디버그-모드">Debug Mode</h2>
<p lang="en">When <code>.env</code> sets <code>DEBUG_MODE=true</code>:</p>
<pre><code>history/{issue_key}/
├── 20250205_143000_subagent_general-purpose.jsonl  ← 원본 트랜스크립트
├── 20250205_143000_summary.md                       ← 요약 리포트
└── 20250205_143000_hook_metadata.json               ← 메타데이터
</code></pre>
<p lang="en">Claude Code <code>hooks</code> automatically save the records on <code>Stop</code> and <code>SubagentStop</code> events.</p>
<hr lang="en">
<h2 lang="en" id="bash-실행-9대-원칙">Nine Principles for Bash Execution</h2>























































<table lang="en"><thead><tr><th>#</th><th>Principle</th><th>Key point</th></tr></thead><tbody><tr><td>1</td><td>Dynamic project root</td><td>Prefer <code>CLAUDE_PROJECT_DIR</code>; fall back to <code>git rev-parse</code></td></tr><tr><td>2</td><td>Always load <code>.env</code></td><td>Never hardcode API tokens</td></tr><tr><td>3</td><td>Capture error output</td><td>Capture stderr with <code>2>&#x26;1</code></td></tr><tr><td>4</td><td>Validate API responses</td><td>Check HTTP status codes separately</td></tr><tr><td>5</td><td>Pipeline safety</td><td><code>set -euo pipefail</code></td></tr><tr><td>6</td><td>Isolate directory changes</td><td>Isolate <code>cd</code> in a subshell</td></tr><tr><td>7</td><td>Standard template</td><td>Use a consistent starting structure for all scripts</td></tr><tr><td>8</td><td>Safe JSON parsing</td><td>Always validate <code>jq</code> results</td></tr><tr><td>9</td><td>Standardized output</td><td>Use consistent success/failure message formats</td></tr></tbody></table>
<hr lang="en">
<h2 lang="en" id="실전-시나리오">A Practical Scenario</h2>
<p lang="en">Assume 3 projects, 3,000 issues, and 5 assignees.</p>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="bash" data-theme="github-dark"><code data-language="bash" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#6A737D"># 1. 이슈 수집</span></span>
<span data-line=""><span style="color:#B392F0">/sonar</span><span style="color:#9ECBFF"> intake</span><span style="color:#9ECBFF"> project-a</span></span>
<span data-line=""><span style="color:#B392F0">/sonar</span><span style="color:#9ECBFF"> intake</span><span style="color:#9ECBFF"> project-b</span></span>
<span data-line=""><span style="color:#B392F0">/sonar</span><span style="color:#9ECBFF"> intake</span><span style="color:#9ECBFF"> project-c</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#6A737D"># 2. 담당자별 배정</span></span>
<span data-line=""><span style="color:#B392F0">/sonar</span><span style="color:#9ECBFF"> claim</span><span style="color:#79B8FF"> --distribute</span><span style="color:#79B8FF"> --members</span><span style="color:#9ECBFF"> "A,B,C,D,E"</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#6A737D"># 3. 자동 처리</span></span>
<span data-line=""><span style="color:#B392F0">/sonar</span><span style="color:#9ECBFF"> run</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#6A737D"># 4. 현황 확인</span></span>
<span data-line=""><span style="color:#B392F0">/sonar</span><span style="color:#9ECBFF"> status</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#6A737D"># 5. 완료 이슈 승인</span></span>
<span data-line=""><span style="color:#B392F0">/sonar</span><span style="color:#9ECBFF"> approve</span><span style="color:#9ECBFF"> ODIN-123</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#6A737D"># 6. 수동 커밋 &#x26; PR (07_final_deliverable.md 참조)</span></span>
<span data-line=""><span style="color:#B392F0">git</span><span style="color:#9ECBFF"> add</span><span style="color:#9ECBFF"> .</span><span style="color:#E1E4E8"> &#x26;&#x26; </span><span style="color:#B392F0">git</span><span style="color:#9ECBFF"> commit</span><span style="color:#79B8FF"> -m</span><span style="color:#9ECBFF"> "복사한 커밋 메시지"</span></span>
<span data-line=""><span style="color:#B392F0">gh</span><span style="color:#9ECBFF"> pr</span><span style="color:#9ECBFF"> create</span><span style="color:#79B8FF"> --title</span><span style="color:#9ECBFF"> "제목"</span><span style="color:#79B8FF"> --body</span><span style="color:#9ECBFF"> "복사한 PR 설명"</span></span></code></pre></figure>
<hr lang="en">
<h2 lang="en" id="정리">Summary</h2>









































<table lang="en"><thead><tr><th>Problem</th><th>Solution</th></tr></thead><tbody><tr><td>Tens of thousands of issues cannot be processed manually</td><td>Automatic parallel processing with 5 AI subagents</td></tr><tr><td>One person cannot handle every project</td><td>Multi-person collaboration through Google Sheets</td></tr><tr><td>No traceability of the process</td><td>Eight-stage reports + SQLite audit logs</td></tr><tr><td>Arbitrary AI behavior</td><td>Technical blocks in settings.json + Red Flags rules</td></tr><tr><td>Inconsistent analysis and fix quality</td><td>Independent review agents + Up to 4 retries</td></tr><tr><td>Unnecessary tests</td><td>TDD Gating</td></tr><tr><td>Failure to follow project-specific coding rules</td><td>Inject CODING_RULES_PATH</td></tr><tr><td>Difficult debugging</td><td>Preserve full conversation records with DEBUG_MODE</td></tr></tbody></table>
<h3 lang="en" id="기술-스택">Technology Stack</h3>

















































<table lang="en"><thead><tr><th>Area</th><th>Technology</th></tr></thead><tbody><tr><td>Orchestration</td><td>Claude Code Skills (SKILL.md-based multi-agent system)</td></tr><tr><td>State management</td><td>Google Sheets API (gspread + oauth2client)</td></tr><tr><td>Issue source</td><td>SonarQube REST API</td></tr><tr><td>Issue tracking</td><td>Jira REST API (optional)</td></tr><tr><td>Code isolation</td><td>Git Worktree</td></tr><tr><td>Data storage</td><td>SQLite (tracking database)</td></tr><tr><td>Dashboard</td><td>HTML generation (generate_dashboard.py)</td></tr><tr><td>Security</td><td>settings.json permissions.deny</td></tr><tr><td>Debugging</td><td>Claude Code Hooks (save-history.sh)</td></tr><tr><td>Testing</td><td>Characterization-test pattern (TDD gating)</td></tr></tbody></table>
<hr lang="en">
<blockquote lang="en">
<p><strong>“Let AI do what AI can do, and humans do what only humans can do.”</strong></p>
</blockquote>
<p lang="en">AI writes the analysis, fixes, tests, and reports. Humans review the reports, commit, open PRs, and give final approval.</p>
<p lang="en">Having humans verify AI deliverables is what makes the whole system trustworthy.</p>]]></content:encoded>
    </item>
    <item>
      <title><![CDATA[Using an AI Agent from Telegram with Clawdbot]]></title>
      <link>https://blog.funq.kr/en/blogs/clawdbot-setup-20260131-v01</link>
      <guid isPermaLink="true">https://blog.funq.kr/en/blogs/clawdbot-setup-20260131-v01</guid>
      <description><![CDATA[How to install Clawdbot on an Ubuntu server and connect it to a Telegram bot. Claude Pro/Max subscribers can link their account and use Opus 4.5 without an API key.]]></description>
      <category><![CDATA[ai]]></category><category><![CDATA[clawdbot]]></category><category><![CDATA[telegram]]></category><category><![CDATA[ubuntu]]></category><category><![CDATA[claude]]></category>
      <media:content url="https://blog.funq.kr/blog-cover/clawdbot-setup-20260131-v01.jpg" medium="image" type="image/jpeg"/>
      <pubDate>Sat, 31 Jan 2026 01:00:00 GMT</pubDate>
      <author>funqdev@gmail.com (fundev)</author>
      <content:encoded><![CDATA[<p lang="en">Clawdbot is a gateway that extends an AI LLM's capabilities to your PC. It lets you talk to an AI agent anytime, anywhere through messaging apps such as Telegram, WhatsApp, and Discord.</p>
<hr lang="en">
<h2 lang="en" id="clawdbot이란">What Is Clawdbot?</h2>
<p lang="en">Clawdbot is a gateway service connecting messaging apps to AI agents. Install it on a server, and you can send messages through Telegram or similar apps to talk to AI.</p>





























<table lang="en"><thead><tr><th>Feature</th><th>Description</th></tr></thead><tbody><tr><td>Multiple channels</td><td>Supports WhatsApp, Telegram, Discord, and iMessage</td></tr><tr><td>Subscription authentication</td><td>Claude Pro/Max and ChatGPT/Codex OAuth integration</td></tr><tr><td>Media support</td><td>Send and receive images, audio, and documents</td></tr><tr><td>Voice memos</td><td>Automatically transcribe voice messages</td></tr><tr><td>Dashboard</td><td>Manage settings, cron jobs, and skills through a web UI</td></tr></tbody></table>
<p lang="en"><strong>A name change is underway</strong>: because the similar name causes confusion with Claude, it is being renamed “moltbot.” The installation-script URL has also changed to <code>molt.bot</code>.</p>
<ul lang="en">
<li>Official site: <a href="https://clawd.bot/">https://clawd.bot/</a></li>
</ul>
<hr lang="en">
<h2 lang="en" id="claude-구독자-혜택">Benefits for Claude Subscribers</h2>
<p lang="en">If you are on a Claude Pro or Max plan, you can use free tokens by linking your account, without an API key.</p>





























<table lang="en"><thead><tr><th>Plan</th><th>API key</th><th>Account linking</th><th>Available models</th></tr></thead><tbody><tr><td>Free</td><td>Required</td><td>Unavailable</td><td>Models included in the API plan</td></tr><tr><td>Pro</td><td>Not required</td><td>Available</td><td>Sonnet and others</td></tr><tr><td>Max</td><td>Not required</td><td>Available</td><td>Includes Opus 4.5</td></tr></tbody></table>
<p lang="en">I use the Max plan, so after linking my account I used Opus 4.5.</p>
<hr lang="en">
<h2 lang="en" id="설치-방법">Installation</h2>
<h3 lang="en" id="1-설치-스크립트-실행">1. Run the Installation Script</h3>
<p lang="en">Install it on an Ubuntu server with a single command.</p>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="bash" data-theme="github-dark"><code data-language="bash" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#B392F0">curl</span><span style="color:#79B8FF"> -fsSL</span><span style="color:#9ECBFF"> https://molt.bot/install.sh</span><span style="color:#F97583"> |</span><span style="color:#B392F0"> bash</span></span></code></pre></figure>
<p lang="en">Node.js 22 or later is required.</p>
<h3 lang="en" id="2-온보딩-및-서비스-등록">2. Onboard and Register the Service</h3>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="bash" data-theme="github-dark"><code data-language="bash" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#6A737D"># 온보딩 + systemd 서비스 설치</span></span>
<span data-line=""><span style="color:#B392F0">clawdbot</span><span style="color:#9ECBFF"> onboard</span><span style="color:#79B8FF"> --install-daemon</span></span></code></pre></figure>
<h3 lang="en" id="3-버전-확인">3. Check the Version</h3>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="bash" data-theme="github-dark"><code data-language="bash" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#B392F0">clawdbot</span><span style="color:#79B8FF"> --version</span></span>
<span data-line=""><span style="color:#6A737D"># 예시: 2026.1.24-3</span></span></code></pre></figure>
<hr lang="en">
<h2 lang="en" id="텔레그램-봇-연동">Connecting a Telegram Bot</h2>
<h3 lang="en" id="1-텔레그램-봇-생성">1. Create a Telegram Bot</h3>
<ol lang="en">
<li>Talk to @BotFather on Telegram</li>
<li>Create a new bot with <code>/newbot</code></li>
<li>Choose a name for the bot and obtain its token</li>
</ol>
<h3 lang="en" id="2-clawdbot에-토큰-등록">2. Register the Token with Clawdbot</h3>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="bash" data-theme="github-dark"><code data-language="bash" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#6A737D"># 텔레그램 채널 연동</span></span>
<span data-line=""><span style="color:#B392F0">clawdbot</span><span style="color:#9ECBFF"> channels</span><span style="color:#9ECBFF"> login</span></span></code></pre></figure>
<p lang="en">Enter the token for the Telegram bot you created.</p>
<h3 lang="en" id="3-페어링-승인">3. Approve Pairing</h3>
<p lang="en">Send <code>/start</code> to the Telegram bot to receive a code. Enter that code in Clawdbot to complete pairing.</p>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="bash" data-theme="github-dark"><code data-language="bash" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#B392F0">clawdbot</span><span style="color:#9ECBFF"> pairing</span><span style="color:#9ECBFF"> approve</span><span style="color:#9ECBFF"> telegram</span><span style="color:#F97583"> &#x3C;</span><span style="color:#9ECBFF">페어링코</span><span style="color:#E1E4E8">드</span><span style="color:#F97583">></span></span></code></pre></figure>
<h3 lang="en" id="4-연결-확인">4. Check the Connection</h3>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="bash" data-theme="github-dark"><code data-language="bash" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#6A737D"># 상태 확인</span></span>
<span data-line=""><span style="color:#B392F0">clawdbot</span><span style="color:#9ECBFF"> status</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#6A737D"># TUI로 테스트</span></span>
<span data-line=""><span style="color:#B392F0">clawdbot</span><span style="color:#9ECBFF"> tui</span></span></code></pre></figure>
<hr lang="en">
<h2 lang="en" id="트러블슈팅-gateway-connection-문제">Troubleshooting: Gateway Connection Problems</h2>
<h3 lang="en" id="증상">Symptoms</h3>
<p lang="en">After connecting Telegram, the gateway connection in <code>clawdbot tui</code> kept disconnecting and reconnecting.</p>
<pre><code>gateway disconnected
gateway connected
gateway disconnected
...
</code></pre>
<p lang="en">I checked with <code>clawdbot status</code> and restarted it using <code>clawdbot gateway stop</code> / <code>clawdbot gateway start</code>, but it would work briefly and then disconnect again.</p>
<h3 lang="en" id="원인-분석">Diagnosing the Cause</h3>
<p lang="en">The logs showed an error in Node.js when sending a message to Telegram.</p>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="bash" data-theme="github-dark"><code data-language="bash" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#B392F0">journalctl</span><span style="color:#79B8FF"> --user</span><span style="color:#79B8FF"> -u</span><span style="color:#9ECBFF"> clawdbot-gateway.service</span><span style="color:#79B8FF"> -n</span><span style="color:#79B8FF"> 200</span><span style="color:#79B8FF"> --no-pager</span></span></code></pre></figure>
<p lang="en">The error was a failed connection to <code>api.telegram.org</code>. Searching Reddit pointed to a DNS/IPv6 issue: Node.js was trying the IPv6 address first and timing out.</p>
<h3 lang="en" id="해결-방법">The Fix</h3>
<p lang="en">I forced the Telegram API server's IPv4 address in <code>/etc/hosts</code>.</p>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="bash" data-theme="github-dark"><code data-language="bash" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#B392F0">sudo</span><span style="color:#9ECBFF"> vim</span><span style="color:#9ECBFF"> /etc/hosts</span></span></code></pre></figure>
<p lang="en">Add the following:</p>
<pre><code>149.154.166.110 api.telegram.org
</code></pre>
<p lang="en">This bypasses DNS resolution and always connects over IPv4.</p>
<h3 lang="en" id="원인-상세">More About the Cause</h3>





















<table lang="en"><thead><tr><th>Possible cause</th><th>Description</th></tr></thead><tbody><tr><td>DNS resolution failure</td><td>Unreliable DNS responses in the server's network environment</td></tr><tr><td>IPv6 preferred</td><td>Node.js tries IPv6 first, but routing problems cause a timeout</td></tr><tr><td>Network policy</td><td>A specific domain is blocked by the cloud provider or data center</td></tr></tbody></table>
<p lang="en"><strong>Caution</strong>: hardcoding an IP can cause problems again if Telegram's infrastructure changes. A more fundamental solution is to fix the DNS settings or configure Node.js to prefer IPv4.</p>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="bash" data-theme="github-dark"><code data-language="bash" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#6A737D"># Node.js IPv4 우선 옵션</span></span>
<span data-line=""><span style="color:#E1E4E8">NODE_OPTIONS</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">--dns-result-order</span><span style="color:#F97583">=</span><span style="color:#9ECBFF">ipv4first</span></span></code></pre></figure>
<hr lang="en">
<h2 lang="en" id="대시보드-접속">Accessing the Dashboard</h2>
<p lang="en">The Clawdbot dashboard is accessible only from localhost. If installed on a remote server, access it through an SSH tunnel.</p>
<h3 lang="en" id="1-ssh-터널-연결">1. Connect the SSH Tunnel</h3>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="bash" data-theme="github-dark"><code data-language="bash" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#B392F0">ssh</span><span style="color:#79B8FF"> -N</span><span style="color:#79B8FF"> -L</span><span style="color:#9ECBFF"> 18789:127.0.0.1:18789</span><span style="color:#9ECBFF"> user@서버IP</span></span></code></pre></figure>
<h3 lang="en" id="2-대시보드-url-확인">2. Find the Dashboard URL</h3>
<p lang="en">Run the following command on the server to get the URL containing the token.</p>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="bash" data-theme="github-dark"><code data-language="bash" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#B392F0">clawdbot</span><span style="color:#9ECBFF"> dashboard</span></span>
<span data-line=""><span style="color:#6A737D"># 출력 예시: http://localhost:18789/?token=baf8...</span></span></code></pre></figure>
<h3 lang="en" id="3-브라우저에서-접속">3. Open It in a Browser</h3>
<p lang="en">With the SSH tunnel connected, open that URL to view the dashboard.</p>
<p lang="en">The dashboard makes it easy to manage cron jobs, skill settings, and more.</p>
<hr lang="en">
<h2 lang="en" id="활용-방법">Ways to Use It</h2>
<p lang="en">Claude Code's web version lets you give development instructions from your phone while away and connect to GitHub. Clawdbot lets you do similar work in a local environment.</p>



































<table lang="en"><thead><tr><th>Feature</th><th>Claude Code Web</th><th>Clawdbot</th></tr></thead><tbody><tr><td>Remote access</td><td>Available</td><td>Available (Telegram)</td></tr><tr><td>Execution environment</td><td>Anthropic's servers</td><td>My server</td></tr><tr><td>Docker management</td><td>Unavailable</td><td>Available</td></tr><tr><td>Server monitoring</td><td>Unavailable</td><td>Available</td></tr><tr><td>File access</td><td>Limited</td><td>Full</td></tr></tbody></table>
<p lang="en">Through Telegram, you can check Docker container status, monitor the server, run simple scripts, and more.</p>
<hr lang="en">
<h2 lang="en" id="보안-주의사항">Security Precautions</h2>
<p lang="en">Clawdbot has broad access to computer resources. Security needs particular attention.</p>
<h3 lang="en" id="1-권한-범위">1. Scope of Permissions</h3>
<p lang="en">Clawdbot can access all resources on the server where it is installed.</p>





















<table lang="en"><thead><tr><th>Risk</th><th>Description</th></tr></thead><tbody><tr><td>File access</td><td>Can read and write all files</td></tr><tr><td>Command execution</td><td>Can execute system commands</td></tr><tr><td>Network</td><td>Can call external APIs</td></tr></tbody></table>
<p lang="en">An incorrect prompt could lead to unwanted emails or payments.</p>
<h3 lang="en" id="2-스킬-마켓-주의">2. Be Careful with the Skill Marketplace</h3>
<p lang="en">Skills that have not yet been verified may appear in the marketplace.</p>





















<table lang="en"><thead><tr><th>Risk type</th><th>Description</th></tr></thead><tbody><tr><td>Prompt injection</td><td>Skills containing hidden malicious prompts</td></tr><tr><td>API key theft</td><td>Sending API keys outside the system through a skill</td></tr><tr><td>Gaslighting</td><td>Manipulating the LLM into disclosing sensitive information</td></tr></tbody></table>
<p lang="en"><strong>Recommendations</strong>:</p>
<ul lang="en">
<li>Install only verified skills</li>
<li>Check skill source code</li>
<li>On important servers, isolate it under a separate account</li>
</ul>
<hr lang="en">
<h2 lang="en" id="유용한-명령어">Useful Commands</h2>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="bash" data-theme="github-dark"><code data-language="bash" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#6A737D"># 상태 확인</span></span>
<span data-line=""><span style="color:#B392F0">clawdbot</span><span style="color:#9ECBFF"> status</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#6A737D"># 게이트웨이 재시작</span></span>
<span data-line=""><span style="color:#B392F0">clawdbot</span><span style="color:#9ECBFF"> gateway</span><span style="color:#9ECBFF"> stop</span></span>
<span data-line=""><span style="color:#B392F0">clawdbot</span><span style="color:#9ECBFF"> gateway</span><span style="color:#9ECBFF"> start</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#6A737D"># 환경 점검 및 자동 수리</span></span>
<span data-line=""><span style="color:#B392F0">clawdbot</span><span style="color:#9ECBFF"> doctor</span><span style="color:#79B8FF"> --repair</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#6A737D"># TUI 실행</span></span>
<span data-line=""><span style="color:#B392F0">clawdbot</span><span style="color:#9ECBFF"> tui</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#6A737D"># 대시보드 URL 확인</span></span>
<span data-line=""><span style="color:#B392F0">clawdbot</span><span style="color:#9ECBFF"> dashboard</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#6A737D"># 페어링 승인</span></span>
<span data-line=""><span style="color:#B392F0">clawdbot</span><span style="color:#9ECBFF"> pairing</span><span style="color:#9ECBFF"> approve</span><span style="color:#9ECBFF"> telegram</span><span style="color:#F97583"> &#x3C;</span><span style="color:#9ECBFF">코</span><span style="color:#E1E4E8">드</span><span style="color:#F97583">></span></span></code></pre></figure>
<hr lang="en">
<h2 lang="en" id="마무리">Wrapping Up</h2>
<p lang="en">Clawdbot lets you talk to an AI agent and manage your server through Telegram anytime, anywhere.</p>
<p lang="en">Many developers are using it, so updates are arriving quickly. Still, security and permission management require attention.</p>
<ul lang="en">
<li>Official site: <a href="https://clawd.bot/">https://clawd.bot/</a></li>
<li>Official documentation: <a href="https://docs.clawd.bot">https://docs.clawd.bot</a></li>
</ul>]]></content:encoded>
    </item>
    <item>
      <title><![CDATA[Automating Deployment with Docker, GHCR, and GitHub Actions]]></title>
      <link>https://blog.funq.kr/en/blogs/docker-migration-20260124-v01</link>
      <guid isPermaLink="true">https://blog.funq.kr/en/blogs/docker-migration-20260124-v01</guid>
      <description><![CDATA[A practical guide to moving from systemd and venv to Docker, GHCR, and GitHub Actions: containerizing PostgreSQL, FastAPI, and Next.js and building a CI/CD pipeline.]]></description>
      <category><![CDATA[docker]]></category><category><![CDATA[devops]]></category><category><![CDATA[github-actions]]></category><category><![CDATA[cicd]]></category><category><![CDATA[postgresql]]></category>
      <media:content url="https://blog.funq.kr/blog-cover/docker-migration-20260124-v01.jpg" medium="image" type="image/jpeg"/>
      <pubDate>Sat, 24 Jan 2026 01:00:00 GMT</pubDate>
      <author>funqdev@gmail.com (fundev)</author>
      <content:encoded><![CDATA[<p lang="en">This post covers migrating a FastAPI backend and Next.js frontend from an existing systemd + venv setup to Docker.</p>
<hr lang="en">
<h2 lang="en" id="전환-대상">Migration Targets</h2>

























<table lang="en"><thead><tr><th>Project</th><th>Previous approach</th><th>After migration</th></tr></thead><tbody><tr><td>Backend API (FastAPI)</td><td>systemd + venv</td><td>Docker + GHCR</td></tr><tr><td>Frontend (Next.js)</td><td>npm run start</td><td>Docker + GHCR</td></tr><tr><td>PostgreSQL</td><td>apt install</td><td>Docker Container</td></tr></tbody></table>
<h3 lang="en" id="기존-구조">Previous Architecture</h3>
<pre><code>서버에서 직접 실행:
git pull → pip install / npm install → alembic upgrade → systemctl restart
</code></pre>
<h3 lang="en" id="변경-후-구조">New Architecture</h3>
<pre><code>GitHub Actions에서 빌드:
테스트 → Docker 이미지 빌드 → GHCR push → 서버에서 pull → 실행
</code></pre>
<hr lang="en">
<h2 lang="en" id="왜-docker로-전환했나">Why Move to Docker?</h2>
<h3 lang="en" id="1-다양한-개발-환경-통일">1. Unify Different Development Environments</h3>

























<table lang="en"><thead><tr><th>Environment</th><th>OS</th><th>Problems</th></tr></thead><tbody><tr><td>Server</td><td>Ubuntu</td><td>Actual production environment</td></tr><tr><td>Development 1</td><td>macOS</td><td>Different package paths and commands</td></tr><tr><td>Development 2</td><td>Windows</td><td>Different line endings and path separators</td></tr></tbody></table>
<p lang="en">With Docker, the same container image runs in every environment.</p>
<h3 lang="en" id="2-인프라-확장-대비">2. Prepare for Infrastructure Growth</h3>
<p lang="en">Current setup:</p>
<ul lang="en">
<li>PostgreSQL (DB)</li>
</ul>
<p lang="en">Planned additions:</p>
<ul lang="en">
<li>RabbitMQ / Redis (Message Queue)</li>
<li>MinIO / S3 (Object Storage)</li>
<li>Other services</li>
</ul>
<p lang="en">Docker Compose makes adding and removing services straightforward.</p>
<h3 lang="en" id="3-보안-강화">3. Improve Security</h3>
<pre><code>기존: 소스 폴더 내 .env 파일
      └─ git에 실수로 커밋될 위험

변경: /home/funq/config/서비스명/.env.prod
      └─ 소스와 완전 분리, 서버에만 존재
</code></pre>
<h3 lang="en" id="4-기존-방식과-비교">4. Comparison with the Previous Approach</h3>

























<table lang="en"><thead><tr><th>Previous approach</th><th>Docker approach</th></tr></thead><tbody><tr><td>Run pip/npm install on the server (slow)</td><td>Just pull the image (fast)</td></tr><tr><td>Potential Python/Node version conflicts</td><td>Safe through container isolation</td></tr><tr><td>Cumbersome rollbacks</td><td>Roll back immediately to an earlier image tag</td></tr><tr><td>Environments may differ between servers</td><td>The same environment everywhere</td></tr></tbody></table>
<hr lang="en">
<h2 lang="en" id="postgresql-docker-이전">Moving PostgreSQL to Docker</h2>
<p lang="en">Before converting FastAPI to Docker, I first moved the on-premises PostgreSQL 16 installation into Docker.</p>
<h3 lang="en" id="기존-vs-변경-후">Before and After</h3>






























<table lang="en"><thead><tr><th>Item</th><th>Before</th><th>After migration</th></tr></thead><tbody><tr><td>Installation method</td><td>apt install postgresql-16</td><td>Docker container</td></tr><tr><td>Management method</td><td>systemctl</td><td>docker compose</td></tr><tr><td>Data location</td><td>/var/lib/postgresql/16/main</td><td>Docker Volume</td></tr><tr><td>Automatic startup</td><td>systemctl enable</td><td>restart: unless-stopped</td></tr></tbody></table>
<h3 lang="en" id="이전-절차">Migration Procedure</h3>
<p lang="en"><strong>1. Back Up the Database</strong></p>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="bash" data-theme="github-dark"><code data-language="bash" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#B392F0">sudo</span><span style="color:#79B8FF"> -u</span><span style="color:#9ECBFF"> postgres</span><span style="color:#9ECBFF"> pg_dumpall</span><span style="color:#F97583"> ></span><span style="color:#9ECBFF"> /tmp/pg_backup_all.sql</span></span></code></pre></figure>
<p lang="en"><strong>2. Configure Docker Compose</strong></p>
<p lang="en"><code>/home/funq/dev/infra/postgres/docker-compose.yml</code>:</p>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="yaml" data-theme="github-dark"><code data-language="yaml" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#85E89D">services</span><span style="color:#E1E4E8">:</span></span>
<span data-line=""><span style="color:#85E89D">  postgres</span><span style="color:#E1E4E8">:</span></span>
<span data-line=""><span style="color:#85E89D">    image</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">postgres:16</span></span>
<span data-line=""><span style="color:#85E89D">    container_name</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">postgres</span></span>
<span data-line=""><span style="color:#85E89D">    restart</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">unless-stopped</span></span>
<span data-line=""><span style="color:#85E89D">    environment</span><span style="color:#E1E4E8">:</span></span>
<span data-line=""><span style="color:#85E89D">      POSTGRES_USER</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">postgres</span></span>
<span data-line=""><span style="color:#85E89D">      POSTGRES_PASSWORD</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">${POSTGRES_PASSWORD}</span></span>
<span data-line=""><span style="color:#85E89D">      POSTGRES_DB</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">postgres</span></span>
<span data-line=""><span style="color:#85E89D">      TZ</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">Asia/Seoul</span></span>
<span data-line=""><span style="color:#85E89D">    ports</span><span style="color:#E1E4E8">:</span></span>
<span data-line=""><span style="color:#E1E4E8">      - </span><span style="color:#9ECBFF">"5432:5432"</span></span>
<span data-line=""><span style="color:#85E89D">    volumes</span><span style="color:#E1E4E8">:</span></span>
<span data-line=""><span style="color:#E1E4E8">      - </span><span style="color:#9ECBFF">postgres_data:/var/lib/postgresql/data</span></span>
<span data-line=""><span style="color:#E1E4E8">      - </span><span style="color:#9ECBFF">./init:/docker-entrypoint-initdb.d</span></span>
<span data-line=""><span style="color:#85E89D">    healthcheck</span><span style="color:#E1E4E8">:</span></span>
<span data-line=""><span style="color:#85E89D">      test</span><span style="color:#E1E4E8">: [</span><span style="color:#9ECBFF">"CMD-SHELL"</span><span style="color:#E1E4E8">, </span><span style="color:#9ECBFF">"pg_isready -U postgres"</span><span style="color:#E1E4E8">]</span></span>
<span data-line=""><span style="color:#85E89D">      interval</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">10s</span></span>
<span data-line=""><span style="color:#85E89D">      timeout</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">5s</span></span>
<span data-line=""><span style="color:#85E89D">      retries</span><span style="color:#E1E4E8">: </span><span style="color:#79B8FF">5</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#85E89D">volumes</span><span style="color:#E1E4E8">:</span></span>
<span data-line=""><span style="color:#85E89D">  postgres_data</span><span style="color:#E1E4E8">:</span></span></code></pre></figure>
<p lang="en"><strong>3. Set Environment Variables</strong></p>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="bash" data-theme="github-dark"><code data-language="bash" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#79B8FF">echo</span><span style="color:#9ECBFF"> "POSTGRES_PASSWORD=your_password"</span><span style="color:#F97583"> ></span><span style="color:#9ECBFF"> .env</span></span></code></pre></figure>
<p lang="en"><strong>4. Stop and Disable the Existing PostgreSQL Service</strong></p>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="bash" data-theme="github-dark"><code data-language="bash" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#B392F0">sudo</span><span style="color:#9ECBFF"> systemctl</span><span style="color:#9ECBFF"> stop</span><span style="color:#9ECBFF"> postgresql</span></span>
<span data-line=""><span style="color:#B392F0">sudo</span><span style="color:#9ECBFF"> systemctl</span><span style="color:#9ECBFF"> disable</span><span style="color:#9ECBFF"> postgresql</span></span></code></pre></figure>
<p lang="en"><strong>5. Start the Docker Container</strong></p>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="bash" data-theme="github-dark"><code data-language="bash" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#79B8FF">cd</span><span style="color:#9ECBFF"> /home/funq/dev/infra/postgres</span></span>
<span data-line=""><span style="color:#B392F0">docker</span><span style="color:#9ECBFF"> compose</span><span style="color:#9ECBFF"> up</span><span style="color:#79B8FF"> -d</span></span></code></pre></figure>
<p lang="en"><strong>6. Restore the Data</strong></p>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="bash" data-theme="github-dark"><code data-language="bash" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#6A737D"># 백업 파일 복사</span></span>
<span data-line=""><span style="color:#B392F0">cp</span><span style="color:#9ECBFF"> /tmp/pg_backup_all.sql</span><span style="color:#9ECBFF"> ./init/01_restore.sql</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#6A737D"># 비표준 명령어 제거 (pg_dumpall 출력에 포함된 경우)</span></span>
<span data-line=""><span style="color:#B392F0">sed</span><span style="color:#79B8FF"> -i</span><span style="color:#9ECBFF"> '/^\\restrict/d; /^\\unrestrict/d'</span><span style="color:#9ECBFF"> ./init/01_restore.sql</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#6A737D"># 수동 복원 (볼륨이 이미 초기화된 경우)</span></span>
<span data-line=""><span style="color:#B392F0">docker</span><span style="color:#9ECBFF"> exec</span><span style="color:#79B8FF"> -i</span><span style="color:#9ECBFF"> postgres</span><span style="color:#9ECBFF"> psql</span><span style="color:#79B8FF"> -U</span><span style="color:#9ECBFF"> postgres</span><span style="color:#F97583"> &#x3C;</span><span style="color:#9ECBFF"> ./init/01_restore.sql</span></span></code></pre></figure>
<p lang="en"><strong>7. Verify the Restore</strong></p>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="bash" data-theme="github-dark"><code data-language="bash" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#6A737D"># 데이터베이스 목록 확인</span></span>
<span data-line=""><span style="color:#B392F0">docker</span><span style="color:#9ECBFF"> exec</span><span style="color:#9ECBFF"> postgres</span><span style="color:#9ECBFF"> psql</span><span style="color:#79B8FF"> -U</span><span style="color:#9ECBFF"> postgres</span><span style="color:#79B8FF"> -c</span><span style="color:#9ECBFF"> "\l"</span></span></code></pre></figure>
<hr lang="en">
<h2 lang="en" id="dockerfile--docker-compose">Dockerfile &#x26; Docker Compose</h2>
<h3 lang="en" id="docker-개념-정리">Docker Concepts</h3>
<pre><code>Dockerfile = 요리 레시피
Docker Image = 완성된 요리 (냉동 보관)
Docker Container = 요리를 데워서 서빙한 상태 (실행 중)
</code></pre>
<h3 lang="en" id="dockerfile-multi-stage-build">Dockerfile (Multi-stage Build)</h3>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="dockerfile" data-theme="github-dark"><code data-language="dockerfile" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#6A737D"># Stage 1: 빌드 단계 - 의존성 설치</span></span>
<span data-line=""><span style="color:#F97583">FROM</span><span style="color:#E1E4E8"> python:3.12-slim </span><span style="color:#F97583">AS</span><span style="color:#E1E4E8"> builder</span></span>
<span data-line=""><span style="color:#F97583">WORKDIR</span><span style="color:#E1E4E8"> /app</span></span>
<span data-line=""><span style="color:#F97583">RUN</span><span style="color:#E1E4E8"> apt-get update &#x26;&#x26; apt-get install -y gcc libpq-dev</span></span>
<span data-line=""><span style="color:#F97583">COPY</span><span style="color:#E1E4E8"> requirements.txt .</span></span>
<span data-line=""><span style="color:#F97583">RUN</span><span style="color:#E1E4E8"> pip install --no-cache-dir --user -r requirements.txt</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#6A737D"># Stage 2: 프로덕션 - 최소 런타임</span></span>
<span data-line=""><span style="color:#F97583">FROM</span><span style="color:#E1E4E8"> python:3.12-slim </span><span style="color:#F97583">AS</span><span style="color:#E1E4E8"> production</span></span>
<span data-line=""><span style="color:#F97583">WORKDIR</span><span style="color:#E1E4E8"> /app</span></span>
<span data-line=""><span style="color:#F97583">RUN</span><span style="color:#E1E4E8"> apt-get update &#x26;&#x26; apt-get install -y libpq5 curl \</span></span>
<span data-line=""><span style="color:#E1E4E8">    &#x26;&#x26; useradd --create-home appuser</span></span>
<span data-line=""><span style="color:#F97583">COPY</span><span style="color:#E1E4E8"> --from=builder /root/.local /home/appuser/.local</span></span>
<span data-line=""><span style="color:#F97583">COPY</span><span style="color:#E1E4E8"> --chown=appuser:appuser app/ ./app/</span></span>
<span data-line=""><span style="color:#F97583">COPY</span><span style="color:#E1E4E8"> --chown=appuser:appuser alembic/ ./alembic/</span></span>
<span data-line=""><span style="color:#F97583">USER</span><span style="color:#E1E4E8"> appuser</span></span>
<span data-line=""><span style="color:#F97583">CMD</span><span style="color:#E1E4E8"> [</span><span style="color:#9ECBFF">"uvicorn"</span><span style="color:#E1E4E8">, </span><span style="color:#9ECBFF">"app.main:app"</span><span style="color:#E1E4E8">, </span><span style="color:#9ECBFF">"--host"</span><span style="color:#E1E4E8">, </span><span style="color:#9ECBFF">"0.0.0.0"</span><span style="color:#E1E4E8">, </span><span style="color:#9ECBFF">"--port"</span><span style="color:#E1E4E8">, </span><span style="color:#9ECBFF">"8000"</span><span style="color:#E1E4E8">]</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#6A737D"># Stage 3: 개발 - 테스트 도구 포함</span></span>
<span data-line=""><span style="color:#F97583">FROM</span><span style="color:#E1E4E8"> production </span><span style="color:#F97583">AS</span><span style="color:#E1E4E8"> development</span></span>
<span data-line=""><span style="color:#F97583">USER</span><span style="color:#E1E4E8"> root</span></span>
<span data-line=""><span style="color:#F97583">RUN</span><span style="color:#E1E4E8"> pip install pytest pytest-cov pytest-asyncio httpx</span></span>
<span data-line=""><span style="color:#F97583">USER</span><span style="color:#E1E4E8"> appuser</span></span>
<span data-line=""><span style="color:#F97583">CMD</span><span style="color:#E1E4E8"> [</span><span style="color:#9ECBFF">"uvicorn"</span><span style="color:#E1E4E8">, </span><span style="color:#9ECBFF">"app.main:app"</span><span style="color:#E1E4E8">, </span><span style="color:#9ECBFF">"--host"</span><span style="color:#E1E4E8">, </span><span style="color:#9ECBFF">"0.0.0.0"</span><span style="color:#E1E4E8">, </span><span style="color:#9ECBFF">"--port"</span><span style="color:#E1E4E8">, </span><span style="color:#9ECBFF">"8000"</span><span style="color:#E1E4E8">, </span><span style="color:#9ECBFF">"--reload"</span><span style="color:#E1E4E8">]</span></span></code></pre></figure>
<p lang="en"><strong>Benefits of a Multi-stage Build</strong>:</p>
<ul lang="en">
<li>Build tools such as gcc from the builder stage are excluded from the final image</li>
<li>Smaller image (~800MB → ~300MB)</li>
<li>Improved security (runs as a non-root user)</li>
</ul>
<h3 lang="en" id="multi-stage-build-구조">Multi-stage Build Structure</h3>
<pre><code>┌─────────────────────────────────────────────────────────────┐
│ Stage 1: builder                                            │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Python 3.12-slim                                        │ │
│ │ + gcc (C 컴파일러)        ← 빌드에만 필요               │ │
│ │ + libpq-dev (개발 헤더)   ← 빌드에만 필요               │ │
│ │ + pip 패키지들                                          │ │
│ └─────────────────────────────────────────────────────────┘ │
│                            │                                 │
│                    패키지만 복사                             │
│                            ▼                                 │
│ Stage 2: production                                         │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Python 3.12-slim (깨끗한 상태)                          │ │
│ │ + libpq5 (런타임 라이브러리만)                          │ │
│ │ + pip 패키지들 (builder에서 복사)                       │ │
│ │ + 소스 코드                                             │ │
│ │                                                         │ │
│ │ gcc 없음! libpq-dev 없음! → 이미지 크기 감소            │ │
│ └─────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
</code></pre>
<h3 lang="en" id="docker-compose-설정">Docker Compose Configuration</h3>
<p lang="en"><strong>Development (docker-compose.yml)</strong>:</p>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="yaml" data-theme="github-dark"><code data-language="yaml" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#85E89D">services</span><span style="color:#E1E4E8">:</span></span>
<span data-line=""><span style="color:#85E89D">  backend-api</span><span style="color:#E1E4E8">:</span></span>
<span data-line=""><span style="color:#85E89D">    build</span><span style="color:#E1E4E8">:</span></span>
<span data-line=""><span style="color:#85E89D">      context</span><span style="color:#E1E4E8">: </span><span style="color:#79B8FF">.</span></span>
<span data-line=""><span style="color:#85E89D">      target</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">development</span><span style="color:#6A737D">    # 개발 단계 사용</span></span>
<span data-line=""><span style="color:#85E89D">    ports</span><span style="color:#E1E4E8">:</span></span>
<span data-line=""><span style="color:#E1E4E8">      - </span><span style="color:#9ECBFF">"28000:8000"</span><span style="color:#6A737D">         # 로컬:컨테이너 포트 매핑</span></span>
<span data-line=""><span style="color:#85E89D">    volumes</span><span style="color:#E1E4E8">:</span></span>
<span data-line=""><span style="color:#E1E4E8">      - </span><span style="color:#9ECBFF">./app:/app/app:ro</span><span style="color:#6A737D">    # 코드 변경 시 즉시 반영</span></span>
<span data-line=""><span style="color:#85E89D">    environment</span><span style="color:#E1E4E8">:</span></span>
<span data-line=""><span style="color:#E1E4E8">      - </span><span style="color:#9ECBFF">DATABASE_URL=postgresql://postgres:postgres@postgres:5432/backend_api</span></span>
<span data-line=""><span style="color:#85E89D">    networks</span><span style="color:#E1E4E8">:</span></span>
<span data-line=""><span style="color:#E1E4E8">      - </span><span style="color:#9ECBFF">funq-network</span></span></code></pre></figure>
<p lang="en"><strong>Production (docker-compose.prod.yml)</strong>:</p>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="yaml" data-theme="github-dark"><code data-language="yaml" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#85E89D">services</span><span style="color:#E1E4E8">:</span></span>
<span data-line=""><span style="color:#85E89D">  backend-api</span><span style="color:#E1E4E8">:</span></span>
<span data-line=""><span style="color:#85E89D">    image</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">ghcr.io/nasodev/backend-api:${IMAGE_TAG:-latest}</span></span>
<span data-line=""><span style="color:#85E89D">    ports</span><span style="color:#E1E4E8">:</span></span>
<span data-line=""><span style="color:#E1E4E8">      - </span><span style="color:#9ECBFF">"127.0.0.1:8000:8000"</span><span style="color:#6A737D">  # Nginx 프록시만 접근 가능</span></span>
<span data-line=""><span style="color:#85E89D">    env_file</span><span style="color:#E1E4E8">:</span></span>
<span data-line=""><span style="color:#E1E4E8">      - </span><span style="color:#9ECBFF">/home/funq/config/backend-api/.env.prod</span></span>
<span data-line=""><span style="color:#85E89D">    deploy</span><span style="color:#E1E4E8">:</span></span>
<span data-line=""><span style="color:#85E89D">      resources</span><span style="color:#E1E4E8">:</span></span>
<span data-line=""><span style="color:#85E89D">        limits</span><span style="color:#E1E4E8">:</span></span>
<span data-line=""><span style="color:#85E89D">          memory</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">512M</span></span>
<span data-line=""><span style="color:#85E89D">    logging</span><span style="color:#E1E4E8">:</span></span>
<span data-line=""><span style="color:#85E89D">      options</span><span style="color:#E1E4E8">:</span></span>
<span data-line=""><span style="color:#85E89D">        max-size</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"10m"</span></span>
<span data-line=""><span style="color:#85E89D">        max-file</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"3"</span></span></code></pre></figure>
<p lang="en"><strong>Differences Between Development and Production:</strong></p>








































<table lang="en"><thead><tr><th>Item</th><th>Development</th><th>Production</th></tr></thead><tbody><tr><td>Image</td><td><code>build:</code> (build locally)</td><td><code>image:</code> (pull from GHCR)</td></tr><tr><td>Ports</td><td><code>28000:8000</code></td><td><code>127.0.0.1:8000:8000</code></td></tr><tr><td>Environment variables</td><td><code>environment:</code></td><td><code>env_file:</code></td></tr><tr><td>Volumes</td><td>Enabled (code synchronization)</td><td>None</td></tr><tr><td>Resource limits</td><td>None</td><td><code>memory: 512M</code></td></tr><tr><td>Log configuration</td><td>None</td><td>Rotation configured</td></tr></tbody></table>
<h3 lang="en" id="docker-network">Docker Network</h3>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="bash" data-theme="github-dark"><code data-language="bash" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#6A737D"># 네트워크 생성</span></span>
<span data-line=""><span style="color:#B392F0">docker</span><span style="color:#9ECBFF"> network</span><span style="color:#9ECBFF"> create</span><span style="color:#9ECBFF"> funq-network</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#6A737D"># PostgreSQL 컨테이너 연결</span></span>
<span data-line=""><span style="color:#B392F0">docker</span><span style="color:#9ECBFF"> network</span><span style="color:#9ECBFF"> connect</span><span style="color:#9ECBFF"> funq-network</span><span style="color:#9ECBFF"> postgres</span></span></code></pre></figure>
<p lang="en">Containers must be on the same network to communicate by container name.</p>
<pre><code>┌─────────────────────────────────────┐
│           funq-network              │
│                                     │
│  backend-api  ←──→  postgres        │
│  (FastAPI)          (PostgreSQL)    │
└─────────────────────────────────────┘
</code></pre>
<hr lang="en">
<h2 lang="en" id="ghcr--github-actions-cicd">GHCR &#x26; GitHub Actions CI/CD</h2>
<h3 lang="en" id="ghcr이-뭔가">What Is GHCR?</h3>
<pre><code>GHCR = GitHub Container Registry
     = GitHub이 제공하는 Docker 이미지 저장소
</code></pre>
<p lang="en">It serves a similar purpose to Docker Hub:</p>





















<table lang="en"><thead><tr><th>Registry</th><th>Example image address</th></tr></thead><tbody><tr><td>Docker Hub</td><td><code>docker.io/nginx:latest</code></td></tr><tr><td>GHCR</td><td><code>ghcr.io/nasodev/backend-api:latest</code></td></tr><tr><td>AWS ECR</td><td><code>123456.dkr.ecr.ap-northeast-2.amazonaws.com/my-app:latest</code></td></tr></tbody></table>
<h3 lang="en" id="왜-ghcr을-사용하나">Why Use GHCR?</h3>
<p lang="en"><strong>Previous Approach (Without GHCR):</strong></p>
<pre><code>개발자 PC에서 코드 작성
       ↓
GitHub에 push
       ↓
서버에서 git pull
       ↓
서버에서 docker build  ← 서버 리소스 사용, 느림
       ↓
docker compose up
</code></pre>
<p lang="en"><strong>With GHCR:</strong></p>
<pre><code>개발자 PC에서 코드 작성
       ↓
GitHub에 push
       ↓
GitHub Actions에서 docker build  ← GitHub 서버가 빌드 (무료)
       ↓
빌드된 이미지를 GHCR에 저장
       ↓
서버에서 이미지만 pull  ← 빌드 없이 다운로드만
       ↓
docker compose up
</code></pre>
<p lang="en"><strong>Benefit</strong>: the server only downloads an image that has already been built → fast, with no build load on the server</p>
<h3 lang="en" id="전체-흐름">The Full Flow</h3>
<pre><code>┌──────────────────────────────────────────────────────────────┐
│                     GitHub Actions                            │
│                                                               │
│  ┌─────────┐      ┌─────────────┐      ┌─────────┐          │
│  │  test   │ ───→ │ build-push  │ ───→ │ deploy  │          │
│  └─────────┘      └─────────────┘      └─────────┘          │
│                          │                   │               │
│                          ▼                   │               │
│                   ┌───────────┐              │               │
│                   │   GHCR    │              │               │
│                   │ (이미지   │              │               │
│                   │  저장소)  │              │               │
│                   └───────────┘              │               │
└──────────────────────────────────────────────│───────────────┘
                          │                    │
                          │         SSH로 서버에 접속
                          │                    │
                          ▼                    ▼
┌─────────────────────────────────────────────────────────────┐
│                    Production Server                         │
│                                                              │
│   docker pull ghcr.io/nasodev/backend-api:latest            │
│          ↓                                                   │
│   docker compose up -d                                       │
│                                                              │
└─────────────────────────────────────────────────────────────┘
</code></pre>
<h3 lang="en" id="github-actions-설정">GitHub Actions Configuration</h3>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="yaml" data-theme="github-dark"><code data-language="yaml" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#85E89D">jobs</span><span style="color:#E1E4E8">:</span></span>
<span data-line=""><span style="color:#6A737D">  # 1단계: 테스트</span></span>
<span data-line=""><span style="color:#85E89D">  test</span><span style="color:#E1E4E8">:</span></span>
<span data-line=""><span style="color:#85E89D">    runs-on</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">ubuntu-latest</span></span>
<span data-line=""><span style="color:#85E89D">    steps</span><span style="color:#E1E4E8">:</span></span>
<span data-line=""><span style="color:#E1E4E8">      - </span><span style="color:#85E89D">run</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">pytest tests/ -v</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#6A737D">  # 2단계: 이미지 빌드 &#x26; GHCR 푸시</span></span>
<span data-line=""><span style="color:#85E89D">  build-and-push</span><span style="color:#E1E4E8">:</span></span>
<span data-line=""><span style="color:#85E89D">    needs</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">test</span></span>
<span data-line=""><span style="color:#85E89D">    steps</span><span style="color:#E1E4E8">:</span></span>
<span data-line=""><span style="color:#E1E4E8">      - </span><span style="color:#85E89D">uses</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">docker/login-action@v3</span></span>
<span data-line=""><span style="color:#85E89D">        with</span><span style="color:#E1E4E8">:</span></span>
<span data-line=""><span style="color:#85E89D">          registry</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">ghcr.io</span></span>
<span data-line=""><span style="color:#85E89D">          username</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">${{ github.actor }}</span></span>
<span data-line=""><span style="color:#85E89D">          password</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">${{ secrets.GITHUB_TOKEN }}</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#E1E4E8">      - </span><span style="color:#85E89D">uses</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">docker/build-push-action@v5</span></span>
<span data-line=""><span style="color:#85E89D">        with</span><span style="color:#E1E4E8">:</span></span>
<span data-line=""><span style="color:#85E89D">          push</span><span style="color:#E1E4E8">: </span><span style="color:#79B8FF">true</span></span>
<span data-line=""><span style="color:#85E89D">          tags</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">ghcr.io/nasodev/backend-api:latest</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#6A737D">  # 3단계: 서버 배포</span></span>
<span data-line=""><span style="color:#85E89D">  deploy</span><span style="color:#E1E4E8">:</span></span>
<span data-line=""><span style="color:#85E89D">    needs</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">build-and-push</span></span>
<span data-line=""><span style="color:#85E89D">    steps</span><span style="color:#E1E4E8">:</span></span>
<span data-line=""><span style="color:#E1E4E8">      - </span><span style="color:#85E89D">name</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">SSH로 배포</span></span>
<span data-line=""><span style="color:#85E89D">        uses</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">appleboy/ssh-action@v1.0.3</span></span>
<span data-line=""><span style="color:#85E89D">        with</span><span style="color:#E1E4E8">:</span></span>
<span data-line=""><span style="color:#85E89D">          host</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">${{ secrets.SSH_HOST }}</span></span>
<span data-line=""><span style="color:#85E89D">          username</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">${{ secrets.SSH_USER }}</span></span>
<span data-line=""><span style="color:#85E89D">          key</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">${{ secrets.SSH_KEY }}</span></span>
<span data-line=""><span style="color:#85E89D">          script</span><span style="color:#E1E4E8">: </span><span style="color:#F97583">|</span></span>
<span data-line=""><span style="color:#9ECBFF">            echo "${{ secrets.GHCR_TOKEN }}" | docker login ghcr.io -u nasodev --password-stdin</span></span>
<span data-line=""><span style="color:#9ECBFF">            docker pull ghcr.io/nasodev/backend-api:latest</span></span>
<span data-line=""><span style="color:#9ECBFF">            docker compose -f docker-compose.prod.yml up -d</span></span></code></pre></figure>
<h3 lang="en" id="pat-personal-access-token-설정">Setting Up a PAT (Personal Access Token)</h3>
<p lang="en">Authentication is required to pull images from GHCR.</p>




















<table lang="en"><thead><tr><th>Situation</th><th>Token used</th><th>Description</th></tr></thead><tbody><tr><td>GitHub Actions → GHCR push</td><td><code>GITHUB_TOKEN</code></td><td>Provided automatically; no setup needed</td></tr><tr><td>Server → Pull from GHCR</td><td><code>PAT (GHCR_TOKEN)</code></td><td>Must be created manually</td></tr></tbody></table>
<p lang="en"><strong>How to Create a PAT:</strong></p>
<ol lang="en">
<li>GitHub → Settings → Developer settings → Personal access tokens</li>
<li>Create a token with <code>read:packages</code> permission</li>
<li>Register it as <code>GHCR_TOKEN</code> in GitHub Actions Secrets</li>
</ol>
<h3 lang="en" id="이미지-태그-전략">Image Tag Strategy</h3>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="yaml" data-theme="github-dark"><code data-language="yaml" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#85E89D">tags</span><span style="color:#E1E4E8">: </span><span style="color:#F97583">|</span></span>
<span data-line=""><span style="color:#9ECBFF">  type=sha,prefix=       # 커밋 SHA (예: abc1234)</span></span>
<span data-line=""><span style="color:#9ECBFF">  type=raw,value=latest  # 항상 latest</span></span></code></pre></figure>
<p lang="en">Result:</p>
<pre><code>ghcr.io/nasodev/backend-api:latest   ← 최신 버전
ghcr.io/nasodev/backend-api:abc1234  ← 특정 커밋 버전
</code></pre>
<p lang="en">If a rollback is needed:</p>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="bash" data-theme="github-dark"><code data-language="bash" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#B392F0">docker</span><span style="color:#9ECBFF"> pull</span><span style="color:#9ECBFF"> ghcr.io/nasodev/backend-api:abc1234</span></span>
<span data-line=""><span style="color:#B392F0">docker</span><span style="color:#9ECBFF"> compose</span><span style="color:#9ECBFF"> up</span><span style="color:#79B8FF"> -d</span></span></code></pre></figure>
<hr lang="en">
<h2 lang="en" id="운영-명령어">Operations Commands</h2>
<h3 lang="en" id="상태-확인">Check Status</h3>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="bash" data-theme="github-dark"><code data-language="bash" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#B392F0">docker</span><span style="color:#9ECBFF"> ps</span></span>
<span data-line=""><span style="color:#B392F0">docker</span><span style="color:#9ECBFF"> logs</span><span style="color:#9ECBFF"> backend-api</span></span>
<span data-line=""><span style="color:#B392F0">curl</span><span style="color:#9ECBFF"> http://localhost:8000/health</span></span></code></pre></figure>
<h3 lang="en" id="재시작">Restart</h3>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="bash" data-theme="github-dark"><code data-language="bash" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#B392F0">docker</span><span style="color:#9ECBFF"> compose</span><span style="color:#79B8FF"> -f</span><span style="color:#9ECBFF"> docker-compose.prod.yml</span><span style="color:#9ECBFF"> restart</span></span></code></pre></figure>
<h3 lang="en" id="롤백">Roll Back</h3>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="bash" data-theme="github-dark"><code data-language="bash" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#6A737D"># 이전 버전으로 롤백</span></span>
<span data-line=""><span style="color:#F97583">export</span><span style="color:#E1E4E8"> IMAGE_TAG</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">abc1234</span></span>
<span data-line=""><span style="color:#B392F0">docker</span><span style="color:#9ECBFF"> compose</span><span style="color:#79B8FF"> -f</span><span style="color:#9ECBFF"> docker-compose.prod.yml</span><span style="color:#9ECBFF"> up</span><span style="color:#79B8FF"> -d</span></span></code></pre></figure>
<h3 lang="en" id="로그-확인">View Logs</h3>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="bash" data-theme="github-dark"><code data-language="bash" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#6A737D"># 실시간 로그</span></span>
<span data-line=""><span style="color:#B392F0">docker</span><span style="color:#9ECBFF"> logs</span><span style="color:#79B8FF"> -f</span><span style="color:#9ECBFF"> backend-api</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#6A737D"># 최근 100줄</span></span>
<span data-line=""><span style="color:#B392F0">docker</span><span style="color:#9ECBFF"> logs</span><span style="color:#79B8FF"> --tail</span><span style="color:#79B8FF"> 100</span><span style="color:#9ECBFF"> backend-api</span></span></code></pre></figure>
<h3 lang="en" id="디스크-정리">Clean Up Disk Space</h3>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="bash" data-theme="github-dark"><code data-language="bash" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#B392F0">docker</span><span style="color:#9ECBFF"> system</span><span style="color:#9ECBFF"> prune</span><span style="color:#79B8FF"> -a</span><span style="color:#6A737D">  # 미사용 이미지/컨테이너 정리</span></span></code></pre></figure>
<hr lang="en">
<h2 lang="en" id="파일-구조">File Structure</h2>
<pre><code># PostgreSQL (infra 저장소)
/home/funq/dev/infra/postgres/
├── docker-compose.yml
├── .env
├── .env.example
└── init/
    └── 01_restore.sql

# Backend API
backend-api/
├── Dockerfile              # 멀티 스테이지 빌드 설정
├── .dockerignore           # 빌드 제외 파일
├── docker-compose.yml      # 개발 환경
├── docker-compose.prod.yml # 프로덕션 환경
├── deploy/
│   └── docker-setup.sh     # 서버 초기 설정 스크립트
├── run-local.sh            # 로컬 실행 스크립트
└── .github/
    └── workflows/
        └── deploy.yml      # CI/CD 파이프라인
</code></pre>
<hr lang="en">
<h2 lang="en" id="결론">Conclusion</h2>
<p lang="en">Benefits gained from moving to Docker:</p>
<ol lang="en">
<li><strong>Faster deployment</strong>: just pull an image, without pip install</li>
<li><strong>Consistent environments</strong>: the local and server environments match</li>
<li><strong>Easy rollbacks</strong>: recover immediately with an earlier image tag</li>
<li><strong>Resource management</strong>: memory and CPU limits improve stability</li>
<li><strong>Log management</strong>: automatic rotation keeps disk usage under control</li>
<li><strong>Simpler DB management</strong>: manage PostgreSQL as a container too</li>
</ol>
<p lang="en">The initial setup is complex, but once it is in place, operations become much easier.</p>]]></content:encoded>
    </item>
    <item>
      <title><![CDATA[Building a Claude Code Plugin: Automating Git Workflows]]></title>
      <link>https://blog.funq.kr/en/blogs/claude-code-plugin-20260118-v01</link>
      <guid isPermaLink="true">https://blog.funq.kr/en/blogs/claude-code-plugin-20260118-v01</guid>
      <description><![CDATA[Create a personal marketplace and develop a Claude Code plugin that automates Git tasks through slash commands.]]></description>
      <category><![CDATA[claude-code]]></category><category><![CDATA[plugin]]></category><category><![CDATA[git]]></category><category><![CDATA[automation]]></category><category><![CDATA[devtools]]></category>
      <media:content url="https://blog.funq.kr/blog-cover/claude-code-plugin-20260118-v01.jpg" medium="image" type="image/jpeg"/>
      <pubDate>Sun, 18 Jan 2026 03:00:00 GMT</pubDate>
      <author>funqdev@gmail.com (fundev)</author>
      <content:encoded><![CDATA[<p lang="en">I wanted to make Git work more efficient.</p>
<p lang="en">At first, I wrote shell scripts and put prompts telling Claude to run them in <code>.md</code> files. Pasting them into Claude Code ran the scripts and produced good results.</p>
<p lang="en">In an IDE such as Cursor, pasting prompts into split terminal panes was convenient. Warp, which I use these days, is a terminal with an attached editor, so copying prompts from files was awkward. Converting them to slash commands seemed likely to solve that too.</p>
<p lang="en">While I was at it, I thought I might as well turn all my Git tasks into commands: branch creation, commits, pushes, PRs, and merges. Automating the whole workflow seemed like something that would pay off later.</p>
<p lang="en">But once there were seven commands, managing them became difficult. Copying them into every project was cumbersome too.</p>
<p lang="en">So I made a plugin.</p>
<hr lang="en">
<h2 lang="en" id="완성된-결과물">The Finished Result</h2>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="bash" data-theme="github-dark"><code data-language="bash" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#6A737D"># Marketplace 추가</span></span>
<span data-line=""><span style="color:#B392F0">/plugin</span><span style="color:#9ECBFF"> marketplace</span><span style="color:#9ECBFF"> add</span><span style="color:#9ECBFF"> nasodev/nasodev-marketplace</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#6A737D"># 플러그인 설치</span></span>
<span data-line=""><span style="color:#B392F0">/plugin</span><span style="color:#9ECBFF"> install</span><span style="color:#9ECBFF"> git-workflow@nasodev-marketplace</span></span></code></pre></figure>
<p lang="en">Installing it adds seven slash commands:</p>





































<table lang="en"><thead><tr><th>Command</th><th>Description</th></tr></thead><tbody><tr><td><code>/git:0.check</code></td><td>Check Git status and configuration</td></tr><tr><td><code>/git:1.branch</code></td><td>Create a new branch</td></tr><tr><td><code>/git:2.sync</code></td><td>Synchronize the main branch</td></tr><tr><td><code>/git:3.commit</code></td><td>Commit changes</td></tr><tr><td><code>/git:4.push</code></td><td>Push to the remote</td></tr><tr><td><code>/git:5.pr</code></td><td>Create a pull request</td></tr><tr><td><code>/git:6.merge</code></td><td>Merge into main</td></tr></tbody></table>
<p lang="en">I numbered them in workflow order so it would be clear what to do first.</p>
<hr lang="en">
<h2 lang="en" id="왜-플러그인인가">Why a Plugin?</h2>
<p lang="en">Initially, I just placed the command files in the project's <code>.claude/commands/</code> directory.</p>
<pre><code>.claude/commands/
├── git-branch.md
├── git-commit.md
└── git-push.md
</code></pre>
<p lang="en">That worked, but had problems:</p>
<ol lang="en">
<li><strong>Copying into every project</strong>—copy the files whenever a new project starts</li>
<li><strong>Cumbersome updates</strong>—copy scripts back into all projects whenever they change</li>
<li><strong>No version tracking</strong>—no clear record of which project has which version</li>
</ol>
<p lang="en">With a plugin:</p>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="bash" data-theme="github-dark"><code data-language="bash" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#6A737D"># 한 번만 설치하면 모든 프로젝트에서 사용</span></span>
<span data-line=""><span style="color:#B392F0">/plugin</span><span style="color:#9ECBFF"> install</span><span style="color:#9ECBFF"> git-workflow@nasodev-marketplace</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#6A737D"># 업데이트도 한 줄</span></span>
<span data-line=""><span style="color:#B392F0">/plugin</span><span style="color:#9ECBFF"> update</span><span style="color:#9ECBFF"> git-workflow@nasodev-marketplace</span></span></code></pre></figure>
<hr lang="en">
<h2 lang="en" id="플러그인-구조">Plugin Structure</h2>
<p lang="en">A Claude Code plugin is simpler than you might expect.</p>
<pre><code>claude-git-workflow/
├── .claude-plugin/
│   └── plugin.json          # 플러그인 메타데이터
├── commands/
│   ├── git:0.check.md       # 슬래시 커맨드 정의
│   ├── git:1.branch.md
│   └── ...
├── scripts/
│   ├── git-check.sh         # 실제 실행될 스크립트
│   ├── git-branch.sh
│   └── ...
└── README.md
</code></pre>
<h3 lang="en" id="pluginjson">plugin.json</h3>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="json" data-theme="github-dark"><code data-language="json" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#E1E4E8">{</span></span>
<span data-line=""><span style="color:#79B8FF">  "name"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"git-workflow"</span><span style="color:#E1E4E8">,</span></span>
<span data-line=""><span style="color:#79B8FF">  "description"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"Git workflow automation with interactive scripts"</span><span style="color:#E1E4E8">,</span></span>
<span data-line=""><span style="color:#79B8FF">  "version"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"1.3.0"</span><span style="color:#E1E4E8">,</span></span>
<span data-line=""><span style="color:#79B8FF">  "author"</span><span style="color:#E1E4E8">: {</span></span>
<span data-line=""><span style="color:#79B8FF">    "name"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"nasodev"</span><span style="color:#E1E4E8">,</span></span>
<span data-line=""><span style="color:#79B8FF">    "email"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"creationand2017@gmail.com"</span></span>
<span data-line=""><span style="color:#E1E4E8">  },</span></span>
<span data-line=""><span style="color:#79B8FF">  "repository"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"https://github.com/nasodev/claude-git-workflow"</span><span style="color:#E1E4E8">,</span></span>
<span data-line=""><span style="color:#79B8FF">  "license"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"MIT"</span></span>
<span data-line=""><span style="color:#E1E4E8">}</span></span></code></pre></figure>
<h3 lang="en" id="커맨드-파일-git0checkmd">Command File (git:0.check.md)</h3>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="markdown" data-theme="github-dark"><code data-language="markdown" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#E1E4E8">---</span></span>
<span data-line=""><span style="color:#85E89D">name</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">git:0.check</span></span>
<span data-line=""><span style="color:#85E89D">description</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"Git 상태 확인 (isolated fork)"</span></span>
<span data-line=""><span style="color:#85E89D">context</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">fork</span></span>
<span data-line=""><span style="color:#85E89D">allowed-tools</span><span style="color:#E1E4E8">: [</span><span style="color:#9ECBFF">Bash</span><span style="color:#E1E4E8">]</span></span>
<span data-line=""><span style="color:#85E89D">model</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">haiku</span></span>
<span data-line=""><span style="color:#E1E4E8">---</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#79B8FF;font-weight:bold"># Git 상태 및 설정 확인</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#E1E4E8">현재 저장소의 브랜치, 사용자 설정, 작업 상태를 확인한다.</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#79B8FF;font-weight:bold">---</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#E1E4E8;font-weight:bold">**반드시 Bash tool로 아래 스크립트를 실행해라. 다른 방법은 허용하지 않는다.**</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#79B8FF">\`\`\`</span><span style="color:#E1E4E8">bash</span></span>
<span data-line=""><span style="color:#E1E4E8">./scripts/git-check.sh</span></span>
<span data-line=""><span style="color:#79B8FF">\`\`\`</span></span></code></pre></figure>
<p lang="en">Two key points:</p>
<ol lang="en">
<li><strong>Frontmatter</strong>: isolated execution requires <code>context: fork</code> along with <code>name</code>, <code>allowed-tools</code>, and <code>model</code></li>
<li><strong>Explicit instructions</strong>: “Always execute the script with the Bash tool.” Without this, Claude sometimes explains the script instead of running it</li>
</ol>
<hr lang="en">
<h2 lang="en" id="marketplace가-필요한-이유">Why a Marketplace Is Needed</h2>
<p lang="en">The plugin is built—how do I distribute it?</p>
<p lang="en">Claude Code uses a <strong>marketplace-based</strong> plugin system.</p>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="bash" data-theme="github-dark"><code data-language="bash" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#6A737D"># 이렇게 직접 설치 불가</span></span>
<span data-line=""><span style="color:#B392F0">/plugin</span><span style="color:#9ECBFF"> install</span><span style="color:#9ECBFF"> nasodev/claude-git-workflow</span><span style="color:#6A737D">  # 에러</span></span></code></pre></figure>
<p lang="en">Plugins can be installed only through a marketplace:</p>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="bash" data-theme="github-dark"><code data-language="bash" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#6A737D"># 1. Marketplace 등록</span></span>
<span data-line=""><span style="color:#B392F0">/plugin</span><span style="color:#9ECBFF"> marketplace</span><span style="color:#9ECBFF"> add</span><span style="color:#9ECBFF"> nasodev/nasodev-marketplace</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#6A737D"># 2. 그 다음에 설치 가능</span></span>
<span data-line=""><span style="color:#B392F0">/plugin</span><span style="color:#9ECBFF"> install</span><span style="color:#9ECBFF"> git-workflow@nasodev-marketplace</span></span></code></pre></figure>
<h3 lang="en" id="marketplace-구조">Marketplace Structure</h3>
<p lang="en">A marketplace is also a GitHub repository:</p>
<pre><code>nasodev-marketplace/
├── .claude-plugin/
│   └── marketplace.json     # 플러그인 목록
├── README.md
└── LICENSE
</code></pre>
<h3 lang="en" id="marketplacejson">marketplace.json</h3>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="json" data-theme="github-dark"><code data-language="json" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#E1E4E8">{</span></span>
<span data-line=""><span style="color:#79B8FF">  "name"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"nasodev-marketplace"</span><span style="color:#E1E4E8">,</span></span>
<span data-line=""><span style="color:#79B8FF">  "owner"</span><span style="color:#E1E4E8">: {</span></span>
<span data-line=""><span style="color:#79B8FF">    "name"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"nasodev"</span><span style="color:#E1E4E8">,</span></span>
<span data-line=""><span style="color:#79B8FF">    "email"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"creationand2017@gmail.com"</span></span>
<span data-line=""><span style="color:#E1E4E8">  },</span></span>
<span data-line=""><span style="color:#79B8FF">  "metadata"</span><span style="color:#E1E4E8">: {</span></span>
<span data-line=""><span style="color:#79B8FF">    "description"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"Claude Code plugins by nasodev"</span><span style="color:#E1E4E8">,</span></span>
<span data-line=""><span style="color:#79B8FF">    "version"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"1.0.0"</span></span>
<span data-line=""><span style="color:#E1E4E8">  },</span></span>
<span data-line=""><span style="color:#79B8FF">  "plugins"</span><span style="color:#E1E4E8">: [</span></span>
<span data-line=""><span style="color:#E1E4E8">    {</span></span>
<span data-line=""><span style="color:#79B8FF">      "name"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"git-workflow"</span><span style="color:#E1E4E8">,</span></span>
<span data-line=""><span style="color:#79B8FF">      "description"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"Git workflow automation with interactive scripts"</span><span style="color:#E1E4E8">,</span></span>
<span data-line=""><span style="color:#79B8FF">      "version"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"1.3.0"</span><span style="color:#E1E4E8">,</span></span>
<span data-line=""><span style="color:#79B8FF">      "source"</span><span style="color:#E1E4E8">: {</span></span>
<span data-line=""><span style="color:#79B8FF">        "source"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"url"</span><span style="color:#E1E4E8">,</span></span>
<span data-line=""><span style="color:#79B8FF">        "url"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"https://github.com/nasodev/claude-git-workflow.git"</span></span>
<span data-line=""><span style="color:#E1E4E8">      }</span></span>
<span data-line=""><span style="color:#E1E4E8">    }</span></span>
<span data-line=""><span style="color:#E1E4E8">  ]</span></span>
<span data-line=""><span style="color:#E1E4E8">}</span></span></code></pre></figure>
<p lang="en">Add the plugin to the <code>plugins</code> array to make it installable by users.</p>
<hr lang="en">
<h2 lang="en" id="스크립트-하이라이트">Script Highlights</h2>
<h3 lang="en" id="git-checksh---현재-상태-한눈에">git-check.sh: Current Status at a Glance</h3>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="bash" data-theme="github-dark"><code data-language="bash" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#B392F0">./scripts/git-check.sh</span></span></code></pre></figure>
<p lang="en">Running it shows the following:</p>
<ul lang="en">
<li><strong>Repository</strong>: name, path, and remote URL</li>
<li><strong>Branch</strong>: current branch, default branch, and ahead/behind status</li>
<li><strong>User Configuration</strong>: local, global, and effective settings</li>
<li><strong>Working Directory</strong>: counts of staged, unstaged, and untracked files</li>
<li><strong>Recent Commits</strong>: the five most recent commits</li>
</ul>
<p lang="en">Showing <strong>Local vs Global</strong> user settings together is particularly useful for avoiding the question, “Whose name will this commit use?”</p>
<h3 lang="en" id="git-pushsh---멀티-언어-린트-지원">git-push.sh: Linting Across Languages</h3>
<p lang="en">It runs lint checks automatically before pushing:</p>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="bash" data-theme="github-dark"><code data-language="bash" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#6A737D"># 프로젝트 타입 자동 감지</span></span>
<span data-line=""><span style="color:#F97583">if</span><span style="color:#E1E4E8"> [ </span><span style="color:#F97583">-f</span><span style="color:#9ECBFF"> "package.json"</span><span style="color:#E1E4E8"> ]; </span><span style="color:#F97583">then</span></span>
<span data-line=""><span style="color:#B392F0">    npm</span><span style="color:#9ECBFF"> run</span><span style="color:#9ECBFF"> lint</span></span>
<span data-line=""><span style="color:#F97583">elif</span><span style="color:#E1E4E8"> [ </span><span style="color:#F97583">-f</span><span style="color:#9ECBFF"> "pyproject.toml"</span><span style="color:#E1E4E8"> ]; </span><span style="color:#F97583">then</span></span>
<span data-line=""><span style="color:#B392F0">    ruff</span><span style="color:#9ECBFF"> check</span><span style="color:#9ECBFF"> .</span><span style="color:#6A737D">  # 또는 pylint</span></span>
<span data-line=""><span style="color:#F97583">elif</span><span style="color:#E1E4E8"> [ </span><span style="color:#F97583">-f</span><span style="color:#9ECBFF"> "go.mod"</span><span style="color:#E1E4E8"> ]; </span><span style="color:#F97583">then</span></span>
<span data-line=""><span style="color:#B392F0">    go</span><span style="color:#9ECBFF"> vet</span><span style="color:#9ECBFF"> ./...</span></span>
<span data-line=""><span style="color:#F97583">elif</span><span style="color:#E1E4E8"> [ </span><span style="color:#F97583">-f</span><span style="color:#9ECBFF"> "Cargo.toml"</span><span style="color:#E1E4E8"> ]; </span><span style="color:#F97583">then</span></span>
<span data-line=""><span style="color:#B392F0">    cargo</span><span style="color:#9ECBFF"> clippy</span></span>
<span data-line=""><span style="color:#F97583">fi</span></span></code></pre></figure>
<p lang="en">It detects Node.js, Python, Go, Rust, and Java projects and runs the appropriate linter.</p>
<h3 lang="en" id="git-branchsh---안전한-브랜치-생성">git-branch.sh: Safe Branch Creation</h3>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="bash" data-theme="github-dark"><code data-language="bash" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#6A737D"># 변경사항이 있으면 자동 stash</span></span>
<span data-line=""><span style="color:#B392F0">git</span><span style="color:#9ECBFF"> stash</span><span style="color:#9ECBFF"> push</span><span style="color:#79B8FF"> -m</span><span style="color:#9ECBFF"> "Auto stash before branch switch"</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#6A737D"># 브랜치 생성</span></span>
<span data-line=""><span style="color:#B392F0">git</span><span style="color:#9ECBFF"> checkout</span><span style="color:#79B8FF"> -b</span><span style="color:#9ECBFF"> feature/new-feature</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#6A737D"># stash 복원</span></span>
<span data-line=""><span style="color:#B392F0">git</span><span style="color:#9ECBFF"> stash</span><span style="color:#9ECBFF"> pop</span></span></code></pre></figure>
<p lang="en">It switches branches safely even when there are uncommitted changes.</p>
<hr lang="en">
<h2 lang="en" id="context-격리-context-fork">Context Isolation: context: fork</h2>
<p lang="en">Running the commands repeatedly exposed a problem. After <code>/git:0.check</code> and then <code>/git:3.commit</code>, the previous conversation context kept accumulating. That wasted tokens, and sometimes the commands referred to earlier results and behaved unexpectedly.</p>
<p lang="en">With <code>context: fork</code>, the command runs in <strong>an isolated context</strong>:</p>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="yaml" data-theme="github-dark"><code data-language="yaml" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#B392F0">---</span></span>
<span data-line=""><span style="color:#85E89D">name</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">git:0.check</span></span>
<span data-line=""><span style="color:#85E89D">description</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"Git 상태 확인 (isolated fork)"</span></span>
<span data-line=""><span style="color:#85E89D">context</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">fork</span></span>
<span data-line=""><span style="color:#85E89D">allowed-tools</span><span style="color:#E1E4E8">: [</span><span style="color:#9ECBFF">Bash</span><span style="color:#E1E4E8">]</span></span>
<span data-line=""><span style="color:#85E89D">model</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">haiku</span></span>
<span data-line=""><span style="color:#B392F0">---</span></span></code></pre></figure>
<h3 lang="en" id="필수-필드">Required Fields</h3>
<p lang="en">A few additional fields are needed for <code>context: fork</code> to work:</p>

























<table lang="en"><thead><tr><th>Field</th><th>Description</th><th>Example</th></tr></thead><tbody><tr><td><code>name</code></td><td>Command identifier</td><td><code>git:0.check</code></td></tr><tr><td><code>allowed-tools</code></td><td>Available tools</td><td><code>[Bash]</code></td></tr><tr><td><code>model</code></td><td>Model to run</td><td><code>haiku</code>, <code>sonnet</code>, <code>opus</code></td></tr></tbody></table>
<p lang="en">Including only <code>description</code> and <code>context: fork</code> does not provide isolation. <code>name</code>, <code>allowed-tools</code>, and <code>model</code> must also be present for “(forked execution)” to appear and actual isolation to occur.</p>
<h3 lang="en" id="모델-선택">Choosing a Model</h3>
<p lang="en">Not every command needs the same model:</p>

























<table lang="en"><thead><tr><th>Model</th><th>Purpose</th><th>Command</th></tr></thead><tbody><tr><td><code>haiku</code></td><td>Simple script execution</td><td>0.check, 1.branch, 2.sync, 4.push, 6.merge</td></tr><tr><td><code>sonnet</code></td><td>Message writing required</td><td>3.commit, 5.pr</td></tr><tr><td><code>opus</code></td><td>Complex analysis or judgment</td><td>(Not used in this plugin)</td></tr></tbody></table>
<p lang="en"><code>haiku</code> is fast and inexpensive, making it suitable for commands that only need to run a script.</p>
<p lang="en"><code>sonnet</code> is used to write commit messages or PR descriptions. A stronger model is needed to create meaningful messages in Korean.</p>
<p lang="en"><code>opus</code> is used when complex analysis or judgment is needed. It suits advanced tasks such as code review and architectural design.</p>
<hr lang="en">
<h2 lang="en" id="설치-테스트">Testing Installation</h2>
<p lang="en">Let's install it:</p>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="bash" data-theme="github-dark"><code data-language="bash" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#6A737D"># 터미널에서</span></span>
<span data-line=""><span style="color:#B392F0">claude</span><span style="color:#9ECBFF"> plugin</span><span style="color:#9ECBFF"> marketplace</span><span style="color:#9ECBFF"> add</span><span style="color:#9ECBFF"> nasodev/nasodev-marketplace</span></span>
<span data-line=""><span style="color:#6A737D"># ✔ Successfully added marketplace: nasodev-marketplace</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#B392F0">claude</span><span style="color:#9ECBFF"> plugin</span><span style="color:#9ECBFF"> install</span><span style="color:#9ECBFF"> git-workflow@nasodev-marketplace</span></span>
<span data-line=""><span style="color:#6A737D"># ✔ Successfully installed plugin: git-workflow@nasodev-marketplace</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#B392F0">claude</span><span style="color:#9ECBFF"> plugin</span><span style="color:#9ECBFF"> list</span></span>
<span data-line=""><span style="color:#6A737D"># ❯ git-workflow@nasodev-marketplace</span></span>
<span data-line=""><span style="color:#6A737D">#   Version: 1.3.0</span></span>
<span data-line=""><span style="color:#6A737D">#   Scope: user</span></span>
<span data-line=""><span style="color:#6A737D">#   Status: ✔ enabled</span></span></code></pre></figure>
<p lang="en">Restart Claude Code after installation to use the <code>/git:</code> commands.</p>
<h3 lang="en" id="설치-스코프">Installation Scopes</h3>
<p lang="en">Plugins can be installed in three scopes:</p>

























<table lang="en"><thead><tr><th>Scope</th><th>Description</th><th>Use case</th></tr></thead><tbody><tr><td><code>user</code></td><td>Available in every project (default)</td><td>Shared tools</td></tr><tr><td><code>project</code></td><td>Current project only; tracked by git</td><td>Team sharing</td></tr><tr><td><code>local</code></td><td>Current project only; ignored by git</td><td>Personal settings</td></tr></tbody></table>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="bash" data-theme="github-dark"><code data-language="bash" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#6A737D"># 글로벌 설치 (기본값)</span></span>
<span data-line=""><span style="color:#B392F0">claude</span><span style="color:#9ECBFF"> plugin</span><span style="color:#9ECBFF"> install</span><span style="color:#9ECBFF"> git-workflow@nasodev-marketplace</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#6A737D"># 프로젝트별 설치</span></span>
<span data-line=""><span style="color:#B392F0">claude</span><span style="color:#9ECBFF"> plugin</span><span style="color:#9ECBFF"> install</span><span style="color:#79B8FF"> -s</span><span style="color:#9ECBFF"> project</span><span style="color:#9ECBFF"> git-workflow@nasodev-marketplace</span></span></code></pre></figure>
<p lang="en">Since the default is <code>user</code>, installing once makes <code>/git:*</code> commands available in any project.</p>
<hr lang="en">
<h2 lang="en" id="github-리포지토리">GitHub Repositories</h2>
<ul lang="en">
<li><strong>Plugin</strong>: <a href="https://github.com/nasodev/claude-git-workflow">https://github.com/nasodev/claude-git-workflow</a></li>
<li><strong>Marketplace</strong>: <a href="https://github.com/nasodev/nasodev-marketplace">https://github.com/nasodev/nasodev-marketplace</a></li>
</ul>
<hr lang="en">
<h2 lang="en" id="마무리">Wrapping Up</h2>
<p lang="en">Claude Code plugins are easier to build than I expected.</p>
<ol lang="en">
<li>In <code>commands/</code>, create <code>.md</code> files</li>
<li>Write <code>.claude-plugin/plugin.json</code></li>
<li>Create a marketplace repository and register the plugin</li>
</ol>
<p lang="en">Then you can use it as <code>/my-command</code> in every project.</p>
<p lang="en">If you have a recurring workflow, try turning it into a plugin.</p>]]></content:encoded>
    </item>
    <item>
      <title><![CDATA[Building a Precision Timer App with React 19, Vite 7, and PWA]]></title>
      <link>https://blog.funq.kr/en/blogs/react-vite-pwa-timer-20260116-v01</link>
      <guid isPermaLink="true">https://blog.funq.kr/en/blogs/react-vite-pwa-timer-20260116-v01</guid>
      <description><![CDATA[Building a sports-stacking timer PWA: a requestAnimationFrame-based precision timer, touch-event handling, and LocalStorage patterns.]]></description>
      <category><![CDATA[react]]></category><category><![CDATA[vite]]></category><category><![CDATA[pwa]]></category><category><![CDATA[typescript]]></category><category><![CDATA[tailwindcss]]></category>
      <media:content url="https://blog.funq.kr/blog-cover/react-vite-pwa-timer-20260116-v01.jpg" medium="image" type="image/jpeg"/>
      <pubDate>Fri, 16 Jan 2026 01:00:00 GMT</pubDate>
      <author>funqdev@gmail.com (fundev)</author>
      <content:encoded><![CDATA[<p lang="en">My child learned cup stacking, or sport stacking, at school and wanted a timer to practice at home. Buying a dedicated timer felt hard to justify, and a smartphone stopwatch was awkward to operate.</p>
<p lang="en">So I decided to build a simple tablet timer that starts and stops when you <strong>touch anywhere on the screen</strong>. Making it a PWA would let us add it to the home screen, use it like an app, and run it offline.</p>
<blockquote lang="en">
<p><strong>Demo</strong>: <a href="https://cupstacking.funq.kr">cupstacking.funq.kr</a></p>
</blockquote>
<h2 lang="en" id="기술-스택">Technology Stack</h2>

























<table lang="en"><thead><tr><th>Stack</th><th>Technology</th></tr></thead><tbody><tr><td>Framework</td><td>React 19.2 + TypeScript 5.9</td></tr><tr><td>Build</td><td>Vite 7.2 + vite-plugin-pwa</td></tr><tr><td>Styling</td><td>Tailwind CSS v4</td></tr><tr><td>Routing</td><td>React Router v7</td></tr></tbody></table>
<hr lang="en">
<h2 lang="en" id="1-requestanimationframe-기반-타이머">1. A requestAnimationFrame-Based Timer</h2>
<h3 lang="en" id="setinterval의-한계">The Limitations of setInterval</h3>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="typescript" data-theme="github-dark"><code data-language="typescript" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#6A737D">// ❌ 부정확한 방식</span></span>
<span data-line=""><span style="color:#B392F0">setInterval</span><span style="color:#E1E4E8">(() </span><span style="color:#F97583">=></span><span style="color:#E1E4E8"> {</span></span>
<span data-line=""><span style="color:#B392F0">  setTime</span><span style="color:#E1E4E8">(</span><span style="color:#FFAB70">prev</span><span style="color:#F97583"> =></span><span style="color:#E1E4E8"> prev </span><span style="color:#F97583">+</span><span style="color:#79B8FF"> 10</span><span style="color:#E1E4E8">);</span></span>
<span data-line=""><span style="color:#E1E4E8">}, </span><span style="color:#79B8FF">10</span><span style="color:#E1E4E8">);</span></span></code></pre></figure>
<p lang="en"><code>setInterval</code> does not guarantee precise intervals. It is throttled when the browser tab goes into the background, and callback execution time accumulates, causing drift.</p>
<h3 lang="en" id="requestanimationframe--datenow">requestAnimationFrame + Date.now()</h3>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="typescript" data-theme="github-dark"><code data-language="typescript" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#F97583">export</span><span style="color:#F97583"> type</span><span style="color:#B392F0"> TimerState</span><span style="color:#F97583"> =</span><span style="color:#9ECBFF"> 'idle'</span><span style="color:#F97583"> |</span><span style="color:#9ECBFF"> 'running'</span><span style="color:#F97583"> |</span><span style="color:#9ECBFF"> 'stopped'</span><span style="color:#E1E4E8">;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F97583">export</span><span style="color:#F97583"> function</span><span style="color:#B392F0"> useTimer</span><span style="color:#E1E4E8">() {</span></span>
<span data-line=""><span style="color:#F97583">  const</span><span style="color:#E1E4E8"> [</span><span style="color:#79B8FF">time</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">setTime</span><span style="color:#E1E4E8">] </span><span style="color:#F97583">=</span><span style="color:#B392F0"> useState</span><span style="color:#E1E4E8">(</span><span style="color:#79B8FF">0</span><span style="color:#E1E4E8">);</span></span>
<span data-line=""><span style="color:#F97583">  const</span><span style="color:#E1E4E8"> [</span><span style="color:#79B8FF">state</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">setState</span><span style="color:#E1E4E8">] </span><span style="color:#F97583">=</span><span style="color:#B392F0"> useState</span><span style="color:#E1E4E8">&#x3C;</span><span style="color:#B392F0">TimerState</span><span style="color:#E1E4E8">>(</span><span style="color:#9ECBFF">'idle'</span><span style="color:#E1E4E8">);</span></span>
<span data-line=""><span style="color:#F97583">  const</span><span style="color:#79B8FF"> startTimeRef</span><span style="color:#F97583"> =</span><span style="color:#B392F0"> useRef</span><span style="color:#E1E4E8">&#x3C;</span><span style="color:#79B8FF">number</span><span style="color:#E1E4E8">>(</span><span style="color:#79B8FF">0</span><span style="color:#E1E4E8">);</span></span>
<span data-line=""><span style="color:#F97583">  const</span><span style="color:#79B8FF"> rafRef</span><span style="color:#F97583"> =</span><span style="color:#B392F0"> useRef</span><span style="color:#E1E4E8">&#x3C;</span><span style="color:#79B8FF">number</span><span style="color:#E1E4E8">>(</span><span style="color:#79B8FF">0</span><span style="color:#E1E4E8">);</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#B392F0">  useEffect</span><span style="color:#E1E4E8">(() </span><span style="color:#F97583">=></span><span style="color:#E1E4E8"> {</span></span>
<span data-line=""><span style="color:#F97583">    if</span><span style="color:#E1E4E8"> (state </span><span style="color:#F97583">!==</span><span style="color:#9ECBFF"> 'running'</span><span style="color:#E1E4E8">) </span><span style="color:#F97583">return</span><span style="color:#E1E4E8">;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F97583">    const</span><span style="color:#B392F0"> updateTime</span><span style="color:#F97583"> =</span><span style="color:#E1E4E8"> () </span><span style="color:#F97583">=></span><span style="color:#E1E4E8"> {</span></span>
<span data-line=""><span style="color:#F97583">      const</span><span style="color:#79B8FF"> elapsed</span><span style="color:#F97583"> =</span><span style="color:#E1E4E8"> Date.</span><span style="color:#B392F0">now</span><span style="color:#E1E4E8">() </span><span style="color:#F97583">-</span><span style="color:#E1E4E8"> startTimeRef.current;</span></span>
<span data-line=""><span style="color:#B392F0">      setTime</span><span style="color:#E1E4E8">(elapsed);</span></span>
<span data-line=""><span style="color:#E1E4E8">      rafRef.current </span><span style="color:#F97583">=</span><span style="color:#B392F0"> requestAnimationFrame</span><span style="color:#E1E4E8">(updateTime);</span></span>
<span data-line=""><span style="color:#E1E4E8">    };</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#E1E4E8">    rafRef.current </span><span style="color:#F97583">=</span><span style="color:#B392F0"> requestAnimationFrame</span><span style="color:#E1E4E8">(updateTime);</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F97583">    return</span><span style="color:#E1E4E8"> () </span><span style="color:#F97583">=></span><span style="color:#E1E4E8"> {</span></span>
<span data-line=""><span style="color:#B392F0">      cancelAnimationFrame</span><span style="color:#E1E4E8">(rafRef.current);</span></span>
<span data-line=""><span style="color:#E1E4E8">    };</span></span>
<span data-line=""><span style="color:#E1E4E8">  }, [state]);</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F97583">  const</span><span style="color:#79B8FF"> start</span><span style="color:#F97583"> =</span><span style="color:#B392F0"> useCallback</span><span style="color:#E1E4E8">(() </span><span style="color:#F97583">=></span><span style="color:#E1E4E8"> {</span></span>
<span data-line=""><span style="color:#E1E4E8">    startTimeRef.current </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> Date.</span><span style="color:#B392F0">now</span><span style="color:#E1E4E8">();</span></span>
<span data-line=""><span style="color:#B392F0">    setState</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">'running'</span><span style="color:#E1E4E8">);</span></span>
<span data-line=""><span style="color:#E1E4E8">  }, []);</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F97583">  const</span><span style="color:#79B8FF"> stop</span><span style="color:#F97583"> =</span><span style="color:#B392F0"> useCallback</span><span style="color:#E1E4E8">(() </span><span style="color:#F97583">=></span><span style="color:#E1E4E8"> {</span></span>
<span data-line=""><span style="color:#B392F0">    cancelAnimationFrame</span><span style="color:#E1E4E8">(rafRef.current);</span></span>
<span data-line=""><span style="color:#F97583">    const</span><span style="color:#79B8FF"> finalTime</span><span style="color:#F97583"> =</span><span style="color:#E1E4E8"> Date.</span><span style="color:#B392F0">now</span><span style="color:#E1E4E8">() </span><span style="color:#F97583">-</span><span style="color:#E1E4E8"> startTimeRef.current;</span></span>
<span data-line=""><span style="color:#B392F0">    setTime</span><span style="color:#E1E4E8">(finalTime);</span></span>
<span data-line=""><span style="color:#B392F0">    setState</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">'stopped'</span><span style="color:#E1E4E8">);</span></span>
<span data-line=""><span style="color:#F97583">    return</span><span style="color:#E1E4E8"> finalTime;</span></span>
<span data-line=""><span style="color:#E1E4E8">  }, []);</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F97583">  const</span><span style="color:#79B8FF"> reset</span><span style="color:#F97583"> =</span><span style="color:#B392F0"> useCallback</span><span style="color:#E1E4E8">(() </span><span style="color:#F97583">=></span><span style="color:#E1E4E8"> {</span></span>
<span data-line=""><span style="color:#B392F0">    cancelAnimationFrame</span><span style="color:#E1E4E8">(rafRef.current);</span></span>
<span data-line=""><span style="color:#B392F0">    setTime</span><span style="color:#E1E4E8">(</span><span style="color:#79B8FF">0</span><span style="color:#E1E4E8">);</span></span>
<span data-line=""><span style="color:#B392F0">    setState</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">'idle'</span><span style="color:#E1E4E8">);</span></span>
<span data-line=""><span style="color:#E1E4E8">  }, []);</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F97583">  const</span><span style="color:#79B8FF"> toggle</span><span style="color:#F97583"> =</span><span style="color:#B392F0"> useCallback</span><span style="color:#E1E4E8">(() </span><span style="color:#F97583">=></span><span style="color:#E1E4E8"> {</span></span>
<span data-line=""><span style="color:#F97583">    if</span><span style="color:#E1E4E8"> (state </span><span style="color:#F97583">===</span><span style="color:#9ECBFF"> 'idle'</span><span style="color:#E1E4E8">) {</span></span>
<span data-line=""><span style="color:#B392F0">      start</span><span style="color:#E1E4E8">();</span></span>
<span data-line=""><span style="color:#F97583">      return</span><span style="color:#79B8FF"> null</span><span style="color:#E1E4E8">;</span></span>
<span data-line=""><span style="color:#E1E4E8">    } </span><span style="color:#F97583">else</span><span style="color:#F97583"> if</span><span style="color:#E1E4E8"> (state </span><span style="color:#F97583">===</span><span style="color:#9ECBFF"> 'running'</span><span style="color:#E1E4E8">) {</span></span>
<span data-line=""><span style="color:#F97583">      return</span><span style="color:#B392F0"> stop</span><span style="color:#E1E4E8">();</span></span>
<span data-line=""><span style="color:#E1E4E8">    }</span></span>
<span data-line=""><span style="color:#F97583">    return</span><span style="color:#79B8FF"> null</span><span style="color:#E1E4E8">;</span></span>
<span data-line=""><span style="color:#E1E4E8">  }, [state, start, stop]);</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F97583">  return</span><span style="color:#E1E4E8"> { time, state, start, stop, reset, toggle };</span></span>
<span data-line=""><span style="color:#E1E4E8">}</span></span></code></pre></figure>
<p lang="en"><strong>Key points:</strong></p>
<ul lang="en">
<li>Calculate elapsed time from <code>Date.now()</code> → no cumulative timing error</li>
<li><code>requestAnimationFrame</code> → synchronized with the display refresh rate, usually 60fps</li>
<li>Store the start time in <code>useRef</code> → unaffected by rerenders</li>
<li><code>toggle()</code> → handle start and stop in one function, returning the final time on stop</li>
</ul>
<h3 lang="en" id="시간-포맷팅">Formatting Time</h3>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="typescript" data-theme="github-dark"><code data-language="typescript" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#F97583">export</span><span style="color:#F97583"> function</span><span style="color:#B392F0"> formatTime</span><span style="color:#E1E4E8">(</span><span style="color:#FFAB70">ms</span><span style="color:#F97583">:</span><span style="color:#79B8FF"> number</span><span style="color:#E1E4E8">)</span><span style="color:#F97583">:</span><span style="color:#79B8FF"> string</span><span style="color:#E1E4E8"> {</span></span>
<span data-line=""><span style="color:#F97583">  const</span><span style="color:#79B8FF"> seconds</span><span style="color:#F97583"> =</span><span style="color:#E1E4E8"> Math.</span><span style="color:#B392F0">floor</span><span style="color:#E1E4E8">(ms </span><span style="color:#F97583">/</span><span style="color:#79B8FF"> 1000</span><span style="color:#E1E4E8">);</span></span>
<span data-line=""><span style="color:#F97583">  const</span><span style="color:#79B8FF"> milliseconds</span><span style="color:#F97583"> =</span><span style="color:#E1E4E8"> ms </span><span style="color:#F97583">%</span><span style="color:#79B8FF"> 1000</span><span style="color:#E1E4E8">;</span></span>
<span data-line=""><span style="color:#F97583">  return</span><span style="color:#9ECBFF"> `${</span><span style="color:#E1E4E8">seconds</span><span style="color:#9ECBFF">}.${</span><span style="color:#E1E4E8">milliseconds</span><span style="color:#9ECBFF">.</span><span style="color:#B392F0">toString</span><span style="color:#9ECBFF">().</span><span style="color:#B392F0">padStart</span><span style="color:#9ECBFF">(</span><span style="color:#79B8FF">3</span><span style="color:#9ECBFF">, </span><span style="color:#9ECBFF">'0'</span><span style="color:#9ECBFF">)</span><span style="color:#9ECBFF">}`</span><span style="color:#E1E4E8">;</span></span>
<span data-line=""><span style="color:#E1E4E8">}</span></span>
<span data-line=""><span style="color:#6A737D">// 12345 → "12.345"</span></span></code></pre></figure>
<hr lang="en">
<h2 lang="en" id="2-pwa-progressive-web-app-완벽-가이드">2. A Complete Guide to PWA (Progressive Web App)</h2>
<p lang="en">PWA technology lets an app built with web technologies be used like a native app. Here is why I applied it to this timer:</p>
<ol lang="en">
<li><strong>Home-screen installation</strong>—launch immediately by tapping an icon</li>
<li><strong>Full screen</strong>—looks like an app without the browser address bar</li>
<li><strong>Offline support</strong>—works without internet access</li>
<li><strong>Automatic updates</strong>—new deployments are applied automatically</li>
</ol>
<h3 lang="en" id="vite-plugin-pwa-설정">Configuring vite-plugin-pwa</h3>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="typescript" data-theme="github-dark"><code data-language="typescript" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#F97583">import</span><span style="color:#E1E4E8"> { defineConfig } </span><span style="color:#F97583">from</span><span style="color:#9ECBFF"> 'vite'</span></span>
<span data-line=""><span style="color:#F97583">import</span><span style="color:#E1E4E8"> react </span><span style="color:#F97583">from</span><span style="color:#9ECBFF"> '@vitejs/plugin-react'</span></span>
<span data-line=""><span style="color:#F97583">import</span><span style="color:#E1E4E8"> tailwindcss </span><span style="color:#F97583">from</span><span style="color:#9ECBFF"> '@tailwindcss/vite'</span></span>
<span data-line=""><span style="color:#F97583">import</span><span style="color:#E1E4E8"> { VitePWA } </span><span style="color:#F97583">from</span><span style="color:#9ECBFF"> 'vite-plugin-pwa'</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F97583">export</span><span style="color:#F97583"> default</span><span style="color:#B392F0"> defineConfig</span><span style="color:#E1E4E8">({</span></span>
<span data-line=""><span style="color:#E1E4E8">  plugins: [</span></span>
<span data-line=""><span style="color:#B392F0">    react</span><span style="color:#E1E4E8">(),</span></span>
<span data-line=""><span style="color:#B392F0">    tailwindcss</span><span style="color:#E1E4E8">(),</span></span>
<span data-line=""><span style="color:#B392F0">    VitePWA</span><span style="color:#E1E4E8">({</span></span>
<span data-line=""><span style="color:#E1E4E8">      registerType: </span><span style="color:#9ECBFF">'autoUpdate'</span><span style="color:#E1E4E8">,</span></span>
<span data-line=""><span style="color:#E1E4E8">      includeAssets: [</span><span style="color:#9ECBFF">'favicon.ico'</span><span style="color:#E1E4E8">, </span><span style="color:#9ECBFF">'apple-touch-icon.png'</span><span style="color:#E1E4E8">],</span></span>
<span data-line=""><span style="color:#E1E4E8">      manifest: {</span></span>
<span data-line=""><span style="color:#E1E4E8">        name: </span><span style="color:#9ECBFF">'컵쌓기 타이머'</span><span style="color:#E1E4E8">,</span></span>
<span data-line=""><span style="color:#E1E4E8">        short_name: </span><span style="color:#9ECBFF">'컵타이머'</span><span style="color:#E1E4E8">,</span></span>
<span data-line=""><span style="color:#E1E4E8">        description: </span><span style="color:#9ECBFF">'스포츠 스태킹 타이머 앱'</span><span style="color:#E1E4E8">,</span></span>
<span data-line=""><span style="color:#E1E4E8">        theme_color: </span><span style="color:#9ECBFF">'#10b981'</span><span style="color:#E1E4E8">,</span></span>
<span data-line=""><span style="color:#E1E4E8">        background_color: </span><span style="color:#9ECBFF">'#ffffff'</span><span style="color:#E1E4E8">,</span></span>
<span data-line=""><span style="color:#E1E4E8">        display: </span><span style="color:#9ECBFF">'standalone'</span><span style="color:#E1E4E8">,</span></span>
<span data-line=""><span style="color:#E1E4E8">        orientation: </span><span style="color:#9ECBFF">'portrait'</span><span style="color:#E1E4E8">,</span></span>
<span data-line=""><span style="color:#E1E4E8">        icons: [</span></span>
<span data-line=""><span style="color:#E1E4E8">          {</span></span>
<span data-line=""><span style="color:#E1E4E8">            src: </span><span style="color:#9ECBFF">'pwa-192x192.png'</span><span style="color:#E1E4E8">,</span></span>
<span data-line=""><span style="color:#E1E4E8">            sizes: </span><span style="color:#9ECBFF">'192x192'</span><span style="color:#E1E4E8">,</span></span>
<span data-line=""><span style="color:#E1E4E8">            type: </span><span style="color:#9ECBFF">'image/png'</span></span>
<span data-line=""><span style="color:#E1E4E8">          },</span></span>
<span data-line=""><span style="color:#E1E4E8">          {</span></span>
<span data-line=""><span style="color:#E1E4E8">            src: </span><span style="color:#9ECBFF">'pwa-512x512.png'</span><span style="color:#E1E4E8">,</span></span>
<span data-line=""><span style="color:#E1E4E8">            sizes: </span><span style="color:#9ECBFF">'512x512'</span><span style="color:#E1E4E8">,</span></span>
<span data-line=""><span style="color:#E1E4E8">            type: </span><span style="color:#9ECBFF">'image/png'</span></span>
<span data-line=""><span style="color:#E1E4E8">          }</span></span>
<span data-line=""><span style="color:#E1E4E8">        ]</span></span>
<span data-line=""><span style="color:#E1E4E8">      }</span></span>
<span data-line=""><span style="color:#E1E4E8">    })</span></span>
<span data-line=""><span style="color:#E1E4E8">  ],</span></span>
<span data-line=""><span style="color:#E1E4E8">})</span></span></code></pre></figure>
<h3 lang="en" id="manifest-속성-설명">Manifest Properties</h3>








































<table lang="en"><thead><tr><th>Property</th><th>Value</th><th>Description</th></tr></thead><tbody><tr><td><code>name</code></td><td>Cup Stacking Timer</td><td>Full name shown during app installation</td></tr><tr><td><code>short_name</code></td><td>Cup Timer</td><td>Shown below the home-screen icon (12 characters or fewer recommended)</td></tr><tr><td><code>display</code></td><td>standalone</td><td>Hide the browser UI and display like a native app</td></tr><tr><td><code>orientation</code></td><td>portrait</td><td>Lock to portrait mode for the timer's use case</td></tr><tr><td><code>theme_color</code></td><td>#10b981</td><td>Status-bar color (emerald-500)</td></tr><tr><td><code>background_color</code></td><td>#ffffff</td><td>Splash-screen background while the app loads</td></tr></tbody></table>
<h3 lang="en" id="display-모드-비교">Comparing Display Modes</h3>
<pre><code>┌─────────────────────────────────────────────────────┐
│  fullscreen   │  standalone   │    browser         │
├───────────────┼───────────────┼────────────────────┤
│  상태바 숨김    │  상태바만 표시  │  브라우저 UI 전체   │
│  게임/몰입형    │  일반 앱       │  웹사이트          │
│  주소창 없음    │  주소창 없음    │  주소창 있음        │
└─────────────────────────────────────────────────────┘
</code></pre>
<p lang="en">Why I chose <code>standalone</code>: it shows the status bar, including the clock and battery, while hiding the browser UI.</p>
<h3 lang="en" id="registertype-autoupdate">registerType: autoUpdate</h3>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="typescript" data-theme="github-dark"><code data-language="typescript" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#B392F0">registerType</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">'autoUpdate'</span></span></code></pre></figure>
<p lang="en">Service Worker update strategies:</p>

















<table lang="en"><thead><tr><th>Strategy</th><th>Behavior</th></tr></thead><tbody><tr><td><code>autoUpdate</code></td><td>Automatically update the Service Worker when a new version is detected</td></tr><tr><td><code>prompt</code></td><td>Show a UI asking whether the user wants to update</td></tr></tbody></table>
<p lang="en">Why I chose <code>autoUpdate</code>: there is little concern about losing state in this timer app, and it is better to always use the latest version.</p>
<h3 lang="en" id="아이콘-준비">Preparing Icons</h3>
<p lang="en">A PWA needs at least two icons:</p>
<pre><code>public/
├── apple-touch-icon.png  # iOS Safari용 (180x180)
├── favicon.ico           # 브라우저 탭용
├── pwa-192x192.png       # Android 홈 화면용
└── pwa-512x512.png       # 스플래시 화면용
</code></pre>
<p lang="en">Generate them automatically from the source SVG using sharp:</p>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="typescript" data-theme="github-dark"><code data-language="typescript" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#F97583">import</span><span style="color:#E1E4E8"> sharp </span><span style="color:#F97583">from</span><span style="color:#9ECBFF"> 'sharp'</span><span style="color:#E1E4E8">;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F97583">const</span><span style="color:#79B8FF"> sizes</span><span style="color:#F97583"> =</span><span style="color:#E1E4E8"> [</span><span style="color:#79B8FF">192</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">512</span><span style="color:#E1E4E8">];</span></span>
<span data-line=""><span style="color:#F97583">const</span><span style="color:#79B8FF"> svgBuffer</span><span style="color:#F97583"> =</span><span style="color:#F97583"> await</span><span style="color:#E1E4E8"> fs.</span><span style="color:#B392F0">readFile</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">'public/icon.svg'</span><span style="color:#E1E4E8">);</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F97583">for</span><span style="color:#E1E4E8"> (</span><span style="color:#F97583">const</span><span style="color:#79B8FF"> size</span><span style="color:#F97583"> of</span><span style="color:#E1E4E8"> sizes) {</span></span>
<span data-line=""><span style="color:#F97583">  await</span><span style="color:#B392F0"> sharp</span><span style="color:#E1E4E8">(svgBuffer)</span></span>
<span data-line=""><span style="color:#E1E4E8">    .</span><span style="color:#B392F0">resize</span><span style="color:#E1E4E8">(size, size)</span></span>
<span data-line=""><span style="color:#E1E4E8">    .</span><span style="color:#B392F0">png</span><span style="color:#E1E4E8">()</span></span>
<span data-line=""><span style="color:#E1E4E8">    .</span><span style="color:#B392F0">toFile</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">`public/pwa-${</span><span style="color:#E1E4E8">size</span><span style="color:#9ECBFF">}x${</span><span style="color:#E1E4E8">size</span><span style="color:#9ECBFF">}.png`</span><span style="color:#E1E4E8">);</span></span>
<span data-line=""><span style="color:#E1E4E8">}</span></span></code></pre></figure>
<h3 lang="en" id="ios-safari-대응">Supporting iOS Safari</h3>
<p lang="en">On iOS, specify <code>apple-touch-icon</code> separately:</p>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="html" data-theme="github-dark"><code data-language="html" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#E1E4E8">&#x3C;</span><span style="color:#85E89D">link</span><span style="color:#B392F0"> rel</span><span style="color:#E1E4E8">=</span><span style="color:#9ECBFF">"apple-touch-icon"</span><span style="color:#B392F0"> href</span><span style="color:#E1E4E8">=</span><span style="color:#9ECBFF">"/apple-touch-icon.png"</span><span style="color:#E1E4E8"> /></span></span>
<span data-line=""><span style="color:#E1E4E8">&#x3C;</span><span style="color:#85E89D">meta</span><span style="color:#B392F0"> name</span><span style="color:#E1E4E8">=</span><span style="color:#9ECBFF">"apple-mobile-web-app-capable"</span><span style="color:#B392F0"> content</span><span style="color:#E1E4E8">=</span><span style="color:#9ECBFF">"yes"</span><span style="color:#E1E4E8"> /></span></span>
<span data-line=""><span style="color:#E1E4E8">&#x3C;</span><span style="color:#85E89D">meta</span><span style="color:#B392F0"> name</span><span style="color:#E1E4E8">=</span><span style="color:#9ECBFF">"apple-mobile-web-app-status-bar-style"</span><span style="color:#B392F0"> content</span><span style="color:#E1E4E8">=</span><span style="color:#9ECBFF">"default"</span><span style="color:#E1E4E8"> /></span></span></code></pre></figure>
<h3 lang="en" id="빌드-결과물">Build Output</h3>
<p lang="en">Files generated automatically when <code>npm run build</code> runs:</p>
<pre><code>dist/
├── sw.js              # Service Worker
├── workbox-*.js       # Workbox 런타임
├── manifest.webmanifest
└── registerSW.js      # SW 등록 스크립트
</code></pre>
<hr lang="en">
<h2 lang="en" id="3-터치클릭-이벤트-중복-방지">3. Preventing Duplicate Touch and Click Events</h2>
<p lang="en">On mobile, a <code>touchend</code> is followed by a <code>click</code> event after a 300ms delay. If both are handled, the timer toggles twice.</p>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="typescript" data-theme="github-dark"><code data-language="typescript" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#F97583">const</span><span style="color:#79B8FF"> touchHandledRef</span><span style="color:#F97583"> =</span><span style="color:#B392F0"> useRef</span><span style="color:#E1E4E8">(</span><span style="color:#79B8FF">false</span><span style="color:#E1E4E8">);</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F97583">const</span><span style="color:#79B8FF"> handleTouchEnd</span><span style="color:#F97583"> =</span><span style="color:#B392F0"> useCallback</span><span style="color:#E1E4E8">(</span></span>
<span data-line=""><span style="color:#E1E4E8">  (</span><span style="color:#FFAB70">e</span><span style="color:#F97583">:</span><span style="color:#B392F0"> React</span><span style="color:#E1E4E8">.</span><span style="color:#B392F0">TouchEvent</span><span style="color:#E1E4E8">) </span><span style="color:#F97583">=></span><span style="color:#E1E4E8"> {</span></span>
<span data-line=""><span style="color:#E1E4E8">    e.</span><span style="color:#B392F0">preventDefault</span><span style="color:#E1E4E8">();</span></span>
<span data-line=""><span style="color:#E1E4E8">    touchHandledRef.current </span><span style="color:#F97583">=</span><span style="color:#79B8FF"> true</span><span style="color:#E1E4E8">;</span></span>
<span data-line=""><span style="color:#B392F0">    handleTouch</span><span style="color:#E1E4E8">();</span></span>
<span data-line=""><span style="color:#B392F0">    setTimeout</span><span style="color:#E1E4E8">(() </span><span style="color:#F97583">=></span><span style="color:#E1E4E8"> {</span></span>
<span data-line=""><span style="color:#E1E4E8">      touchHandledRef.current </span><span style="color:#F97583">=</span><span style="color:#79B8FF"> false</span><span style="color:#E1E4E8">;</span></span>
<span data-line=""><span style="color:#E1E4E8">    }, </span><span style="color:#79B8FF">300</span><span style="color:#E1E4E8">);</span></span>
<span data-line=""><span style="color:#E1E4E8">  },</span></span>
<span data-line=""><span style="color:#E1E4E8">  [handleTouch]</span></span>
<span data-line=""><span style="color:#E1E4E8">);</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F97583">const</span><span style="color:#79B8FF"> handleClick</span><span style="color:#F97583"> =</span><span style="color:#B392F0"> useCallback</span><span style="color:#E1E4E8">(() </span><span style="color:#F97583">=></span><span style="color:#E1E4E8"> {</span></span>
<span data-line=""><span style="color:#F97583">  if</span><span style="color:#E1E4E8"> (touchHandledRef.current) </span><span style="color:#F97583">return</span><span style="color:#E1E4E8">; </span><span style="color:#6A737D">// 터치 후 클릭 무시</span></span>
<span data-line=""><span style="color:#B392F0">  handleTouch</span><span style="color:#E1E4E8">();</span></span>
<span data-line=""><span style="color:#E1E4E8">}, [handleTouch]);</span></span></code></pre></figure>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="tsx" data-theme="github-dark"><code data-language="tsx" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#E1E4E8">&#x3C;</span><span style="color:#85E89D">div</span></span>
<span data-line=""><span style="color:#B392F0">  className</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">{</span><span style="color:#9ECBFF">`min-h-full ${</span><span style="color:#E1E4E8">bgColor</span><span style="color:#9ECBFF">} flex flex-col`</span><span style="color:#E1E4E8">}</span></span>
<span data-line=""><span style="color:#B392F0">  onClick</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">{handleClick}</span></span>
<span data-line=""><span style="color:#B392F0">  onTouchEnd</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">{handleTouchEnd}</span></span>
<span data-line=""><span style="color:#E1E4E8">></span></span></code></pre></figure>
<p lang="en"><strong>Event flow:</strong></p>
<ol lang="en">
<li>Touch → <code>touchHandledRef = true</code> → toggle the timer</li>
<li>Click within 300ms → check <code>touchHandledRef</code> → ignore</li>
<li>After 300ms → <code>touchHandledRef = false</code> → wait for the next input</li>
</ol>
<hr lang="en">
<h2 lang="en" id="4-localstorage-커스텀-훅-패턴">4. A Custom LocalStorage Hook Pattern</h2>
<h3 lang="en" id="플레이어기록-관리">Managing Players and Records</h3>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="typescript" data-theme="github-dark"><code data-language="typescript" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#F97583">const</span><span style="color:#79B8FF"> PLAYERS_KEY</span><span style="color:#F97583"> =</span><span style="color:#9ECBFF"> 'cupstacking_players'</span><span style="color:#E1E4E8">;</span></span>
<span data-line=""><span style="color:#F97583">const</span><span style="color:#79B8FF"> RECORDS_KEY</span><span style="color:#F97583"> =</span><span style="color:#9ECBFF"> 'cupstacking_records'</span><span style="color:#E1E4E8">;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F97583">function</span><span style="color:#B392F0"> generateId</span><span style="color:#E1E4E8">()</span><span style="color:#F97583">:</span><span style="color:#79B8FF"> string</span><span style="color:#E1E4E8"> {</span></span>
<span data-line=""><span style="color:#F97583">  return</span><span style="color:#E1E4E8"> Date.</span><span style="color:#B392F0">now</span><span style="color:#E1E4E8">().</span><span style="color:#B392F0">toString</span><span style="color:#E1E4E8">(</span><span style="color:#79B8FF">36</span><span style="color:#E1E4E8">) </span><span style="color:#F97583">+</span><span style="color:#E1E4E8"> Math.</span><span style="color:#B392F0">random</span><span style="color:#E1E4E8">().</span><span style="color:#B392F0">toString</span><span style="color:#E1E4E8">(</span><span style="color:#79B8FF">36</span><span style="color:#E1E4E8">).</span><span style="color:#B392F0">substr</span><span style="color:#E1E4E8">(</span><span style="color:#79B8FF">2</span><span style="color:#E1E4E8">);</span></span>
<span data-line=""><span style="color:#E1E4E8">}</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F97583">export</span><span style="color:#F97583"> function</span><span style="color:#B392F0"> usePlayers</span><span style="color:#E1E4E8">() {</span></span>
<span data-line=""><span style="color:#F97583">  const</span><span style="color:#E1E4E8"> [</span><span style="color:#79B8FF">players</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">setPlayers</span><span style="color:#E1E4E8">] </span><span style="color:#F97583">=</span><span style="color:#B392F0"> useState</span><span style="color:#E1E4E8">&#x3C;</span><span style="color:#B392F0">Player</span><span style="color:#E1E4E8">[]>(() </span><span style="color:#F97583">=></span><span style="color:#E1E4E8"> {</span></span>
<span data-line=""><span style="color:#F97583">    const</span><span style="color:#79B8FF"> stored</span><span style="color:#F97583"> =</span><span style="color:#E1E4E8"> localStorage.</span><span style="color:#B392F0">getItem</span><span style="color:#E1E4E8">(</span><span style="color:#79B8FF">PLAYERS_KEY</span><span style="color:#E1E4E8">);</span></span>
<span data-line=""><span style="color:#F97583">    return</span><span style="color:#E1E4E8"> stored </span><span style="color:#F97583">?</span><span style="color:#79B8FF"> JSON</span><span style="color:#E1E4E8">.</span><span style="color:#B392F0">parse</span><span style="color:#E1E4E8">(stored) </span><span style="color:#F97583">:</span><span style="color:#E1E4E8"> [];</span></span>
<span data-line=""><span style="color:#E1E4E8">  });</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#B392F0">  useEffect</span><span style="color:#E1E4E8">(() </span><span style="color:#F97583">=></span><span style="color:#E1E4E8"> {</span></span>
<span data-line=""><span style="color:#E1E4E8">    localStorage.</span><span style="color:#B392F0">setItem</span><span style="color:#E1E4E8">(</span><span style="color:#79B8FF">PLAYERS_KEY</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">JSON</span><span style="color:#E1E4E8">.</span><span style="color:#B392F0">stringify</span><span style="color:#E1E4E8">(players));</span></span>
<span data-line=""><span style="color:#E1E4E8">  }, [players]);</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F97583">  const</span><span style="color:#79B8FF"> addPlayer</span><span style="color:#F97583"> =</span><span style="color:#B392F0"> useCallback</span><span style="color:#E1E4E8">((</span><span style="color:#FFAB70">name</span><span style="color:#F97583">:</span><span style="color:#79B8FF"> string</span><span style="color:#E1E4E8">)</span><span style="color:#F97583">:</span><span style="color:#B392F0"> Player</span><span style="color:#F97583"> =></span><span style="color:#E1E4E8"> {</span></span>
<span data-line=""><span style="color:#F97583">    const</span><span style="color:#79B8FF"> newPlayer</span><span style="color:#F97583">:</span><span style="color:#B392F0"> Player</span><span style="color:#F97583"> =</span><span style="color:#E1E4E8"> {</span></span>
<span data-line=""><span style="color:#E1E4E8">      id: </span><span style="color:#B392F0">generateId</span><span style="color:#E1E4E8">(),</span></span>
<span data-line=""><span style="color:#E1E4E8">      name: name.</span><span style="color:#B392F0">trim</span><span style="color:#E1E4E8">(),</span></span>
<span data-line=""><span style="color:#E1E4E8">      createdAt: Date.</span><span style="color:#B392F0">now</span><span style="color:#E1E4E8">(),</span></span>
<span data-line=""><span style="color:#E1E4E8">    };</span></span>
<span data-line=""><span style="color:#B392F0">    setPlayers</span><span style="color:#E1E4E8">((</span><span style="color:#FFAB70">prev</span><span style="color:#E1E4E8">) </span><span style="color:#F97583">=></span><span style="color:#E1E4E8"> [</span><span style="color:#F97583">...</span><span style="color:#E1E4E8">prev, newPlayer]);</span></span>
<span data-line=""><span style="color:#F97583">    return</span><span style="color:#E1E4E8"> newPlayer;</span></span>
<span data-line=""><span style="color:#E1E4E8">  }, []);</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F97583">  const</span><span style="color:#79B8FF"> removePlayer</span><span style="color:#F97583"> =</span><span style="color:#B392F0"> useCallback</span><span style="color:#E1E4E8">((</span><span style="color:#FFAB70">id</span><span style="color:#F97583">:</span><span style="color:#79B8FF"> string</span><span style="color:#E1E4E8">) </span><span style="color:#F97583">=></span><span style="color:#E1E4E8"> {</span></span>
<span data-line=""><span style="color:#B392F0">    setPlayers</span><span style="color:#E1E4E8">((</span><span style="color:#FFAB70">prev</span><span style="color:#E1E4E8">) </span><span style="color:#F97583">=></span><span style="color:#E1E4E8"> prev.</span><span style="color:#B392F0">filter</span><span style="color:#E1E4E8">((</span><span style="color:#FFAB70">p</span><span style="color:#E1E4E8">) </span><span style="color:#F97583">=></span><span style="color:#E1E4E8"> p.id </span><span style="color:#F97583">!==</span><span style="color:#E1E4E8"> id));</span></span>
<span data-line=""><span style="color:#E1E4E8">  }, []);</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F97583">  const</span><span style="color:#79B8FF"> getPlayer</span><span style="color:#F97583"> =</span><span style="color:#B392F0"> useCallback</span><span style="color:#E1E4E8">(</span></span>
<span data-line=""><span style="color:#E1E4E8">    (</span><span style="color:#FFAB70">id</span><span style="color:#F97583">:</span><span style="color:#79B8FF"> string</span><span style="color:#E1E4E8">) </span><span style="color:#F97583">=></span><span style="color:#E1E4E8"> players.</span><span style="color:#B392F0">find</span><span style="color:#E1E4E8">((</span><span style="color:#FFAB70">p</span><span style="color:#E1E4E8">) </span><span style="color:#F97583">=></span><span style="color:#E1E4E8"> p.id </span><span style="color:#F97583">===</span><span style="color:#E1E4E8"> id),</span></span>
<span data-line=""><span style="color:#E1E4E8">    [players]</span></span>
<span data-line=""><span style="color:#E1E4E8">  );</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F97583">  return</span><span style="color:#E1E4E8"> { players, addPlayer, removePlayer, getPlayer };</span></span>
<span data-line=""><span style="color:#E1E4E8">}</span></span></code></pre></figure>
<p lang="en"><strong>Design decisions:</strong></p>
<ul lang="en">
<li>Initial value: read localStorage in the <code>useState</code> initializer function (SSR-compatible)</li>
<li>Persistence: use <code>useEffect</code> to detect state changes and save automatically</li>
<li>ID generation: timestamp + random value to prevent collisions</li>
</ul>
<h3 lang="en" id="기록-조회-최적화">Optimizing Record Lookups</h3>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="typescript" data-theme="github-dark"><code data-language="typescript" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#F97583">const</span><span style="color:#79B8FF"> getBestRecord</span><span style="color:#F97583"> =</span><span style="color:#B392F0"> useCallback</span><span style="color:#E1E4E8">(</span></span>
<span data-line=""><span style="color:#E1E4E8">  (</span><span style="color:#FFAB70">eventType</span><span style="color:#F97583">:</span><span style="color:#B392F0"> TimeRecord</span><span style="color:#E1E4E8">[</span><span style="color:#9ECBFF">'eventType'</span><span style="color:#E1E4E8">], </span><span style="color:#FFAB70">playerIds</span><span style="color:#F97583">:</span><span style="color:#79B8FF"> string</span><span style="color:#E1E4E8">[]) </span><span style="color:#F97583">=></span><span style="color:#E1E4E8"> {</span></span>
<span data-line=""><span style="color:#F97583">    const</span><span style="color:#79B8FF"> key</span><span style="color:#F97583"> =</span><span style="color:#E1E4E8"> [</span><span style="color:#F97583">...</span><span style="color:#E1E4E8">playerIds].</span><span style="color:#B392F0">sort</span><span style="color:#E1E4E8">().</span><span style="color:#B392F0">join</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">','</span><span style="color:#E1E4E8">);</span></span>
<span data-line=""><span style="color:#F97583">    return</span><span style="color:#E1E4E8"> records</span></span>
<span data-line=""><span style="color:#E1E4E8">      .</span><span style="color:#B392F0">filter</span><span style="color:#E1E4E8">(</span></span>
<span data-line=""><span style="color:#E1E4E8">        (</span><span style="color:#FFAB70">r</span><span style="color:#E1E4E8">) </span><span style="color:#F97583">=></span></span>
<span data-line=""><span style="color:#E1E4E8">          r.eventType </span><span style="color:#F97583">===</span><span style="color:#E1E4E8"> eventType </span><span style="color:#F97583">&#x26;&#x26;</span></span>
<span data-line=""><span style="color:#E1E4E8">          [</span><span style="color:#F97583">...</span><span style="color:#E1E4E8">r.playerIds].</span><span style="color:#B392F0">sort</span><span style="color:#E1E4E8">().</span><span style="color:#B392F0">join</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">','</span><span style="color:#E1E4E8">) </span><span style="color:#F97583">===</span><span style="color:#E1E4E8"> key</span></span>
<span data-line=""><span style="color:#E1E4E8">      )</span></span>
<span data-line=""><span style="color:#E1E4E8">      .</span><span style="color:#B392F0">sort</span><span style="color:#E1E4E8">((</span><span style="color:#FFAB70">a</span><span style="color:#E1E4E8">, </span><span style="color:#FFAB70">b</span><span style="color:#E1E4E8">) </span><span style="color:#F97583">=></span><span style="color:#E1E4E8"> a.time </span><span style="color:#F97583">-</span><span style="color:#E1E4E8"> b.time)[</span><span style="color:#79B8FF">0</span><span style="color:#E1E4E8">];</span></span>
<span data-line=""><span style="color:#E1E4E8">  },</span></span>
<span data-line=""><span style="color:#E1E4E8">  [records]</span></span>
<span data-line=""><span style="color:#E1E4E8">);</span></span></code></pre></figure>
<p lang="en">Sort <code>playerIds</code> and convert them to a string for comparison → treat <code>[A, B]</code> and <code>[B, A]</code> as identical.</p>
<hr lang="en">
<h2 lang="en" id="5-typescript-도메인-모델링">5. Domain Modeling with TypeScript</h2>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="typescript" data-theme="github-dark"><code data-language="typescript" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#F97583">export</span><span style="color:#F97583"> type</span><span style="color:#B392F0"> EventType</span><span style="color:#F97583"> =</span><span style="color:#9ECBFF"> '3-3-3'</span><span style="color:#F97583"> |</span><span style="color:#9ECBFF"> '3-6-3'</span><span style="color:#F97583"> |</span><span style="color:#9ECBFF"> 'cycle'</span><span style="color:#F97583"> |</span><span style="color:#9ECBFF"> 'doubles'</span><span style="color:#F97583"> |</span><span style="color:#9ECBFF"> 'team-3-6-3'</span><span style="color:#E1E4E8">;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F97583">export</span><span style="color:#F97583"> interface</span><span style="color:#B392F0"> Player</span><span style="color:#E1E4E8"> {</span></span>
<span data-line=""><span style="color:#FFAB70">  id</span><span style="color:#F97583">:</span><span style="color:#79B8FF"> string</span><span style="color:#E1E4E8">;</span></span>
<span data-line=""><span style="color:#FFAB70">  name</span><span style="color:#F97583">:</span><span style="color:#79B8FF"> string</span><span style="color:#E1E4E8">;</span></span>
<span data-line=""><span style="color:#FFAB70">  createdAt</span><span style="color:#F97583">:</span><span style="color:#79B8FF"> number</span><span style="color:#E1E4E8">;</span></span>
<span data-line=""><span style="color:#E1E4E8">}</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F97583">export</span><span style="color:#F97583"> interface</span><span style="color:#B392F0"> TimeRecord</span><span style="color:#E1E4E8"> {</span></span>
<span data-line=""><span style="color:#FFAB70">  id</span><span style="color:#F97583">:</span><span style="color:#79B8FF"> string</span><span style="color:#E1E4E8">;</span></span>
<span data-line=""><span style="color:#FFAB70">  eventType</span><span style="color:#F97583">:</span><span style="color:#B392F0"> EventType</span><span style="color:#E1E4E8">;</span></span>
<span data-line=""><span style="color:#FFAB70">  playerIds</span><span style="color:#F97583">:</span><span style="color:#79B8FF"> string</span><span style="color:#E1E4E8">[];</span></span>
<span data-line=""><span style="color:#FFAB70">  time</span><span style="color:#F97583">:</span><span style="color:#79B8FF"> number</span><span style="color:#E1E4E8">; </span><span style="color:#6A737D">// milliseconds</span></span>
<span data-line=""><span style="color:#FFAB70">  createdAt</span><span style="color:#F97583">:</span><span style="color:#79B8FF"> number</span><span style="color:#E1E4E8">;</span></span>
<span data-line=""><span style="color:#E1E4E8">}</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F97583">export</span><span style="color:#F97583"> const</span><span style="color:#79B8FF"> INDIVIDUAL_EVENTS</span><span style="color:#F97583">:</span><span style="color:#B392F0"> EventType</span><span style="color:#E1E4E8">[] </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> [</span><span style="color:#9ECBFF">'3-3-3'</span><span style="color:#E1E4E8">, </span><span style="color:#9ECBFF">'3-6-3'</span><span style="color:#E1E4E8">, </span><span style="color:#9ECBFF">'cycle'</span><span style="color:#E1E4E8">];</span></span>
<span data-line=""><span style="color:#F97583">export</span><span style="color:#F97583"> const</span><span style="color:#79B8FF"> TEAM_EVENTS</span><span style="color:#F97583">:</span><span style="color:#B392F0"> EventType</span><span style="color:#E1E4E8">[] </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> [</span><span style="color:#9ECBFF">'doubles'</span><span style="color:#E1E4E8">, </span><span style="color:#9ECBFF">'team-3-6-3'</span><span style="color:#E1E4E8">];</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F97583">export</span><span style="color:#F97583"> const</span><span style="color:#79B8FF"> EVENT_NAMES</span><span style="color:#F97583">:</span><span style="color:#E1E4E8"> { [</span><span style="color:#B392F0">key</span><span style="color:#F97583"> in</span><span style="color:#B392F0"> EventType</span><span style="color:#E1E4E8">]</span><span style="color:#F97583">:</span><span style="color:#79B8FF"> string</span><span style="color:#E1E4E8"> } </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> {</span></span>
<span data-line=""><span style="color:#9ECBFF">  '3-3-3'</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">'3-3-3'</span><span style="color:#E1E4E8">,</span></span>
<span data-line=""><span style="color:#9ECBFF">  '3-6-3'</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">'3-6-3'</span><span style="color:#E1E4E8">,</span></span>
<span data-line=""><span style="color:#9ECBFF">  'cycle'</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">'사이클'</span><span style="color:#E1E4E8">,</span></span>
<span data-line=""><span style="color:#9ECBFF">  'doubles'</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">'더블'</span><span style="color:#E1E4E8">,</span></span>
<span data-line=""><span style="color:#9ECBFF">  'team-3-6-3'</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">'팀 3-6-3'</span><span style="color:#E1E4E8">,</span></span>
<span data-line=""><span style="color:#E1E4E8">};</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F97583">export</span><span style="color:#F97583"> const</span><span style="color:#79B8FF"> EVENT_MIN_PLAYERS</span><span style="color:#F97583">:</span><span style="color:#E1E4E8"> { [</span><span style="color:#B392F0">key</span><span style="color:#F97583"> in</span><span style="color:#B392F0"> EventType</span><span style="color:#E1E4E8">]</span><span style="color:#F97583">:</span><span style="color:#79B8FF"> number</span><span style="color:#E1E4E8"> } </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> {</span></span>
<span data-line=""><span style="color:#9ECBFF">  '3-3-3'</span><span style="color:#E1E4E8">: </span><span style="color:#79B8FF">1</span><span style="color:#E1E4E8">,</span></span>
<span data-line=""><span style="color:#9ECBFF">  '3-6-3'</span><span style="color:#E1E4E8">: </span><span style="color:#79B8FF">1</span><span style="color:#E1E4E8">,</span></span>
<span data-line=""><span style="color:#9ECBFF">  'cycle'</span><span style="color:#E1E4E8">: </span><span style="color:#79B8FF">1</span><span style="color:#E1E4E8">,</span></span>
<span data-line=""><span style="color:#9ECBFF">  'doubles'</span><span style="color:#E1E4E8">: </span><span style="color:#79B8FF">2</span><span style="color:#E1E4E8">,</span></span>
<span data-line=""><span style="color:#9ECBFF">  'team-3-6-3'</span><span style="color:#E1E4E8">: </span><span style="color:#79B8FF">2</span><span style="color:#E1E4E8">,</span></span>
<span data-line=""><span style="color:#E1E4E8">};</span></span></code></pre></figure>
<p lang="en"><code>{ [key in EventType]: string }</code> → a value is required for every event. Adding an event causes a compile error if its value is missing.</p>
<hr lang="en">
<h2 lang="en" id="6-tailwind-css-v4-설정">6. Tailwind CSS v4 Configuration</h2>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="css" data-theme="github-dark"><code data-language="css" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#F97583">@import</span><span style="color:#9ECBFF"> "tailwindcss"</span><span style="color:#E1E4E8">;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#85E89D">html</span><span style="color:#E1E4E8">, </span><span style="color:#85E89D">body</span><span style="color:#E1E4E8">, </span><span style="color:#B392F0">#root</span><span style="color:#E1E4E8"> {</span></span>
<span data-line=""><span style="color:#79B8FF">  height</span><span style="color:#E1E4E8">: </span><span style="color:#79B8FF">100</span><span style="color:#F97583">%</span><span style="color:#E1E4E8">;</span></span>
<span data-line=""><span style="color:#79B8FF">  margin</span><span style="color:#E1E4E8">: </span><span style="color:#79B8FF">0</span><span style="color:#E1E4E8">;</span></span>
<span data-line=""><span style="color:#79B8FF">  padding</span><span style="color:#E1E4E8">: </span><span style="color:#79B8FF">0</span><span style="color:#E1E4E8">;</span></span>
<span data-line=""><span style="color:#E1E4E8">}</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#6A737D">/* Prevent pull-to-refresh and overscroll */</span></span>
<span data-line=""><span style="color:#85E89D">html</span><span style="color:#E1E4E8"> {</span></span>
<span data-line=""><span style="color:#79B8FF">  overscroll-behavior</span><span style="color:#E1E4E8">: </span><span style="color:#79B8FF">none</span><span style="color:#E1E4E8">;</span></span>
<span data-line=""><span style="color:#E1E4E8">}</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#6A737D">/* Disable text selection on timer screen */</span></span>
<span data-line=""><span style="color:#B392F0">.no-select</span><span style="color:#E1E4E8"> {</span></span>
<span data-line=""><span style="color:#79B8FF">  -webkit-user-select</span><span style="color:#E1E4E8">: </span><span style="color:#79B8FF">none</span><span style="color:#E1E4E8">;</span></span>
<span data-line=""><span style="color:#79B8FF">  user-select</span><span style="color:#E1E4E8">: </span><span style="color:#79B8FF">none</span><span style="color:#E1E4E8">;</span></span>
<span data-line=""><span style="color:#79B8FF">  -webkit-touch-callout</span><span style="color:#E1E4E8">: </span><span style="color:#79B8FF">none</span><span style="color:#E1E4E8">;</span></span>
<span data-line=""><span style="color:#E1E4E8">}</span></span></code></pre></figure>
<h3 lang="en" id="상태별-배경색">Background Colors by State</h3>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="tsx" data-theme="github-dark"><code data-language="tsx" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#F97583">const</span><span style="color:#79B8FF"> bgColor</span><span style="color:#F97583"> =</span></span>
<span data-line=""><span style="color:#E1E4E8">  state </span><span style="color:#F97583">===</span><span style="color:#9ECBFF"> 'idle'</span></span>
<span data-line=""><span style="color:#F97583">    ?</span><span style="color:#9ECBFF"> 'bg-white'</span></span>
<span data-line=""><span style="color:#F97583">    :</span><span style="color:#E1E4E8"> state </span><span style="color:#F97583">===</span><span style="color:#9ECBFF"> 'running'</span></span>
<span data-line=""><span style="color:#F97583">    ?</span><span style="color:#9ECBFF"> 'bg-emerald-400'</span></span>
<span data-line=""><span style="color:#F97583">    :</span><span style="color:#9ECBFF"> 'bg-amber-300'</span><span style="color:#E1E4E8">;</span></span></code></pre></figure>
<p lang="en">Visual feedback: ready (white) → running (green) → finished (yellow)</p>
<h3 lang="en" id="반응형-폰트-크기">Responsive Font Sizes</h3>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="tsx" data-theme="github-dark"><code data-language="tsx" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#E1E4E8">&#x3C;</span><span style="color:#85E89D">p</span></span>
<span data-line=""><span style="color:#B392F0">  className</span><span style="color:#F97583">=</span><span style="color:#9ECBFF">"font-mono font-bold text-gray-800"</span></span>
<span data-line=""><span style="color:#B392F0">  style</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">{{ fontSize: </span><span style="color:#9ECBFF">'min(20vw, 120px)'</span><span style="color:#E1E4E8"> }}</span></span>
<span data-line=""><span style="color:#E1E4E8">></span></span>
<span data-line=""><span style="color:#E1E4E8">  {</span><span style="color:#B392F0">formatTime</span><span style="color:#E1E4E8">(time)}</span></span>
<span data-line=""><span style="color:#E1E4E8">&#x3C;/</span><span style="color:#85E89D">p</span><span style="color:#E1E4E8">></span></span></code></pre></figure>
<p lang="en"><code>min(20vw, 120px)</code> → 20% of the viewport width, capped at 120px. It keeps the size appropriate from tablets to desktops.</p>
<hr lang="en">
<h2 lang="en" id="7-react-router-v7-url-파라미터">7. URL Parameters with React Router v7</h2>
<h3 lang="en" id="라우트-정의">Route Definitions</h3>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="tsx" data-theme="github-dark"><code data-language="tsx" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#E1E4E8">&#x3C;</span><span style="color:#79B8FF">Routes</span><span style="color:#E1E4E8">></span></span>
<span data-line=""><span style="color:#E1E4E8">  &#x3C;</span><span style="color:#79B8FF">Route</span><span style="color:#B392F0"> path</span><span style="color:#F97583">=</span><span style="color:#9ECBFF">"/"</span><span style="color:#B392F0"> element</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">{&#x3C;</span><span style="color:#79B8FF">Home</span><span style="color:#E1E4E8"> />} /></span></span>
<span data-line=""><span style="color:#E1E4E8">  &#x3C;</span><span style="color:#79B8FF">Route</span><span style="color:#B392F0"> path</span><span style="color:#F97583">=</span><span style="color:#9ECBFF">"/select/:eventType"</span><span style="color:#B392F0"> element</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">{&#x3C;</span><span style="color:#79B8FF">PlayerSelect</span><span style="color:#E1E4E8"> />} /></span></span>
<span data-line=""><span style="color:#E1E4E8">  &#x3C;</span><span style="color:#79B8FF">Route</span><span style="color:#B392F0"> path</span><span style="color:#F97583">=</span><span style="color:#9ECBFF">"/timer/:eventType"</span><span style="color:#B392F0"> element</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">{&#x3C;</span><span style="color:#79B8FF">Timer</span><span style="color:#E1E4E8"> />} /></span></span>
<span data-line=""><span style="color:#E1E4E8">  &#x3C;</span><span style="color:#79B8FF">Route</span><span style="color:#B392F0"> path</span><span style="color:#F97583">=</span><span style="color:#9ECBFF">"/result/:eventType"</span><span style="color:#B392F0"> element</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">{&#x3C;</span><span style="color:#79B8FF">Result</span><span style="color:#E1E4E8"> />} /></span></span>
<span data-line=""><span style="color:#E1E4E8">  &#x3C;</span><span style="color:#79B8FF">Route</span><span style="color:#B392F0"> path</span><span style="color:#F97583">=</span><span style="color:#9ECBFF">"/ranking"</span><span style="color:#B392F0"> element</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">{&#x3C;</span><span style="color:#79B8FF">Ranking</span><span style="color:#E1E4E8"> />} /></span></span>
<span data-line=""><span style="color:#E1E4E8">  &#x3C;</span><span style="color:#79B8FF">Route</span><span style="color:#B392F0"> path</span><span style="color:#F97583">=</span><span style="color:#9ECBFF">"/players"</span><span style="color:#B392F0"> element</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">{&#x3C;</span><span style="color:#79B8FF">PlayerManage</span><span style="color:#E1E4E8"> />} /></span></span>
<span data-line=""><span style="color:#E1E4E8">&#x3C;/</span><span style="color:#79B8FF">Routes</span><span style="color:#E1E4E8">></span></span></code></pre></figure>
<h3 lang="en" id="path--query-파라미터-조합">Combining Path and Query Parameters</h3>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="typescript" data-theme="github-dark"><code data-language="typescript" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#F97583">const</span><span style="color:#E1E4E8"> { </span><span style="color:#79B8FF">eventType</span><span style="color:#E1E4E8"> } </span><span style="color:#F97583">=</span><span style="color:#B392F0"> useParams</span><span style="color:#E1E4E8">&#x3C;{ </span><span style="color:#FFAB70">eventType</span><span style="color:#F97583">:</span><span style="color:#B392F0"> EventType</span><span style="color:#E1E4E8"> }>();</span></span>
<span data-line=""><span style="color:#F97583">const</span><span style="color:#E1E4E8"> [</span><span style="color:#79B8FF">searchParams</span><span style="color:#E1E4E8">] </span><span style="color:#F97583">=</span><span style="color:#B392F0"> useSearchParams</span><span style="color:#E1E4E8">();</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F97583">const</span><span style="color:#79B8FF"> playerIds</span><span style="color:#F97583"> =</span><span style="color:#B392F0"> useMemo</span><span style="color:#E1E4E8">(() </span><span style="color:#F97583">=></span><span style="color:#E1E4E8"> {</span></span>
<span data-line=""><span style="color:#F97583">  const</span><span style="color:#79B8FF"> param</span><span style="color:#F97583"> =</span><span style="color:#E1E4E8"> searchParams.</span><span style="color:#B392F0">get</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">'players'</span><span style="color:#E1E4E8">);</span></span>
<span data-line=""><span style="color:#F97583">  return</span><span style="color:#E1E4E8"> param </span><span style="color:#F97583">?</span><span style="color:#E1E4E8"> param.</span><span style="color:#B392F0">split</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">','</span><span style="color:#E1E4E8">).</span><span style="color:#B392F0">filter</span><span style="color:#E1E4E8">(Boolean) </span><span style="color:#F97583">:</span><span style="color:#E1E4E8"> [];</span></span>
<span data-line=""><span style="color:#E1E4E8">}, [searchParams]);</span></span></code></pre></figure>
<p lang="en">URL: <code>/timer/3-6-3?players=abc123,def456</code> → <code>playerIds = ["abc123", "def456"]</code></p>
<hr lang="en">
<h2 lang="en" id="8-실수-방지-로직">8. Preventing Accidental Input</h2>
<h3 lang="en" id="1초-보호">A One-Second Guard</h3>
<p lang="en">Prevent children from accidentally stopping the timer immediately:</p>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="typescript" data-theme="github-dark"><code data-language="typescript" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#F97583">const</span><span style="color:#79B8FF"> handleTouch</span><span style="color:#F97583"> =</span><span style="color:#B392F0"> useCallback</span><span style="color:#E1E4E8">(() </span><span style="color:#F97583">=></span><span style="color:#E1E4E8"> {</span></span>
<span data-line=""><span style="color:#F97583">  if</span><span style="color:#E1E4E8"> (state </span><span style="color:#F97583">===</span><span style="color:#9ECBFF"> 'stopped'</span><span style="color:#E1E4E8">) </span><span style="color:#F97583">return</span><span style="color:#E1E4E8">;</span></span>
<span data-line=""><span style="color:#6A737D">  // 1초 미만에서 정지 불가</span></span>
<span data-line=""><span style="color:#F97583">  if</span><span style="color:#E1E4E8"> (state </span><span style="color:#F97583">===</span><span style="color:#9ECBFF"> 'running'</span><span style="color:#F97583"> &#x26;&#x26;</span><span style="color:#E1E4E8"> time </span><span style="color:#F97583">&#x3C;</span><span style="color:#79B8FF"> 1000</span><span style="color:#E1E4E8">) </span><span style="color:#F97583">return</span><span style="color:#E1E4E8">;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F97583">  const</span><span style="color:#79B8FF"> finalTime</span><span style="color:#F97583"> =</span><span style="color:#B392F0"> toggle</span><span style="color:#E1E4E8">();</span></span>
<span data-line=""><span style="color:#F97583">  if</span><span style="color:#E1E4E8"> (finalTime </span><span style="color:#F97583">!==</span><span style="color:#79B8FF"> null</span><span style="color:#E1E4E8">) {</span></span>
<span data-line=""><span style="color:#F97583">    const</span><span style="color:#79B8FF"> record</span><span style="color:#F97583"> =</span><span style="color:#B392F0"> addRecord</span><span style="color:#E1E4E8">(eventType </span><span style="color:#F97583">as</span><span style="color:#B392F0"> EventType</span><span style="color:#E1E4E8">, playerIds, finalTime);</span></span>
<span data-line=""><span style="color:#B392F0">    setSavedRecordId</span><span style="color:#E1E4E8">(record.id);</span></span>
<span data-line=""><span style="color:#E1E4E8">  }</span></span>
<span data-line=""><span style="color:#E1E4E8">}, [state, time, toggle, addRecord, eventType, playerIds]);</span></span></code></pre></figure>
<h3 lang="en" id="기록-삭제-후-재시도">Delete a Record and Try Again</h3>
<p lang="en">Delete an incorrectly measured record and restart:</p>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="typescript" data-theme="github-dark"><code data-language="typescript" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#F97583">const</span><span style="color:#E1E4E8"> [</span><span style="color:#79B8FF">savedRecordId</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">setSavedRecordId</span><span style="color:#E1E4E8">] </span><span style="color:#F97583">=</span><span style="color:#B392F0"> useState</span><span style="color:#E1E4E8">&#x3C;</span><span style="color:#79B8FF">string</span><span style="color:#F97583"> |</span><span style="color:#79B8FF"> null</span><span style="color:#E1E4E8">>(</span><span style="color:#79B8FF">null</span><span style="color:#E1E4E8">);</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F97583">const</span><span style="color:#79B8FF"> handleDeleteRecord</span><span style="color:#F97583"> =</span><span style="color:#B392F0"> useCallback</span><span style="color:#E1E4E8">(() </span><span style="color:#F97583">=></span><span style="color:#E1E4E8"> {</span></span>
<span data-line=""><span style="color:#F97583">  if</span><span style="color:#E1E4E8"> (savedRecordId) {</span></span>
<span data-line=""><span style="color:#B392F0">    deleteRecord</span><span style="color:#E1E4E8">(savedRecordId);</span></span>
<span data-line=""><span style="color:#B392F0">    setSavedRecordId</span><span style="color:#E1E4E8">(</span><span style="color:#79B8FF">null</span><span style="color:#E1E4E8">);</span></span>
<span data-line=""><span style="color:#E1E4E8">  }</span></span>
<span data-line=""><span style="color:#B392F0">  reset</span><span style="color:#E1E4E8">();</span></span>
<span data-line=""><span style="color:#E1E4E8">}, [savedRecordId, deleteRecord, reset]);</span></span></code></pre></figure>
<p lang="en">Keep the ID when saving a record → the delete button can remove only that record.</p>
<hr lang="en">
<h2 lang="en" id="9-nginx-배포-설정">9. Nginx Deployment Configuration</h2>
<p lang="en">This Nginx configuration deploys a Vite-built SPA to an Ubuntu server.</p>
<h3 lang="en" id="정적-파일-서빙-vs-리버스-프록시">Static File Serving vs. Reverse Proxying</h3>




















<table lang="en"><thead><tr><th>Method</th><th>Purpose</th><th>Example</th></tr></thead><tbody><tr><td><strong>Static file serving</strong></td><td>Vite/React SPA</td><td><code>root /path/to/dist</code></td></tr><tr><td><strong>Reverse proxying</strong></td><td>Next.js/Express</td><td><code>proxy_pass http://127.0.0.1:3000</code></td></tr></tbody></table>
<p lang="en">Since this timer is a Vite SPA, Nginx serves the build output in <code>dist/</code> directly.</p>
<h3 lang="en" id="nginx-설정-파일">Nginx Configuration File</h3>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="nginx" data-theme="github-dark"><code data-language="nginx" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#F97583">server</span><span style="color:#E1E4E8"> {</span></span>
<span data-line=""><span style="color:#F97583">    listen </span><span style="color:#79B8FF">443</span><span style="color:#E1E4E8"> ssl;</span></span>
<span data-line=""><span style="color:#F97583">    listen </span><span style="color:#E1E4E8">[::]:443 ssl;</span></span>
<span data-line=""><span style="color:#F97583">    server_name </span><span style="color:#E1E4E8">cupstacking.funq.kr;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#6A737D">    # 빌드 결과물 경로</span></span>
<span data-line=""><span style="color:#F97583">    root </span><span style="color:#E1E4E8">/home/funq/dev/cupstacking-timer/dist;</span></span>
<span data-line=""><span style="color:#F97583">    index </span><span style="color:#E1E4E8">index.html;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#6A737D">    # SPA 라우팅: 모든 경로를 index.html로</span></span>
<span data-line=""><span style="color:#F97583">    location</span><span style="color:#B392F0"> / </span><span style="color:#E1E4E8">{</span></span>
<span data-line=""><span style="color:#F97583">        try_files </span><span style="color:#E1E4E8">$uri $uri/ /index.html;</span></span>
<span data-line=""><span style="color:#E1E4E8">    }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#6A737D">    # 정적 에셋 캐싱 (1년)</span></span>
<span data-line=""><span style="color:#F97583">    location</span><span style="color:#F97583"> ~*</span><span style="color:#DBEDFF"> \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ </span><span style="color:#E1E4E8">{</span></span>
<span data-line=""><span style="color:#F97583">        expires </span><span style="color:#E1E4E8">1y;</span></span>
<span data-line=""><span style="color:#F97583">        add_header </span><span style="color:#E1E4E8">Cache-Control </span><span style="color:#9ECBFF">"public, immutable"</span><span style="color:#E1E4E8">;</span></span>
<span data-line=""><span style="color:#E1E4E8">    }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#6A737D">    # Gzip 압축</span></span>
<span data-line=""><span style="color:#F97583">    gzip </span><span style="color:#79B8FF">on</span><span style="color:#E1E4E8">;</span></span>
<span data-line=""><span style="color:#F97583">    gzip_types</span></span>
<span data-line=""><span style="color:#E1E4E8">        text/plain</span></span>
<span data-line=""><span style="color:#E1E4E8">        text/css</span></span>
<span data-line=""><span style="color:#E1E4E8">        application/json</span></span>
<span data-line=""><span style="color:#E1E4E8">        application/javascript</span></span>
<span data-line=""><span style="color:#E1E4E8">        text/xml</span></span>
<span data-line=""><span style="color:#E1E4E8">        application/xml</span></span>
<span data-line=""><span style="color:#E1E4E8">        text/javascript;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#6A737D">    # SSL 인증서 (Let's Encrypt)</span></span>
<span data-line=""><span style="color:#F97583">    ssl_certificate </span><span style="color:#E1E4E8">/etc/letsencrypt/live/cupstacking.funq.kr/fullchain.pem;</span></span>
<span data-line=""><span style="color:#F97583">    ssl_certificate_key </span><span style="color:#E1E4E8">/etc/letsencrypt/live/cupstacking.funq.kr/privkey.pem;</span></span>
<span data-line=""><span style="color:#F97583">    include </span><span style="color:#E1E4E8">/etc/letsencrypt/options-ssl-nginx.conf;</span></span>
<span data-line=""><span style="color:#F97583">    ssl_dhparam </span><span style="color:#E1E4E8">/etc/letsencrypt/ssl-dhparams.pem;</span></span>
<span data-line=""><span style="color:#E1E4E8">}</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#6A737D"># HTTP → HTTPS 리다이렉트</span></span>
<span data-line=""><span style="color:#F97583">server</span><span style="color:#E1E4E8"> {</span></span>
<span data-line=""><span style="color:#F97583">    listen </span><span style="color:#79B8FF">80</span><span style="color:#E1E4E8">;</span></span>
<span data-line=""><span style="color:#F97583">    listen </span><span style="color:#E1E4E8">[::]:80;</span></span>
<span data-line=""><span style="color:#F97583">    server_name </span><span style="color:#E1E4E8">cupstacking.funq.kr;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F97583">    return</span><span style="color:#79B8FF"> 301</span><span style="color:#E1E4E8"> https://$host$request_uri;</span></span>
<span data-line=""><span style="color:#E1E4E8">}</span></span></code></pre></figure>
<h3 lang="en" id="핵심-설정-설명">Key Settings Explained</h3>
<h4 lang="en" id="try_files-spa-라우팅">try_files (SPA Routing)</h4>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="nginx" data-theme="github-dark"><code data-language="nginx" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#F97583">try_files </span><span style="color:#E1E4E8">$uri $uri/ /index.html;</span></span></code></pre></figure>





















<table lang="en"><thead><tr><th>Request</th><th>Nginx behavior</th></tr></thead><tbody><tr><td><code>/</code></td><td>Serve <code>dist/index.html</code></td></tr><tr><td><code>/assets/index.js</code></td><td>Serve the requested file</td></tr><tr><td><code>/timer/3-6-3</code></td><td>File not found → serve <code>index.html</code> → React Router handles the route</td></tr></tbody></table>
<p lang="en">An SPA uses client-side routing, so even paths that do not exist as files must return <code>index.html</code>.</p>
<h4 lang="en" id="정적-에셋-캐싱">Caching Static Assets</h4>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="nginx" data-theme="github-dark"><code data-language="nginx" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#F97583">location</span><span style="color:#F97583"> ~*</span><span style="color:#DBEDFF"> \.(js|css|png)$ </span><span style="color:#E1E4E8">{</span></span>
<span data-line=""><span style="color:#F97583">    expires </span><span style="color:#E1E4E8">1y;</span></span>
<span data-line=""><span style="color:#F97583">    add_header </span><span style="color:#E1E4E8">Cache-Control </span><span style="color:#9ECBFF">"public, immutable"</span><span style="color:#E1E4E8">;</span></span>
<span data-line=""><span style="color:#E1E4E8">}</span></span></code></pre></figure>
<p lang="en">Vite includes a hash in filenames during the build, such as <code>index-abc123.js</code>. The hash changes with the content, so caching for one year is safe.</p>
<h4 lang="en" id="gzip-압축">Gzip Compression</h4>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="nginx" data-theme="github-dark"><code data-language="nginx" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#F97583">gzip </span><span style="color:#79B8FF">on</span><span style="color:#E1E4E8">;</span></span>
<span data-line=""><span style="color:#F97583">gzip_types </span><span style="color:#E1E4E8">text/css application/javascript;</span></span></code></pre></figure>
<p lang="en">Compresses 100KB of JavaScript to about 30KB, improving loading speed on mobile.</p>
<h3 lang="en" id="배포-명령">Deployment Commands</h3>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="bash" data-theme="github-dark"><code data-language="bash" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#6A737D"># 1. 빌드</span></span>
<span data-line=""><span style="color:#B392F0">npm</span><span style="color:#9ECBFF"> run</span><span style="color:#9ECBFF"> build</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#6A737D"># 2. 서버에 업로드 (또는 git pull)</span></span>
<span data-line=""><span style="color:#B392F0">rsync</span><span style="color:#79B8FF"> -avz</span><span style="color:#9ECBFF"> dist/</span><span style="color:#9ECBFF"> funq@server:/home/funq/dev/cupstacking-timer/dist/</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#6A737D"># 3. Nginx 설정 테스트 및 재시작</span></span>
<span data-line=""><span style="color:#B392F0">sudo</span><span style="color:#9ECBFF"> nginx</span><span style="color:#79B8FF"> -t</span></span>
<span data-line=""><span style="color:#B392F0">sudo</span><span style="color:#9ECBFF"> systemctl</span><span style="color:#9ECBFF"> reload</span><span style="color:#9ECBFF"> nginx</span></span></code></pre></figure>
<h3 lang="en" id="lets-encrypt-ssl-인증서-발급">Issuing a Let's Encrypt SSL Certificate</h3>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="bash" data-theme="github-dark"><code data-language="bash" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#6A737D"># Certbot 설치</span></span>
<span data-line=""><span style="color:#B392F0">sudo</span><span style="color:#9ECBFF"> apt</span><span style="color:#9ECBFF"> install</span><span style="color:#9ECBFF"> certbot</span><span style="color:#9ECBFF"> python3-certbot-nginx</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#6A737D"># 인증서 발급 (Nginx 설정 자동 수정)</span></span>
<span data-line=""><span style="color:#B392F0">sudo</span><span style="color:#9ECBFF"> certbot</span><span style="color:#79B8FF"> --nginx</span><span style="color:#79B8FF"> -d</span><span style="color:#9ECBFF"> cupstacking.funq.kr</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#6A737D"># 자동 갱신 확인</span></span>
<span data-line=""><span style="color:#B392F0">sudo</span><span style="color:#9ECBFF"> certbot</span><span style="color:#9ECBFF"> renew</span><span style="color:#79B8FF"> --dry-run</span></span></code></pre></figure>
<hr lang="en">
<h2 lang="en" id="정리">Summary</h2>





































<table lang="en"><thead><tr><th>Technology</th><th>Application</th></tr></thead><tbody><tr><td><code>requestAnimationFrame</code></td><td>Timer without drift</td></tr><tr><td><code>useRef</code></td><td>Prevent duplicate touch handling and store the start time</td></tr><tr><td>LocalStorage hook</td><td>Persist players and records</td></tr><tr><td>TypeScript Literal Types</td><td>Type safety for stacking events</td></tr><tr><td>Vite PWA</td><td>Offline support and app installation</td></tr><tr><td>CSS <code>min()</code></td><td>Responsive Font Sizes</td></tr><tr><td>Nginx + Let's Encrypt</td><td>Static SPA serving and HTTPS</td></tr></tbody></table>
<p lang="en">My child now starts the timer by tapping the tablet and watches the records to see their progress. It is a simple app, but PWA made it possible to provide an experience on par with a native app.</p>]]></content:encoded>
    </item>
    <item>
      <title><![CDATA[Claude Code Keeps Crashing? Try Switching to Warp Terminal]]></title>
      <link>https://blog.funq.kr/en/blogs/warp-terminal-20260110-v01</link>
      <guid isPermaLink="true">https://blog.funq.kr/en/blogs/warp-terminal-20260110-v01</guid>
      <description><![CDATA[Endless scrolling, garbled text, memory failures... A different terminal can change your Claude Code workflow.]]></description>
      <category><![CDATA[warp]]></category><category><![CDATA[terminal]]></category><category><![CDATA[claude-code]]></category><category><![CDATA[productivity]]></category><category><![CDATA[devtools]]></category>
      <media:content url="https://blog.funq.kr/blog-cover/warp-terminal-20260110-v01.jpg" medium="image" type="image/jpeg"/>
      <pubDate>Sat, 10 Jan 2026 01:00:00 GMT</pubDate>
      <author>funqdev@gmail.com (fundev)</author>
      <content:encoded><![CDATA[<p lang="en">If you have run Claude Code for a long time, this may sound familiar.</p>
<p lang="en">The first 10–20 minutes are fine, but as the conversation grows and output piles up...</p>
<ul lang="en">
<li>The screen scrolls wildly and will not stop</li>
<li>As soon as you type, the view jumps upward and the input line disappears</li>
<li>Characters, especially Unicode and Korean text, appear garbled</li>
<li>In the worst cases, the IDE or even the OS crashes completely</li>
</ul>
<p lang="en">These terminal and UI-rendering problems are reported fairly often in GitHub issues: endless scroll loops after long conversations, a frozen UI while the window is minimized, VS Code crashes caused by terminal-buffer redraws, JavaScript heap out of memory, and more.</p>
<p lang="en">This post explains **“how to improve Claude Code's perceived stability by moving it to Warp.”**</p>
<hr lang="en">
<h2 lang="en" id="왜-터미널을-바꾸는-것이-효과가-있나">Why Can Changing the Terminal Help?</h2>
<p lang="en">The problem may look like Claude Code's, but symptoms often become worse through its interaction with <strong>the terminal's rendering, scrollback, and reflow behavior</strong>. A TUI-based agent interface with long output and frequent updates can trigger buffer redraws, scrolling jumps, and CPU or memory spikes depending on the terminal implementation.</p>
<p lang="en"><strong>Warp</strong> is closer to a redesign of terminal UX along IDE lines than merely a “pretty terminal”:</p>
<ul lang="en">
<li>It groups input and output into <strong>Blocks</strong></li>
<li>The command input area <strong>works like a modern text editor</strong></li>
<li>It has strong tab, pane, and session management, with multiple sessions built into its design</li>
</ul>
<p lang="en">That structure changes how long output and extended sessions feel.</p>
<hr lang="en">
<h2 lang="en" id="설치-appwarpdev">Installation: app.warp.dev</h2>
<h3 lang="en" id="로그인-필수">Is Login Required?</h3>
<h2 lang="en" id="예전엔-로그인-요구가-강했지만-현재는-로그인-없이도-기본-기능-사용이-가능하도록-정책이-바뀌었다">Login used to be strongly required, but the policy has changed so that <strong>basic features can now be used without logging in</strong>.</h2>
<h2 lang="en" id="멀티-세션-운영-split-panes--tabs">Managing Multiple Sessions: Split Panes + Tabs</h2>
<p lang="en">The more Claude Code sessions you run at once, the more immediately useful Warp's <strong>split panes and tabs</strong> become.</p>
<p lang="en">It reminds me of the convenience of <strong>SecureCRT</strong> or <strong>PuTTY Connection Manager</strong> when managing several servers with PuTTY: organizing sessions into tabs, saving frequently used connections, and switching between terminals on one screen. Warp brings that feeling back to the local terminal.</p>
<h3 lang="en" id="패널-분할로-claude-code-세션-23개-동시에-돌리기">Run 2–3 Claude Code Sessions with Split Panes</h3>
<p lang="en">Warp can split a single tab into panes, each with its own terminal session.</p>





















<table lang="en"><thead><tr><th>Shortcut</th><th>Action</th></tr></thead><tbody><tr><td><code>CMD-D</code></td><td>Split right</td></tr><tr><td>Right-click</td><td>Choose a split direction</td></tr><tr><td><code>CMD-[</code> / <code>CMD-]</code></td><td>Move between panes</td></tr></tbody></table>
<p lang="en">In each pane, navigate to the project folder and launch Claude Code. That is all.</p>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="bash" data-theme="github-dark"><code data-language="bash" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#79B8FF">cd</span><span style="color:#9ECBFF"> /path/to/project</span></span>
<span data-line=""><span style="color:#B392F0">claude</span></span></code></pre></figure>
<blockquote lang="en">
<p><strong>Tip</strong>: assign one role to each pane. Left: analysis/planning; middle: implementation; right: tests/log monitoring.</p>
</blockquote>
<h3 lang="en" id="탭으로-claude-code-전용과-일반-작업-분리">Use Tabs to Separate Claude Code from General Work</h3>
<p lang="en">Suggested setup:</p>
<ul lang="en">
<li><strong>Tab 1</strong>: 2–3 Claude Code panes</li>
<li><strong>Tab 2</strong>: long-running tasks such as servers, logs, and DB tunnels</li>
<li><strong>Tab 3</strong>: general shell work (git, grep, deployment, etc.)</li>
</ul>
<hr lang="en">
<h2 lang="en" id="자동완성으로-반복-작업-줄이기">Reduce Repetition with Autocomplete</h2>
<p lang="en">Running multiple Claude Code sessions means repeating “navigate to the project folder → launch” dozens of times. Warp shortens that sequence.</p>
<h3 lang="en" id="tab-completions">Tab Completions</h3>
<p lang="en">It suggests commands, options, and paths, and works over SSH too.</p>
<h3 lang="en" id="autosuggestions">Autosuggestions</h3>
<p lang="en">It automatically suggests commands based on shell history. Settings let you adjust behaviors such as accepting suggestions with Tab.</p>
<p lang="en"><strong>What you notice:</strong></p>
<ul lang="en">
<li>Suggestions appear as soon as you type <code>cd</code></li>
<li><code>claude</code> is immediately suggested from history too</li>
<li>Repeating the same work across sessions becomes faster</li>
</ul>
<hr lang="en">
<h2 lang="en" id="롱러닝-태스크-알림">Notifications for Long-Running Tasks</h2>
<p lang="en">If Claude Code changes something on the server during a task, or a development server exits unexpectedly, you may not notice immediately.</p>
<p lang="en">Warp provides <strong>desktop notifications</strong>:</p>
<ul lang="en">
<li>When a command running longer than a specified duration <strong>finishes</strong></li>
<li>When a running command <strong>asks for a password</strong></li>
</ul>
<p lang="en">You can immediately notice when a process such as <code>npm run dev</code> or <code>docker compose up</code> dies—that is, when the command exits.</p>
<hr lang="en">
<h2 lang="en" id="블록-단위-선택-복사">Select and Copy Whole Blocks</h2>
<p lang="en">Common tasks when working with Claude Code:</p>
<ul lang="en">
<li>Copy and paste logs</li>
<li>Extract an error section for a follow-up question</li>
<li>Provide only a particular command's output as context</li>
</ul>
<p lang="en">Warp groups input and output into <strong>Blocks</strong>, letting you:</p>
<ul lang="en">
<li>Copy the command</li>
<li>Copy the output</li>
<li>Scroll straight to the start of the output</li>
</ul>
<p lang="en">Dragging through long logs to select “just the part you need” is always error-prone, and even harder when the UI has bugs. Block-level copying solves that problem.</p>
<hr lang="en">
<h2 lang="en" id="파일-시스템-ui--터미널-내-편집">A File-System UI and Editing Inside the Terminal</h2>
<h3 lang="en" id="file-tree">File Tree</h3>
<p lang="en">Open <strong>File Tree</strong> in the Tools panel on the left, and click a file to open it directly in Warp's code editor.</p>
<p lang="en">Right-click a file or folder to:</p>
<ul lang="en">
<li>Open it in a new tab or pane</li>
<li>Copy its path</li>
<li>Attach it to the agent's context</li>
</ul>
<h3 lang="en" id="code-editor">Code Editor</h3>
<p lang="en">Warp's built-in code editor provides:</p>
<ul lang="en">
<li>Syntax highlighting</li>
<li>A tabbed file viewer</li>
<li>Find and replace</li>
<li>Vim keybindings</li>
</ul>
<p lang="en">Instead of switching to an IDE to inspect files changed by Claude Code and then returning to the terminal, you get a workflow in which you <strong>make a quick edit inside Warp and immediately give the next instruction</strong>.</p>
<hr lang="en">
<h2 lang="en" id="git-변경사항-리뷰">Reviewing Git Changes</h2>
<p lang="en">When Claude Code edits several files, checking “What changed, and where?” can be painful.</p>
<p lang="en">Warp's <strong>Code Review panel</strong> supports:</p>
<ul lang="en">
<li>Diff review</li>
<li>Apply, edit, or revert changes</li>
<li>Open files</li>
<li>Attach a diff to the agent's context</li>
</ul>
<p lang="en">The structure is designed for flows such as reviewing an agent's changes, leaving feedback, and having it apply that feedback.</p>
<hr lang="en">
<h2 lang="en" id="탭별-폴더-위치-자동-기억">Remembering Each Tab's Folder Automatically</h2>
<h3 lang="en" id="working-directory-설정">Working Directory Settings</h3>
<p lang="en">You can choose the default directory for new sessions—windows, tabs, or panes:</p>
<ul lang="en">
<li>Home directory</li>
<li><strong>Previous session's directory</strong> (recommended)</li>
<li>Custom directory</li>
<li>Advanced (different settings for windows, tabs, and panes)</li>
</ul>
<blockquote lang="en">
<p><strong>Recommended</strong>: “Previous session's directory.” New tabs and panes start directly in the current project directory.</p>
</blockquote>
<h3 lang="en" id="launch-configurations">Launch Configurations</h3>
<p lang="en">A feature for <strong>saving a window, tab, and pane layout for each project</strong> and reopening it later exactly as it was.</p>
<p lang="en">Create a setup such as “three Claude Code panes + server tab + logs tab” once, and it is easy to restore whenever you open the project.</p>
<hr lang="en">
<h2 lang="en" id="가격">Pricing</h2>
<p lang="en">For running Claude Code—that is, ordinary terminal use—<strong>starting with the free plan is often sufficient</strong>.</p>
<ul lang="en">
<li>Free plan: a limited monthly AI credit allowance (150/month for the first 2 months, then 75/month)</li>
<li>Higher plans: more credits and features</li>
</ul>
<p lang="en">Since you can start without logging in, the barrier to installing it and trying it out is low.</p>
<hr lang="en">
<h2 lang="en" id="주의사항">Things to Keep in Mind</h2>
<h3 lang="en" id="텔레메트리개인정보">Telemetry and Privacy</h3>
<ul lang="en">
<li>Warp uses Sentry for crash reporting and Rudderstack for app analytics</li>
<li>Telemetry can be disabled in settings; <strong>when disabled, console interactions are not stored on the server</strong></li>
</ul>
<hr lang="en">
<h2 lang="en" id="마치며-이런-분께-추천">Closing Thoughts: Who I Recommend It For</h2>
<ul lang="en">
<li>You run Claude Code <strong>for hours each day</strong>, especially in <strong>multiple sessions</strong></li>
<li>You experience <strong>flickering, endless scrolling, garbled text, or crashes</strong> in VS Code's integrated terminal</li>
<li>You want to <strong>minimize context switching</strong> while copying logs, inspecting diffs, and making quick file edits</li>
</ul>
<p lang="en">In those cases, Warp may feel close to a life-changing improvement for your Claude Code workflow.</p>
<p lang="en">At the very least, <strong>multiple panes, block copying, notifications, the file tree, and diff review</strong> can make the workflow feel like an entirely different tool.</p>
<hr lang="en">
<p lang="en"><strong>Download Warp</strong>: <a href="https://app.warp.dev">https://app.warp.dev</a></p>
<p lang="en"><em>Leave a comment if you have any questions!</em></p>]]></content:encoded>
    </item>
    <item>
      <title><![CDATA[Playwright Agents: AI Plans, Writes, and Repairs Tests]]></title>
      <link>https://blog.funq.kr/en/blogs/ai-playwright-agents-20260109-v01</link>
      <guid isPermaLink="true">https://blog.funq.kr/en/blogs/ai-playwright-agents-20260109-v01</guid>
      <description><![CDATA[Planner, Generator, and Healer: experiencing a new approach to E2E test automation through three collaborating agents.]]></description>
      <category><![CDATA[playwright]]></category><category><![CDATA[testing]]></category><category><![CDATA[ai]]></category><category><![CDATA[agent]]></category><category><![CDATA[claude-code]]></category><category><![CDATA[mcp]]></category>
      <media:content url="https://blog.funq.kr/blog-cover/ai-playwright-agents-20260109-v01.jpg" medium="image" type="image/jpeg"/>
      <pubDate>Fri, 09 Jan 2026 01:00:00 GMT</pubDate>
      <author>funqdev@gmail.com (fundev)</author>
      <content:encoded><![CDATA[<p lang="en">Writing E2E tests takes substantial resources, so from an efficiency standpoint they rarely get written. Tests break when selectors change, and the time spent repairing them makes it hard to focus on developing new features.</p>
<p lang="en">The <strong>Test Agents</strong> introduced in <strong>Playwright</strong> offer an interesting answer. AI explores the application, creates a test plan, generates code, and even repairs failing tests itself.</p>
<hr lang="en">
<h2 lang="en" id="세-에이전트의-역할">The Roles of the Three Agents</h2>
<pre><code>┌─────────────────────────────────────────────────────────────────┐
│                    Playwright Agents Flow                       │
│                                                                 │
│   🎭 Planner          🎭 Generator         🎭 Healer            │
│   ┌───────────┐       ┌───────────┐       ┌───────────┐        │
│   │ 앱 탐색     │  ──▶  │ 코드 생성   │  ──▶  │ 실패 수정   │        │
│   │           │       │           │       │           │        │
│   │ specs/*.md│       │ *.spec.ts │       │ 선택자 갱신  │        │
│   └───────────┘       └───────────┘       └───────────┘        │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘
</code></pre>
<h3 lang="en" id="planner-앱을-탐색하고-테스트-계획을-세운다">Planner: Explore the App and Create a Test Plan</h3>
<p lang="en">Planner opens a browser and explores the application directly. If there is a login page, it identifies the fields and buttons, then creates <strong>a test plan in Markdown</strong> based on what it finds.</p>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="markdown" data-theme="github-dark"><code data-language="markdown" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#79B8FF;font-weight:bold"># Login Test Plan</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#79B8FF;font-weight:bold">## Scenario 1: Successful Login</span></span>
<span data-line=""><span style="color:#E1E4E8;font-weight:bold">**Steps:**</span></span>
<span data-line=""><span style="color:#FFAB70">  1.</span><span style="color:#E1E4E8"> Navigate to /login</span></span>
<span data-line=""><span style="color:#FFAB70">  2.</span><span style="color:#E1E4E8"> Enter 'funq' in the 이름 field</span></span>
<span data-line=""><span style="color:#FFAB70">  3.</span><span style="color:#E1E4E8"> Enter 'password' in the 비밀번호 field</span></span>
<span data-line=""><span style="color:#FFAB70">  4.</span><span style="color:#E1E4E8"> Click 로그인 button</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#E1E4E8;font-weight:bold">**Expected Results:**</span></span>
<span data-line=""><span style="color:#FFAB70">  -</span><span style="color:#E1E4E8"> Redirect to main calendar page</span></span>
<span data-line=""><span style="color:#FFAB70">  -</span><span style="color:#E1E4E8"> User session is established</span></span></code></pre></figure>
<p lang="en">In effect, AI performs exploratory testing for you.</p>
<h3 lang="en" id="generator-계획을-실행-가능한-코드로-변환한다">Generator: Turn the Plan into Runnable Code</h3>
<p lang="en">Generator reads Planner's plan and produces <strong>Playwright test code that actually works</strong>.</p>
<p lang="en">The key is <strong>live verification</strong>. Before writing code, it opens a browser and checks whether the action actually works. It verifies that <code>getByRole('button', { name: '로그인' })</code> really finds the button before writing the test.</p>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="typescript" data-theme="github-dark"><code data-language="typescript" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#B392F0">test</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">'should verify successful login'</span><span style="color:#E1E4E8">, </span><span style="color:#F97583">async</span><span style="color:#E1E4E8"> ({ </span><span style="color:#FFAB70">page</span><span style="color:#E1E4E8"> }) </span><span style="color:#F97583">=></span><span style="color:#E1E4E8"> {</span></span>
<span data-line=""><span style="color:#F97583">  await</span><span style="color:#E1E4E8"> page.</span><span style="color:#B392F0">goto</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">'/login'</span><span style="color:#E1E4E8">);</span></span>
<span data-line=""><span style="color:#F97583">  await</span><span style="color:#E1E4E8"> page.</span><span style="color:#B392F0">getByLabel</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">'이름'</span><span style="color:#E1E4E8">).</span><span style="color:#B392F0">fill</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">'funq'</span><span style="color:#E1E4E8">);</span></span>
<span data-line=""><span style="color:#F97583">  await</span><span style="color:#E1E4E8"> page.</span><span style="color:#B392F0">getByLabel</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">'비밀번호'</span><span style="color:#E1E4E8">).</span><span style="color:#B392F0">fill</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">'password'</span><span style="color:#E1E4E8">);</span></span>
<span data-line=""><span style="color:#F97583">  await</span><span style="color:#E1E4E8"> page.</span><span style="color:#B392F0">getByRole</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">'button'</span><span style="color:#E1E4E8">, { name: </span><span style="color:#9ECBFF">'로그인'</span><span style="color:#E1E4E8"> }).</span><span style="color:#B392F0">click</span><span style="color:#E1E4E8">();</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F97583">  await</span><span style="color:#B392F0"> expect</span><span style="color:#E1E4E8">(page).</span><span style="color:#B392F0">toHaveURL</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">'/'</span><span style="color:#E1E4E8">);</span></span>
<span data-line=""><span style="color:#E1E4E8">});</span></span></code></pre></figure>
<h3 lang="en" id="healer-실패한-테스트를-자동으로-수정한다">Healer: Automatically Repair Failing Tests</h3>
<p lang="en">Suppose a developer changes a button's Korean “Log in” label to “Sign In.” The test fails.</p>
<p lang="en">Healer analyzes the **accessibility tree** at the failure point to find a semantically equivalent element. “There is no button with the Korean 'Log in' name, but there is a 'Sign In' button in the same place” → update the selector and rerun the test.</p>
<pre><code>❌ Error: getByRole('button', { name: '로그인' }) - Element not found
✅ Fix: Updated to getByRole('button', { name: 'Sign In' })
</code></pre>
<hr lang="en">
<h2 lang="en" id="설치-및-설정-claude-code">Installation and Setup (Claude Code)</h2>
<h3 lang="en" id="1-playwright-설치">1. Install Playwright</h3>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="bash" data-theme="github-dark"><code data-language="bash" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#B392F0">npm</span><span style="color:#9ECBFF"> init</span><span style="color:#9ECBFF"> playwright@latest</span></span></code></pre></figure>
<h3 lang="en" id="2-에이전트-초기화">2. Initialize the Agents</h3>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="bash" data-theme="github-dark"><code data-language="bash" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#B392F0">npx</span><span style="color:#9ECBFF"> playwright</span><span style="color:#9ECBFF"> init-agents</span><span style="color:#79B8FF"> --loop=claude</span></span></code></pre></figure>
<p lang="en">Files generated by this command:</p>
<pre><code>.claude/agents/
├── playwright-test-planner.md    # Planner 에이전트 정의
├── playwright-test-generator.md  # Generator 에이전트 정의
└── playwright-test-healer.md     # Healer 에이전트 정의
specs/
└── README.md                     # 테스트 계획서 저장 위치
tests/
└── seed.spec.ts                  # 시드 테스트 (로그인 등 초기 설정)
.mcp.json                         # MCP 서버 설정
</code></pre>
<h3 lang="en" id="3-mcp-서버-추가">3. Add the MCP Server</h3>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="bash" data-theme="github-dark"><code data-language="bash" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#B392F0">claude</span><span style="color:#9ECBFF"> mcp</span><span style="color:#9ECBFF"> add</span><span style="color:#9ECBFF"> playwright</span><span style="color:#9ECBFF"> npx</span><span style="color:#9ECBFF"> @playwright/mcp@latest</span></span></code></pre></figure>
<p lang="en">You can now check the agent list with <code>/agents</code> in Claude Code.</p>
<hr lang="en">
<h2 lang="en" id="실제-사용-워크플로우">The Workflow in Practice</h2>
<h3 lang="en" id="step-1-planner에게-테스트-계획-요청">Step 1: Ask Planner for a Test Plan</h3>
<pre><code>Use the playwright-test-planner subagent to explore http://localhost:3000
and create a test plan for:
1. 로그인 기능
2. 캘린더 뷰 전환 (월/주/일)
3. 일정 생성

Save it as specs/calendar.md.
Use tests/seed.spec.ts as the seed.
</code></pre>
<p lang="en">Planner explores the app and writes detailed test scenarios to <code>specs/calendar.md</code>.</p>
<h3 lang="en" id="step-2-generator에게-코드-생성-요청">Step 2: Ask Generator to Generate Code</h3>
<pre><code>Use the playwright-test-generator subagent to generate tests
from specs/calendar.md.
Put generated tests under tests/calendar/.
</code></pre>
<p lang="en">Generator creates runnable test code from the plan.</p>
<h3 lang="en" id="step-3-테스트-실행-및-healer-활용">Step 3: Run the Tests and Use Healer</h3>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="bash" data-theme="github-dark"><code data-language="bash" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#B392F0">npx</span><span style="color:#9ECBFF"> playwright</span><span style="color:#9ECBFF"> test</span></span></code></pre></figure>
<p lang="en">If any tests fail:</p>
<pre><code>Use the playwright-test-healer subagent to fix the failing tests.
Keep the original test intent - only fix locators and waits.
</code></pre>
<hr lang="en">
<h2 lang="en" id="실제-적용-가족-캘린더-프로젝트">A Real Application: The Family Calendar Project</h2>
<p lang="en">I tried Playwright Agents on the <a href="https://blog.funq.kr/blogs/ai-claude-calendar-tech-20260104-v01">calendar project</a>.</p>
<h3 lang="en" id="planner가-생성한-테스트-계획-일부">Part of the Test Plan Created by Planner</h3>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="markdown" data-theme="github-dark"><code data-language="markdown" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#79B8FF;font-weight:bold">### 4.3. Create weekly recurring event with specific weekdays</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#E1E4E8;font-weight:bold">**File:**</span><span style="color:#79B8FF"> `tests/event-management/recurring-weekly-custom.spec.ts`</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#E1E4E8;font-weight:bold">**Steps:**</span></span>
<span data-line=""><span style="color:#FFAB70">  1.</span><span style="color:#E1E4E8"> Open event creation dialog</span></span>
<span data-line=""><span style="color:#FFAB70">  2.</span><span style="color:#E1E4E8"> Enter title '수영 수업'</span></span>
<span data-line=""><span style="color:#FFAB70">  3.</span><span style="color:#E1E4E8"> Select '매주' (Weekly) from recurrence dropdown</span></span>
<span data-line=""><span style="color:#FFAB70">  4.</span><span style="color:#E1E4E8"> Click weekday buttons: 월, 수, 금</span></span>
<span data-line=""><span style="color:#FFAB70">  5.</span><span style="color:#E1E4E8"> Verify selected buttons are highlighted</span></span>
<span data-line=""><span style="color:#FFAB70">  6.</span><span style="color:#E1E4E8"> Save the event</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#E1E4E8;font-weight:bold">**Expected Results:**</span></span>
<span data-line=""><span style="color:#FFAB70">  -</span><span style="color:#E1E4E8"> Multiple weekdays can be selected</span></span>
<span data-line=""><span style="color:#FFAB70">  -</span><span style="color:#E1E4E8"> Backend receives weekdays: ['MO', 'WE', 'FR']</span></span>
<span data-line=""><span style="color:#FFAB70">  -</span><span style="color:#E1E4E8"> Calendar shows event on correct days</span></span></code></pre></figure>
<p lang="en">It understood the Korean UI naturally and included even the complex recurring-event logic in the plan.</p>
<h3 lang="en" id="generator가-생성한-코드">Code Created by Generator</h3>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="typescript" data-theme="github-dark"><code data-language="typescript" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#E1E4E8">test.</span><span style="color:#B392F0">describe</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">'Adding New Todos'</span><span style="color:#E1E4E8">, () </span><span style="color:#F97583">=></span><span style="color:#E1E4E8"> {</span></span>
<span data-line=""><span style="color:#B392F0">  test</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">'Add Valid Todo'</span><span style="color:#E1E4E8">, </span><span style="color:#F97583">async</span><span style="color:#E1E4E8"> ({ </span><span style="color:#FFAB70">page</span><span style="color:#E1E4E8"> }) </span><span style="color:#F97583">=></span><span style="color:#E1E4E8"> {</span></span>
<span data-line=""><span style="color:#F97583">    const</span><span style="color:#79B8FF"> todoInput</span><span style="color:#F97583"> =</span><span style="color:#E1E4E8"> page.</span><span style="color:#B392F0">getByRole</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">'textbox'</span><span style="color:#E1E4E8">,</span></span>
<span data-line=""><span style="color:#E1E4E8">      { name: </span><span style="color:#9ECBFF">'What needs to be done?'</span><span style="color:#E1E4E8"> });</span></span>
<span data-line=""><span style="color:#F97583">    await</span><span style="color:#E1E4E8"> todoInput.</span><span style="color:#B392F0">click</span><span style="color:#E1E4E8">();</span></span>
<span data-line=""><span style="color:#F97583">    await</span><span style="color:#E1E4E8"> todoInput.</span><span style="color:#B392F0">fill</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">'Buy groceries'</span><span style="color:#E1E4E8">);</span></span>
<span data-line=""><span style="color:#F97583">    await</span><span style="color:#E1E4E8"> todoInput.</span><span style="color:#B392F0">press</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">'Enter'</span><span style="color:#E1E4E8">);</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F97583">    await</span><span style="color:#B392F0"> expect</span><span style="color:#E1E4E8">(page.</span><span style="color:#B392F0">getByText</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">'Buy groceries'</span><span style="color:#E1E4E8">)).</span><span style="color:#B392F0">toBeVisible</span><span style="color:#E1E4E8">();</span></span>
<span data-line=""><span style="color:#F97583">    await</span><span style="color:#B392F0"> expect</span><span style="color:#E1E4E8">(page.</span><span style="color:#B392F0">getByText</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">'1 item left'</span><span style="color:#E1E4E8">)).</span><span style="color:#B392F0">toBeVisible</span><span style="color:#E1E4E8">();</span></span>
<span data-line=""><span style="color:#E1E4E8">  });</span></span>
<span data-line=""><span style="color:#E1E4E8">});</span></span></code></pre></figure>
<p lang="en">It prioritizes <code>getByRole</code> and <code>getByText</code>—<strong>user-facing locators</strong>. Since they do not depend on CSS selectors, they are more resilient to UI changes.</p>
<hr lang="en">
<h2 lang="en" id="핵심-설계-원리-접근성-트리accessibility-tree">The Core Design Principle: The Accessibility Tree</h2>
<p lang="en">What distinguishes Playwright Agents from other AI automation tools is its use of <strong>the accessibility tree rather than screenshots</strong>.</p>
<pre><code>┌─────────────────────────────────────────────────────────────┐
│  Vision Model 방식              │  Accessibility Tree 방식   │
├─────────────────────────────────┼────────────────────────────┤
│  스크린샷 픽셀 분석              │  DOM 구조화된 데이터        │
│  해상도에 따라 오차 발생          │  역할(Role) + 이름(Name)    │
│  토큰 소모량 높음                │  토큰 효율적               │
│  이미지 처리 오버헤드            │  빠른 응답 속도            │
└─────────────────────────────────┴────────────────────────────┘
</code></pre>
<p lang="en">It conveys the page structure to AI in the same way a screen reader describes a web page to a visually impaired user. Noise such as advertisements and decorative elements is removed, leaving only <strong>semantic information</strong> such as buttons, links, and input fields.</p>
<hr lang="en">
<h2 lang="en" id="seed-test의-중요성">The Importance of a Seed Test</h2>
<p lang="en">For apps with complex authentication, <code>seed.spec.ts</code> is essential.</p>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="typescript" data-theme="github-dark"><code data-language="typescript" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#6A737D">// tests/seed.spec.ts</span></span>
<span data-line=""><span style="color:#F97583">import</span><span style="color:#E1E4E8"> { test </span><span style="color:#F97583">as</span><span style="color:#E1E4E8"> setup, expect } </span><span style="color:#F97583">from</span><span style="color:#9ECBFF"> '@playwright/test'</span><span style="color:#E1E4E8">;</span></span>
<span data-line=""><span style="color:#F97583">import</span><span style="color:#E1E4E8"> path </span><span style="color:#F97583">from</span><span style="color:#9ECBFF"> 'path'</span><span style="color:#E1E4E8">;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F97583">const</span><span style="color:#79B8FF"> authFile</span><span style="color:#F97583"> =</span><span style="color:#E1E4E8"> path.</span><span style="color:#B392F0">join</span><span style="color:#E1E4E8">(__dirname, </span><span style="color:#9ECBFF">'../playwright/.auth/user.json'</span><span style="color:#E1E4E8">);</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#B392F0">setup</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">'authenticate'</span><span style="color:#E1E4E8">, </span><span style="color:#F97583">async</span><span style="color:#E1E4E8"> ({ </span><span style="color:#FFAB70">page</span><span style="color:#E1E4E8"> }) </span><span style="color:#F97583">=></span><span style="color:#E1E4E8"> {</span></span>
<span data-line=""><span style="color:#F97583">  await</span><span style="color:#E1E4E8"> page.</span><span style="color:#B392F0">goto</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">'/login'</span><span style="color:#E1E4E8">);</span></span>
<span data-line=""><span style="color:#F97583">  await</span><span style="color:#E1E4E8"> page.</span><span style="color:#B392F0">getByLabel</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">'이름'</span><span style="color:#E1E4E8">).</span><span style="color:#B392F0">fill</span><span style="color:#E1E4E8">(process.env.</span><span style="color:#79B8FF">E2E_USER</span><span style="color:#F97583">!</span><span style="color:#E1E4E8">);</span></span>
<span data-line=""><span style="color:#F97583">  await</span><span style="color:#E1E4E8"> page.</span><span style="color:#B392F0">getByLabel</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">'비밀번호'</span><span style="color:#E1E4E8">).</span><span style="color:#B392F0">fill</span><span style="color:#E1E4E8">(process.env.</span><span style="color:#79B8FF">E2E_PASS</span><span style="color:#F97583">!</span><span style="color:#E1E4E8">);</span></span>
<span data-line=""><span style="color:#F97583">  await</span><span style="color:#E1E4E8"> page.</span><span style="color:#B392F0">getByRole</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">'button'</span><span style="color:#E1E4E8">, { name: </span><span style="color:#9ECBFF">'로그인'</span><span style="color:#E1E4E8"> }).</span><span style="color:#B392F0">click</span><span style="color:#E1E4E8">();</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F97583">  await</span><span style="color:#B392F0"> expect</span><span style="color:#E1E4E8">(page).</span><span style="color:#B392F0">toHaveURL</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">'/'</span><span style="color:#E1E4E8">);</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#6A737D">  // 세션 상태 저장</span></span>
<span data-line=""><span style="color:#F97583">  await</span><span style="color:#E1E4E8"> page.</span><span style="color:#B392F0">context</span><span style="color:#E1E4E8">().</span><span style="color:#B392F0">storageState</span><span style="color:#E1E4E8">({ path: authFile });</span></span>
<span data-line=""><span style="color:#E1E4E8">});</span></span></code></pre></figure>
<p lang="en">With this setup, Planner, Generator, and Healer all <strong>start authenticated</strong>. They can focus on testing the core features without wasting tokens analyzing the login page.</p>
<hr lang="en">
<h2 lang="en" id="실무-팁">Practical Tips</h2>
<h3 lang="en" id="1-healer에게-명확한-가이드라인을-준다">1. Give Healer Clear Guidelines</h3>
<p lang="en">Healer may delete assertions or change the test's intent to make it “pass.” Specify constraints in the prompt:</p>
<pre><code>Fix the failing tests, but:
- Keep the original test intent
- Do NOT remove assertions
- Only update locators and waits
</code></pre>
<h3 lang="en" id="2-playwright-업데이트-시-에이전트-재생성">2. Regenerate the Agents When Updating Playwright</h3>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="bash" data-theme="github-dark"><code data-language="bash" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#B392F0">npx</span><span style="color:#9ECBFF"> playwright</span><span style="color:#9ECBFF"> init-agents</span><span style="color:#79B8FF"> --loop=claude</span></span></code></pre></figure>
<p lang="en">Refresh the agent definition files to incorporate tools and instructions added in new versions.</p>
<h3 lang="en" id="3-토큰-비용-관리">3. Manage Token Costs</h3>
<p lang="en">Loading the MCP server alone consumes 4,000–8,000 tokens. It is a good idea to disable the server when testing is done, or use a separate session.</p>
<hr lang="en">
<h2 lang="en" id="한계점">Limitations</h2>
<h3 lang="en" id="로직-변경은-감지하지-못한다">It Cannot Detect Logic Changes</h3>
<p lang="en">Healer is good at handling <strong>selector changes</strong>, but cannot distinguish cases where a test fails because the application's <strong>business logic has changed</strong>. In those cases, it marks the test with <code>test.fixme()</code> and requests developer intervention.</p>
<h3 lang="en" id="비대화형-설계">Non-Interactive Design</h3>
<p lang="en">Healer attempts the most reasonable fix without asking the user questions. In complex situations, the applied change may differ from the intended one, so its changes must be reviewed.</p>
<h3 lang="en" id="토큰-비용">Token Costs</h3>
<p lang="en">Exploring a complex app can consume a substantial number of tokens.</p>
<hr lang="en">
<h2 lang="en" id="마치며">Closing Thoughts</h2>
<p lang="en">Playwright Agents is <strong>an attempt to change the way tests are written</strong>.</p>
<p lang="en">Previously, developers had to code every selector and step explicitly. Now, they can provide a high-level intent such as “Verify the user-registration flow.”</p>
<p lang="en">It is not perfect yet. Token costs, detection of complex logic changes, and the non-interactive design still need improvement. But the direction is clear: <strong>the QA engineer's role will evolve from “test scripter” to “agent architect.”</strong></p>
<p lang="en">Rather than writing individual test cases, the core skills will be designing the system prompts agents follow and building robust seed tests and fixtures.</p>
<hr lang="en">
<p lang="en"><strong>References</strong></p>
<ul lang="en">
<li><a href="https://playwright.dev/docs/test-agents">Official Playwright Test Agents documentation</a></li>
<li><a href="https://modelcontextprotocol.io/">Model Context Protocol (MCP)</a></li>
</ul>
<p lang="en"><em>Leave a comment if you have any questions!</em></p>]]></content:encoded>
    </item>
    <item>
      <title><![CDATA[Full-Stack Calendar Development: Recurring Events with FastAPI and Next.js]]></title>
      <link>https://blog.funq.kr/en/blogs/ai-claude-calendar-tech-20260104-v01</link>
      <guid isPermaLink="true">https://blog.funq.kr/en/blogs/ai-claude-calendar-tech-20260104-v01</guid>
      <description><![CDATA[From the Protocol pattern and the RRULE standard to time-zone handling: the backend and frontend implementation of a shared family calendar.]]></description>
      <category><![CDATA[fastapi]]></category><category><![CDATA[nextjs]]></category><category><![CDATA[postgresql]]></category><category><![CDATA[firebase]]></category><category><![CDATA[typescript]]></category><category><![CDATA[python]]></category>
      <media:content url="https://blog.funq.kr/blog-cover/ai-claude-calendar-tech-20260104-v01.jpg" medium="image" type="image/jpeg"/>
      <pubDate>Sun, 04 Jan 2026 02:00:00 GMT</pubDate>
      <author>funqdev@gmail.com (fundev)</author>
      <content:encoded><![CDATA[<blockquote lang="en">
<p>This is a follow-up to <a href="https://blog.funq.kr/blogs/ai-claude-calendar-20260104-v01">Building a Calendar with Claude Code and Superpowers</a>. If you are interested in the AI-assisted development workflow, read the earlier post first.</p>
</blockquote>
<p lang="en">This post shares the full process of building a family calendar with a <strong>FastAPI backend</strong> and a <strong>Next.js frontend</strong>.</p>
<hr lang="en">
<h2 lang="en" id="기술-스택">Technology Stack</h2>
<h3 lang="en" id="백엔드">Backend</h3>
<pre><code>Python 3.13 + FastAPI 0.115
PostgreSQL + SQLAlchemy 2.0
Firebase Authentication
python-dateutil (반복 일정)
pytest (TDD)
</code></pre>
<h3 lang="en" id="프론트엔드">Frontend</h3>
<pre><code>Next.js 16.1.1 + React 19
TypeScript 5 (Strict Mode)
Tailwind CSS 4
shadcn/ui + Radix UI
Firebase SDK 12.7
date-fns 4.1 (한국어 로케일)
</code></pre>
<p lang="en">Authentication is unified through <strong>Firebase</strong>. Signing in with Firebase on the frontend produces an ID token, which the backend verifies to identify the user.</p>
<hr lang="en">
<h2 lang="en" id="풀스택-아키텍처">Full-Stack Architecture</h2>
<pre><code>┌─────────────────────────────────────────────────────────────────┐
│                         Frontend                                │
│  ┌─────────────┐  ┌──────────────┐  ┌────────────────────────┐  │
│  │  Next.js    │  │  shadcn/ui   │  │     Firebase Auth      │  │
│  │  App Router │  │  Components  │  │  (로그인 + ID Token)     │  │
│  └──────┬──────┘  └──────────────┘  └───────────┬────────────┘  │
│         │                                        │              │
│         │         API Client (Bearer Token)      │              │
│         └───────────────────┬────────────────────┘              │
└─────────────────────────────┼───────────────────────────────────┘
                              │ HTTPS
┌─────────────────────────────┼───────────────────────────────────┐
│                         Backend                                 │
│                              │                                  │
│  ┌───────────────────────────┴────────────────────────────────┐ │
│  │                      FastAPI                               │ │
│  │  ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌────────────┐  │ │
│  │  │  Auth    │  │ Members  │  │Categories│  │   Events   │  │ │
│  │  │  Router  │  │  Router  │  │  Router  │  │   Router   │  │ │
│  │  └────┬─────┘  └────┬─────┘  └────┬─────┘  └─────┬──────┘  │ │
│  │       │             │             │              │         │ │
│  │       └─────────────┴──────┬──────┴──────────────┘         │ │
│  │                            │                               │ │
│  │  ┌─────────────────────────┴─────────────────────────────┐ │ │
│  │  │              Service Layer (Protocol 기반)             │ │ │
│  │  │   MemberService │ CategoryService │ EventService      │ │ │
│  │  └─────────────────────────┬─────────────────────────────┘ │ │
│  │                            │                               │ │
│  │  ┌─────────────────────────┴─────────────────────────────┐ │ │
│  │  │           PostgreSQL + SQLAlchemy ORM                 │ │ │
│  │  │   FamilyMember │ Category │ Event │ RecurrenceException││ │
│  │  └───────────────────────────────────────────────────────┘ │ │
│  └────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
</code></pre>
<hr lang="en">
<h2 lang="en" id="part-1-백엔드-구현">Part 1: Backend Implementation</h2>
<h3 lang="en" id="protocol-패턴으로-테스트-용이성-확보">Improving Testability with the Protocol Pattern</h3>
<p lang="en">I used Python's <code>Protocol</code> to define interfaces and separate the real services from fake implementations.</p>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="python" data-theme="github-dark"><code data-language="python" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#6A737D"># protocol.py</span></span>
<span data-line=""><span style="color:#F97583">class</span><span style="color:#B392F0"> MemberServiceProtocol</span><span style="color:#E1E4E8">(</span><span style="color:#B392F0">Protocol</span><span style="color:#E1E4E8">):</span></span>
<span data-line=""><span style="color:#F97583">    def</span><span style="color:#B392F0"> get_all</span><span style="color:#E1E4E8">(self) -> list[FamilyMemberResponse]: </span><span style="color:#79B8FF">...</span></span>
<span data-line=""><span style="color:#F97583">    def</span><span style="color:#B392F0"> create</span><span style="color:#E1E4E8">(self, data: FamilyMemberCreate) -> FamilyMemberResponse: </span><span style="color:#79B8FF">...</span></span>
<span data-line=""><span style="color:#F97583">    def</span><span style="color:#B392F0"> verify_and_link</span><span style="color:#E1E4E8">(self, email: </span><span style="color:#79B8FF">str</span><span style="color:#E1E4E8">, firebase_uid: </span><span style="color:#79B8FF">str</span><span style="color:#E1E4E8">) -> FamilyMemberResponse: </span><span style="color:#79B8FF">...</span></span></code></pre></figure>
<p lang="en">This pattern lets <strong>92 tests run without PostgreSQL</strong> in just 0.12 seconds.</p>
<h3 lang="en" id="반복-일정-핵심-rrule">The Core of Recurring Events: RRULE</h3>
<p lang="en">RRULE (Recurrence Rule) is the recurrence format defined in the iCalendar standard, RFC 5545.</p>
<pre><code>FREQ=WEEKLY;INTERVAL=1;BYDAY=MO,WE,FR
</code></pre>
<p lang="en">This rule means “repeat every Monday, Wednesday, and Friday.” For a user-friendly API, I also support <strong>a pattern-object approach</strong>.</p>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="json" data-theme="github-dark"><code data-language="json" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#E1E4E8">{</span></span>
<span data-line=""><span style="color:#79B8FF">  "title"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"학원"</span><span style="color:#E1E4E8">,</span></span>
<span data-line=""><span style="color:#79B8FF">  "start_time"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"2024-01-15T16:00:00"</span><span style="color:#E1E4E8">,</span></span>
<span data-line=""><span style="color:#79B8FF">  "end_time"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"2024-01-15T18:00:00"</span><span style="color:#E1E4E8">,</span></span>
<span data-line=""><span style="color:#79B8FF">  "recurrence_pattern"</span><span style="color:#E1E4E8">: {</span></span>
<span data-line=""><span style="color:#79B8FF">    "frequency"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"WEEKLY"</span><span style="color:#E1E4E8">,</span></span>
<span data-line=""><span style="color:#79B8FF">    "weekdays"</span><span style="color:#E1E4E8">: [</span><span style="color:#9ECBFF">"MO"</span><span style="color:#E1E4E8">, </span><span style="color:#9ECBFF">"WE"</span><span style="color:#E1E4E8">, </span><span style="color:#9ECBFF">"FR"</span><span style="color:#E1E4E8">]</span></span>
<span data-line=""><span style="color:#E1E4E8">  },</span></span>
<span data-line=""><span style="color:#79B8FF">  "recurrence_end"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"2024-12-31"</span></span>
<span data-line=""><span style="color:#E1E4E8">}</span></span></code></pre></figure>
<h3 lang="en" id="반복-일정-확장">Expanding Recurring Events</h3>
<p lang="en">When events are queried, recurring events are <strong>expanded</strong> into every occurrence within the requested period.</p>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="python" data-theme="github-dark"><code data-language="python" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#F97583">def</span><span style="color:#B392F0"> get_by_date_range</span><span style="color:#E1E4E8">(self, start_date: date, end_date: date):</span></span>
<span data-line=""><span style="color:#E1E4E8">    results </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> []</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#6A737D">    # 1. 일반 일정 조회</span></span>
<span data-line=""><span style="color:#E1E4E8">    non_recurring </span><span style="color:#F97583">=</span><span style="color:#79B8FF"> self</span><span style="color:#E1E4E8">.db.query(Event).filter(</span></span>
<span data-line=""><span style="color:#E1E4E8">        Event.recurrence_rule.is_(</span><span style="color:#79B8FF">None</span><span style="color:#E1E4E8">),</span></span>
<span data-line=""><span style="color:#E1E4E8">        Event.start_time.between(start_date, end_date),</span></span>
<span data-line=""><span style="color:#E1E4E8">    ).all()</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#6A737D">    # 2. 반복 일정 확장</span></span>
<span data-line=""><span style="color:#E1E4E8">    recurring </span><span style="color:#F97583">=</span><span style="color:#79B8FF"> self</span><span style="color:#E1E4E8">.db.query(Event).filter(</span></span>
<span data-line=""><span style="color:#E1E4E8">        Event.recurrence_rule.isnot(</span><span style="color:#79B8FF">None</span><span style="color:#E1E4E8">),</span></span>
<span data-line=""><span style="color:#E1E4E8">        Event.start_time </span><span style="color:#F97583">&#x3C;=</span><span style="color:#E1E4E8"> end_date,</span></span>
<span data-line=""><span style="color:#E1E4E8">    ).all()</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F97583">    for</span><span style="color:#E1E4E8"> event </span><span style="color:#F97583">in</span><span style="color:#E1E4E8"> recurring:</span></span>
<span data-line=""><span style="color:#E1E4E8">        occurrences </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> get_occurrences(</span></span>
<span data-line=""><span style="color:#FFAB70">            rrule_str</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">event.recurrence_rule,</span></span>
<span data-line=""><span style="color:#FFAB70">            dtstart</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">event.start_time,</span></span>
<span data-line=""><span style="color:#FFAB70">            range_start</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">start_date,</span></span>
<span data-line=""><span style="color:#FFAB70">            range_end</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">end_date,</span></span>
<span data-line=""><span style="color:#E1E4E8">        )</span></span>
<span data-line=""><span style="color:#F97583">        for</span><span style="color:#E1E4E8"> occurrence_date </span><span style="color:#F97583">in</span><span style="color:#E1E4E8"> occurrences:</span></span>
<span data-line=""><span style="color:#E1E4E8">            results.append(</span><span style="color:#79B8FF">self</span><span style="color:#E1E4E8">._event_to_response(event, occurrence_date))</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F97583">    return</span><span style="color:#E1E4E8"> results</span></span></code></pre></figure>
<hr lang="en">
<h2 lang="en" id="part-2-프론트엔드-구현">Part 2: Frontend Implementation</h2>
<h3 lang="en" id="프로젝트-구조">Project Structure</h3>
<pre><code>src/
├── app/
│   ├── page.tsx          # 메인 캘린더 (인증 필요)
│   └── login/page.tsx    # 로그인 페이지
├── components/
│   ├── calendar/         # 캘린더 컴포넌트
│   │   ├── CalendarHeader.tsx   # 뷰 전환 + 네비게이션
│   │   ├── MonthView.tsx        # 월간 그리드
│   │   ├── WeekView.tsx         # 주간 타임라인
│   │   ├── DayView.tsx          # 일간 상세
│   │   ├── EventDialog.tsx      # 일정 생성/수정
│   │   └── CategoryDialog.tsx   # 카테고리 관리
│   └── ui/               # shadcn/ui 컴포넌트
├── lib/
│   ├── api.ts            # API 클라이언트
│   ├── auth.ts           # Firebase 인증 함수
│   └── firebase.ts       # Firebase 초기화
└── hooks/
    └── useAuth.ts        # 인증 상태 관리 훅
</code></pre>
<h3 lang="en" id="api-클라이언트-bearer-token-자동-주입">API Client: Automatic Bearer Token Injection</h3>
<p lang="en">Automatically add the Firebase ID token to every API request.</p>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="typescript" data-theme="github-dark"><code data-language="typescript" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#F97583">async</span><span style="color:#F97583"> function</span><span style="color:#B392F0"> fetchWithAuth</span><span style="color:#E1E4E8">(</span><span style="color:#FFAB70">url</span><span style="color:#F97583">:</span><span style="color:#79B8FF"> string</span><span style="color:#E1E4E8">, </span><span style="color:#FFAB70">options</span><span style="color:#F97583">:</span><span style="color:#B392F0"> RequestInit</span><span style="color:#F97583"> =</span><span style="color:#E1E4E8"> {}) {</span></span>
<span data-line=""><span style="color:#F97583">  const</span><span style="color:#79B8FF"> token</span><span style="color:#F97583"> =</span><span style="color:#F97583"> await</span><span style="color:#B392F0"> getIdToken</span><span style="color:#E1E4E8">();</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F97583">  const</span><span style="color:#79B8FF"> headers</span><span style="color:#F97583"> =</span><span style="color:#E1E4E8"> {</span></span>
<span data-line=""><span style="color:#9ECBFF">    'Content-Type'</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">'application/json'</span><span style="color:#E1E4E8">,</span></span>
<span data-line=""><span style="color:#F97583">    ...</span><span style="color:#E1E4E8">(token </span><span style="color:#F97583">&#x26;&#x26;</span><span style="color:#E1E4E8"> { Authorization: </span><span style="color:#9ECBFF">`Bearer ${</span><span style="color:#E1E4E8">token</span><span style="color:#9ECBFF">}`</span><span style="color:#E1E4E8"> }),</span></span>
<span data-line=""><span style="color:#F97583">    ...</span><span style="color:#E1E4E8">options.headers,</span></span>
<span data-line=""><span style="color:#E1E4E8">  };</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F97583">  const</span><span style="color:#79B8FF"> response</span><span style="color:#F97583"> =</span><span style="color:#F97583"> await</span><span style="color:#B392F0"> fetch</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">`${</span><span style="color:#79B8FF">API_URL</span><span style="color:#9ECBFF">}${</span><span style="color:#E1E4E8">url</span><span style="color:#9ECBFF">}`</span><span style="color:#E1E4E8">, {</span></span>
<span data-line=""><span style="color:#F97583">    ...</span><span style="color:#E1E4E8">options,</span></span>
<span data-line=""><span style="color:#E1E4E8">    headers,</span></span>
<span data-line=""><span style="color:#E1E4E8">  });</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F97583">  if</span><span style="color:#E1E4E8"> (</span><span style="color:#F97583">!</span><span style="color:#E1E4E8">response.ok) {</span></span>
<span data-line=""><span style="color:#F97583">    const</span><span style="color:#79B8FF"> error</span><span style="color:#F97583"> =</span><span style="color:#F97583"> await</span><span style="color:#E1E4E8"> response.</span><span style="color:#B392F0">json</span><span style="color:#E1E4E8">().</span><span style="color:#B392F0">catch</span><span style="color:#E1E4E8">(() </span><span style="color:#F97583">=></span><span style="color:#E1E4E8"> ({ detail: </span><span style="color:#9ECBFF">'Unknown error'</span><span style="color:#E1E4E8"> }));</span></span>
<span data-line=""><span style="color:#F97583">    throw</span><span style="color:#F97583"> new</span><span style="color:#B392F0"> Error</span><span style="color:#E1E4E8">(error.detail </span><span style="color:#F97583">||</span><span style="color:#9ECBFF"> `HTTP ${</span><span style="color:#E1E4E8">response</span><span style="color:#9ECBFF">.</span><span style="color:#E1E4E8">status</span><span style="color:#9ECBFF">}`</span><span style="color:#E1E4E8">);</span></span>
<span data-line=""><span style="color:#E1E4E8">  }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F97583">  return</span><span style="color:#E1E4E8"> response.</span><span style="color:#B392F0">json</span><span style="color:#E1E4E8">();</span></span>
<span data-line=""><span style="color:#E1E4E8">}</span></span></code></pre></figure>
<h3 lang="en" id="멀티-뷰-캘린더">A Calendar with Multiple Views</h3>
<p lang="en">The calendar provides three views.</p>

























<table lang="en"><thead><tr><th>View</th><th>Purpose</th><th>Features</th></tr></thead><tbody><tr><td><strong>Monthly</strong></td><td>See the overall schedule</td><td>Responsive event display (1–3 events)</td></tr><tr><td><strong>Weekly</strong></td><td>Review events by time</td><td>24-hour timeline</td></tr><tr><td><strong>Daily</strong></td><td>Manage events in detail</td><td>Click a time slot to create an event</td></tr></tbody></table>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="typescript" data-theme="github-dark"><code data-language="typescript" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#E1E4E8">{view </span><span style="color:#F97583">===</span><span style="color:#9ECBFF"> 'month'</span><span style="color:#F97583"> &#x26;&#x26;</span><span style="color:#E1E4E8"> &#x3C;</span><span style="color:#B392F0">MonthView</span><span style="color:#B392F0"> currentDate</span><span style="color:#E1E4E8">={</span><span style="color:#FFAB70">currentDate</span><span style="color:#E1E4E8">} </span><span style="color:#B392F0">events</span><span style="color:#E1E4E8">={</span><span style="color:#FFAB70">events</span><span style="color:#E1E4E8">} />}</span></span>
<span data-line=""><span style="color:#E1E4E8">{view </span><span style="color:#F97583">===</span><span style="color:#9ECBFF"> 'week'</span><span style="color:#F97583"> &#x26;&#x26;</span><span style="color:#E1E4E8"> &#x3C;</span><span style="color:#B392F0">WeekView</span><span style="color:#B392F0"> currentDate</span><span style="color:#E1E4E8">={</span><span style="color:#FFAB70">currentDate</span><span style="color:#E1E4E8">} </span><span style="color:#B392F0">events</span><span style="color:#E1E4E8">={</span><span style="color:#FFAB70">events</span><span style="color:#E1E4E8">} />}</span></span>
<span data-line=""><span style="color:#E1E4E8">{view </span><span style="color:#F97583">===</span><span style="color:#9ECBFF"> 'day'</span><span style="color:#F97583"> &#x26;&#x26;</span><span style="color:#E1E4E8"> &#x3C;</span><span style="color:#B392F0">DayView</span><span style="color:#B392F0"> currentDate</span><span style="color:#E1E4E8">={</span><span style="color:#FFAB70">currentDate</span><span style="color:#E1E4E8">} </span><span style="color:#B392F0">events</span><span style="color:#E1E4E8">={</span><span style="color:#FFAB70">events</span><span style="color:#E1E4E8">} />}</span></span></code></pre></figure>
<h3 lang="en" id="반복-일정-ui">Recurring-Event UI</h3>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="typescript" data-theme="github-dark"><code data-language="typescript" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#F97583">type</span><span style="color:#B392F0"> Frequency</span><span style="color:#F97583"> =</span><span style="color:#9ECBFF"> 'DAILY'</span><span style="color:#F97583"> |</span><span style="color:#9ECBFF"> 'WEEKLY'</span><span style="color:#F97583"> |</span><span style="color:#9ECBFF"> 'MONTHLY'</span><span style="color:#F97583"> |</span><span style="color:#9ECBFF"> 'YEARLY'</span><span style="color:#E1E4E8">;</span></span>
<span data-line=""><span style="color:#F97583">type</span><span style="color:#B392F0"> Weekday</span><span style="color:#F97583"> =</span><span style="color:#9ECBFF"> 'MO'</span><span style="color:#F97583"> |</span><span style="color:#9ECBFF"> 'TU'</span><span style="color:#F97583"> |</span><span style="color:#9ECBFF"> 'WE'</span><span style="color:#F97583"> |</span><span style="color:#9ECBFF"> 'TH'</span><span style="color:#F97583"> |</span><span style="color:#9ECBFF"> 'FR'</span><span style="color:#F97583"> |</span><span style="color:#9ECBFF"> 'SA'</span><span style="color:#F97583"> |</span><span style="color:#9ECBFF"> 'SU'</span><span style="color:#E1E4E8">;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F97583">interface</span><span style="color:#B392F0"> RecurrencePattern</span><span style="color:#E1E4E8"> {</span></span>
<span data-line=""><span style="color:#FFAB70">  frequency</span><span style="color:#F97583">:</span><span style="color:#B392F0"> Frequency</span><span style="color:#E1E4E8">;</span></span>
<span data-line=""><span style="color:#FFAB70">  interval</span><span style="color:#F97583">?:</span><span style="color:#79B8FF"> number</span><span style="color:#E1E4E8">;</span></span>
<span data-line=""><span style="color:#FFAB70">  weekdays</span><span style="color:#F97583">?:</span><span style="color:#B392F0"> Weekday</span><span style="color:#E1E4E8">[];</span></span>
<span data-line=""><span style="color:#E1E4E8">}</span></span></code></pre></figure>
<p lang="en">Selecting weekly recurrence shows weekday buttons.</p>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="typescript" data-theme="github-dark"><code data-language="typescript" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#E1E4E8">{frequency </span><span style="color:#F97583">===</span><span style="color:#9ECBFF"> 'WEEKLY'</span><span style="color:#F97583"> &#x26;&#x26;</span><span style="color:#E1E4E8"> (</span></span>
<span data-line=""><span style="color:#F97583">  &#x3C;</span><span style="color:#E1E4E8">div className</span><span style="color:#F97583">=</span><span style="color:#9ECBFF">"flex flex-wrap gap-1"</span><span style="color:#F97583">></span></span>
<span data-line=""><span style="color:#E1E4E8">    {WEEKDAY_OPTIONS.map((opt) => (</span></span>
<span data-line=""><span style="color:#E1E4E8">      &#x3C;Button</span></span>
<span data-line=""><span style="color:#E1E4E8">        key</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">{opt.value}</span></span>
<span data-line=""><span style="color:#E1E4E8">        variant</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">{weekdays.includes(opt.value) ? </span><span style="color:#9ECBFF">'default'</span><span style="color:#E1E4E8"> : </span><span style="color:#9ECBFF">'outline'</span><span style="color:#E1E4E8">}</span></span>
<span data-line=""><span style="color:#E1E4E8">        size</span><span style="color:#F97583">=</span><span style="color:#9ECBFF">"sm"</span></span>
<span data-line=""><span style="color:#E1E4E8">        className</span><span style="color:#F97583">=</span><span style="color:#9ECBFF">"w-9 h-9 p-0"</span></span>
<span data-line=""><span style="color:#E1E4E8">        onClick</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">{() => </span><span style="color:#B392F0">toggleWeekday</span><span style="color:#E1E4E8">(opt.value)}</span></span>
<span data-line=""><span style="color:#E1E4E8">      ></span></span>
<span data-line=""><span style="color:#E1E4E8">        {opt.label}</span></span>
<span data-line=""><span style="color:#E1E4E8">      &#x3C;/Button></span></span>
<span data-line=""><span style="color:#E1E4E8">    ))}</span></span>
<span data-line=""><span style="color:#F97583">  &#x3C;/</span><span style="color:#E1E4E8">div</span><span style="color:#F97583">></span></span>
<span data-line=""><span style="color:#E1E4E8">)}</span></span></code></pre></figure>
<h3 lang="en" id="반응형-디자인">Responsive Design</h3>
<p lang="en">Render the same data differently according to the screen size.</p>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="typescript" data-theme="github-dark"><code data-language="typescript" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#6A737D">// 화면 크기별 표시 개수</span></span>
<span data-line=""><span style="color:#F97583">const</span><span style="color:#79B8FF"> mobileCount</span><span style="color:#F97583"> =</span><span style="color:#79B8FF"> 1</span><span style="color:#E1E4E8">;</span></span>
<span data-line=""><span style="color:#F97583">const</span><span style="color:#79B8FF"> tabletCount</span><span style="color:#F97583"> =</span><span style="color:#79B8FF"> 2</span><span style="color:#E1E4E8">;</span></span>
<span data-line=""><span style="color:#F97583">const</span><span style="color:#79B8FF"> desktopCount</span><span style="color:#F97583"> =</span><span style="color:#79B8FF"> 3</span><span style="color:#E1E4E8">;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#6A737D">// 모바일: 1개만 표시</span></span>
<span data-line=""><span style="color:#F97583">&#x3C;</span><span style="color:#E1E4E8">div className</span><span style="color:#F97583">=</span><span style="color:#9ECBFF">"md:hidden"</span><span style="color:#F97583">></span></span>
<span data-line=""><span style="color:#E1E4E8">  {dayEvents.</span><span style="color:#B392F0">slice</span><span style="color:#E1E4E8">(</span><span style="color:#79B8FF">0</span><span style="color:#E1E4E8">, mobileCount).</span><span style="color:#B392F0">map</span><span style="color:#E1E4E8">(renderEvent)}</span></span>
<span data-line=""><span style="color:#E1E4E8">  {dayEvents.</span><span style="color:#79B8FF">length</span><span style="color:#F97583"> ></span><span style="color:#E1E4E8"> mobileCount </span><span style="color:#F97583">&#x26;&#x26;</span><span style="color:#E1E4E8"> (</span></span>
<span data-line=""><span style="color:#F97583">    &#x3C;</span><span style="color:#E1E4E8">p className</span><span style="color:#F97583">=</span><span style="color:#9ECBFF">"text-xs"</span><span style="color:#F97583">>+</span><span style="color:#E1E4E8">{dayEvents.length - mobileCount}</span><span style="color:#F97583">&#x3C;/</span><span style="color:#E1E4E8">p</span><span style="color:#F97583">></span></span>
<span data-line=""><span style="color:#E1E4E8">  )}</span></span>
<span data-line=""><span style="color:#F97583">&#x3C;/</span><span style="color:#E1E4E8">div</span><span style="color:#F97583">></span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#6A737D">// 태블릿: 2개</span></span>
<span data-line=""><span style="color:#F97583">&#x3C;</span><span style="color:#E1E4E8">div className</span><span style="color:#F97583">=</span><span style="color:#9ECBFF">"hidden md:block lg:hidden"</span><span style="color:#F97583">></span></span>
<span data-line=""><span style="color:#E1E4E8">  {dayEvents.</span><span style="color:#B392F0">slice</span><span style="color:#E1E4E8">(</span><span style="color:#79B8FF">0</span><span style="color:#E1E4E8">, tabletCount).</span><span style="color:#B392F0">map</span><span style="color:#E1E4E8">(renderEvent)}</span></span>
<span data-line=""><span style="color:#F97583">&#x3C;/</span><span style="color:#E1E4E8">div</span><span style="color:#F97583">></span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#6A737D">// 데스크톱: 3개</span></span>
<span data-line=""><span style="color:#F97583">&#x3C;</span><span style="color:#E1E4E8">div className</span><span style="color:#F97583">=</span><span style="color:#9ECBFF">"hidden lg:block"</span><span style="color:#F97583">></span></span>
<span data-line=""><span style="color:#E1E4E8">  {dayEvents.</span><span style="color:#B392F0">slice</span><span style="color:#E1E4E8">(</span><span style="color:#79B8FF">0</span><span style="color:#E1E4E8">, desktopCount).</span><span style="color:#B392F0">map</span><span style="color:#E1E4E8">(renderEvent)}</span></span>
<span data-line=""><span style="color:#F97583">&#x3C;/</span><span style="color:#E1E4E8">div</span><span style="color:#F97583">></span></span></code></pre></figure>
<hr lang="en">
<h2 lang="en" id="part-3-트러블-슈팅">Part 3: Troubleshooting</h2>
<h3 lang="en" id="트러블-슈팅-1-타임존">Troubleshooting 1: Time Zones</h3>
<p lang="en"><strong>Symptom</strong>: an event for January 3 appears on January 4</p>
<p lang="en"><strong>Cause</strong>: conversion to UTC by <code>toISOString()</code> shifts the date</p>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="typescript" data-theme="github-dark"><code data-language="typescript" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#6A737D">// 문제 코드</span></span>
<span data-line=""><span style="color:#F97583">const</span><span style="color:#79B8FF"> dateStr</span><span style="color:#F97583"> =</span><span style="color:#E1E4E8"> date.</span><span style="color:#B392F0">toISOString</span><span style="color:#E1E4E8">().</span><span style="color:#B392F0">split</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">'T'</span><span style="color:#E1E4E8">)[</span><span style="color:#79B8FF">0</span><span style="color:#E1E4E8">];</span></span>
<span data-line=""><span style="color:#6A737D">// 1월 3일 00:00 KST → "2026-01-02" (UTC 기준)</span></span></code></pre></figure>
<p lang="en"><strong>Fix</strong>: use local Date methods</p>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="typescript" data-theme="github-dark"><code data-language="typescript" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#6A737D">// 해결 코드</span></span>
<span data-line=""><span style="color:#F97583">const</span><span style="color:#B392F0"> getEventsForDate</span><span style="color:#F97583"> =</span><span style="color:#E1E4E8"> (</span><span style="color:#FFAB70">date</span><span style="color:#F97583">:</span><span style="color:#B392F0"> Date</span><span style="color:#E1E4E8">) </span><span style="color:#F97583">=></span><span style="color:#E1E4E8"> {</span></span>
<span data-line=""><span style="color:#F97583">  const</span><span style="color:#79B8FF"> year</span><span style="color:#F97583"> =</span><span style="color:#E1E4E8"> date.</span><span style="color:#B392F0">getFullYear</span><span style="color:#E1E4E8">();</span></span>
<span data-line=""><span style="color:#F97583">  const</span><span style="color:#79B8FF"> month</span><span style="color:#F97583"> =</span><span style="color:#E1E4E8"> date.</span><span style="color:#B392F0">getMonth</span><span style="color:#E1E4E8">();</span></span>
<span data-line=""><span style="color:#F97583">  const</span><span style="color:#79B8FF"> day</span><span style="color:#F97583"> =</span><span style="color:#E1E4E8"> date.</span><span style="color:#B392F0">getDate</span><span style="color:#E1E4E8">();</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F97583">  return</span><span style="color:#E1E4E8"> events.</span><span style="color:#B392F0">filter</span><span style="color:#E1E4E8">((</span><span style="color:#FFAB70">event</span><span style="color:#E1E4E8">) </span><span style="color:#F97583">=></span><span style="color:#E1E4E8"> {</span></span>
<span data-line=""><span style="color:#6A737D">    // 반복 일정은 occurrence_date 사용</span></span>
<span data-line=""><span style="color:#F97583">    if</span><span style="color:#E1E4E8"> (event.is_recurring </span><span style="color:#F97583">&#x26;&#x26;</span><span style="color:#E1E4E8"> event.occurrence_date) {</span></span>
<span data-line=""><span style="color:#F97583">      const</span><span style="color:#E1E4E8"> [</span><span style="color:#79B8FF">y</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">m</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">d</span><span style="color:#E1E4E8">] </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> event.occurrence_date.</span><span style="color:#B392F0">split</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">'-'</span><span style="color:#E1E4E8">).</span><span style="color:#B392F0">map</span><span style="color:#E1E4E8">(Number);</span></span>
<span data-line=""><span style="color:#F97583">      return</span><span style="color:#E1E4E8"> y </span><span style="color:#F97583">===</span><span style="color:#E1E4E8"> year </span><span style="color:#F97583">&#x26;&#x26;</span><span style="color:#E1E4E8"> m </span><span style="color:#F97583">-</span><span style="color:#79B8FF"> 1</span><span style="color:#F97583"> ===</span><span style="color:#E1E4E8"> month </span><span style="color:#F97583">&#x26;&#x26;</span><span style="color:#E1E4E8"> d </span><span style="color:#F97583">===</span><span style="color:#E1E4E8"> day;</span></span>
<span data-line=""><span style="color:#E1E4E8">    }</span></span>
<span data-line=""><span style="color:#6A737D">    // 일반 일정은 start_time 파싱</span></span>
<span data-line=""><span style="color:#F97583">    const</span><span style="color:#79B8FF"> eventDate</span><span style="color:#F97583"> =</span><span style="color:#F97583"> new</span><span style="color:#B392F0"> Date</span><span style="color:#E1E4E8">(event.start_time);</span></span>
<span data-line=""><span style="color:#F97583">    return</span><span style="color:#E1E4E8"> (</span></span>
<span data-line=""><span style="color:#E1E4E8">      eventDate.</span><span style="color:#B392F0">getFullYear</span><span style="color:#E1E4E8">() </span><span style="color:#F97583">===</span><span style="color:#E1E4E8"> year </span><span style="color:#F97583">&#x26;&#x26;</span></span>
<span data-line=""><span style="color:#E1E4E8">      eventDate.</span><span style="color:#B392F0">getMonth</span><span style="color:#E1E4E8">() </span><span style="color:#F97583">===</span><span style="color:#E1E4E8"> month </span><span style="color:#F97583">&#x26;&#x26;</span></span>
<span data-line=""><span style="color:#E1E4E8">      eventDate.</span><span style="color:#B392F0">getDate</span><span style="color:#E1E4E8">() </span><span style="color:#F97583">===</span><span style="color:#FFAB70"> day</span></span>
<span data-line=""><span style="color:#E1E4E8">    );</span></span>
<span data-line=""><span style="color:#E1E4E8">  });</span></span>
<span data-line=""><span style="color:#E1E4E8">};</span></span></code></pre></figure>
<h3 lang="en" id="트러블-슈팅-2-api-응답-형식-불일치">Troubleshooting 2: API Response Format Mismatch</h3>
<p lang="en"><strong>Symptom</strong>: a <code>t.filter is not a function</code> error</p>
<p lang="en"><strong>Cause</strong>: the backend returns an object, <code>{ events: [...] }</code>, while the frontend expects an array</p>
<p lang="en"><strong>Fix</strong>: add response-extraction logic</p>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="typescript" data-theme="github-dark"><code data-language="typescript" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#F97583">export</span><span style="color:#F97583"> async</span><span style="color:#F97583"> function</span><span style="color:#B392F0"> getEvents</span><span style="color:#E1E4E8">(</span><span style="color:#FFAB70">startDate</span><span style="color:#F97583">:</span><span style="color:#79B8FF"> string</span><span style="color:#E1E4E8">, </span><span style="color:#FFAB70">endDate</span><span style="color:#F97583">:</span><span style="color:#79B8FF"> string</span><span style="color:#E1E4E8">) {</span></span>
<span data-line=""><span style="color:#F97583">  const</span><span style="color:#79B8FF"> response</span><span style="color:#F97583"> =</span><span style="color:#F97583"> await</span><span style="color:#B392F0"> fetchWithAuth</span><span style="color:#E1E4E8">(</span></span>
<span data-line=""><span style="color:#9ECBFF">    `/calendar/events?start_date=${</span><span style="color:#E1E4E8">startDate</span><span style="color:#9ECBFF">}&#x26;end_date=${</span><span style="color:#E1E4E8">endDate</span><span style="color:#9ECBFF">}`</span></span>
<span data-line=""><span style="color:#E1E4E8">  );</span></span>
<span data-line=""><span style="color:#6A737D">  // 객체에서 배열 추출</span></span>
<span data-line=""><span style="color:#F97583">  return</span><span style="color:#E1E4E8"> response?.events </span><span style="color:#F97583">||</span><span style="color:#E1E4E8"> [];</span></span>
<span data-line=""><span style="color:#E1E4E8">}</span></span></code></pre></figure>
<p lang="en"><strong>Resolving It with AI</strong>: I documented the calendar-related requests and responses in the backend project, then used AI in the frontend project to review that document and make the necessary corrections.</p>
<h3 lang="en" id="트러블-슈팅-3-eslint-react-hooks-에러">Troubleshooting 3: ESLint react-hooks Errors</h3>
<p lang="en"><strong>Symptom</strong>: a <code>react-hooks/set-state-in-effect</code> error</p>
<p lang="en"><strong>Cause</strong>: multiple setState calls inside useEffect</p>
<p lang="en"><strong>Fix</strong>: combine the form state into a single object</p>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="typescript" data-theme="github-dark"><code data-language="typescript" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#6A737D">// Before: 10개의 개별 useState</span></span>
<span data-line=""><span style="color:#F97583">const</span><span style="color:#E1E4E8"> [</span><span style="color:#79B8FF">title</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">setTitle</span><span style="color:#E1E4E8">] </span><span style="color:#F97583">=</span><span style="color:#B392F0"> useState</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">''</span><span style="color:#E1E4E8">);</span></span>
<span data-line=""><span style="color:#F97583">const</span><span style="color:#E1E4E8"> [</span><span style="color:#79B8FF">description</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">setDescription</span><span style="color:#E1E4E8">] </span><span style="color:#F97583">=</span><span style="color:#B392F0"> useState</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">''</span><span style="color:#E1E4E8">);</span></span>
<span data-line=""><span style="color:#6A737D">// ...</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#6A737D">// After: 단일 FormState 객체</span></span>
<span data-line=""><span style="color:#F97583">interface</span><span style="color:#B392F0"> FormState</span><span style="color:#E1E4E8"> {</span></span>
<span data-line=""><span style="color:#FFAB70">  title</span><span style="color:#F97583">:</span><span style="color:#79B8FF"> string</span><span style="color:#E1E4E8">;</span></span>
<span data-line=""><span style="color:#FFAB70">  description</span><span style="color:#F97583">:</span><span style="color:#79B8FF"> string</span><span style="color:#E1E4E8">;</span></span>
<span data-line=""><span style="color:#FFAB70">  startDate</span><span style="color:#F97583">:</span><span style="color:#B392F0"> Date</span><span style="color:#E1E4E8">;</span></span>
<span data-line=""><span style="color:#FFAB70">  startTime</span><span style="color:#F97583">:</span><span style="color:#79B8FF"> string</span><span style="color:#E1E4E8">;</span></span>
<span data-line=""><span style="color:#6A737D">  // ...</span></span>
<span data-line=""><span style="color:#E1E4E8">}</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F97583">const</span><span style="color:#E1E4E8"> [</span><span style="color:#79B8FF">formState</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">setFormState</span><span style="color:#E1E4E8">] </span><span style="color:#F97583">=</span><span style="color:#B392F0"> useState</span><span style="color:#E1E4E8">&#x3C;</span><span style="color:#B392F0">FormState</span><span style="color:#E1E4E8">>(getInitialFormState);</span></span></code></pre></figure>
<hr lang="en">
<h2 lang="en" id="part-4-테스트-전략">Part 4: Test Strategy</h2>
<h3 lang="en" id="백엔드-테스트-피라미드트로피-방식이-되었다">The Backend Test Pyramid (Which Became a Trophy)</h3>
<pre><code>          / \
         /   \
        /     \
       /  E2E  \ 8개 (실제 Firebase, DB)
      /─────────\
     /Integration\ 43개 (Fake DI)
    /─────────────\
   /     Unit      \ 19개 (순수 로직)
   ─────────────────
</code></pre>
<h3 lang="en" id="반복-로직-unit-테스트">Unit Tests for Recurrence Logic</h3>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="python" data-theme="github-dark"><code data-language="python" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#F97583">def</span><span style="color:#B392F0"> test_weekly_specific_days</span><span style="color:#E1E4E8">(self):</span></span>
<span data-line=""><span style="color:#9ECBFF">    """매주 월, 수, 금 반복"""</span></span>
<span data-line=""><span style="color:#E1E4E8">    occurrences </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> get_occurrences(</span></span>
<span data-line=""><span style="color:#FFAB70">        rrule_str</span><span style="color:#F97583">=</span><span style="color:#9ECBFF">"FREQ=WEEKLY;INTERVAL=1;BYDAY=MO,WE,FR"</span><span style="color:#E1E4E8">,</span></span>
<span data-line=""><span style="color:#FFAB70">        dtstart</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">datetime(</span><span style="color:#79B8FF">2024</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">1</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">1</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">10</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">0</span><span style="color:#E1E4E8">),</span></span>
<span data-line=""><span style="color:#FFAB70">        range_start</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">date(</span><span style="color:#79B8FF">2024</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">1</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">1</span><span style="color:#E1E4E8">),</span></span>
<span data-line=""><span style="color:#FFAB70">        range_end</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">date(</span><span style="color:#79B8FF">2024</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">1</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">7</span><span style="color:#E1E4E8">),</span></span>
<span data-line=""><span style="color:#E1E4E8">    )</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F97583">    assert</span><span style="color:#79B8FF"> len</span><span style="color:#E1E4E8">(occurrences) </span><span style="color:#F97583">==</span><span style="color:#79B8FF"> 3</span></span>
<span data-line=""><span style="color:#F97583">    assert</span><span style="color:#E1E4E8"> date(</span><span style="color:#79B8FF">2024</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">1</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">1</span><span style="color:#E1E4E8">) </span><span style="color:#F97583">in</span><span style="color:#E1E4E8"> occurrences  </span><span style="color:#6A737D"># 월</span></span>
<span data-line=""><span style="color:#F97583">    assert</span><span style="color:#E1E4E8"> date(</span><span style="color:#79B8FF">2024</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">1</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">3</span><span style="color:#E1E4E8">) </span><span style="color:#F97583">in</span><span style="color:#E1E4E8"> occurrences  </span><span style="color:#6A737D"># 수</span></span>
<span data-line=""><span style="color:#F97583">    assert</span><span style="color:#E1E4E8"> date(</span><span style="color:#79B8FF">2024</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">1</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">5</span><span style="color:#E1E4E8">) </span><span style="color:#F97583">in</span><span style="color:#E1E4E8"> occurrences  </span><span style="color:#6A737D"># 금</span></span></code></pre></figure>
<h3 lang="en" id="프론트엔드-e2e-테스트-playwright">Frontend E2E Tests (Playwright)</h3>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="typescript" data-theme="github-dark"><code data-language="typescript" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#B392F0">test</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">'일정 생성 및 표시'</span><span style="color:#E1E4E8">, </span><span style="color:#F97583">async</span><span style="color:#E1E4E8"> ({ </span><span style="color:#FFAB70">page</span><span style="color:#E1E4E8"> }) </span><span style="color:#F97583">=></span><span style="color:#E1E4E8"> {</span></span>
<span data-line=""><span style="color:#F97583">  await</span><span style="color:#E1E4E8"> page.</span><span style="color:#B392F0">goto</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">'/'</span><span style="color:#E1E4E8">);</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#6A737D">  // 일정 추가 버튼 클릭</span></span>
<span data-line=""><span style="color:#F97583">  await</span><span style="color:#E1E4E8"> page.</span><span style="color:#B392F0">click</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">'text=일정 추가'</span><span style="color:#E1E4E8">);</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#6A737D">  // 폼 작성</span></span>
<span data-line=""><span style="color:#F97583">  await</span><span style="color:#E1E4E8"> page.</span><span style="color:#B392F0">fill</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">'[name=title]'</span><span style="color:#E1E4E8">, </span><span style="color:#9ECBFF">'테스트 일정'</span><span style="color:#E1E4E8">);</span></span>
<span data-line=""><span style="color:#F97583">  await</span><span style="color:#E1E4E8"> page.</span><span style="color:#B392F0">click</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">'text=저장'</span><span style="color:#E1E4E8">);</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#6A737D">  // 캘린더에 일정 표시 확인</span></span>
<span data-line=""><span style="color:#F97583">  await</span><span style="color:#B392F0"> expect</span><span style="color:#E1E4E8">(page.</span><span style="color:#B392F0">locator</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">'text=테스트 일정'</span><span style="color:#E1E4E8">)).</span><span style="color:#B392F0">toBeVisible</span><span style="color:#E1E4E8">();</span></span>
<span data-line=""><span style="color:#E1E4E8">});</span></span></code></pre></figure>
<hr lang="en">
<h2 lang="en" id="part-5-배포">Part 5: Deployment</h2>
<h3 lang="en" id="nginx-설정-및-ssl-발급">Nginx Configuration and SSL Certificate Issuance</h3>
<blockquote lang="en">
<p>The server's base environment—Ubuntu Server, Nginx, Certbot, firewall, and so on—was already configured in <a href="https://blog.funq.kr/blogs/server-setup-20251214-v01">Setting Up the Blog Server</a>. Here, only the calendar app's configuration needs to be added.</p>
</blockquote>
<p lang="en"><strong>1. Create the Configuration File</strong></p>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="bash" data-theme="github-dark"><code data-language="bash" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#B392F0">sudo</span><span style="color:#9ECBFF"> nano</span><span style="color:#9ECBFF"> /etc/nginx/sites-available/calendar.conf</span></span></code></pre></figure>
<p lang="en"><strong>2. Paste the Contents and Save</strong></p>
<p lang="en">Paste the following into the editor, then press <code>Ctrl+O</code> (save) → <code>Enter</code> → <code>Ctrl+X</code> (exit).</p>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="nginx" data-theme="github-dark"><code data-language="nginx" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#F97583">server</span><span style="color:#E1E4E8"> {</span></span>
<span data-line=""><span style="color:#F97583">    server_name </span><span style="color:#E1E4E8">calendar.funq.kr;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F97583">    location</span><span style="color:#B392F0"> / </span><span style="color:#E1E4E8">{</span></span>
<span data-line=""><span style="color:#F97583">        proxy_pass </span><span style="color:#E1E4E8">http://127.0.0.1:3002;</span></span>
<span data-line=""><span style="color:#F97583">        proxy_http_version </span><span style="color:#79B8FF">1.1</span><span style="color:#E1E4E8">;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F97583">        proxy_set_header </span><span style="color:#E1E4E8">Host $host;</span></span>
<span data-line=""><span style="color:#F97583">        proxy_set_header </span><span style="color:#E1E4E8">X-Real-IP $remote_addr;</span></span>
<span data-line=""><span style="color:#F97583">        proxy_set_header </span><span style="color:#E1E4E8">X-Forwarded-For $proxy_add_x_forwarded_for;</span></span>
<span data-line=""><span style="color:#F97583">        proxy_set_header </span><span style="color:#E1E4E8">X-Forwarded-Proto $scheme;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F97583">        proxy_set_header </span><span style="color:#E1E4E8">Upgrade $http_upgrade;</span></span>
<span data-line=""><span style="color:#F97583">        proxy_set_header </span><span style="color:#E1E4E8">Connection </span><span style="color:#9ECBFF">"upgrade"</span><span style="color:#E1E4E8">;</span></span>
<span data-line=""><span style="color:#E1E4E8">    }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F97583">    listen </span><span style="color:#79B8FF">80</span><span style="color:#E1E4E8">;</span></span>
<span data-line=""><span style="color:#E1E4E8">}</span></span></code></pre></figure>
<p lang="en"><strong>3. Create a Symbolic Link</strong></p>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="bash" data-theme="github-dark"><code data-language="bash" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#B392F0">sudo</span><span style="color:#9ECBFF"> ln</span><span style="color:#79B8FF"> -s</span><span style="color:#9ECBFF"> /etc/nginx/sites-available/calendar.conf</span><span style="color:#9ECBFF"> /etc/nginx/sites-enabled/</span></span></code></pre></figure>
<p lang="en"><strong>4. Test the Nginx Configuration Syntax</strong></p>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="bash" data-theme="github-dark"><code data-language="bash" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#B392F0">sudo</span><span style="color:#9ECBFF"> nginx</span><span style="color:#79B8FF"> -t</span></span>
<span data-line=""><span style="color:#6A737D"># syntax is ok 메시지 확인</span></span></code></pre></figure>
<p lang="en"><strong>5. Restart Nginx</strong></p>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="bash" data-theme="github-dark"><code data-language="bash" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#B392F0">sudo</span><span style="color:#9ECBFF"> systemctl</span><span style="color:#9ECBFF"> reload</span><span style="color:#9ECBFF"> nginx</span></span></code></pre></figure>
<p lang="en"><strong>6. Issue an SSL Certificate (Certbot)</strong></p>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="bash" data-theme="github-dark"><code data-language="bash" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#B392F0">sudo</span><span style="color:#9ECBFF"> certbot</span><span style="color:#79B8FF"> --nginx</span><span style="color:#79B8FF"> -d</span><span style="color:#9ECBFF"> calendar.funq.kr</span></span></code></pre></figure>
<p lang="en">Certbot automatically modifies the Nginx configuration and applies the HTTPS certificate.</p>
<hr lang="en">
<h2 lang="en" id="결과물">The Result</h2>
<h3 lang="en" id="지원하는-기능">Supported Features</h3>





































<table lang="en"><thead><tr><th>Feature</th><th>Description</th></tr></thead><tbody><tr><td><strong>Multiple views</strong></td><td>Switch between monthly, weekly, and daily views</td></tr><tr><td><strong>Event CRUD</strong></td><td>Create, read, update, and delete</td></tr><tr><td><strong>Recurring events</strong></td><td>Daily, weekly, monthly, yearly, and specific weekdays</td></tr><tr><td><strong>Categories</strong></td><td>Color-based categorization</td></tr><tr><td><strong>Family members</strong></td><td>A different color for each member</td></tr><tr><td><strong>Automatic registration</strong></td><td>Create a user automatically on first login</td></tr><tr><td><strong>Responsive layout</strong></td><td>Supports mobile, tablet, and desktop</td></tr></tbody></table>
<h3 lang="en" id="지원하는-반복-패턴">Supported Recurrence Patterns</h3>

































<table lang="en"><thead><tr><th>Pattern</th><th>Example API request</th></tr></thead><tbody><tr><td>Every day</td><td><code>{"frequency": "DAILY"}</code></td></tr><tr><td>Every week</td><td><code>{"frequency": "WEEKLY"}</code></td></tr><tr><td>Every Monday, Wednesday, and Friday</td><td><code>{"frequency": "WEEKLY", "weekdays": ["MO", "WE", "FR"]}</code></td></tr><tr><td>Every other week</td><td><code>{"frequency": "WEEKLY", "interval": 2}</code></td></tr><tr><td>Every month</td><td><code>{"frequency": "MONTHLY"}</code></td></tr><tr><td>Every year</td><td><code>{"frequency": "YEARLY"}</code></td></tr></tbody></table>
<hr lang="en">
<h2 lang="en" id="마치며">Closing Thoughts</h2>
<h3 lang="en" id="배운-점">What I Learned</h3>
<p lang="en"><strong>Backend</strong></p>
<ol lang="en">
<li><strong>The Protocol pattern</strong>: greatly improves testability</li>
<li><strong>The RRULE standard</strong>: do not reinvent the wheel; RFC 5545 is a proven standard</li>
<li><strong>Incremental improvement</strong>: improve as errors appear; with tests, there is less to fear</li>
</ol>
<p lang="en"><strong>Frontend</strong></p>
<ol lang="en">
<li><strong>TypeScript strict mode</strong>: catches runtime errors at build time</li>
<li><strong>shadcn/ui</strong>: customizable components speed up development</li>
<li><strong>Time-zone handling</strong>: use local Date methods instead of <code>toISOString()</code></li>
</ol>
<p lang="en"><strong>Full stack</strong></p>
<ol lang="en">
<li><strong>API contracts</strong>: clearly define the response format between backend and frontend</li>
<li><strong>Automatic registration</strong>: reduce friction in user onboarding</li>
<li><strong>Error handling</strong>: use graceful degradation to keep working offline</li>
</ol>
<h2 lang="en" id="시리즈-안내">About the Series</h2>
<p lang="en">This is part 2 of the calendar project series:</p>
<ol lang="en">
<li><a href="https://blog.funq.kr/blogs/ai-claude-calendar-20260104-v01">Building a Calendar with Claude Code and Superpowers: Brainstorm → Plan → Execute</a>—the development workflow</li>
<li><strong>Full-Stack Calendar Development</strong> (this post)—implementation details</li>
</ol>
<hr lang="en">
<p lang="en"><strong>GitHub</strong>: <a href="https://github.com/nasodev/calendar">https://github.com/nasodev/calendar</a></p>
<p lang="en"><strong>Live Demo</strong>: <a href="https://calendar.funq.kr">https://calendar.funq.kr</a></p>
<p lang="en"><em>Leave a comment if you have any questions!</em></p>]]></content:encoded>
    </item>
    <item>
      <title><![CDATA[Building a Calendar with Claude Code and Superpowers: Brainstorm → Plan → Execute]]></title>
      <link>https://blog.funq.kr/en/blogs/ai-claude-calendar-20260104-v01</link>
      <guid isPermaLink="true">https://blog.funq.kr/en/blogs/ai-claude-calendar-20260104-v01</guid>
      <description><![CDATA[A practical guide to planning and implementing a shared family calendar with Claude Code's Superpowers skill system.]]></description>
      <category><![CDATA[claude-code]]></category><category><![CDATA[ai]]></category><category><![CDATA[superpowers]]></category><category><![CDATA[nextjs]]></category><category><![CDATA[fastapi]]></category><category><![CDATA[mcp]]></category>
      <media:content url="https://blog.funq.kr/blog-cover/ai-claude-calendar-20260104-v01.jpg" medium="image" type="image/jpeg"/>
      <pubDate>Sun, 04 Jan 2026 01:00:00 GMT</pubDate>
      <author>funqdev@gmail.com (fundev)</author>
      <content:encoded><![CDATA[<blockquote lang="en">
<p>“I have an idea, but I don't know where to start.”</p>
</blockquote>
<p lang="en">Every developer encounters this. Claude Code's <strong>Superpowers</strong> skill system addresses it systematically. This post shares my experience using the <strong>brainstorm → plan → execute</strong> workflow to build a shared calendar.</p>
<hr lang="en">
<h2 lang="en" id="superpowers란">What Is Superpowers?</h2>
<p lang="en">Superpowers is an open-source library built on Claude Code's skill system, providing more than 20 skills. Each consists of a SKILL.md file, activates automatically according to context, and covers areas such as testing, debugging, and collaboration. It is designed to enforce proven methods instead of letting AI write code without a process. Commands such as <code>/superpowers:brainstorm</code>, <code>/superpowers:write-plan</code>, and <code>/superpowers:execute-plan</code> let you work through complex tasks in stages.</p>
<h3 lang="en" id="설치-및-사용법">Installation and Usage</h3>
<p lang="en">Installation in Claude Code is simple: run /plugin marketplace add obra/superpowers-marketplace, followed by /plugin install superpowers@superpowers-marketplace. Restart the session to enable commands such as /superpowers:brainstorm.</p>
<pre><code>브레인스토밍 → 설계 문서 → 구현 플랜 → 자동 실행
</code></pre>
<p lang="en">The core of this workflow is that <strong>AI asks questions, the user chooses, and AI executes</strong>.</p>
<hr lang="en">
<h2 lang="en" id="phase-1-brainstorm---요구사항-수집">Phase 1: Brainstorm—Gather Requirements</h2>
<h3 lang="en" id="시작하기">Getting Started</h3>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="bash" data-theme="github-dark"><code data-language="bash" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#B392F0">/superpowers:brainstorm</span><span style="color:#9ECBFF"> 가족끼리</span><span style="color:#9ECBFF"> 일정을</span><span style="color:#9ECBFF"> 공유할</span><span style="color:#9ECBFF"> 달력</span><span style="color:#9ECBFF"> 프로그램을</span><span style="color:#9ECBFF"> 만들것이다.</span></span>
<span data-line=""><span style="color:#B392F0">디자인은</span><span style="color:#9ECBFF"> https://calendar-shadcn.vercel.app/</span><span style="color:#9ECBFF"> 를</span><span style="color:#9ECBFF"> 참조하고</span><span style="color:#9ECBFF"> shadcn을</span><span style="color:#9ECBFF"> 사용할</span><span style="color:#9ECBFF"> 것이다.</span></span>
<span data-line=""><span style="color:#B392F0">로그인은</span><span style="color:#9ECBFF"> firebase를</span><span style="color:#9ECBFF"> 이용할</span><span style="color:#9ECBFF"> 것이고</span><span style="color:#9ECBFF"> '/Users/.../backend-api/'</span><span style="color:#9ECBFF"> 에</span><span style="color:#9ECBFF"> 백엔드</span><span style="color:#9ECBFF"> api를</span><span style="color:#9ECBFF"> 쓸수</span><span style="color:#9ECBFF"> 있다.</span></span></code></pre></figure>
<p lang="en">This single line starts the brainstorming session. Claude first checks the current state of the project.</p>
<h3 lang="en" id="질문-응답-방식의-요구사항-수집">Gathering Requirements Through Questions and Answers</h3>
<p lang="en">Claude asks questions with choices for each feature:</p>
<p lang="en"><strong>1. Family Size</strong></p>
<pre><code>가족 구성원은 몇 명 정도를 예상하시나요?

1. 소규모 (2-4명) - 부부와 자녀 1-2명
2. 중규모 (5-8명) - 조부모, 부모, 자녀 포함 확대가족
3. 유동적 - 사용자가 직접 가족을 초대/관리
</code></pre>
<blockquote lang="en">
<p>Answer: 2</p>
</blockquote>
<p lang="en"><strong>2. Permission Model</strong></p>
<pre><code>일정 수정 권한은 어떻게 설정하고 싶으신가요?

1. 모두 동등 - 가족 누구나 모든 일정을 생성/수정/삭제 가능
2. 본인 일정만 - 각자 본인이 만든 일정만 수정 가능
3. 관리자 체계 - 부모만 관리자 권한, 자녀는 본인 일정만 수정
</code></pre>
<blockquote lang="en">
<p>Answer: 1</p>
</blockquote>
<p lang="en"><strong>3. Color Coding</strong></p>
<pre><code>일정을 어떻게 구분해서 보고 싶으신가요?

1. 구성원별 색상 - 각 가족 구성원마다 고유 색상
2. 카테고리별 색상 - 일정 유형별 색상 (병원, 학교, 가족행사)
3. 둘 다 - 구성원 + 카테고리 조합
</code></pre>
<blockquote lang="en">
<p>Answer: 3</p>
</blockquote>
<p lang="en">In this way, we decide on <strong>calendar views</strong>, <strong>notifications</strong>, <strong>recurring events</strong>, <strong>authentication</strong>, and more, one at a time.</p>
<h3 lang="en" id="자동-생성된-설계-문서">The Automatically Generated Design Document</h3>
<p lang="en">Once all the questions are answered, Claude automatically generates a design document:</p>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="markdown" data-theme="github-dark"><code data-language="markdown" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#79B8FF;font-weight:bold"># 가족 공유 캘린더 설계 문서</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#79B8FF;font-weight:bold">## 1. 개요</span></span>
<span data-line=""><span style="color:#E1E4E8;font-weight:bold">**목적**</span><span style="color:#E1E4E8">: 확대가족(5-8명)이 일정을 공유하는 웹 캘린더</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#E1E4E8;font-weight:bold">**핵심 특징**</span><span style="color:#E1E4E8">:</span></span>
<span data-line=""><span style="color:#FFAB70">-</span><span style="color:#E1E4E8"> 구성원별 색상 + 카테고리별 색상 조합</span></span>
<span data-line=""><span style="color:#FFAB70">-</span><span style="color:#E1E4E8"> 월간/주간/일간 뷰 전환</span></span>
<span data-line=""><span style="color:#FFAB70">-</span><span style="color:#E1E4E8"> 상세 반복 일정 (매주 월·수·금, 매월 셋째 토요일 등)</span></span>
<span data-line=""><span style="color:#FFAB70">-</span><span style="color:#E1E4E8"> Firebase Auth 공유 (kid-chat과 동일 계정)</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#79B8FF;font-weight:bold">## 2. 아키텍처</span></span>
<span data-line=""><span style="color:#E1E4E8">┌─────────────────┐     ┌─────────────────┐     ┌──────────────┐</span></span>
<span data-line=""><span style="color:#E1E4E8">│  Next.js App    │────▶│  FastAPI        │────▶│  PostgreSQL  │</span></span>
<span data-line=""><span style="color:#E1E4E8">│  (calendar)     │     │  (backend-api)  │     │              │</span></span>
<span data-line=""><span style="color:#E1E4E8">└────────┬────────┘     └─────────────────┘     └──────────────┘</span></span>
<span data-line=""><span style="color:#E1E4E8">         │</span></span>
<span data-line=""><span style="color:#E1E4E8">         ▼</span></span>
<span data-line=""><span style="color:#E1E4E8">┌─────────────────┐</span></span>
<span data-line=""><span style="color:#E1E4E8">│  Firebase Auth  │ ◀── kid-chat과 공유</span></span>
<span data-line=""><span style="color:#E1E4E8">└─────────────────┘</span></span></code></pre></figure>
<p lang="en"><strong>The key point</strong>: the user only made choices, yet a complete design document emerged.</p>
<hr lang="en">
<h2 lang="en" id="phase-2-plan---구현-플랜-작성">Phase 2: Plan—Write an Implementation Plan</h2>
<h3 lang="en" id="플랜-생성-명령">The Plan-Generation Command</h3>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="bash" data-theme="github-dark"><code data-language="bash" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#B392F0">/superpowers:write-plan</span><span style="color:#9ECBFF"> '/path/to/2026-01-01-family-calendar-design.md'</span></span>
<span data-line=""><span style="color:#B392F0">브레인스토밍으로</span><span style="color:#9ECBFF"> 나온</span><span style="color:#9ECBFF"> 결과이다</span></span></code></pre></figure>
<p lang="en">Claude reads the design document and creates a detailed implementation plan containing <strong>18 tasks</strong>.</p>
<h3 lang="en" id="플랜-구조">Plan Structure</h3>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="markdown" data-theme="github-dark"><code data-language="markdown" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#79B8FF;font-weight:bold"># Family Calendar Implementation Plan</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#79B8FF;font-weight:bold">## Phase 1: Frontend Foundation (Tasks 1-5)</span></span>
<span data-line=""><span style="color:#E1E4E8">├── Task 1: Initialize Next.js Project</span></span>
<span data-line=""><span style="color:#E1E4E8">├── Task 2: Install and Configure shadcn/ui</span></span>
<span data-line=""><span style="color:#E1E4E8">├── Task 3: Configure Firebase</span></span>
<span data-line=""><span style="color:#E1E4E8">├── Task 4: Create Auth Hook</span></span>
<span data-line=""><span style="color:#E1E4E8">└── Task 5: Create Login Page</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#79B8FF;font-weight:bold">## Phase 2: Backend API (Tasks 6-12)</span></span>
<span data-line=""><span style="color:#E1E4E8">├── Task 6: Create Database Models</span></span>
<span data-line=""><span style="color:#E1E4E8">├── Task 7: Create Alembic Migration</span></span>
<span data-line=""><span style="color:#E1E4E8">├── Task 8: Create Pydantic Schemas</span></span>
<span data-line=""><span style="color:#E1E4E8">├── Task 9: Create Members Router with Tests (TDD)</span></span>
<span data-line=""><span style="color:#E1E4E8">├── Task 10: Create Categories Router with Tests (TDD)</span></span>
<span data-line=""><span style="color:#E1E4E8">├── Task 11: Create Events Router with Tests (TDD)</span></span>
<span data-line=""><span style="color:#E1E4E8">└── Task 12: Add Auth Verify Endpoint</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#79B8FF;font-weight:bold">## Phase 3: Calendar UI (Tasks 13-18)</span></span>
<span data-line=""><span style="color:#E1E4E8">├── Task 13: Create API Client</span></span>
<span data-line=""><span style="color:#E1E4E8">├── Task 14: Create Calendar Header Component</span></span>
<span data-line=""><span style="color:#E1E4E8">├── Task 15: Create Month View Component</span></span>
<span data-line=""><span style="color:#E1E4E8">├── Task 16: Create Week and Day View Components</span></span>
<span data-line=""><span style="color:#E1E4E8">├── Task 17: Create Event Dialog Component</span></span>
<span data-line=""><span style="color:#E1E4E8">└── Task 18: Integrate Calendar Components</span></span></code></pre></figure>
<h3 lang="en" id="task-상세-예시">An Example Task in Detail</h3>
<p lang="en">Each task includes specific code and a verification method:</p>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="markdown" data-theme="github-dark"><code data-language="markdown" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#79B8FF;font-weight:bold">### Task 1: Initialize Next.js Project</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#E1E4E8;font-weight:bold">**Command:**</span></span>
<span data-line=""><span style="color:#E1E4E8">npx create-next-app@latest . --typescript --tailwind --eslint \</span></span>
<span data-line=""><span style="color:#E1E4E8">  --app --src-dir --import-alias "@/*" --use-npm</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#E1E4E8;font-weight:bold">**Verification:**</span></span>
<span data-line=""><span style="color:#FFAB70">-</span><span style="color:#E1E4E8"> npm run dev 실행 → localhost:3000 접속 확인</span></span>
<span data-line=""><span style="color:#FFAB70">-</span><span style="color:#E1E4E8"> src/app/page.tsx 존재 확인</span></span></code></pre></figure>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="markdown" data-theme="github-dark"><code data-language="markdown" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#79B8FF;font-weight:bold">### Task 9: Create Members Router with Tests (TDD)</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#E1E4E8;font-weight:bold">**Test First:**</span></span>
<span data-line=""><span style="color:#E1E4E8">def test_get_members_returns_list(client_with_fake_auth):</span></span>
<span data-line=""><span style="color:#E1E4E8">    response = client_with_fake_auth.get("/calendar/members")</span></span>
<span data-line=""><span style="color:#E1E4E8">    assert response.status_code == 200</span></span>
<span data-line=""><span style="color:#E1E4E8">    assert isinstance(response.json(), list)</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#E1E4E8;font-weight:bold">**Implementation:**</span></span>
<span data-line=""><span style="color:#E1E4E8">@router.get("/members", response_model=list[</span><span style="color:#DBEDFF;text-decoration:underline">FamilyMemberResponse</span><span style="color:#E1E4E8">])</span></span>
<span data-line=""><span style="color:#E1E4E8">async def get_members(</span></span>
<span data-line=""><span style="color:#E1E4E8">    service: MemberServiceProtocol = Depends(get_member_service),</span></span>
<span data-line=""><span style="color:#E1E4E8">):</span></span>
<span data-line=""><span style="color:#E1E4E8">    return service.get_all()</span></span></code></pre></figure>
<hr lang="en">
<h2 lang="en" id="phase-3-execute---자동-실행">Phase 3: Execute—Run Automatically</h2>
<h3 lang="en" id="실행-명령">The Execution Command</h3>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="bash" data-theme="github-dark"><code data-language="bash" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#B392F0">/superpowers:execute-plan</span><span style="color:#9ECBFF"> '/path/to/implementation.md'</span></span></code></pre></figure>
<p lang="en">Claude reads the plan and executes its tasks in order.</p>
<h3 lang="en" id="실행-과정">The Execution Process</h3>
<p lang="en"><strong>Task 1 execution log:</strong></p>
<pre><code>⏺ Task 1: Initialize Next.js Project

⏺ Bash(npx create-next-app@latest . --typescript --tailwind ...)
  ⎿  Creating a new Next.js app in /Users/.../calendar.
     Using npm.
     Initializing project with template: app-tw
     Installing dependencies...

⏺ Bash(npm run dev)
  ⎿  ▲ Next.js 16.1.1
     - Local: http://localhost:3000
     ✓ Starting...
     ✓ Ready in 1532ms

✅ Task 1 완료
</code></pre>
<p lang="en"><strong>Task 2 execution log:</strong></p>
<pre><code>⏺ Task 2: Install and Configure shadcn/ui

⏺ Bash(npx shadcn@latest init -y -d)
  ⎿  ✔ Writing components.json
     ✔ Checking registry.
     ✔ Updating tailwind.config.ts

⏺ Bash(npx shadcn@latest add button input label card dialog ...)
  ⎿  ✔ Installing dependencies.
     ✔ Created 8 components.

✅ Task 2 완료
</code></pre>
<h3 lang="en" id="자동-커밋">Automatic Commits</h3>
<p lang="en">Claude commits automatically whenever a task is completed:</p>
<pre><code>2b0aae4 feat: initialize Next.js 16.1.1 project with TypeScript and Tailwind
14277af feat: add shadcn/ui with essential components
ca3ab5f feat: add Firebase configuration (shared with kid-chat)
fbc207d feat: add Firebase auth hook and utilities
</code></pre>
<hr lang="en">
<h2 lang="en" id="결과물">The Result</h2>
<h3 lang="en" id="지원하는-기능">Supported Features</h3>

































<table lang="en"><thead><tr><th>Feature</th><th>Description</th></tr></thead><tbody><tr><td><strong>Multiple views</strong></td><td>Switch between monthly, weekly, and daily views</td></tr><tr><td><strong>Event CRUD</strong></td><td>Create, read, update, and delete</td></tr><tr><td><strong>Recurring events</strong></td><td>Daily, weekly, monthly, yearly, and specific weekdays</td></tr><tr><td><strong>Categories</strong></td><td>Color-based categorization</td></tr><tr><td><strong>Family members</strong></td><td>A different color for each member</td></tr><tr><td><strong>Responsive layout</strong></td><td>Supports mobile, tablet, and desktop</td></tr></tbody></table>
<h3 lang="en" id="기술-스택">Technology Stack</h3>

































<table lang="en"><thead><tr><th>Layer</th><th>Technology</th></tr></thead><tbody><tr><td>Frontend</td><td>Next.js 16.1.1 (App Router)</td></tr><tr><td>UI</td><td>shadcn/ui + Tailwind CSS 4</td></tr><tr><td>Auth</td><td>Firebase Authentication</td></tr><tr><td>Backend</td><td>FastAPI + SQLAlchemy</td></tr><tr><td>Database</td><td>PostgreSQL</td></tr><tr><td>Recurring events</td><td>RRULE (RFC 5545)</td></tr></tbody></table>
<hr lang="en">
<h2 lang="en" id="워크플로우-요약">Workflow Summary</h2>
<pre><code>┌─────────────────────────────────────────────────────────────┐
│  /superpowers:brainstorm                                    │
│  "가족 캘린더를 만들고 싶다"                                  │
│                     ↓                                        │
│  Claude가 질문 → 사용자가 선택 → 설계 문서 자동 생성         │
└─────────────────────────────────────────────────────────────┘
                              ↓
┌─────────────────────────────────────────────────────────────┐
│  /superpowers:write-plan                                    │
│  설계 문서 → 18개 Task로 분해된 구현 플랜                    │
└─────────────────────────────────────────────────────────────┘
                              ↓
┌─────────────────────────────────────────────────────────────┐
│  /superpowers:execute-plan                                  │
│  Task 1 실행 → 커밋 → Task 2 실행 → 커밋 → ... → 완성       │
└─────────────────────────────────────────────────────────────┘
</code></pre>
<hr lang="en">
<h2 lang="en" id="배운-점">What I Learned</h2>
<h3 lang="en" id="1-ai와의-협업은-질문이-핵심이다">1. Questions Are the Key to Collaborating with AI</h3>
<p lang="en">Superpowers' brainstorm does more than transcribe requirements. It <strong>asks the right questions to draw out the user's implicit knowledge</strong>. Questions such as “How large is the family?” and “What is the permission model?” made a clear design possible from the start.</p>
<h3 lang="en" id="2-플랜이-있으면-실행이-쉽다">2. A Plan Makes Execution Easier</h3>
<p lang="en">Breaking the plan into 18 tasks let Claude focus on one thing at a time. Each task was independent and verifiable, so problems could be corrected quickly.</p>
<h3 lang="en" id="3-tdd가-자연스럽게-적용된다">3. TDD Happens Naturally</h3>
<p lang="en">The plan includes a “Test First” section, enforcing the order <strong>write tests → implement → verify</strong>. Tests are naturally written first even when the developer is not consciously applying TDD.</p>
<h3 lang="en" id="4-컨텍스트-유지가-중요하다">4. Preserving Context Matters</h3>
<p lang="en">Claude can forget earlier context in long sessions. Because Superpowers saves design documents and plans as files, <strong>work can resume even after a session is interrupted</strong>.</p>
<hr lang="en">
<h2 lang="en" id="마치며">Closing Thoughts</h2>
<p lang="en">Claude Code and Superpowers greatly shorten the distance from an idea to an implementation.</p>
<ul lang="en">
<li><strong>Brainstorm</strong>: clarify requirements through questions and answers</li>
<li><strong>Plan</strong>: break the work into tasks systematically</li>
<li><strong>Execute</strong>: automatic execution and commits</li>
</ul>
<p lang="en">Even complex projects become much easier when started with this workflow. Try it on your next project.</p>
<hr lang="en">
<h2 lang="en" id="다음-글-기술-구현-상세">Next Post: Implementation Details</h2>
<p lang="en">This post covered the Claude Code and Superpowers workflow. For <strong>the detailed structure of the actual implementation</strong>, see the next post:</p>
<p lang="en">👉 <a href="https://blog.funq.kr/blogs/ai-claude-calendar-tech-20260104-v01">Building a Full-Stack Family Calendar: Recurring Events with FastAPI and Next.js</a></p>
<ul lang="en">
<li>Improve testability with the Protocol pattern</li>
<li>Implement recurring events with the RRULE standard</li>
<li>The backend test pyramid</li>
</ul>
<hr lang="en">
<p lang="en"><strong>Live Demo</strong>: <a href="https://calendar.funq.kr">https://calendar.funq.kr</a></p>
<p lang="en"><em>Leave a comment if you have any questions!</em></p>]]></content:encoded>
    </item>
    <item>
      <title><![CDATA[Development and Testing: A Practical TDD Guide for Full-Stack Developers]]></title>
      <link>https://blog.funq.kr/en/blogs/tdd-20260101-v01</link>
      <guid isPermaLink="true">https://blog.funq.kr/en/blogs/tdd-20260101-v01</guid>
      <description><![CDATA[Practical TDD patterns and test strategies you can apply immediately in FastAPI and Next.js projects.]]></description>
      <category><![CDATA[tdd]]></category><category><![CDATA[testing]]></category><category><![CDATA[fastapi]]></category><category><![CDATA[nextjs]]></category><category><![CDATA[python]]></category>
      <media:content url="https://blog.funq.kr/blog-cover/tdd-20260101-v01.jpg" medium="image" type="image/jpeg"/>
      <pubDate>Thu, 01 Jan 2026 01:00:00 GMT</pubDate>
      <author>funqdev@gmail.com (fundev)</author>
      <content:encoded><![CDATA[<blockquote lang="en">
<p>“I know I should write tests, but I don't know where or how to start.”</p>
</blockquote>
<p lang="en">Many developers face this question. Testing is more than simply “catching bugs.” It is <strong>a cost-effective risk-management mechanism that makes changes fast and safe</strong>.</p>
<p lang="en">This post goes beyond concepts to cover <strong>practical patterns you can apply immediately</strong> in FastAPI and Next.js.</p>
<hr lang="en">
<h2 lang="en" id="1-테스트의-3가지-계층">1. The Three Layers of Testing</h2>
<p lang="en">From a system-architecture perspective, tests fall into three layers.</p>

























<table lang="en"><thead><tr><th>Layer</th><th>Target</th><th>Characteristics</th></tr></thead><tbody><tr><td><strong>Unit tests</strong></td><td>Functions and classes</td><td>Fast; easy to locate problems</td></tr><tr><td><strong>Integration tests</strong></td><td>Interactions between modules</td><td>API + DB, component + API</td></tr><tr><td><strong>E2E tests</strong></td><td>The whole system</td><td>Verify user scenarios; slow</td></tr></tbody></table>
<p lang="en">In addition, <strong>static analysis</strong> with TypeScript, ESLint, or mypy catches problems without running the code. It prevents many bugs before tests are written, offering the best return for the cost.</p>
<h3 lang="en" id="각-계층의-트레이드오프">Trade-offs Between Layers</h3>



































<table lang="en"><thead><tr><th>Characteristic</th><th>Unit</th><th>Integration</th><th>E2E</th></tr></thead><tbody><tr><td>Execution speed</td><td>Fast</td><td>Medium</td><td>Slow</td></tr><tr><td>Maintenance cost</td><td>Low</td><td>Medium</td><td>High</td></tr><tr><td>Diagnosing failures</td><td>Easy</td><td>Medium</td><td>Difficult</td></tr><tr><td>Fidelity to real behavior</td><td>Low</td><td>Medium</td><td>High</td></tr></tbody></table>
<p lang="en"><strong>When an E2E test fails</strong>, you know something is wrong but may struggle to pinpoint it. <strong>Unit tests</strong> identify the failure point clearly, making fixes faster.</p>
<hr lang="en">
<h2 lang="en" id="2-피라미드-vs-트로피-어떤-전략을-선택할까">2. Pyramid vs. Trophy: Which Strategy Should You Choose?</h2>
<p lang="en">There are two philosophies for deciding how many tests to write and where to put them.</p>
<h3 lang="en" id="테스트-피라미드">The Testing Pyramid</h3>
<pre><code>        /\
       /  \      E2E (적게)
      /────\
     /      \    Integration (중간)
    /────────\
   /          \  Unit (많이)
  ──────────────
</code></pre>
<p lang="en"><strong>The idea</strong>: if the small units are verified thoroughly, the whole system will work.</p>
<h3 lang="en" id="테스트-트로피">The Testing Trophy</h3>
<pre><code>       /\           E2E
      /  \
     /────\
    /      \
   / Integration \  ← 가장 두꺼움
  /───────────────\
  │     Unit      │
  │───────────────│
  │    Static     │
  ─────────────────
</code></pre>
<p lang="en"><strong>The idea</strong>: tests are trustworthy when they behave like users.</p>
<p lang="en">Kent C. Dodds proposed this in the frontend context. Testing components in isolation can make tests less representative of reality.</p>
<h3 lang="en" id="실무-선택-기준">Practical Selection Criteria</h3>

























<table lang="en"><thead><tr><th>Category</th><th>Pyramid</th><th>Trophy</th></tr></thead><tbody><tr><td><strong>Core value</strong></td><td>Fast feedback</td><td>High confidence</td></tr><tr><td><strong>Largest share</strong></td><td>Unit</td><td>Integration</td></tr><tr><td><strong>Best suited to</strong></td><td>Complex business logic (backend)</td><td>UI and state management (frontend)</td></tr></tbody></table>
<p lang="en"><strong>Conclusion</strong>: use the pyramid for the backend and the trophy for the frontend.</p>
<hr lang="en">
<h2 lang="en" id="3-tdd-red-green-refactor">3. TDD: Red-Green-Refactor</h2>
<p lang="en">TDD is not “write lots of tests.” It is a development method that <strong>first fixes the requirements as an executable specification</strong>.</p>
<pre><code>Red (실패) → Green (통과) → Refactor (개선) → 반복
</code></pre>
<ol lang="en">
<li><strong>Red</strong>: write a failing test first.</li>
<li><strong>Green</strong>: write the <strong>minimum</strong> code needed to pass it.</li>
<li><strong>Refactor</strong>: improve the structure while keeping tests passing.</li>
</ol>
<h3 lang="en" id="tdd의-진짜-가치">The Real Value of TDD</h3>
<ul lang="en">
<li>Requirements are fixed as an executable specification</li>
<li>Refactoring becomes safer</li>
<li>It naturally enforces <strong>separation of boundaries</strong> through dependency injection and interface separation</li>
</ul>
<p lang="en">The key is **“minimum.”** During Green, do not try to make the code beautiful. Just make the test pass.</p>
<hr lang="en">
<h2 lang="en" id="4-백엔드-tdd-실무-패턴">4. Practical Backend TDD Patterns</h2>
<p lang="en">Features with many external integrations and DB operations need clear layers for TDD to work.</p>
<h3 lang="en" id="의존성-흐름-dependency-flow">Dependency Flow</h3>
<pre><code>  ┌──────────────┐      ┌──────────────┐      ┌──────────────┐
  │   Router     │ ───▶ │   Protocol   │ ◀─── │    Fake      │
  │  (ai.py)     │      │ (인터페이스)    │      │  (테스트용)    │
  └──────────────┘      └──────────────┘      └──────────────┘
                               │
                               ▼
                        ┌──────────────┐
                        │   Service    │
                        │   (구현체)    │
                        └──────────────┘
                               │
                               ▼
                        ┌──────────────┐
                        │   External   │
                        │  (DB/API)    │
                        └──────────────┘
</code></pre>



































<table lang="en"><thead><tr><th>Layer</th><th>Role</th><th>Test strategy</th></tr></thead><tbody><tr><td>Routers</td><td>HTTP requests and responses</td><td>Integration (Fake DI)</td></tr><tr><td>Dependencies</td><td>Protocol + DI</td><td>Unit (inject a Fake)</td></tr><tr><td>Services</td><td>Business logic</td><td>Unit (Protocol mock)</td></tr><tr><td>External</td><td>DB/Firebase integration</td><td>E2E (real services)</td></tr><tr><td>Fakes</td><td>Implementations for Testing</td><td>-</td></tr></tbody></table>
<h3 lang="en" id="test-double-stub-fake-mock">Test Double: Stub, Fake, Mock</h3>
<p lang="en">A substitute object used in place of a real one during testing is called a <strong>Test Double</strong>.</p>
<h4 lang="en" id="stub--정해진-값만-반환">Stub—returns predefined values only</h4>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="python" data-theme="github-dark"><code data-language="python" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#F97583">class</span><span style="color:#B392F0"> StubProductRepo</span><span style="color:#E1E4E8">:</span></span>
<span data-line=""><span style="color:#F97583">    def</span><span style="color:#B392F0"> get_by_id</span><span style="color:#E1E4E8">(self, id):</span></span>
<span data-line=""><span style="color:#F97583">        return</span><span style="color:#E1E4E8"> Product(</span><span style="color:#FFAB70">id</span><span style="color:#F97583">=</span><span style="color:#79B8FF">1</span><span style="color:#E1E4E8">, </span><span style="color:#FFAB70">price</span><span style="color:#F97583">=</span><span style="color:#79B8FF">10000</span><span style="color:#E1E4E8">)  </span><span style="color:#6A737D"># 항상 같은 값</span></span></code></pre></figure>
<h4 lang="en" id="fake--실제처럼-동작하는-간단한-구현체">Fake—a simple implementation that behaves like the real thing</h4>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="python" data-theme="github-dark"><code data-language="python" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#F97583">class</span><span style="color:#B392F0"> FakeUserRepo</span><span style="color:#E1E4E8">:</span></span>
<span data-line=""><span style="color:#F97583">    def</span><span style="color:#79B8FF"> __init__</span><span style="color:#E1E4E8">(self):</span></span>
<span data-line=""><span style="color:#79B8FF">        self</span><span style="color:#E1E4E8">.users </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> {}</span></span>
<span data-line=""><span style="color:#79B8FF">        self</span><span style="color:#E1E4E8">.next_id </span><span style="color:#F97583">=</span><span style="color:#79B8FF"> 1</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F97583">    async</span><span style="color:#F97583"> def</span><span style="color:#B392F0"> save</span><span style="color:#E1E4E8">(self, user):</span></span>
<span data-line=""><span style="color:#E1E4E8">        user.id </span><span style="color:#F97583">=</span><span style="color:#79B8FF"> self</span><span style="color:#E1E4E8">.next_id</span></span>
<span data-line=""><span style="color:#79B8FF">        self</span><span style="color:#E1E4E8">.users[</span><span style="color:#79B8FF">self</span><span style="color:#E1E4E8">.next_id] </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> user</span></span>
<span data-line=""><span style="color:#79B8FF">        self</span><span style="color:#E1E4E8">.next_id </span><span style="color:#F97583">+=</span><span style="color:#79B8FF"> 1</span></span>
<span data-line=""><span style="color:#F97583">        return</span><span style="color:#E1E4E8"> user</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F97583">    async</span><span style="color:#F97583"> def</span><span style="color:#B392F0"> get_by_id</span><span style="color:#E1E4E8">(self, id):</span></span>
<span data-line=""><span style="color:#F97583">        return</span><span style="color:#79B8FF"> self</span><span style="color:#E1E4E8">.users.get(</span><span style="color:#79B8FF">id</span><span style="color:#E1E4E8">)</span></span></code></pre></figure>
<h4 lang="en" id="mock--호출-여부를-검증">Mock—verifies whether calls occurred</h4>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="python" data-theme="github-dark"><code data-language="python" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#F97583">def</span><span style="color:#B392F0"> test_주문시_이메일_발송</span><span style="color:#E1E4E8">():</span></span>
<span data-line=""><span style="color:#E1E4E8">    mock_email </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> Mock()</span></span>
<span data-line=""><span style="color:#E1E4E8">    service </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> OrderService(</span><span style="color:#FFAB70">email_service</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">mock_email)</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#E1E4E8">    service.create_order(</span><span style="color:#FFAB70">user_email</span><span style="color:#F97583">=</span><span style="color:#9ECBFF">"test@test.com"</span><span style="color:#E1E4E8">)</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#E1E4E8">    mock_email.send.assert_called_once()</span></span></code></pre></figure>
<h4 lang="en" id="선택-기준">How to Choose</h4>





















<table lang="en"><thead><tr><th>Situation</th><th>Choice</th></tr></thead><tbody><tr><td>If you only need a value</td><td><strong>Stub</strong></td></tr><tr><td>If you need save and retrieve behavior</td><td><strong>Fake</strong></td></tr><tr><td>If you need to verify a call</td><td><strong>Mock</strong></td></tr></tbody></table>
<blockquote lang="en">
<p><strong>Important</strong>: a Fake does not exist to replicate the DB. It only needs to satisfy the repository contract—the meaning of its methods. Verify DB constraints and transactions <strong>against a real DB in integration tests</strong>.</p>
</blockquote>
<h3 lang="en" id="protocol로-인터페이스-정의하기">Defining Interfaces with Protocol</h3>
<p lang="en">Python's <code>typing.Protocol</code> lets you define interfaces without explicit inheritance.</p>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="python" data-theme="github-dark"><code data-language="python" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#6A737D"># app/dependencies/protocol.py</span></span>
<span data-line=""><span style="color:#F97583">from</span><span style="color:#E1E4E8"> typing </span><span style="color:#F97583">import</span><span style="color:#E1E4E8"> Protocol, Optional</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F97583">class</span><span style="color:#B392F0"> UserRepository</span><span style="color:#E1E4E8">(</span><span style="color:#B392F0">Protocol</span><span style="color:#E1E4E8">):</span></span>
<span data-line=""><span style="color:#F97583">    async</span><span style="color:#F97583"> def</span><span style="color:#B392F0"> get_by_id</span><span style="color:#E1E4E8">(self, user_id: </span><span style="color:#79B8FF">int</span><span style="color:#E1E4E8">) -> Optional[User]:</span></span>
<span data-line=""><span style="color:#79B8FF">        ...</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F97583">    async</span><span style="color:#F97583"> def</span><span style="color:#B392F0"> save</span><span style="color:#E1E4E8">(self, user: User) -> User:</span></span>
<span data-line=""><span style="color:#79B8FF">        ...</span></span></code></pre></figure>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="python" data-theme="github-dark"><code data-language="python" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#6A737D"># app/external/repositories.py (실제 구현)</span></span>
<span data-line=""><span style="color:#F97583">class</span><span style="color:#B392F0"> SqlAlchemyUserRepository</span><span style="color:#E1E4E8">:</span></span>
<span data-line=""><span style="color:#F97583">    def</span><span style="color:#79B8FF"> __init__</span><span style="color:#E1E4E8">(self, session: AsyncSession):</span></span>
<span data-line=""><span style="color:#79B8FF">        self</span><span style="color:#E1E4E8">.session </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> session</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F97583">    async</span><span style="color:#F97583"> def</span><span style="color:#B392F0"> get_by_id</span><span style="color:#E1E4E8">(self, user_id: </span><span style="color:#79B8FF">int</span><span style="color:#E1E4E8">) -> Optional[User]:</span></span>
<span data-line=""><span style="color:#E1E4E8">        result </span><span style="color:#F97583">=</span><span style="color:#F97583"> await</span><span style="color:#79B8FF"> self</span><span style="color:#E1E4E8">.session.execute(</span></span>
<span data-line=""><span style="color:#E1E4E8">            select(User).where(User.id </span><span style="color:#F97583">==</span><span style="color:#E1E4E8"> user_id)</span></span>
<span data-line=""><span style="color:#E1E4E8">        )</span></span>
<span data-line=""><span style="color:#F97583">        return</span><span style="color:#E1E4E8"> result.scalars().first()</span></span></code></pre></figure>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="python" data-theme="github-dark"><code data-language="python" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#6A737D"># tests/fakes/fake_user_repo.py (테스트용)</span></span>
<span data-line=""><span style="color:#F97583">class</span><span style="color:#B392F0"> FakeUserRepository</span><span style="color:#E1E4E8">:</span></span>
<span data-line=""><span style="color:#F97583">    def</span><span style="color:#79B8FF"> __init__</span><span style="color:#E1E4E8">(self):</span></span>
<span data-line=""><span style="color:#79B8FF">        self</span><span style="color:#E1E4E8">._storage </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> {}</span></span>
<span data-line=""><span style="color:#79B8FF">        self</span><span style="color:#E1E4E8">._current_id </span><span style="color:#F97583">=</span><span style="color:#79B8FF"> 0</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F97583">    async</span><span style="color:#F97583"> def</span><span style="color:#B392F0"> get_by_id</span><span style="color:#E1E4E8">(self, user_id: </span><span style="color:#79B8FF">int</span><span style="color:#E1E4E8">) -> Optional[User]:</span></span>
<span data-line=""><span style="color:#F97583">        return</span><span style="color:#79B8FF"> self</span><span style="color:#E1E4E8">._storage.get(user_id)</span></span></code></pre></figure>
<h3 lang="en" id="dependency_overrides로-테스트하기">Testing with dependency_overrides</h3>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="python" data-theme="github-dark"><code data-language="python" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#6A737D"># 프로덕션 코드</span></span>
<span data-line=""><span style="color:#F97583">def</span><span style="color:#B392F0"> get_user_repository</span><span style="color:#E1E4E8">():</span></span>
<span data-line=""><span style="color:#F97583">    return</span><span style="color:#E1E4E8"> SqlAlchemyUserRepository(session)</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#B392F0">@app.post</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">"/users"</span><span style="color:#E1E4E8">)</span></span>
<span data-line=""><span style="color:#F97583">async</span><span style="color:#F97583"> def</span><span style="color:#B392F0"> create_user</span><span style="color:#E1E4E8">(</span></span>
<span data-line=""><span style="color:#E1E4E8">    data: UserCreate,</span></span>
<span data-line=""><span style="color:#E1E4E8">    repo: UserRepository </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> Depends(get_user_repository)</span></span>
<span data-line=""><span style="color:#E1E4E8">):</span></span>
<span data-line=""><span style="color:#6A737D">    # ...</span></span></code></pre></figure>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="python" data-theme="github-dark"><code data-language="python" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#6A737D"># 테스트 코드</span></span>
<span data-line=""><span style="color:#B392F0">@pytest.mark.asyncio</span></span>
<span data-line=""><span style="color:#F97583">async</span><span style="color:#F97583"> def</span><span style="color:#B392F0"> test_create_user</span><span style="color:#E1E4E8">():</span></span>
<span data-line=""><span style="color:#E1E4E8">    fake_repo </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> FakeUserRepository()</span></span>
<span data-line=""><span style="color:#E1E4E8">    app.dependency_overrides[get_user_repository] </span><span style="color:#F97583">=</span><span style="color:#F97583"> lambda</span><span style="color:#E1E4E8">: fake_repo</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F97583">    async</span><span style="color:#F97583"> with</span><span style="color:#E1E4E8"> AsyncClient(</span><span style="color:#FFAB70">app</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">app, </span><span style="color:#FFAB70">base_url</span><span style="color:#F97583">=</span><span style="color:#9ECBFF">"http://test"</span><span style="color:#E1E4E8">) </span><span style="color:#F97583">as</span><span style="color:#E1E4E8"> client:</span></span>
<span data-line=""><span style="color:#E1E4E8">        response </span><span style="color:#F97583">=</span><span style="color:#F97583"> await</span><span style="color:#E1E4E8"> client.post(</span><span style="color:#9ECBFF">"/users"</span><span style="color:#E1E4E8">, </span><span style="color:#FFAB70">json</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">{</span><span style="color:#9ECBFF">"email"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"test@test.com"</span><span style="color:#E1E4E8">})</span></span>
<span data-line=""><span style="color:#F97583">        assert</span><span style="color:#E1E4E8"> response.status_code </span><span style="color:#F97583">==</span><span style="color:#79B8FF"> 201</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#6A737D">        # Fake 내부 상태 직접 검증 가능</span></span>
<span data-line=""><span style="color:#E1E4E8">        saved_user </span><span style="color:#F97583">=</span><span style="color:#F97583"> await</span><span style="color:#E1E4E8"> fake_repo.get_by_id(</span><span style="color:#79B8FF">1</span><span style="color:#E1E4E8">)</span></span>
<span data-line=""><span style="color:#F97583">        assert</span><span style="color:#E1E4E8"> saved_user.email </span><span style="color:#F97583">==</span><span style="color:#9ECBFF"> "test@test.com"</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#E1E4E8">    app.dependency_overrides.clear()</span></span></code></pre></figure>
<h3 lang="en" id="모든-상태가-아닌-모든-규칙을-테스트">Test Every Rule, Not Every State</h3>
<p lang="en">If an order has 10 states, must you test all 100 combinations (10×10)? <strong>No.</strong></p>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="python" data-theme="github-dark"><code data-language="python" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#6A737D"># 상태 전이 규칙을 한 곳에 정의</span></span>
<span data-line=""><span style="color:#79B8FF">VALID_TRANSITIONS</span><span style="color:#F97583"> =</span><span style="color:#E1E4E8"> {</span></span>
<span data-line=""><span style="color:#9ECBFF">    "pending"</span><span style="color:#E1E4E8">: [</span><span style="color:#9ECBFF">"approved"</span><span style="color:#E1E4E8">, </span><span style="color:#9ECBFF">"rejected"</span><span style="color:#E1E4E8">],</span></span>
<span data-line=""><span style="color:#9ECBFF">    "approved"</span><span style="color:#E1E4E8">: [</span><span style="color:#9ECBFF">"shipped"</span><span style="color:#E1E4E8">, </span><span style="color:#9ECBFF">"cancelled"</span><span style="color:#E1E4E8">],</span></span>
<span data-line=""><span style="color:#9ECBFF">    "shipped"</span><span style="color:#E1E4E8">: [</span><span style="color:#9ECBFF">"delivered"</span><span style="color:#E1E4E8">],</span></span>
<span data-line=""><span style="color:#E1E4E8">}</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#6A737D"># 허용된 전이만 테스트</span></span>
<span data-line=""><span style="color:#B392F0">@pytest.mark.parametrize</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">"from_status,to_status"</span><span style="color:#E1E4E8">, [</span></span>
<span data-line=""><span style="color:#E1E4E8">    (</span><span style="color:#9ECBFF">"pending"</span><span style="color:#E1E4E8">, </span><span style="color:#9ECBFF">"approved"</span><span style="color:#E1E4E8">),</span></span>
<span data-line=""><span style="color:#E1E4E8">    (</span><span style="color:#9ECBFF">"approved"</span><span style="color:#E1E4E8">, </span><span style="color:#9ECBFF">"shipped"</span><span style="color:#E1E4E8">),</span></span>
<span data-line=""><span style="color:#E1E4E8">])</span></span>
<span data-line=""><span style="color:#F97583">def</span><span style="color:#B392F0"> test_유효한_상태_전이</span><span style="color:#E1E4E8">(from_status, to_status):</span></span>
<span data-line=""><span style="color:#E1E4E8">    order </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> Order(</span><span style="color:#FFAB70">status</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">from_status)</span></span>
<span data-line=""><span style="color:#E1E4E8">    order.transition_to(to_status)</span></span>
<span data-line=""><span style="color:#F97583">    assert</span><span style="color:#E1E4E8"> order.status </span><span style="color:#F97583">==</span><span style="color:#E1E4E8"> to_status</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#6A737D"># 대표적인 불가능한 전이만 테스트</span></span>
<span data-line=""><span style="color:#F97583">def</span><span style="color:#B392F0"> test_배송완료_후_취소_불가</span><span style="color:#E1E4E8">():</span></span>
<span data-line=""><span style="color:#E1E4E8">    order </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> Order(</span><span style="color:#FFAB70">status</span><span style="color:#F97583">=</span><span style="color:#9ECBFF">"delivered"</span><span style="color:#E1E4E8">)</span></span>
<span data-line=""><span style="color:#F97583">    with</span><span style="color:#E1E4E8"> pytest.raises(InvalidTransitionError):</span></span>
<span data-line=""><span style="color:#E1E4E8">        order.transition_to(</span><span style="color:#9ECBFF">"cancelled"</span><span style="color:#E1E4E8">)</span></span></code></pre></figure>
<p lang="en">Even as states increase, you only need to <strong>update the rule table</strong>.</p>
<hr lang="en">
<h2 lang="en" id="5-프론트엔드-tdd-솔직한-현실과-효과적인-영역">5. Frontend TDD: The Honest Reality and Where It Works</h2>
<h3 lang="en" id="솔직한-현실">The Honest Reality</h3>
<p lang="en">TDD is not always effective on the frontend.</p>
<ul lang="en">
<li>UI development is exploratory and changes continually with design feedback.</li>
<li>“How it looks” is difficult to test.</li>
<li>The browser provides faster feedback.</li>
</ul>
<h3 lang="en" id="tdd가-효과적인-영역">Where TDD Is Effective</h3>
<pre><code>TDD 적용 ✓            Test-After ✓          테스트 안 함 ✓
──────────────        ──────────────        ──────────────
• 유틸리티 함수        • UI 컴포넌트          • 단순 프레젠테이션
• 커스텀 훅           • 탐색적 개발          • 스타일만 있는 컴포넌트
• 버그 수정           • 자주 바뀌는 부분
</code></pre>
<h3 lang="en" id="msw로-api-모킹">Mocking APIs with MSW</h3>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="typescript" data-theme="github-dark"><code data-language="typescript" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#6A737D">// mocks/handlers.ts</span></span>
<span data-line=""><span style="color:#F97583">import</span><span style="color:#E1E4E8"> { http, HttpResponse } </span><span style="color:#F97583">from</span><span style="color:#9ECBFF"> 'msw'</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F97583">export</span><span style="color:#F97583"> const</span><span style="color:#79B8FF"> handlers</span><span style="color:#F97583"> =</span><span style="color:#E1E4E8"> [</span></span>
<span data-line=""><span style="color:#E1E4E8">  http.</span><span style="color:#B392F0">post</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">'/api/login'</span><span style="color:#E1E4E8">, </span><span style="color:#F97583">async</span><span style="color:#E1E4E8"> ({ </span><span style="color:#FFAB70">request</span><span style="color:#E1E4E8"> }) </span><span style="color:#F97583">=></span><span style="color:#E1E4E8"> {</span></span>
<span data-line=""><span style="color:#F97583">    const</span><span style="color:#79B8FF"> body</span><span style="color:#F97583"> =</span><span style="color:#F97583"> await</span><span style="color:#E1E4E8"> request.</span><span style="color:#B392F0">json</span><span style="color:#E1E4E8">()</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F97583">    if</span><span style="color:#E1E4E8"> (body.email </span><span style="color:#F97583">===</span><span style="color:#9ECBFF"> 'test@test.com'</span><span style="color:#E1E4E8">) {</span></span>
<span data-line=""><span style="color:#F97583">      return</span><span style="color:#E1E4E8"> HttpResponse.</span><span style="color:#B392F0">json</span><span style="color:#E1E4E8">({ token: </span><span style="color:#9ECBFF">'fake-token'</span><span style="color:#E1E4E8"> })</span></span>
<span data-line=""><span style="color:#E1E4E8">    }</span></span>
<span data-line=""><span style="color:#F97583">    return</span><span style="color:#E1E4E8"> HttpResponse.</span><span style="color:#B392F0">json</span><span style="color:#E1E4E8">({ error: </span><span style="color:#9ECBFF">'Invalid'</span><span style="color:#E1E4E8"> }, { status: </span><span style="color:#79B8FF">401</span><span style="color:#E1E4E8"> })</span></span>
<span data-line=""><span style="color:#E1E4E8">  })</span></span>
<span data-line=""><span style="color:#E1E4E8">]</span></span></code></pre></figure>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="typescript" data-theme="github-dark"><code data-language="typescript" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#6A737D">// LoginForm.test.tsx</span></span>
<span data-line=""><span style="color:#B392F0">test</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">'로그인 성공 시 대시보드로 이동'</span><span style="color:#E1E4E8">, </span><span style="color:#F97583">async</span><span style="color:#E1E4E8"> () </span><span style="color:#F97583">=></span><span style="color:#E1E4E8"> {</span></span>
<span data-line=""><span style="color:#B392F0">  render</span><span style="color:#E1E4E8">(&#x3C;</span><span style="color:#B392F0">LoginForm</span><span style="color:#E1E4E8"> />)</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F97583">  await</span><span style="color:#E1E4E8"> userEvent.</span><span style="color:#B392F0">type</span><span style="color:#E1E4E8">(screen.</span><span style="color:#B392F0">getByLabelText</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">'이메일'</span><span style="color:#E1E4E8">), </span><span style="color:#9ECBFF">'test@test.com'</span><span style="color:#E1E4E8">)</span></span>
<span data-line=""><span style="color:#F97583">  await</span><span style="color:#E1E4E8"> userEvent.</span><span style="color:#B392F0">type</span><span style="color:#E1E4E8">(screen.</span><span style="color:#B392F0">getByLabelText</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">'비밀번호'</span><span style="color:#E1E4E8">), </span><span style="color:#9ECBFF">'password'</span><span style="color:#E1E4E8">)</span></span>
<span data-line=""><span style="color:#F97583">  await</span><span style="color:#E1E4E8"> userEvent.</span><span style="color:#B392F0">click</span><span style="color:#E1E4E8">(screen.</span><span style="color:#B392F0">getByRole</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">'button'</span><span style="color:#E1E4E8">, { name: </span><span style="color:#9ECBFF">'로그인'</span><span style="color:#E1E4E8"> }))</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F97583">  await</span><span style="color:#B392F0"> waitFor</span><span style="color:#E1E4E8">(() </span><span style="color:#F97583">=></span><span style="color:#E1E4E8"> {</span></span>
<span data-line=""><span style="color:#B392F0">    expect</span><span style="color:#E1E4E8">(mockRouter.push).</span><span style="color:#B392F0">toHaveBeenCalledWith</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">'/dashboard'</span><span style="color:#E1E4E8">)</span></span>
<span data-line=""><span style="color:#E1E4E8">  })</span></span>
<span data-line=""><span style="color:#E1E4E8">})</span></span></code></pre></figure>
<h3 lang="en" id="testing-library-핵심-원칙">The Core Principle of Testing Library</h3>
<p lang="en"><strong>Test like a user.</strong></p>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="typescript" data-theme="github-dark"><code data-language="typescript" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#6A737D">// ❌ 구현 세부사항 테스트</span></span>
<span data-line=""><span style="color:#B392F0">expect</span><span style="color:#E1E4E8">(component.state.isLoading).</span><span style="color:#B392F0">toBe</span><span style="color:#E1E4E8">(</span><span style="color:#79B8FF">true</span><span style="color:#E1E4E8">)</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#6A737D">// ✅ 사용자가 보는 것 테스트</span></span>
<span data-line=""><span style="color:#B392F0">expect</span><span style="color:#E1E4E8">(screen.</span><span style="color:#B392F0">getByText</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">'로딩 중...'</span><span style="color:#E1E4E8">)).</span><span style="color:#B392F0">toBeInTheDocument</span><span style="color:#E1E4E8">()</span></span></code></pre></figure>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="typescript" data-theme="github-dark"><code data-language="typescript" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#6A737D">// ❌ 내부 함수 호출 테스트</span></span>
<span data-line=""><span style="color:#B392F0">expect</span><span style="color:#E1E4E8">(handleClick).</span><span style="color:#B392F0">toHaveBeenCalled</span><span style="color:#E1E4E8">()</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#6A737D">// ✅ 결과 테스트</span></span>
<span data-line=""><span style="color:#B392F0">expect</span><span style="color:#E1E4E8">(screen.</span><span style="color:#B392F0">getByText</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">'1'</span><span style="color:#E1E4E8">)).</span><span style="color:#B392F0">toBeInTheDocument</span><span style="color:#E1E4E8">()  </span><span style="color:#6A737D">// 카운트 증가 확인</span></span></code></pre></figure>
<h3 lang="en" id="nextjs-15-환경에서-msw-설정">Configuring MSW in Next.js 15</h3>
<p lang="en"><strong>Server (instrumentation.ts)</strong>:</p>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="typescript" data-theme="github-dark"><code data-language="typescript" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#F97583">export</span><span style="color:#F97583"> async</span><span style="color:#F97583"> function</span><span style="color:#B392F0"> register</span><span style="color:#E1E4E8">() {</span></span>
<span data-line=""><span style="color:#F97583">  if</span><span style="color:#E1E4E8"> (process.env.</span><span style="color:#79B8FF">NEXT_PUBLIC_API_MOCKING</span><span style="color:#F97583"> ===</span><span style="color:#9ECBFF"> 'enabled'</span><span style="color:#E1E4E8">) {</span></span>
<span data-line=""><span style="color:#F97583">    if</span><span style="color:#E1E4E8"> (process.env.</span><span style="color:#79B8FF">NEXT_RUNTIME</span><span style="color:#F97583"> ===</span><span style="color:#9ECBFF"> 'nodejs'</span><span style="color:#E1E4E8">) {</span></span>
<span data-line=""><span style="color:#F97583">      const</span><span style="color:#E1E4E8"> { </span><span style="color:#79B8FF">server</span><span style="color:#E1E4E8"> } </span><span style="color:#F97583">=</span><span style="color:#F97583"> await</span><span style="color:#F97583"> import</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">'./mocks/server'</span><span style="color:#E1E4E8">)</span></span>
<span data-line=""><span style="color:#E1E4E8">      server.</span><span style="color:#B392F0">listen</span><span style="color:#E1E4E8">({ onUnhandledRequest: </span><span style="color:#9ECBFF">'bypass'</span><span style="color:#E1E4E8"> })</span></span>
<span data-line=""><span style="color:#E1E4E8">    }</span></span>
<span data-line=""><span style="color:#E1E4E8">  }</span></span>
<span data-line=""><span style="color:#E1E4E8">}</span></span></code></pre></figure>
<p lang="en"><strong>Client (MSWProvider)</strong>:</p>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="tsx" data-theme="github-dark"><code data-language="tsx" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#9ECBFF">'use client'</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F97583">import</span><span style="color:#E1E4E8"> { useEffect, useState } </span><span style="color:#F97583">from</span><span style="color:#9ECBFF"> 'react'</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F97583">export</span><span style="color:#F97583"> function</span><span style="color:#B392F0"> MSWProvider</span><span style="color:#E1E4E8">({ </span><span style="color:#FFAB70">children</span><span style="color:#E1E4E8"> }</span><span style="color:#F97583">:</span><span style="color:#E1E4E8"> { </span><span style="color:#FFAB70">children</span><span style="color:#F97583">:</span><span style="color:#B392F0"> React</span><span style="color:#E1E4E8">.</span><span style="color:#B392F0">ReactNode</span><span style="color:#E1E4E8"> }) {</span></span>
<span data-line=""><span style="color:#F97583">  const</span><span style="color:#E1E4E8"> [</span><span style="color:#79B8FF">ready</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">setReady</span><span style="color:#E1E4E8">] </span><span style="color:#F97583">=</span><span style="color:#B392F0"> useState</span><span style="color:#E1E4E8">(</span><span style="color:#79B8FF">false</span><span style="color:#E1E4E8">)</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#B392F0">  useEffect</span><span style="color:#E1E4E8">(() </span><span style="color:#F97583">=></span><span style="color:#E1E4E8"> {</span></span>
<span data-line=""><span style="color:#F97583">    const</span><span style="color:#B392F0"> init</span><span style="color:#F97583"> =</span><span style="color:#F97583"> async</span><span style="color:#E1E4E8"> () </span><span style="color:#F97583">=></span><span style="color:#E1E4E8"> {</span></span>
<span data-line=""><span style="color:#F97583">      if</span><span style="color:#E1E4E8"> (process.env.</span><span style="color:#79B8FF">NEXT_PUBLIC_API_MOCKING</span><span style="color:#F97583"> ===</span><span style="color:#9ECBFF"> 'enabled'</span><span style="color:#E1E4E8">) {</span></span>
<span data-line=""><span style="color:#F97583">        const</span><span style="color:#E1E4E8"> { </span><span style="color:#79B8FF">worker</span><span style="color:#E1E4E8"> } </span><span style="color:#F97583">=</span><span style="color:#F97583"> await</span><span style="color:#F97583"> import</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">'@/mocks/browser'</span><span style="color:#E1E4E8">)</span></span>
<span data-line=""><span style="color:#F97583">        await</span><span style="color:#E1E4E8"> worker.</span><span style="color:#B392F0">start</span><span style="color:#E1E4E8">({ onUnhandledRequest: </span><span style="color:#9ECBFF">'bypass'</span><span style="color:#E1E4E8"> })</span></span>
<span data-line=""><span style="color:#E1E4E8">      }</span></span>
<span data-line=""><span style="color:#B392F0">      setReady</span><span style="color:#E1E4E8">(</span><span style="color:#79B8FF">true</span><span style="color:#E1E4E8">)</span></span>
<span data-line=""><span style="color:#E1E4E8">    }</span></span>
<span data-line=""><span style="color:#B392F0">    init</span><span style="color:#E1E4E8">()</span></span>
<span data-line=""><span style="color:#E1E4E8">  }, [])</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F97583">  if</span><span style="color:#E1E4E8"> (</span><span style="color:#F97583">!</span><span style="color:#E1E4E8">ready) </span><span style="color:#F97583">return</span><span style="color:#79B8FF"> null</span></span>
<span data-line=""><span style="color:#F97583">  return</span><span style="color:#E1E4E8"> &#x3C;>{children}&#x3C;/></span></span>
<span data-line=""><span style="color:#E1E4E8">}</span></span></code></pre></figure>
<hr lang="en">
<h2 lang="en" id="6-데이터와-환경-전략">6. Data and Environment Strategy</h2>
<p lang="en">In full-stack development, tests usually fall apart because of <strong>data and environments</strong>, rather than code.</p>
<h3 lang="en" id="기반-데이터를-2종류로-분리">Separate Base Data into Two Types</h3>




















<table lang="en"><thead><tr><th>Type</th><th>Description</th><th>How to manage it</th></tr></thead><tbody><tr><td><strong>Reference Seed</strong></td><td>Lookup data such as status and permission codes</td><td>Load the same versioned seeds locally and in CI</td></tr><tr><td><strong>Business Data</strong></td><td>Business data such as users and products</td><td>Generate with factories or fixtures for each test</td></tr></tbody></table>
<h3 lang="en" id="환경별-전략">Strategy by Environment</h3>

























<table lang="en"><thead><tr><th>Environment</th><th>Strategy</th></tr></thead><tbody><tr><td><strong>Local</strong></td><td>Fast tests (external stubs, local DB) + Static analysis</td></tr><tr><td><strong>CI (PR)</strong></td><td>Unit tests + Short integration tests + Essential smoke tests only</td></tr><tr><td><strong>Nightly/QA</strong></td><td>Heavy integration tests + Core E2E tests</td></tr><tr><td><strong>Production</strong></td><td>Observability, gradual rollout, and rollback strategies rather than tests</td></tr></tbody></table>
<hr lang="en">
<h2 lang="en" id="7-피해야-할-안티패턴-3가지">7. Three Antipatterns to Avoid</h2>
<h3 lang="en" id="1-e2e만-잔뜩-아이스크림-콘">1. Too Many E2E Tests (The Ice Cream Cone)</h3>
<ul lang="en">
<li>Slow and flaky</li>
<li>A nightmare to diagnose</li>
<li>Declining trust in CI</li>
</ul>
<h3 lang="en" id="2-mock-heavy-테스트">2. Mock-Heavy Tests</h3>
<ul lang="en">
<li>Testing the <strong>implementation</strong> means every refactor also requires test changes</li>
<li>Tests become a burden rather than an asset</li>
</ul>
<h3 lang="en" id="3-로컬이-devqa-db를-공유">3. Local Development Shares a dev/qa Database</h3>
<ul lang="en">
<li>Development mistakes corrupt data</li>
<li>Test reproducibility breaks down</li>
<li>Permission and security problems increase operational risk</li>
</ul>
<hr lang="en">
<h2 lang="en" id="8-프로젝트-구조-예시">8. Example Project Structure</h2>
<h3 lang="en" id="백엔드-fastapi">Backend (FastAPI)</h3>
<pre><code>app/
├── config/               # 설정
├── dependencies/         # 의존성 주입
│   ├── protocol.py       # 인터페이스 정의
│   ├── auth.py           # 인증 의존성
│   └── database.py       # DB 세션
├── services/             # 비즈니스 로직
├── external/             # 외부 연동 (DB, Firebase)
├── models/               # SQLAlchemy 모델
├── schemas/              # Pydantic 스키마
└── routers/              # API 엔드포인트

tests/
├── fakes/                # Fake 구현체
├── unit/                 # 단위 테스트
├── integration/          # 통합 테스트 (DI 사용)
└── e2e/                  # E2E 테스트 (실제 서비스)
</code></pre>
<h3 lang="en" id="프론트엔드-nextjs">Frontend (Next.js)</h3>
<pre><code>src/
├── components/
│   └── features/
│       └── LoginForm/
│           ├── LoginForm.tsx
│           └── LoginForm.test.tsx
├── hooks/
│   ├── useAuth.ts
│   └── useAuth.test.ts
└── lib/
    └── utils/
        ├── format.ts
        └── format.test.ts

mocks/
├── handlers.ts           # MSW 핸들러
├── browser.ts            # 클라이언트용
└── server.ts             # 서버용

tests/
└── e2e/                  # Playwright
</code></pre>
<hr lang="en">
<h2 lang="en" id="9-실무-적용-체크리스트">9. Practical Checklist</h2>
<h3 lang="en" id="백엔드">Backend</h3>
<ul lang="en" class="contains-task-list">
<li class="task-list-item"><input type="checkbox" disabled> External integrations (DB, APIs, email) are <strong>separated behind interfaces (Protocol)</strong></li>
<li class="task-list-item"><input type="checkbox" disabled> Dependency injection allows replacement with Fakes or Mocks in tests</li>
<li class="task-list-item"><input type="checkbox" disabled> Business logic is separated into the Service layer</li>
<li class="task-list-item"><input type="checkbox" disabled> State-transition rules are defined in one place</li>
<li class="task-list-item"><input type="checkbox" disabled> Fakes <strong>implement only the contract</strong> rather than replicating the DB</li>
</ul>
<h3 lang="en" id="프론트엔드">Frontend</h3>
<ul lang="en" class="contains-task-list">
<li class="task-list-item"><input type="checkbox" disabled> Complex logic is separated into custom hooks or utility functions</li>
<li class="task-list-item"><input type="checkbox" disabled> APIs are mocked with MSW</li>
<li class="task-list-item"><input type="checkbox" disabled> Tests cover <strong>user behavior</strong> rather than <strong>implementation details</strong></li>
<li class="task-list-item"><input type="checkbox" disabled> Bug fixes begin with a reproduction test</li>
</ul>
<h3 lang="en" id="환경">Environment</h3>
<ul lang="en" class="contains-task-list">
<li class="task-list-item"><input type="checkbox" disabled> Fixed reference seeds and dynamic business data are separated</li>
<li class="task-list-item"><input type="checkbox" disabled> CI keeps only smoke E2E coverage rather than adding it excessively</li>
<li class="task-list-item"><input type="checkbox" disabled> Local development uses an isolated environment instead of a shared DB</li>
</ul>
<hr lang="en">
<h2 lang="en" id="마치며">Closing Thoughts</h2>
<p lang="en">There is no single correct test strategy, but there is a direction.</p>
<ul lang="en">
<li><strong>Backend</strong>: a pyramid structure, business-logic unit tests, and interfaces for external integrations</li>
<li><strong>Frontend</strong>: a trophy structure, emphasis on integration tests, and testing like a user</li>
</ul>
<p lang="en">The most important question is:</p>
<blockquote lang="en">
<p><strong>“If this test passes, would I feel confident deploying?”</strong></p>
</blockquote>
<p lang="en">Write tests that give you that confidence.</p>
<hr lang="en">
<h2 lang="en" id="참고-도구">Tools to Consider</h2>





























<table lang="en"><thead><tr><th>Environment</th><th>Test runner</th><th>Mocking</th><th>E2E</th></tr></thead><tbody><tr><td>Python/FastAPI</td><td>pytest</td><td>unittest.mock, FakeRepository</td><td>-</td></tr><tr><td>Next.js/React</td><td>Vitest</td><td>MSW</td><td>Chrome Extension</td></tr><tr><td>TypeScript</td><td>Vitest</td><td>MSW</td><td>Chrome Extension.</td></tr></tbody></table>]]></content:encoded>
    </item>
    <item>
      <title><![CDATA[Building AI Chat with Four Distinct Personalities Using Claude Code]]></title>
      <link>https://blog.funq.kr/en/blogs/chat-claudecode-20251222-v01</link>
      <guid isPermaLink="true">https://blog.funq.kr/en/blogs/chat-claudecode-20251222-v01</guid>
      <description><![CDATA[Calling Claude Code CLI as a subprocess to implement four AI chat characters: Mallang, Lupin, Pudding, and Mycall.]]></description>
      <category><![CDATA[claude-code]]></category><category><![CDATA[fastapi]]></category><category><![CDATA[ai-chat]]></category><category><![CDATA[firebase]]></category><category><![CDATA[python]]></category>
      <media:content url="https://blog.funq.kr/blog-cover/chat-claudecode-20251222-v01.jpg" medium="image" type="image/jpeg"/>
      <pubDate>Mon, 22 Dec 2025 09:00:00 GMT</pubDate>
      <author>funqdev@gmail.com (fundev)</author>
      <content:encoded><![CDATA[<p lang="en">See the earlier posts:</p>
<ul lang="en">
<li><a href="https://blog.funq.kr/blogs/kid-chat-20251218-v01">Building a Serverless Firebase Web Chat with Vite and TypeScript</a></li>
<li><a href="https://blog.funq.kr/blogs/fastapi-setup-20251220-v01">Setting Up a FastAPI Backend API Project</a></li>
<li><a href="https://blog.funq.kr/blogs/fastapi-firebase-auth-20251221-v01">Implementing Firebase Authentication in FastAPI</a></li>
</ul>
<p lang="en">Following those posts in order, I built the chat application and backend and implemented Firebase authentication. I then added AI chat by making API requests from the chat application.</p>
<p lang="en">I have Anthropic's paid Max plan, which gives me access to Claude Code. I could have built this with an Anthropic or OpenAI API key, but each AI response would incur a cost, so I used Claude Code for the AI chat feature.</p>
<hr lang="en">
<h2 lang="en" id="프로젝트-개요">Project Overview</h2>
<h3 lang="en" id="배경">Background</h3>
<p lang="en">“Kid Chat” is a simple chat app for family communication. I wanted to add a feature where children could call an AI character by name and get a reply from a friend with a distinct personality.</p>
<h3 lang="en" id="ai-캐릭터-소개">Meet the AI Characters</h3>



































<table lang="en"><thead><tr><th>Character</th><th>Trigger</th><th>Personality</th><th>Traits</th></tr></thead><tbody><tr><td>🩷 Mallang</td><td>“말랑아” (Hey, Mallang)</td><td>A caring friend</td><td>Warm and empathetic; offers praise and encouragement</td></tr><tr><td>💛 Lupin</td><td>“루팡아” (Hey, Lupin)</td><td>A confident friend</td><td>Slightly cheeky but helpful and humorous</td></tr><tr><td>💚 Pudding</td><td>“푸딩아” (Hey, Pudding)</td><td>A cute pet</td><td>Uses sound effects; simple and innocent</td></tr><tr><td>🩵 Mycall</td><td>“마이콜아” (Hey, Mycall)</td><td>An English teacher</td><td>Replies in English; educational</td></tr></tbody></table>
<h3 lang="en" id="기술-스택">Technology Stack</h3>
<ul lang="en">
<li><strong>Backend</strong>: FastAPI + Python 3.12</li>
<li><strong>Frontend</strong>: Vite + TypeScript</li>
<li><strong>Database</strong>: PostgreSQL + SQLAlchemy</li>
<li><strong>Authentication</strong>: Firebase Authentication</li>
<li><strong>AI</strong>: Claude Code CLI (subprocess)</li>
</ul>
<h3 lang="en" id="아키텍처">Architecture</h3>
<pre><code>┌─────────────┐  JWT Token   ┌─────────────┐     ┌─────────────┐
│   Kid Chat  │─────────────▶│ Backend API │────▶│ Claude CLI  │
│ (Vite + TS) │◀─────────────│  (FastAPI)  │◀────│ (subprocess)│
└─────────────┘              └─────────────┘     └─────────────┘
       │                            │
       │ Login/Signup               │ Token 검증
       ▼                            ▼
┌─────────────┐              ┌─────────────┐
│  Firebase   │◀─────────────│  Firebase   │
│    Auth     │  Admin SDK   │ Admin SDK   │
└─────────────┘              └─────────────┘
</code></pre>
<hr lang="en">
<h2 lang="en" id="firebase-인증-구현">Implementing Firebase Authentication</h2>
<h3 lang="en" id="1-firebase-admin-sdk-설정">1. Configure the Firebase Admin SDK</h3>
<p lang="en">First, initialize the Firebase Admin SDK. Authenticate with a service-account key file.</p>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="python" data-theme="github-dark"><code data-language="python" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#6A737D"># app/firebase.py</span></span>
<span data-line=""><span style="color:#F97583">import</span><span style="color:#E1E4E8"> firebase_admin</span></span>
<span data-line=""><span style="color:#F97583">from</span><span style="color:#E1E4E8"> firebase_admin </span><span style="color:#F97583">import</span><span style="color:#E1E4E8"> auth, credentials</span></span>
<span data-line=""><span style="color:#F97583">from</span><span style="color:#E1E4E8"> app.config </span><span style="color:#F97583">import</span><span style="color:#E1E4E8"> get_settings</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#E1E4E8">settings </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> get_settings()</span></span>
<span data-line=""><span style="color:#E1E4E8">_firebase_app </span><span style="color:#F97583">=</span><span style="color:#79B8FF"> None</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F97583">def</span><span style="color:#B392F0"> get_firebase_app</span><span style="color:#E1E4E8">():</span></span>
<span data-line=""><span style="color:#F97583">    global</span><span style="color:#E1E4E8"> _firebase_app</span></span>
<span data-line=""><span style="color:#F97583">    if</span><span style="color:#E1E4E8"> _firebase_app </span><span style="color:#F97583">is</span><span style="color:#79B8FF"> None</span><span style="color:#E1E4E8">:</span></span>
<span data-line=""><span style="color:#E1E4E8">        cred </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> credentials.Certificate(settings.firebase_credentials_path)</span></span>
<span data-line=""><span style="color:#E1E4E8">        _firebase_app </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> firebase_admin.initialize_app(cred)</span></span>
<span data-line=""><span style="color:#F97583">    return</span><span style="color:#E1E4E8"> _firebase_app</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F97583">def</span><span style="color:#B392F0"> verify_firebase_token</span><span style="color:#E1E4E8">(id_token: </span><span style="color:#79B8FF">str</span><span style="color:#E1E4E8">) -> </span><span style="color:#79B8FF">dict</span><span style="color:#E1E4E8">:</span></span>
<span data-line=""><span style="color:#9ECBFF">    """Firebase ID 토큰을 검증하고 사용자 정보를 반환합니다."""</span></span>
<span data-line=""><span style="color:#E1E4E8">    get_firebase_app()</span></span>
<span data-line=""><span style="color:#E1E4E8">    decoded_token </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> auth.verify_id_token(id_token)</span></span>
<span data-line=""><span style="color:#F97583">    return</span><span style="color:#E1E4E8"> decoded_token</span></span></code></pre></figure>
<h3 lang="en" id="2-인증-의존성-dependency">2. Authentication Dependency</h3>
<p lang="en">I used FastAPI's Depends to create a reusable authentication dependency.</p>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="python" data-theme="github-dark"><code data-language="python" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#6A737D"># app/dependencies.py</span></span>
<span data-line=""><span style="color:#F97583">from</span><span style="color:#E1E4E8"> fastapi </span><span style="color:#F97583">import</span><span style="color:#E1E4E8"> Depends, HTTPException, status</span></span>
<span data-line=""><span style="color:#F97583">from</span><span style="color:#E1E4E8"> fastapi.security </span><span style="color:#F97583">import</span><span style="color:#E1E4E8"> HTTPBearer, HTTPAuthorizationCredentials</span></span>
<span data-line=""><span style="color:#F97583">from</span><span style="color:#E1E4E8"> pydantic </span><span style="color:#F97583">import</span><span style="color:#E1E4E8"> BaseModel</span></span>
<span data-line=""><span style="color:#F97583">from</span><span style="color:#E1E4E8"> app.firebase </span><span style="color:#F97583">import</span><span style="color:#E1E4E8"> verify_firebase_token</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#E1E4E8">security </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> HTTPBearer()</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F97583">class</span><span style="color:#B392F0"> FirebaseUser</span><span style="color:#E1E4E8">(</span><span style="color:#B392F0">BaseModel</span><span style="color:#E1E4E8">):</span></span>
<span data-line=""><span style="color:#E1E4E8">    uid: </span><span style="color:#79B8FF">str</span></span>
<span data-line=""><span style="color:#E1E4E8">    email: </span><span style="color:#79B8FF">str</span><span style="color:#F97583"> |</span><span style="color:#79B8FF"> None</span><span style="color:#F97583"> =</span><span style="color:#79B8FF"> None</span></span>
<span data-line=""><span style="color:#E1E4E8">    name: </span><span style="color:#79B8FF">str</span><span style="color:#F97583"> |</span><span style="color:#79B8FF"> None</span><span style="color:#F97583"> =</span><span style="color:#79B8FF"> None</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F97583">async</span><span style="color:#F97583"> def</span><span style="color:#B392F0"> get_current_user</span><span style="color:#E1E4E8">(</span></span>
<span data-line=""><span style="color:#E1E4E8">    credentials: HTTPAuthorizationCredentials </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> Depends(security)</span></span>
<span data-line=""><span style="color:#E1E4E8">) -> FirebaseUser:</span></span>
<span data-line=""><span style="color:#9ECBFF">    """Firebase 토큰을 검증하고 현재 사용자를 반환합니다."""</span></span>
<span data-line=""><span style="color:#E1E4E8">    token </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> credentials.credentials</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F97583">    try</span><span style="color:#E1E4E8">:</span></span>
<span data-line=""><span style="color:#E1E4E8">        decoded_token </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> verify_firebase_token(token)</span></span>
<span data-line=""><span style="color:#F97583">        return</span><span style="color:#E1E4E8"> FirebaseUser(</span></span>
<span data-line=""><span style="color:#FFAB70">            uid</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">decoded_token[</span><span style="color:#9ECBFF">"uid"</span><span style="color:#E1E4E8">],</span></span>
<span data-line=""><span style="color:#FFAB70">            email</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">decoded_token.get(</span><span style="color:#9ECBFF">"email"</span><span style="color:#E1E4E8">),</span></span>
<span data-line=""><span style="color:#FFAB70">            name</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">decoded_token.get(</span><span style="color:#9ECBFF">"name"</span><span style="color:#E1E4E8">)</span></span>
<span data-line=""><span style="color:#E1E4E8">        )</span></span>
<span data-line=""><span style="color:#F97583">    except</span><span style="color:#79B8FF"> Exception</span><span style="color:#F97583"> as</span><span style="color:#E1E4E8"> e:</span></span>
<span data-line=""><span style="color:#F97583">        raise</span><span style="color:#E1E4E8"> HTTPException(</span></span>
<span data-line=""><span style="color:#FFAB70">            status_code</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">status.</span><span style="color:#79B8FF">HTTP_401_UNAUTHORIZED</span><span style="color:#E1E4E8">,</span></span>
<span data-line=""><span style="color:#FFAB70">            detail</span><span style="color:#F97583">=</span><span style="color:#F97583">f</span><span style="color:#9ECBFF">"Invalid authentication: </span><span style="color:#79B8FF">{str</span><span style="color:#E1E4E8">(e)</span><span style="color:#79B8FF">}</span><span style="color:#9ECBFF">"</span><span style="color:#E1E4E8">,</span></span>
<span data-line=""><span style="color:#FFAB70">            headers</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">{</span><span style="color:#9ECBFF">"WWW-Authenticate"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"Bearer"</span><span style="color:#E1E4E8">},</span></span>
<span data-line=""><span style="color:#E1E4E8">        )</span></span></code></pre></figure>
<hr lang="en">
<h2 lang="en" id="ai-채팅-api-설계">AI Chat API Design</h2>
<h3 lang="en" id="요구사항">Requirements</h3>
<ol lang="en">
<li>Respond only to messages beginning with a character's Korean trigger: 말랑아 / 루팡아 / 푸딩아 / 마이콜아 (Mallang / Lupin / Pudding / Mycall)</li>
<li>Respond with each character's unique personality</li>
<li>Reply concisely like a friend, within 100 words</li>
<li>Do not modify files, for safety</li>
<li>Use web search when up-to-date information is needed</li>
</ol>
<h3 lang="en" id="pydantic-스키마">Pydantic Schemas</h3>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="python" data-theme="github-dark"><code data-language="python" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#6A737D"># app/schemas/ai.py</span></span>
<span data-line=""><span style="color:#F97583">from</span><span style="color:#E1E4E8"> pydantic </span><span style="color:#F97583">import</span><span style="color:#E1E4E8"> BaseModel, Field</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F97583">class</span><span style="color:#B392F0"> ChatRequest</span><span style="color:#E1E4E8">(</span><span style="color:#B392F0">BaseModel</span><span style="color:#E1E4E8">):</span></span>
<span data-line=""><span style="color:#E1E4E8">    prompt: </span><span style="color:#79B8FF">str</span><span style="color:#F97583"> =</span><span style="color:#E1E4E8"> Field(</span></span>
<span data-line=""><span style="color:#79B8FF">        ...</span><span style="color:#E1E4E8">,</span></span>
<span data-line=""><span style="color:#FFAB70">        min_length</span><span style="color:#F97583">=</span><span style="color:#79B8FF">1</span><span style="color:#E1E4E8">,</span></span>
<span data-line=""><span style="color:#FFAB70">        max_length</span><span style="color:#F97583">=</span><span style="color:#79B8FF">10000</span><span style="color:#E1E4E8">,</span></span>
<span data-line=""><span style="color:#FFAB70">        description</span><span style="color:#F97583">=</span><span style="color:#9ECBFF">"AI에게 보낼 메시지"</span></span>
<span data-line=""><span style="color:#E1E4E8">    )</span></span>
<span data-line=""><span style="color:#E1E4E8">    timeout_seconds: </span><span style="color:#79B8FF">int</span><span style="color:#F97583"> |</span><span style="color:#79B8FF"> None</span><span style="color:#F97583"> =</span><span style="color:#E1E4E8"> Field(</span></span>
<span data-line=""><span style="color:#FFAB70">        default</span><span style="color:#F97583">=</span><span style="color:#79B8FF">None</span><span style="color:#E1E4E8">,</span></span>
<span data-line=""><span style="color:#FFAB70">        ge</span><span style="color:#F97583">=</span><span style="color:#79B8FF">10</span><span style="color:#E1E4E8">,</span></span>
<span data-line=""><span style="color:#FFAB70">        le</span><span style="color:#F97583">=</span><span style="color:#79B8FF">300</span><span style="color:#E1E4E8">,</span></span>
<span data-line=""><span style="color:#FFAB70">        description</span><span style="color:#F97583">=</span><span style="color:#9ECBFF">"응답 대기 시간 (초)"</span></span>
<span data-line=""><span style="color:#E1E4E8">    )</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F97583">class</span><span style="color:#B392F0"> ChatResponse</span><span style="color:#E1E4E8">(</span><span style="color:#B392F0">BaseModel</span><span style="color:#E1E4E8">):</span></span>
<span data-line=""><span style="color:#E1E4E8">    response: </span><span style="color:#79B8FF">str</span><span style="color:#F97583"> =</span><span style="color:#E1E4E8"> Field(</span><span style="color:#79B8FF">...</span><span style="color:#E1E4E8">, </span><span style="color:#FFAB70">description</span><span style="color:#F97583">=</span><span style="color:#9ECBFF">"AI 응답"</span><span style="color:#E1E4E8">)</span></span>
<span data-line=""><span style="color:#E1E4E8">    elapsed_time_ms: </span><span style="color:#79B8FF">int</span><span style="color:#F97583"> =</span><span style="color:#E1E4E8"> Field(</span><span style="color:#79B8FF">...</span><span style="color:#E1E4E8">, </span><span style="color:#FFAB70">description</span><span style="color:#F97583">=</span><span style="color:#9ECBFF">"처리 시간 (밀리초)"</span><span style="color:#E1E4E8">)</span></span>
<span data-line=""><span style="color:#E1E4E8">    truncated: </span><span style="color:#79B8FF">bool</span><span style="color:#F97583"> =</span><span style="color:#E1E4E8"> Field(</span><span style="color:#FFAB70">default</span><span style="color:#F97583">=</span><span style="color:#79B8FF">False</span><span style="color:#E1E4E8">, </span><span style="color:#FFAB70">description</span><span style="color:#F97583">=</span><span style="color:#9ECBFF">"응답 잘림 여부"</span><span style="color:#E1E4E8">)</span></span>
<span data-line=""><span style="color:#E1E4E8">    persona: </span><span style="color:#79B8FF">str</span><span style="color:#F97583"> |</span><span style="color:#79B8FF"> None</span><span style="color:#F97583"> =</span><span style="color:#E1E4E8"> Field(</span></span>
<span data-line=""><span style="color:#FFAB70">        default</span><span style="color:#F97583">=</span><span style="color:#79B8FF">None</span><span style="color:#E1E4E8">,</span></span>
<span data-line=""><span style="color:#FFAB70">        description</span><span style="color:#F97583">=</span><span style="color:#9ECBFF">"응답한 AI 캐릭터 (말랑이/루팡/푸딩/마이콜)"</span></span>
<span data-line=""><span style="color:#E1E4E8">    )</span></span></code></pre></figure>
<h3 lang="en" id="api-엔드포인트">API Endpoint</h3>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="python" data-theme="github-dark"><code data-language="python" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#6A737D"># app/routers/ai.py</span></span>
<span data-line=""><span style="color:#F97583">from</span><span style="color:#E1E4E8"> fastapi </span><span style="color:#F97583">import</span><span style="color:#E1E4E8"> APIRouter, Depends, HTTPException, status</span></span>
<span data-line=""><span style="color:#F97583">from</span><span style="color:#E1E4E8"> app.dependencies </span><span style="color:#F97583">import</span><span style="color:#E1E4E8"> get_current_user, FirebaseUser</span></span>
<span data-line=""><span style="color:#F97583">from</span><span style="color:#E1E4E8"> app.schemas.ai </span><span style="color:#F97583">import</span><span style="color:#E1E4E8"> ChatRequest, ChatResponse</span></span>
<span data-line=""><span style="color:#F97583">from</span><span style="color:#E1E4E8"> app.services.claude_service </span><span style="color:#F97583">import</span><span style="color:#E1E4E8"> ClaudeService, get_claude_service</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#E1E4E8">router </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> APIRouter(</span><span style="color:#FFAB70">prefix</span><span style="color:#F97583">=</span><span style="color:#9ECBFF">"/ai"</span><span style="color:#E1E4E8">, </span><span style="color:#FFAB70">tags</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">[</span><span style="color:#9ECBFF">"ai"</span><span style="color:#E1E4E8">])</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#B392F0">@router.post</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">"/chat"</span><span style="color:#E1E4E8">, </span><span style="color:#FFAB70">response_model</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">ChatResponse)</span></span>
<span data-line=""><span style="color:#F97583">async</span><span style="color:#F97583"> def</span><span style="color:#B392F0"> chat</span><span style="color:#E1E4E8">(</span></span>
<span data-line=""><span style="color:#E1E4E8">    request: ChatRequest,</span></span>
<span data-line=""><span style="color:#E1E4E8">    user: FirebaseUser </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> Depends(get_current_user),</span></span>
<span data-line=""><span style="color:#E1E4E8">    claude_service: ClaudeService </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> Depends(get_claude_service),</span></span>
<span data-line=""><span style="color:#E1E4E8">):</span></span>
<span data-line=""><span style="color:#9ECBFF">    """AI와 대화합니다. Firebase 인증이 필요합니다."""</span></span>
<span data-line=""><span style="color:#F97583">    try</span><span style="color:#E1E4E8">:</span></span>
<span data-line=""><span style="color:#E1E4E8">        result </span><span style="color:#F97583">=</span><span style="color:#F97583"> await</span><span style="color:#E1E4E8"> claude_service.chat(</span></span>
<span data-line=""><span style="color:#FFAB70">            prompt</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">request.prompt,</span></span>
<span data-line=""><span style="color:#FFAB70">            timeout_seconds</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">request.timeout_seconds</span></span>
<span data-line=""><span style="color:#E1E4E8">        )</span></span>
<span data-line=""><span style="color:#F97583">        return</span><span style="color:#E1E4E8"> ChatResponse(</span></span>
<span data-line=""><span style="color:#FFAB70">            response</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">result.output,</span></span>
<span data-line=""><span style="color:#FFAB70">            elapsed_time_ms</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">result.elapsed_ms,</span></span>
<span data-line=""><span style="color:#FFAB70">            truncated</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">result.truncated,</span></span>
<span data-line=""><span style="color:#FFAB70">            persona</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">result.persona_name  </span><span style="color:#6A737D"># AI 캐릭터 이름 반환</span></span>
<span data-line=""><span style="color:#E1E4E8">        )</span></span>
<span data-line=""><span style="color:#F97583">    except</span><span style="color:#79B8FF"> ValueError</span><span style="color:#F97583"> as</span><span style="color:#E1E4E8"> e:</span></span>
<span data-line=""><span style="color:#6A737D">        # AI 트리거가 감지되지 않은 경우</span></span>
<span data-line=""><span style="color:#F97583">        raise</span><span style="color:#E1E4E8"> HTTPException(</span></span>
<span data-line=""><span style="color:#FFAB70">            status_code</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">status.</span><span style="color:#79B8FF">HTTP_400_BAD_REQUEST</span><span style="color:#E1E4E8">,</span></span>
<span data-line=""><span style="color:#FFAB70">            detail</span><span style="color:#F97583">=</span><span style="color:#79B8FF">str</span><span style="color:#E1E4E8">(e)</span></span>
<span data-line=""><span style="color:#E1E4E8">        )</span></span>
<span data-line=""><span style="color:#F97583">    except</span><span style="color:#79B8FF"> TimeoutError</span><span style="color:#E1E4E8">:</span></span>
<span data-line=""><span style="color:#F97583">        raise</span><span style="color:#E1E4E8"> HTTPException(</span></span>
<span data-line=""><span style="color:#FFAB70">            status_code</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">status.</span><span style="color:#79B8FF">HTTP_408_REQUEST_TIMEOUT</span><span style="color:#E1E4E8">,</span></span>
<span data-line=""><span style="color:#FFAB70">            detail</span><span style="color:#F97583">=</span><span style="color:#9ECBFF">"AI response timed out"</span></span>
<span data-line=""><span style="color:#E1E4E8">        )</span></span>
<span data-line=""><span style="color:#F97583">    except</span><span style="color:#79B8FF"> Exception</span><span style="color:#F97583"> as</span><span style="color:#E1E4E8"> e:</span></span>
<span data-line=""><span style="color:#F97583">        raise</span><span style="color:#E1E4E8"> HTTPException(</span></span>
<span data-line=""><span style="color:#FFAB70">            status_code</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">status.</span><span style="color:#79B8FF">HTTP_500_INTERNAL_SERVER_ERROR</span><span style="color:#E1E4E8">,</span></span>
<span data-line=""><span style="color:#FFAB70">            detail</span><span style="color:#F97583">=</span><span style="color:#F97583">f</span><span style="color:#9ECBFF">"AI service error: </span><span style="color:#79B8FF">{str</span><span style="color:#E1E4E8">(e)</span><span style="color:#79B8FF">}</span><span style="color:#9ECBFF">"</span></span>
<span data-line=""><span style="color:#E1E4E8">        )</span></span></code></pre></figure>
<hr lang="en">
<h2 lang="en" id="claude-code-cli-통합">Claude Code CLI Integration</h2>
<h3 lang="en" id="핵심-아이디어">The Core Idea</h3>
<p lang="en">Call Claude Code CLI as a subprocess to generate AI responses. Run it automatically with <code>--dangerously-skip-permissions</code>, and add character-specific system instructions to the prompt.</p>
<hr lang="en">
<h2 lang="en" id="ai-페르소나-시스템">The AI Persona System</h2>
<h3 lang="en" id="페르소나-정의">Persona Definitions</h3>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="python" data-theme="github-dark"><code data-language="python" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#6A737D"># app/services/personas.py</span></span>
<span data-line=""><span style="color:#F97583">from</span><span style="color:#E1E4E8"> enum </span><span style="color:#F97583">import</span><span style="color:#E1E4E8"> Enum</span></span>
<span data-line=""><span style="color:#F97583">from</span><span style="color:#E1E4E8"> dataclasses </span><span style="color:#F97583">import</span><span style="color:#E1E4E8"> dataclass</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F97583">class</span><span style="color:#B392F0"> PersonaType</span><span style="color:#E1E4E8">(</span><span style="color:#79B8FF">str</span><span style="color:#E1E4E8">, </span><span style="color:#B392F0">Enum</span><span style="color:#E1E4E8">):</span></span>
<span data-line=""><span style="color:#79B8FF">    MALLANGI</span><span style="color:#F97583"> =</span><span style="color:#9ECBFF"> "mallangi"</span><span style="color:#6A737D">   # 말랑이</span></span>
<span data-line=""><span style="color:#79B8FF">    LUPIN</span><span style="color:#F97583"> =</span><span style="color:#9ECBFF"> "lupin"</span><span style="color:#6A737D">         # 루팡</span></span>
<span data-line=""><span style="color:#79B8FF">    PUDDING</span><span style="color:#F97583"> =</span><span style="color:#9ECBFF"> "pudding"</span><span style="color:#6A737D">     # 푸딩</span></span>
<span data-line=""><span style="color:#79B8FF">    MICHAEL</span><span style="color:#F97583"> =</span><span style="color:#9ECBFF"> "michael"</span><span style="color:#6A737D">     # 마이콜</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#B392F0">@dataclass</span></span>
<span data-line=""><span style="color:#F97583">class</span><span style="color:#B392F0"> Persona</span><span style="color:#E1E4E8">:</span></span>
<span data-line=""><span style="color:#79B8FF">    type</span><span style="color:#E1E4E8">: PersonaType</span></span>
<span data-line=""><span style="color:#E1E4E8">    name: </span><span style="color:#79B8FF">str</span><span style="color:#6A737D">               # 한글 이름</span></span>
<span data-line=""><span style="color:#E1E4E8">    triggers: list[</span><span style="color:#79B8FF">str</span><span style="color:#E1E4E8">]     </span><span style="color:#6A737D"># 호출 트리거</span></span>
<span data-line=""><span style="color:#E1E4E8">    system_prompt: </span><span style="color:#79B8FF">str</span><span style="color:#6A737D">      # 캐릭터 프롬프트</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#6A737D"># 페르소나 정의</span></span>
<span data-line=""><span style="color:#79B8FF">PERSONAS</span><span style="color:#F97583"> =</span><span style="color:#E1E4E8"> {</span></span>
<span data-line=""><span style="color:#E1E4E8">    PersonaType.</span><span style="color:#79B8FF">MALLANGI</span><span style="color:#E1E4E8">: Persona(</span></span>
<span data-line=""><span style="color:#FFAB70">        type</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">PersonaType.</span><span style="color:#79B8FF">MALLANGI</span><span style="color:#E1E4E8">,</span></span>
<span data-line=""><span style="color:#FFAB70">        name</span><span style="color:#F97583">=</span><span style="color:#9ECBFF">"말랑이"</span><span style="color:#E1E4E8">,</span></span>
<span data-line=""><span style="color:#FFAB70">        triggers</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">[</span><span style="color:#9ECBFF">"말랑아"</span><span style="color:#E1E4E8">, </span><span style="color:#9ECBFF">"말랑이야"</span><span style="color:#E1E4E8">],</span></span>
<span data-line=""><span style="color:#FFAB70">        system_prompt</span><span style="color:#F97583">=</span><span style="color:#9ECBFF">"""너는 '말랑이'야. 다정하고 따뜻한 친구야.</span></span>
<span data-line=""><span style="color:#9ECBFF">        - 항상 공감하고 칭찬해줘</span></span>
<span data-line=""><span style="color:#9ECBFF">        - 부드럽고 친근한 말투를 사용해</span></span>
<span data-line=""><span style="color:#9ECBFF">        - "~해줄게", "~하자" 같은 표현을 사용해</span></span>
<span data-line=""><span style="color:#9ECBFF">        - 이모지를 적절히 사용해 (💕, 🤗, ✨)"""</span></span>
<span data-line=""><span style="color:#E1E4E8">    ),</span></span>
<span data-line=""><span style="color:#E1E4E8">    PersonaType.</span><span style="color:#79B8FF">LUPIN</span><span style="color:#E1E4E8">: Persona(</span></span>
<span data-line=""><span style="color:#FFAB70">        type</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">PersonaType.</span><span style="color:#79B8FF">LUPIN</span><span style="color:#E1E4E8">,</span></span>
<span data-line=""><span style="color:#FFAB70">        name</span><span style="color:#F97583">=</span><span style="color:#9ECBFF">"루팡"</span><span style="color:#E1E4E8">,</span></span>
<span data-line=""><span style="color:#FFAB70">        triggers</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">[</span><span style="color:#9ECBFF">"루팡아"</span><span style="color:#E1E4E8">, </span><span style="color:#9ECBFF">"루팡이야"</span><span style="color:#E1E4E8">],</span></span>
<span data-line=""><span style="color:#FFAB70">        system_prompt</span><span style="color:#F97583">=</span><span style="color:#9ECBFF">"""너는 '루팡'이야. 자신감 넘치는 친구야.</span></span>
<span data-line=""><span style="color:#9ECBFF">        - 약간 건방지지만 실력이 있어</span></span>
<span data-line=""><span style="color:#9ECBFF">        - "흥, 그 정도는 쉽지~" 같은 말투</span></span>
<span data-line=""><span style="color:#9ECBFF">        - 도움을 줄 때도 쿨하게</span></span>
<span data-line=""><span style="color:#9ECBFF">        - 가끔 유머도 섞어서"""</span></span>
<span data-line=""><span style="color:#E1E4E8">    ),</span></span>
<span data-line=""><span style="color:#E1E4E8">    PersonaType.</span><span style="color:#79B8FF">PUDDING</span><span style="color:#E1E4E8">: Persona(</span></span>
<span data-line=""><span style="color:#FFAB70">        type</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">PersonaType.</span><span style="color:#79B8FF">PUDDING</span><span style="color:#E1E4E8">,</span></span>
<span data-line=""><span style="color:#FFAB70">        name</span><span style="color:#F97583">=</span><span style="color:#9ECBFF">"푸딩"</span><span style="color:#E1E4E8">,</span></span>
<span data-line=""><span style="color:#FFAB70">        triggers</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">[</span><span style="color:#9ECBFF">"푸딩아"</span><span style="color:#E1E4E8">, </span><span style="color:#9ECBFF">"푸딩이야"</span><span style="color:#E1E4E8">],</span></span>
<span data-line=""><span style="color:#FFAB70">        system_prompt</span><span style="color:#F97583">=</span><span style="color:#9ECBFF">"""너는 '푸딩'이야. 귀여운 애완동물이야.</span></span>
<span data-line=""><span style="color:#9ECBFF">        - 의성어를 많이 사용해 (멍멍, 왈왈, 낑낑)</span></span>
<span data-line=""><span style="color:#9ECBFF">        - 단순하고 순수하게 반응해</span></span>
<span data-line=""><span style="color:#9ECBFF">        - 꼬리 흔들며 기뻐하는 느낌으로</span></span>
<span data-line=""><span style="color:#9ECBFF">        - 짧고 귀엽게 대답해"""</span></span>
<span data-line=""><span style="color:#E1E4E8">    ),</span></span>
<span data-line=""><span style="color:#E1E4E8">    PersonaType.</span><span style="color:#79B8FF">MICHAEL</span><span style="color:#E1E4E8">: Persona(</span></span>
<span data-line=""><span style="color:#FFAB70">        type</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">PersonaType.</span><span style="color:#79B8FF">MICHAEL</span><span style="color:#E1E4E8">,</span></span>
<span data-line=""><span style="color:#FFAB70">        name</span><span style="color:#F97583">=</span><span style="color:#9ECBFF">"마이콜"</span><span style="color:#E1E4E8">,</span></span>
<span data-line=""><span style="color:#FFAB70">        triggers</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">[</span><span style="color:#9ECBFF">"마이콜아"</span><span style="color:#E1E4E8">, </span><span style="color:#9ECBFF">"마이콜이야"</span><span style="color:#E1E4E8">],</span></span>
<span data-line=""><span style="color:#FFAB70">        system_prompt</span><span style="color:#F97583">=</span><span style="color:#9ECBFF">"""너는 '마이콜'이야. 친절한 영어 선생님이야.</span></span>
<span data-line=""><span style="color:#9ECBFF">        - 한국어와 영어를 섞어서 대답해</span></span>
<span data-line=""><span style="color:#9ECBFF">        - 영어 표현을 가르쳐줄 때 발음도 알려줘</span></span>
<span data-line=""><span style="color:#9ECBFF">        - "In English, we say..." 같은 표현 사용</span></span>
<span data-line=""><span style="color:#9ECBFF">        - 재미있게 영어를 알려줘"""</span></span>
<span data-line=""><span style="color:#E1E4E8">    ),</span></span>
<span data-line=""><span style="color:#E1E4E8">}</span></span></code></pre></figure>
<h3 lang="en" id="페르소나-감지">Persona Detection</h3>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="python" data-theme="github-dark"><code data-language="python" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#F97583">def</span><span style="color:#B392F0"> detect_persona</span><span style="color:#E1E4E8">(message: </span><span style="color:#79B8FF">str</span><span style="color:#E1E4E8">) -> tuple[PersonaType, </span><span style="color:#79B8FF">str</span><span style="color:#E1E4E8">]:</span></span>
<span data-line=""><span style="color:#9ECBFF">    """메시지에서 페르소나 트리거를 감지합니다.</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#9ECBFF">    Returns:</span></span>
<span data-line=""><span style="color:#9ECBFF">        tuple[PersonaType, str]: (페르소나 타입, 트리거 제거된 실제 질문)</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#9ECBFF">    Raises:</span></span>
<span data-line=""><span style="color:#9ECBFF">        ValueError: 트리거가 감지되지 않은 경우</span></span>
<span data-line=""><span style="color:#9ECBFF">    """</span></span>
<span data-line=""><span style="color:#E1E4E8">    message_lower </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> message.strip().lower()</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F97583">    for</span><span style="color:#E1E4E8"> persona_type, persona </span><span style="color:#F97583">in</span><span style="color:#79B8FF"> PERSONAS</span><span style="color:#E1E4E8">.items():</span></span>
<span data-line=""><span style="color:#F97583">        for</span><span style="color:#E1E4E8"> trigger </span><span style="color:#F97583">in</span><span style="color:#E1E4E8"> persona.triggers:</span></span>
<span data-line=""><span style="color:#F97583">            if</span><span style="color:#E1E4E8"> message_lower.startswith(trigger.lower()):</span></span>
<span data-line=""><span style="color:#6A737D">                # 트리거 제거하고 실제 질문만 추출</span></span>
<span data-line=""><span style="color:#E1E4E8">                actual_prompt </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> message[</span><span style="color:#79B8FF">len</span><span style="color:#E1E4E8">(trigger):].strip()</span></span>
<span data-line=""><span style="color:#F97583">                return</span><span style="color:#E1E4E8"> persona_type, actual_prompt</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F97583">    raise</span><span style="color:#79B8FF"> ValueError</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">"no_trigger: AI 캐릭터 호출이 감지되지 않았습니다"</span><span style="color:#E1E4E8">)</span></span></code></pre></figure>
<h3 lang="en" id="캐릭터별-시스템-프롬프트">System Prompts for Each Character</h3>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="python" data-theme="github-dark"><code data-language="python" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#79B8FF">BASE_INSTRUCTIONS</span><span style="color:#F97583"> =</span><span style="color:#9ECBFF"> """</span></span>
<span data-line=""><span style="color:#9ECBFF">다음 규칙을 반드시 따르세요:</span></span>
<span data-line=""><span style="color:#9ECBFF">- 파일을 절대 수정하지 않습니다</span></span>
<span data-line=""><span style="color:#9ECBFF">- 100단어 이내로 간결하게 대답합니다</span></span>
<span data-line=""><span style="color:#9ECBFF">- 최신 정보가 필요하면 웹검색을 활용합니다</span></span>
<span data-line=""><span style="color:#9ECBFF">- 아이들에게 적합한 언어를 사용합니다</span></span>
<span data-line=""><span style="color:#9ECBFF">"""</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F97583">def</span><span style="color:#B392F0"> get_persona_prompt</span><span style="color:#E1E4E8">(persona_type: PersonaType, user_message: </span><span style="color:#79B8FF">str</span><span style="color:#E1E4E8">) -> </span><span style="color:#79B8FF">str</span><span style="color:#E1E4E8">:</span></span>
<span data-line=""><span style="color:#9ECBFF">    """캐릭터별 시스템 프롬프트와 사용자 메시지를 조합합니다."""</span></span>
<span data-line=""><span style="color:#E1E4E8">    persona </span><span style="color:#F97583">=</span><span style="color:#79B8FF"> PERSONAS</span><span style="color:#E1E4E8">[persona_type]</span></span>
<span data-line=""><span style="color:#F97583">    return</span><span style="color:#F97583"> f</span><span style="color:#9ECBFF">"""</span><span style="color:#79B8FF">{</span><span style="color:#E1E4E8">persona.system_prompt</span><span style="color:#79B8FF">}</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#79B8FF">{BASE_INSTRUCTIONS}</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#9ECBFF">사용자 메시지: </span><span style="color:#79B8FF">{</span><span style="color:#E1E4E8">user_message</span><span style="color:#79B8FF">}</span></span>
<span data-line=""><span style="color:#9ECBFF">"""</span></span></code></pre></figure>
<h3 lang="en" id="claudeservice-구현">ClaudeService Implementation</h3>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="python" data-theme="github-dark"><code data-language="python" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#6A737D"># app/services/claude_service.py</span></span>
<span data-line=""><span style="color:#F97583">import</span><span style="color:#E1E4E8"> asyncio</span></span>
<span data-line=""><span style="color:#F97583">import</span><span style="color:#E1E4E8"> time</span></span>
<span data-line=""><span style="color:#F97583">from</span><span style="color:#E1E4E8"> dataclasses </span><span style="color:#F97583">import</span><span style="color:#E1E4E8"> dataclass</span></span>
<span data-line=""><span style="color:#F97583">from</span><span style="color:#E1E4E8"> app.config </span><span style="color:#F97583">import</span><span style="color:#E1E4E8"> get_settings</span></span>
<span data-line=""><span style="color:#F97583">from</span><span style="color:#E1E4E8"> app.services.personas </span><span style="color:#F97583">import</span><span style="color:#E1E4E8"> detect_persona, get_persona_prompt, </span><span style="color:#79B8FF">PERSONAS</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#E1E4E8">settings </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> get_settings()</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#B392F0">@dataclass</span></span>
<span data-line=""><span style="color:#F97583">class</span><span style="color:#B392F0"> ClaudeResponse</span><span style="color:#E1E4E8">:</span></span>
<span data-line=""><span style="color:#E1E4E8">    output: </span><span style="color:#79B8FF">str</span></span>
<span data-line=""><span style="color:#E1E4E8">    elapsed_ms: </span><span style="color:#79B8FF">int</span></span>
<span data-line=""><span style="color:#E1E4E8">    truncated: </span><span style="color:#79B8FF">bool</span><span style="color:#F97583"> =</span><span style="color:#79B8FF"> False</span></span>
<span data-line=""><span style="color:#E1E4E8">    persona_name: </span><span style="color:#79B8FF">str</span><span style="color:#F97583"> |</span><span style="color:#79B8FF"> None</span><span style="color:#F97583"> =</span><span style="color:#79B8FF"> None</span><span style="color:#6A737D">  # AI 캐릭터 이름</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F97583">class</span><span style="color:#B392F0"> ClaudeService</span><span style="color:#E1E4E8">:</span></span>
<span data-line=""><span style="color:#F97583">    def</span><span style="color:#79B8FF"> __init__</span><span style="color:#E1E4E8">(self):</span></span>
<span data-line=""><span style="color:#79B8FF">        self</span><span style="color:#E1E4E8">.cli_path </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> settings.claude_cli_path</span></span>
<span data-line=""><span style="color:#79B8FF">        self</span><span style="color:#E1E4E8">.default_timeout </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> settings.claude_timeout_seconds</span></span>
<span data-line=""><span style="color:#79B8FF">        self</span><span style="color:#E1E4E8">.max_timeout </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> settings.claude_max_timeout_seconds</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F97583">    async</span><span style="color:#F97583"> def</span><span style="color:#B392F0"> chat</span><span style="color:#E1E4E8">(</span></span>
<span data-line=""><span style="color:#E1E4E8">        self,</span></span>
<span data-line=""><span style="color:#E1E4E8">        prompt: </span><span style="color:#79B8FF">str</span><span style="color:#E1E4E8">,</span></span>
<span data-line=""><span style="color:#E1E4E8">        timeout_seconds: </span><span style="color:#79B8FF">int</span><span style="color:#F97583"> |</span><span style="color:#79B8FF"> None</span><span style="color:#F97583"> =</span><span style="color:#79B8FF"> None</span></span>
<span data-line=""><span style="color:#E1E4E8">    ) -> ClaudeResponse:</span></span>
<span data-line=""><span style="color:#6A737D">        # 1. 페르소나 감지 (없으면 ValueError 발생)</span></span>
<span data-line=""><span style="color:#E1E4E8">        persona_type, actual_prompt </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> detect_persona(prompt)</span></span>
<span data-line=""><span style="color:#E1E4E8">        persona </span><span style="color:#F97583">=</span><span style="color:#79B8FF"> PERSONAS</span><span style="color:#E1E4E8">[persona_type]</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#6A737D">        # 2. 캐릭터별 프롬프트 생성</span></span>
<span data-line=""><span style="color:#E1E4E8">        full_prompt </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> get_persona_prompt(persona_type, actual_prompt)</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#E1E4E8">        timeout </span><span style="color:#F97583">=</span><span style="color:#79B8FF"> min</span><span style="color:#E1E4E8">(</span></span>
<span data-line=""><span style="color:#E1E4E8">            timeout_seconds </span><span style="color:#F97583">or</span><span style="color:#79B8FF"> self</span><span style="color:#E1E4E8">.default_timeout,</span></span>
<span data-line=""><span style="color:#79B8FF">            self</span><span style="color:#E1E4E8">.max_timeout</span></span>
<span data-line=""><span style="color:#E1E4E8">        )</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#E1E4E8">        cmd </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> [</span></span>
<span data-line=""><span style="color:#79B8FF">            self</span><span style="color:#E1E4E8">.cli_path,</span></span>
<span data-line=""><span style="color:#9ECBFF">            "--dangerously-skip-permissions"</span><span style="color:#E1E4E8">,</span></span>
<span data-line=""><span style="color:#9ECBFF">            "-p"</span><span style="color:#E1E4E8">,</span></span>
<span data-line=""><span style="color:#E1E4E8">            full_prompt</span></span>
<span data-line=""><span style="color:#E1E4E8">        ]</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#E1E4E8">        start_time </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> time.time()</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#E1E4E8">        process </span><span style="color:#F97583">=</span><span style="color:#F97583"> await</span><span style="color:#E1E4E8"> asyncio.create_subprocess_exec(</span></span>
<span data-line=""><span style="color:#F97583">            *</span><span style="color:#E1E4E8">cmd,</span></span>
<span data-line=""><span style="color:#FFAB70">            stdin</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">asyncio.subprocess.</span><span style="color:#79B8FF">DEVNULL</span><span style="color:#E1E4E8">,  </span><span style="color:#6A737D"># systemd 환경에서 필수!</span></span>
<span data-line=""><span style="color:#FFAB70">            stdout</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">asyncio.subprocess.</span><span style="color:#79B8FF">PIPE</span><span style="color:#E1E4E8">,</span></span>
<span data-line=""><span style="color:#FFAB70">            stderr</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">asyncio.subprocess.</span><span style="color:#79B8FF">PIPE</span></span>
<span data-line=""><span style="color:#E1E4E8">        )</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F97583">        try</span><span style="color:#E1E4E8">:</span></span>
<span data-line=""><span style="color:#E1E4E8">            stdout, stderr </span><span style="color:#F97583">=</span><span style="color:#F97583"> await</span><span style="color:#E1E4E8"> asyncio.wait_for(</span></span>
<span data-line=""><span style="color:#E1E4E8">                process.communicate(),</span></span>
<span data-line=""><span style="color:#FFAB70">                timeout</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">timeout</span></span>
<span data-line=""><span style="color:#E1E4E8">            )</span></span>
<span data-line=""><span style="color:#F97583">        except</span><span style="color:#E1E4E8"> asyncio.TimeoutError:</span></span>
<span data-line=""><span style="color:#E1E4E8">            process.kill()</span></span>
<span data-line=""><span style="color:#F97583">            raise</span><span style="color:#79B8FF"> TimeoutError</span><span style="color:#E1E4E8">(</span><span style="color:#F97583">f</span><span style="color:#9ECBFF">"Claude CLI timed out after </span><span style="color:#79B8FF">{</span><span style="color:#E1E4E8">timeout</span><span style="color:#79B8FF">}</span><span style="color:#9ECBFF">s"</span><span style="color:#E1E4E8">)</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#E1E4E8">        elapsed_ms </span><span style="color:#F97583">=</span><span style="color:#79B8FF"> int</span><span style="color:#E1E4E8">((time.time() </span><span style="color:#F97583">-</span><span style="color:#E1E4E8"> start_time) </span><span style="color:#F97583">*</span><span style="color:#79B8FF"> 1000</span><span style="color:#E1E4E8">)</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F97583">        if</span><span style="color:#E1E4E8"> process.returncode </span><span style="color:#F97583">!=</span><span style="color:#79B8FF"> 0</span><span style="color:#E1E4E8">:</span></span>
<span data-line=""><span style="color:#E1E4E8">            error_msg </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> stderr.decode() </span><span style="color:#F97583">if</span><span style="color:#E1E4E8"> stderr </span><span style="color:#F97583">else</span><span style="color:#9ECBFF"> "Unknown error"</span></span>
<span data-line=""><span style="color:#F97583">            raise</span><span style="color:#79B8FF"> RuntimeError</span><span style="color:#E1E4E8">(</span><span style="color:#F97583">f</span><span style="color:#9ECBFF">"Claude CLI failed: </span><span style="color:#79B8FF">{</span><span style="color:#E1E4E8">error_msg</span><span style="color:#79B8FF">}</span><span style="color:#9ECBFF">"</span><span style="color:#E1E4E8">)</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#E1E4E8">        output </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> stdout.decode().strip()</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#6A737D">        # 응답 길이 제한 (안전장치)</span></span>
<span data-line=""><span style="color:#79B8FF">        MAX_LENGTH</span><span style="color:#F97583"> =</span><span style="color:#79B8FF"> 5000</span></span>
<span data-line=""><span style="color:#E1E4E8">        truncated </span><span style="color:#F97583">=</span><span style="color:#79B8FF"> len</span><span style="color:#E1E4E8">(output) </span><span style="color:#F97583">></span><span style="color:#79B8FF"> MAX_LENGTH</span></span>
<span data-line=""><span style="color:#F97583">        if</span><span style="color:#E1E4E8"> truncated:</span></span>
<span data-line=""><span style="color:#E1E4E8">            output </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> output[:</span><span style="color:#79B8FF">MAX_LENGTH</span><span style="color:#E1E4E8">] </span><span style="color:#F97583">+</span><span style="color:#9ECBFF"> "..."</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F97583">        return</span><span style="color:#E1E4E8"> ClaudeResponse(</span></span>
<span data-line=""><span style="color:#FFAB70">            output</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">output,</span></span>
<span data-line=""><span style="color:#FFAB70">            elapsed_ms</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">elapsed_ms,</span></span>
<span data-line=""><span style="color:#FFAB70">            truncated</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">truncated,</span></span>
<span data-line=""><span style="color:#FFAB70">            persona_name</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">persona.name  </span><span style="color:#6A737D"># 말랑이, 루팡, 푸딩, 마이콜</span></span>
<span data-line=""><span style="color:#E1E4E8">        )</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#6A737D"># 의존성 주입용</span></span>
<span data-line=""><span style="color:#F97583">def</span><span style="color:#B392F0"> get_claude_service</span><span style="color:#E1E4E8">() -> ClaudeService:</span></span>
<span data-line=""><span style="color:#F97583">    return</span><span style="color:#E1E4E8"> ClaudeService()</span></span></code></pre></figure>
<h3 lang="en" id="설정-추가">Additional Configuration</h3>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="python" data-theme="github-dark"><code data-language="python" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#6A737D"># app/config.py</span></span>
<span data-line=""><span style="color:#F97583">class</span><span style="color:#B392F0"> Settings</span><span style="color:#E1E4E8">(</span><span style="color:#B392F0">BaseSettings</span><span style="color:#E1E4E8">):</span></span>
<span data-line=""><span style="color:#6A737D">    # ... 기존 설정 ...</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#6A737D">    # Claude CLI</span></span>
<span data-line=""><span style="color:#E1E4E8">    claude_cli_path: </span><span style="color:#79B8FF">str</span><span style="color:#F97583"> =</span><span style="color:#9ECBFF"> "claude"</span></span>
<span data-line=""><span style="color:#E1E4E8">    claude_timeout_seconds: </span><span style="color:#79B8FF">int</span><span style="color:#F97583"> =</span><span style="color:#79B8FF"> 120</span></span>
<span data-line=""><span style="color:#E1E4E8">    claude_max_timeout_seconds: </span><span style="color:#79B8FF">int</span><span style="color:#F97583"> =</span><span style="color:#79B8FF"> 300</span></span></code></pre></figure>
<h2 lang="en" id="결론">Conclusion</h2>
<h3 lang="en" id="완성된-기능">Completed Features</h3>
<ol lang="en">
<li><strong>Firebase authentication</strong>: JWT-based user authentication</li>
<li><strong>AI chat API</strong>: generate AI responses using Claude Code CLI</li>
<li><strong>AI personas</strong>: four characters with distinct personalities and response styles</li>
<li><strong>Security</strong>: mandatory authentication, prompt-length limits, and timeouts</li>
</ol>
<h3 lang="en" id="프로젝트-구조">Project Structure</h3>
<pre><code>backend-api/
├── app/
│   ├── main.py              # FastAPI 앱 엔트리포인트
│   ├── config.py            # 설정 (pydantic-settings)
│   ├── firebase.py          # Firebase Admin SDK 초기화
│   ├── dependencies.py      # 인증 의존성
│   ├── routers/
│   │   ├── auth.py          # /auth 엔드포인트
│   │   └── ai.py            # /ai 엔드포인트
│   ├── schemas/
│   │   └── ai.py            # Pydantic 스키마
│   └── services/
│       ├── claude_service.py # Claude CLI 서비스
│       └── personas.py       # AI 페르소나 정의
├── tests/
│   ├── test_auth.py         # 인증 테스트
│   └── test_ai.py           # AI 채팅 테스트
└── requirements.txt
</code></pre>
<h3 lang="en" id="트러블-슈팅">Troubleshooting</h3>
<p lang="en">AI chat worked correctly in local development, but after deployment to production:</p>
<pre><code>"인증이 만료되었습니다. 다시 로그인해주세요." (401)
→ "AI 서비스를 이용할 수 없습니다." (503)
→ "Failed to fetch" (타임아웃)
</code></pre>
<p lang="en">As the error messages changed one after another, I discovered three separate problems.</p>
<h2 lang="en" id="문제-1-firebase-인증-실패-401">Problem 1: Firebase Authentication Failure (401)</h2>
<h3 lang="en" id="증상">Symptoms</h3>
<pre><code>INFO: 192.168.0.1:0 - "POST /ai/chat HTTP/1.1" 401 Unauthorized
</code></pre>
<p lang="en">The frontend showed an “Authentication has expired” error.</p>
<h3 lang="en" id="원인-분석">Diagnosing the Cause</h3>
<p lang="en"><strong>Cause</strong>: the Firebase service-account key file was missing from the server.</p>
<h3 lang="en" id="해결">Fix</h3>
<p lang="en">Copy the file from the local machine to the server:</p>
<hr lang="en">
<h2 lang="en" id="문제-2-claude-cli-미발견-503">Problem 2: Claude CLI Not Found (503)</h2>
<h3 lang="en" id="증상-1">Symptoms</h3>
<pre><code>Claude CLI not found at: claude
INFO: 192.168.0.1:0 - "POST /ai/chat HTTP/1.1" 503 Service Unavailable
</code></pre>
<h3 lang="en" id="원인-분석-1">Diagnosing the Cause</h3>
<p lang="en">Check the Claude CLI installation:</p>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="bash" data-theme="github-dark"><code data-language="bash" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#79B8FF">which</span><span style="color:#9ECBFF"> claude</span><span style="color:#E1E4E8"> &#x26;&#x26; </span><span style="color:#B392F0">claude</span><span style="color:#79B8FF"> --version</span></span>
<span data-line=""><span style="color:#6A737D"># /home/funq/.local/bin/claude</span></span>
<span data-line=""><span style="color:#6A737D"># 2.0.75 (Claude Code)</span></span></code></pre></figure>
<p lang="en">The CLI was installed, but the systemd service could not find it.</p>
<p lang="en"><strong>Cause</strong>: the systemd service's PATH did not include <code>~/.local/bin</code>.</p>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="ini" data-theme="github-dark"><code data-language="ini" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#6A737D"># /etc/systemd/system/backend-api.service</span></span>
<span data-line=""><span style="color:#F97583">Environment</span><span style="color:#E1E4E8">=</span><span style="color:#9ECBFF">"PATH=/home/funq/dev/backend-api/venv/bin"</span></span></code></pre></figure>
<h3 lang="en" id="해결-1">Fix</h3>
<p lang="en">Add the full path to <code>.env</code>:</p>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="bash" data-theme="github-dark"><code data-language="bash" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#79B8FF">echo</span><span style="color:#9ECBFF"> 'CLAUDE_CLI_PATH=/home/funq/.local/bin/claude'</span><span style="color:#F97583"> >></span><span style="color:#9ECBFF"> ~/backend-api/.env</span></span>
<span data-line=""><span style="color:#B392F0">sudo</span><span style="color:#9ECBFF"> systemctl</span><span style="color:#9ECBFF"> restart</span><span style="color:#9ECBFF"> backend-api</span></span></code></pre></figure>
<p lang="en"><strong>Note</strong>: <code>config.py</code> reads and uses this environment variable:</p>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="python" data-theme="github-dark"><code data-language="python" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#F97583">class</span><span style="color:#B392F0"> Settings</span><span style="color:#E1E4E8">(</span><span style="color:#B392F0">BaseSettings</span><span style="color:#E1E4E8">):</span></span>
<span data-line=""><span style="color:#E1E4E8">    claude_cli_path: </span><span style="color:#79B8FF">str</span><span style="color:#F97583"> =</span><span style="color:#9ECBFF"> "claude"</span><span style="color:#6A737D">  # .env의 CLAUDE_CLI_PATH로 오버라이드됨</span></span></code></pre></figure>
<h3 lang="en" id="교훈">Lessons</h3>
<ul lang="en">
<li>A systemd service runs in a different environment from the user's shell</li>
<li>Always using absolute paths for external CLI tools is recommended</li>
</ul>
<hr lang="en">
<h2 lang="en" id="문제-3-claude-cli-타임아웃">Problem 3: Claude CLI Timeout</h2>
<h3 lang="en" id="증상-2">Symptoms</h3>
<p lang="en">After fixing the CLI path, another error appeared:</p>
<pre><code>Claude CLI timeout after 120060ms
</code></pre>
<p lang="en">It failed after 120 seconds, the default timeout.</p>
<h3 lang="en" id="원인-분석-2">Diagnosing the Cause</h3>
<h4 lang="en" id="1단계-터미널에서-직접-테스트">Step 1: Test Directly in the Terminal</h4>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="bash" data-theme="github-dark"><code data-language="bash" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#B392F0">timeout</span><span style="color:#79B8FF"> 30</span><span style="color:#9ECBFF"> claude</span><span style="color:#79B8FF"> -p</span><span style="color:#9ECBFF"> "안녕"</span><span style="color:#79B8FF"> --dangerously-skip-permissions</span></span>
<span data-line=""><span style="color:#6A737D"># 성공! "안녕하세요! FastAPI 백엔드 프로젝트에서 무엇을 도와드릴까요?"</span></span></code></pre></figure>
<p lang="en">It worked correctly in the terminal.</p>
<h4 lang="en" id="2단계-systemd-환경-확인">Step 2: Check the systemd Environment</h4>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="bash" data-theme="github-dark"><code data-language="bash" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#B392F0">sudo</span><span style="color:#9ECBFF"> cat</span><span style="color:#9ECBFF"> /etc/systemd/system/backend-api.service</span></span></code></pre></figure>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="ini" data-theme="github-dark"><code data-language="ini" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#B392F0">[Service]</span></span>
<span data-line=""><span style="color:#F97583">User</span><span style="color:#E1E4E8">=funq</span></span>
<span data-line=""><span style="color:#F97583">Group</span><span style="color:#E1E4E8">=funq</span></span>
<span data-line=""><span style="color:#F97583">WorkingDirectory</span><span style="color:#E1E4E8">=/home/funq/dev/backend-api</span></span>
<span data-line=""><span style="color:#F97583">Environment</span><span style="color:#E1E4E8">=</span><span style="color:#9ECBFF">"PATH=/home/funq/dev/backend-api/venv/bin"</span></span>
<span data-line=""><span style="color:#F97583">ExecStart</span><span style="color:#E1E4E8">=/home/funq/dev/backend-api/venv/bin/uvicorn app.main:app --host 0.0.0.0 --port 8000</span></span></code></pre></figure>
<p lang="en"><strong>Discovery</strong>: the <code>HOME</code> environment variable was missing! Claude CLI needs HOME to find its <code>~/.claude/</code> configuration.</p>
<h4 lang="en" id="3단계-tty-없이-테스트">Step 3: Test Without a TTY</h4>
<p lang="en">systemd runs without a TTY (terminal):</p>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="bash" data-theme="github-dark"><code data-language="bash" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#B392F0">timeout</span><span style="color:#79B8FF"> 30</span><span style="color:#9ECBFF"> setsid</span><span style="color:#9ECBFF"> claude</span><span style="color:#79B8FF"> -p</span><span style="color:#9ECBFF"> "안녕"</span><span style="color:#79B8FF"> --dangerously-skip-permissions</span><span style="color:#F97583"> &#x3C;</span><span style="color:#9ECBFF">/dev/null</span><span style="color:#F97583"> 2>&#x26;1</span></span>
<span data-line=""><span style="color:#6A737D"># 성공!</span></span></code></pre></figure>
<p lang="en">It worked without a TTY too. The problem was elsewhere...</p>
<h4 lang="en" id="4단계-python-코드-분석">Step 4: Analyze the Python Code</h4>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="python" data-theme="github-dark"><code data-language="python" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#6A737D"># app/services/claude_service.py</span></span>
<span data-line=""><span style="color:#E1E4E8">process </span><span style="color:#F97583">=</span><span style="color:#F97583"> await</span><span style="color:#E1E4E8"> asyncio.create_subprocess_exec(</span></span>
<span data-line=""><span style="color:#F97583">    *</span><span style="color:#E1E4E8">cmd,</span></span>
<span data-line=""><span style="color:#FFAB70">    stdout</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">asyncio.subprocess.</span><span style="color:#79B8FF">PIPE</span><span style="color:#E1E4E8">,</span></span>
<span data-line=""><span style="color:#FFAB70">    stderr</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">asyncio.subprocess.</span><span style="color:#79B8FF">PIPE</span><span style="color:#E1E4E8">,</span></span>
<span data-line=""><span style="color:#6A737D">    # stdin이 없음!</span></span>
<span data-line=""><span style="color:#E1E4E8">)</span></span></code></pre></figure>
<p lang="en"><strong>Root cause</strong>: <code>stdin</code> was not explicitly set, so the subprocess inherited its parent's stdin. Under systemd, stdin can be in a special state instead of <code>/dev/null</code>, leaving Claude CLI waiting.</p>
<h3 lang="en" id="해결-2">Fix</h3>
<h4 lang="en" id="1-systemd-서비스-파일-수정">1. Modify the systemd Service File</h4>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="ini" data-theme="github-dark"><code data-language="ini" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#B392F0">[Service]</span></span>
<span data-line=""><span style="color:#F97583">User</span><span style="color:#E1E4E8">=funq</span></span>
<span data-line=""><span style="color:#F97583">Group</span><span style="color:#E1E4E8">=funq</span></span>
<span data-line=""><span style="color:#F97583">WorkingDirectory</span><span style="color:#E1E4E8">=/home/funq/dev/backend-api</span></span>
<span data-line=""><span style="color:#F97583">Environment</span><span style="color:#E1E4E8">=</span><span style="color:#9ECBFF">"PATH=/home/funq/dev/backend-api/venv/bin:/home/funq/.local/bin"</span></span>
<span data-line=""><span style="color:#F97583">Environment</span><span style="color:#E1E4E8">=</span><span style="color:#9ECBFF">"HOME=/home/funq"</span></span>
<span data-line=""><span style="color:#F97583">ExecStart</span><span style="color:#E1E4E8">=/home/funq/dev/backend-api/venv/bin/uvicorn app.main:app --host 0.0.0.0 --port 8000</span></span>
<span data-line=""><span style="color:#F97583">Restart</span><span style="color:#E1E4E8">=always</span></span>
<span data-line=""><span style="color:#F97583">RestartSec</span><span style="color:#E1E4E8">=3</span></span></code></pre></figure>
<p lang="en">Changes:</p>
<ul lang="en">
<li>Add <code>HOME=/home/funq</code></li>
<li>Add <code>~/.local/bin</code> to PATH</li>
</ul>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="bash" data-theme="github-dark"><code data-language="bash" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#B392F0">sudo</span><span style="color:#9ECBFF"> systemctl</span><span style="color:#9ECBFF"> daemon-reload</span></span>
<span data-line=""><span style="color:#B392F0">sudo</span><span style="color:#9ECBFF"> systemctl</span><span style="color:#9ECBFF"> restart</span><span style="color:#9ECBFF"> backend-api</span></span></code></pre></figure>
<h4 lang="en" id="2-python-코드-수정">2. Modify the Python Code</h4>
<figure lang="en" data-rehype-pretty-code-figure=""><pre style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="python" data-theme="github-dark"><code data-language="python" data-theme="github-dark" style="display: grid;"><span data-line=""><span style="color:#6A737D"># app/services/claude_service.py</span></span>
<span data-line=""><span style="color:#E1E4E8">process </span><span style="color:#F97583">=</span><span style="color:#F97583"> await</span><span style="color:#E1E4E8"> asyncio.create_subprocess_exec(</span></span>
<span data-line=""><span style="color:#F97583">    *</span><span style="color:#E1E4E8">cmd,</span></span>
<span data-line=""><span style="color:#FFAB70">    stdin</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">asyncio.subprocess.</span><span style="color:#79B8FF">DEVNULL</span><span style="color:#E1E4E8">,  </span><span style="color:#6A737D"># 추가!</span></span>
<span data-line=""><span style="color:#FFAB70">    stdout</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">asyncio.subprocess.</span><span style="color:#79B8FF">PIPE</span><span style="color:#E1E4E8">,</span></span>
<span data-line=""><span style="color:#FFAB70">    stderr</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">asyncio.subprocess.</span><span style="color:#79B8FF">PIPE</span><span style="color:#E1E4E8">,</span></span>
<span data-line=""><span style="color:#E1E4E8">)</span></span></code></pre></figure>
<p lang="en">Add <code>stdin=asyncio.subprocess.DEVNULL</code> so the subprocess does not wait for stdin.</p>
<hr lang="en">]]></content:encoded>
    </item>
  </channel>
</rss>