diff --git a/.jules/bolt.md b/.jules/bolt.md
index 19b4c61..8f849d1 100644
--- a/.jules/bolt.md
+++ b/.jules/bolt.md
@@ -43,3 +43,6 @@
## 2025-01-24 - 단일 readAttributes 호출로 파일 속성 조회 최적화
**학습:** `isDirectory`, `!it.isDirectory()`, `isSymbolicLink` 3개의 개별적인 파일 시스템 I/O 호출을 수행하면 성능 저하가 큽니다. 이를 단일 `Files.readAttributes` 호출로 변경하여 메타데이터를 한 번에 조회함으로써 I/O 오버헤드를 대폭 줄일 수 있음을 확인했습니다.
**조치:** 디렉토리 순회 시 파일의 여러 속성을 확인할 때는 개별적인 stat 호출보다 `Files.readAttributes`를 사용하여 필요한 모든 속성을 한 번에 가져오는 방식을 우선적으로 고려해야 합니다.
+## 2026-07-16 - [Extracting Static Content from Loops in Kotlin]
+**Learning:** In Kotlin, static strings and expensive hashing logic computed inside loops (or repeatedly called functions like `process_dir`) can incur significant performance overhead due to constant re-evaluation and reallocation. Hoisting these to top-level variables caches the result.
+**Action:** Use `private val` for hoisted top-level constants in Kotlin to prevent automatic getter generation, which optimizes memory and cleanly preserves 100% test coverage without requiring synthetic getter tests.
diff --git a/src/main/kotlin/html4tree/main.kt b/src/main/kotlin/html4tree/main.kt
index b455862..33f9279 100644
--- a/src/main/kotlin/html4tree/main.kt
+++ b/src/main/kotlin/html4tree/main.kt
@@ -13,6 +13,83 @@ import com.github.ajalt.clikt.parameters.options.default
import com.github.ajalt.clikt.parameters.arguments.argument
import com.github.ajalt.clikt.parameters.types.int
+// ⚡ Bolt Performance Optimization: Hoisted static variables
+// Moving cssContent, styleHash, and css out of process_dir to top-level avoids
+// repeated string allocations and expensive SHA-256 hashing for every directory processed.
+// private val prevents generating public getters and preserves 100% test coverage.
+private val cssContent = """
+ body {
+ font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
+ line-height: 1.5;
+ padding: 1rem;
+ color: #1f2328;
+ }
+ main {
+ max-width: 800px;
+ margin: 0 auto;
+ }
+ ul {
+ list-style-type: none;
+ padding-left: 0;
+ }
+ a.dir-link {
+ display: flex;
+ align-items: flex-start;
+ gap: 0.5rem;
+ width: 100%;
+ overflow-wrap: anywhere;
+ box-sizing: border-box;
+ }
+ .icon {
+ flex-shrink: 0;
+ width: 1.25rem;
+ text-align: center;
+ }
+ a {
+ padding: 0.5rem;
+ text-decoration: none;
+ color: #0969da;
+ border-radius: 4px;
+ transition: background-color 0.2s ease, outline-color 0.2s ease;
+ }
+ a:hover, a:focus-visible {
+ background-color: #f6f8fa;
+ text-decoration: underline;
+ outline: 2px solid #0969da;
+ outline-offset: -2px;
+ }
+ @media (prefers-reduced-motion: reduce) {
+ a {
+ transition: none;
+ }
+ }
+ @media (prefers-color-scheme: dark) {
+ body {
+ background-color: #0d1117;
+ color: #c9d1d9;
+ }
+ a {
+ color: #58a6ff;
+ }
+ a:hover, a:focus-visible {
+ background-color: #161b22;
+ outline-color: #58a6ff;
+ }
+ }
+ .empty-dir {
+ padding: 0.5rem;
+ opacity: 0.7;
+ font-style: italic;
+ }
+ """
+
+private val styleHash = "sha256-" + Base64.getEncoder().encodeToString(MessageDigest.getInstance("SHA-256").digest(cssContent.toByteArray(Charsets.UTF_8)))
+
+private val css = """
+
+ """
+
class Html4tree : CliktCommand() {
val maxLevel:Int by option(help="Number of levels deep for which to generate an index.html file", hidden = false).int().default(-1)
val topDir: String by argument(help="Top directory to crawl")
@@ -244,79 +321,6 @@ fun process_dir(curr_dir: File, excludeSet: Set? = null, dirFiles: Array
val exclude: Set = excludeSet ?: process_ignore_file(curr_dir)
- val cssContent = """
- body {
- font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
- line-height: 1.5;
- padding: 1rem;
- color: #1f2328;
- }
- main {
- max-width: 800px;
- margin: 0 auto;
- }
- ul {
- list-style-type: none;
- padding-left: 0;
- }
- a.dir-link {
- display: flex;
- align-items: flex-start;
- gap: 0.5rem;
- width: 100%;
- overflow-wrap: anywhere;
- box-sizing: border-box;
- }
- .icon {
- flex-shrink: 0;
- width: 1.25rem;
- text-align: center;
- }
- a {
- padding: 0.5rem;
- text-decoration: none;
- color: #0969da;
- border-radius: 4px;
- transition: background-color 0.2s ease, outline-color 0.2s ease;
- }
- a:hover, a:focus-visible {
- background-color: #f6f8fa;
- text-decoration: underline;
- outline: 2px solid #0969da;
- outline-offset: -2px;
- }
- @media (prefers-reduced-motion: reduce) {
- a {
- transition: none;
- }
- }
- @media (prefers-color-scheme: dark) {
- body {
- background-color: #0d1117;
- color: #c9d1d9;
- }
- a {
- color: #58a6ff;
- }
- a:hover, a:focus-visible {
- background-color: #161b22;
- outline-color: #58a6ff;
- }
- }
- .empty-dir {
- padding: 0.5rem;
- opacity: 0.7;
- font-style: italic;
- }
- """
-
- val styleHash = "sha256-" + Base64.getEncoder().encodeToString(MessageDigest.getInstance("SHA-256").digest(cssContent.toByteArray(Charsets.UTF_8)))
-
- val css = """
-
- """
-
val index_top = """