diff --git a/README.ko.md b/README.ko.md index c2ba49e..3f5ebea 100644 --- a/README.ko.md +++ b/README.ko.md @@ -56,9 +56,8 @@ bundle exec jekyll build # 결과물은 _site/ 에 생성 ``` > **왜 `github-pages` gem이 아니라 순수 `jekyll`인가요?** -> 이 사이트는 `_plugins/`에 직접 만든 Ruby 플러그인(읽기 시간, 이미지 lazy-load, 글 설명문 생성)을 씁니다. -> `github-pages` gem은 보안 샌드박스 때문에 커스텀 플러그인을 막으므로, -> 로컬과 CI 모두 Jekyll을 직접 실행합니다. +> 이 사이트는 `_plugins/`에 직접 만든 Ruby 플러그인을 씁니다. `github-pages` gem은 보안 +> 샌드박스 때문에 커스텀 플러그인을 막으므로, 로컬과 CI 모두 Jekyll을 직접 실행합니다. --- @@ -66,7 +65,7 @@ bundle exec jekyll build # 결과물은 _site/ 에 생성 ``` _posts/ 글 — YYYY-MM-DD-slug.md (한국어 기본, 영어 번역본은 -en.md) -_layouts/ 페이지 템플릿: default → post / page / archive +_layouts/ 페이지 템플릿: default → post / page _includes/ 재사용 조각: head, header, footer, nav_links, page_divider, category-posts, language_switcher, related_posts @@ -76,7 +75,7 @@ _sass/ 스타일: _layout, _post, _tags, _syntax(Rouge 코드 테마 _plugins/ reading_time.rb (한·영 읽기 시간 계산) lazy_images.rb (에 loading="lazy" 추가) post_description.rb (글의 page.description 채우기) - related_posts.rb (글의 page.related 채우기) + related_posts.rb (page.related, 그리고 이전/다음 글 링크) scrollable_tables.rb (넓은 표를 스크롤 컨테이너로 감싸기) css/ main.scss(Sass 진입점) · search.css(검색 페이지 전용) js/ main.js(테마 토글·코드 복사·목차·메뉴·이미지 확대 등) @@ -138,20 +137,29 @@ RSS ``에 실리는 문장입니다. 생략하면 `_plugins/post_descri 카테고리는 **두 단계**입니다. - `categories[0]` — **유형**: `Paper Reviews`, `Paper Summaries`, `Tech Guides`, - `Insights` 중 하나. 글이 어느 섹션 페이지에 들어갈지를 정합니다. 현재 글이 있는 유형은 - `Paper Reviews`와 `Insights`뿐이라 내비에도 이 둘만 있습니다 — `paper-summaries.md`와 - `tech-guides.md`는 `main_nav` 없이 `sitemap: false` 상태이고, 첫 글이 들어오면 어떤 두 - 값을 되살리면 되는지가 각 파일 프런트매터에 적혀 있습니다. + `Insights` 중 하나. 글이 어느 내비 탭에 들어갈지를 정합니다. 아직 글이 없는 유형도 + **탭을 유지하고** 빈 상태 문구를 띄웁니다 — 탭이 없으면 채우는 중인 섹션이 아니라 + 없어진 섹션으로 읽히기 때문입니다. - `categories[1]` — **주제**: `Language-Models`, `Multimodal-Learning`, `Finetuning`, `Retrieval-Augmented-Generation`, `Agentic-AI` 등. 필요하면 자유롭게 추가합니다. +Jekyll은 이 둘과 날짜를 합쳐 출력 경로를 만듭니다. + +``` +categories: ["Paper Reviews", "Language-Models"] + date: 2025-01-23 + ↓ +_site/paper reviews/language-models/2025/01/23/.html +``` + +그래서 **이미 게시된 글의 카테고리나 날짜를 바꾸면 URL이 바뀝니다.** 외부에서 걸린 링크와 +검색 결과가 깨지므로, 한 번 정하면 그대로 둡니다. + ### 태그: 세부 태그 위에 주제 태그를 하나 -태그는 자유 형식·하이픈 연결이고, 대부분 논문 하나의 기여를 서술합니다 -(`Fine-Grained-Expert-Segmentation`). 색인으로는 좋지만 글을 **잇지는** 못합니다 — 태그 -264개 중 220개가 단 한 편에만 붙어 있었고, `_plugins/related_posts.rb`는 **공유** 태그를 -요구하므로 11편이 관련 글을 하나도 얻지 못했습니다. 서로 남남이던 그래프 RAG 논문 네 편이 -거기 있었습니다. +태그는 자유 형식·하이픈 연결입니다. 논문 하나의 기여를 그대로 옮긴 태그 +(`Fine-Grained-Expert-Segmentation`)는 그 논문에만 붙을 수 있습니다. 색인으로는 정확하지만 +글을 **잇지는** 못하고, `_plugins/related_posts.rb`가 **공유** 태그를 요구하므로 그렇게만 +달린 글은 "Related reading"이 아예 붙지 않은 채 배포됩니다. 그래서 글마다 아래 통제된 주제 태그를 최소 하나 답니다. @@ -162,17 +170,11 @@ Retrieval-Augmented-Generation ``` 기존 세부 태그는 **그대로 남깁니다.** 주제 태그가 담지 못하는 내용을 담고 있고, 지우면 이미 -살아 있는 `/tags/` 앵커가 움직입니다. 새 주제 태그를 만들기 전에 위 목록이나 이미 두 편 -이상이 쓰는 태그로 덮이는지 먼저 확인하세요. `Agentic-Architecture`·`Agentic-Patterns`· -`Agentic-Infrastructure`가 한 주제의 세 이름이었던 것이 그 묶음에 링크가 없던 이유입니다. - -Jekyll은 이 둘과 날짜를 합쳐 출력 경로를 만듭니다. +살아 있는 `/tags/` 앵커가 움직입니다. **주제 태그는 얹는 것이지 바꿔 다는 게 아닙니다.** -``` -categories: ["Paper Reviews", "Language-Models"] + date: 2025-01-23 - ↓ -_site/paper reviews/language-models/2025/01/23/.html -``` +새 주제 태그를 만들기 전에 위 목록에 이미 덮이는 게 없는지 확인하세요. 한 개념을 세 이름으로 +부르면(`Agentic-Architecture`·`Agentic-Patterns`·`Agentic-Infrastructure`) 글마다 아무도 +공유하지 않는 태그를 하나씩 들고 있게 되고, 그건 주제 태그가 없는 것과 같습니다. ### 수식은 반드시 `$$…$$`로 @@ -186,39 +188,39 @@ kramdown이 내용을 그대로 보존해 `\(…\)`로 내보내므로 안전합 ### 푸시 전에 확인하세요 +CI가 돌리는 네 게이트를 같은 순서로 미리 돌립니다. `_site/`가 아니라 임시 경로로 빌드하는 +이유는 아래 주의에 있습니다. + ```bash -ruby test/run_all.rb # 플러그인 로직이 여전히 맞나? -bundle exec jekyll build # 빌드가 깨끗한가? -bundle exec htmlproofer ./_site --disable-external # 깨진 링크·이미지는 없나? -script/validate-site.sh # 사이트맵·피드·메타데이터·헤딩 +ruby test/run_all.rb # 플러그인 로직이 여전히 맞나? +bundle exec jekyll build --strict-front-matter \ + --destination /tmp/site-verify # 빌드가 깨끗한가? +bundle exec htmlproofer /tmp/site-verify --disable-external \ + --allow-hash-href --no-enforce-https # 깨진 링크·이미지·앵커는 없나? +script/validate-site.sh /tmp/site-verify # 사이트맵·피드·메타데이터·헤딩 ``` -CI도 같은 세 검사를 돌립니다. 로컬에서 미리 잡으면 배포 실패를 막을 수 있습니다. - -`test/`는 `_plugins/`를 검사합니다 — 설명문 추출, 읽기 시간 계산, 이미지 lazy-load -치환. `bundle exec`가 아니라 plain `ruby`인 이유는, 플러그인이 Jekyll·Liquid 등록을 -`defined?`로 감싸 로직만 단독 로드되게 해 두었고 minitest는 Ruby에 기본 포함이기 -때문입니다. **`_plugins/`를 건드리면 사이트의 모든 페이지가 바뀝니다.** 동작을 바꾸기 -전에 테스트 케이스를 먼저 추가하세요. - > **검사 결과가 말이 안 되면 먼저 떠 있는 `jekyll serve`를 찾으세요.** 파일을 감시하며 > `_site/`를 계속 덮어쓰고, `site.url`을 `http://localhost:4000`으로 바꾸며(사이트맵 URL이 > 전부 틀리게 보입니다), 시작할 때 읽은 `_config.yml`을 계속 들고 있어 그 뒤에 추가한 -> `exclude`가 적용되지 않습니다. 종료하거나, 다른 경로로 빌드해서 검사하세요. +> `exclude`가 적용되지 않습니다. 다른 경로로 빌드하면 세 문제를 한꺼번에 비켜 갑니다. > > ```bash > ps aux | grep '[j]ekyll serve' -> bundle exec jekyll build --destination /tmp/site-verify -> script/validate-site.sh /tmp/site-verify > ``` +`test/`는 `_plugins/`의 순수 로직을 플러그인당 한 파일씩 단위 테스트합니다. `bundle exec`가 +아니라 plain `ruby`인 이유는, 각 플러그인이 Jekyll·Liquid 등록을 `defined?`로 감싸 로직만 +단독 로드되게 해 두었고 minitest는 Ruby에 기본 포함이기 때문입니다. **`_plugins/`를 건드리면 +사이트의 모든 페이지가 바뀝니다.** 동작을 바꾸기 전에 테스트 케이스를 먼저 추가하세요. + --- ## 배포 `.github/workflows/jekyll.yml`은 `main` 푸시뿐 아니라 **`main`으로 향하는 PR에서도** -돕니다. 그래서 아래 네 게이트가 잘못된 머지를 보고만 하지 않고 막습니다. 1~4단계는 양쪽 -모두에서, 5단계는 PR에서 건너뜁니다. +돕니다. 그래서 아래 게이트가 잘못된 머지를 사후에 보고하는 데 그치지 않고 막습니다. +1~4단계는 두 경우 모두, 5단계는 PR에서 건너뜁니다. 순서대로: 1. **`ruby test/run_all.rb`**로 `_plugins/` 단위 테스트를 돌리고 2. `JEKYLL_ENV=production`으로 사이트를 빌드하고 @@ -230,8 +232,8 @@ CI도 같은 세 검사를 돌립니다. 로컬에서 미리 잡으면 배포 중복되지 않는지, 저작 원본이 배포되지 않았는지를 검사한 뒤 5. GitHub Pages에 배포합니다. -워크플로우가 실패한다면 대개 3·4단계입니다. Actions 로그에 어떤 링크·이미지·페이지가 -문제인지 그대로 나옵니다. 수동 배포 단계는 없습니다. +실패하면 대개 3·4단계입니다. Actions 로그에 어떤 링크·이미지·페이지가 문제인지 그대로 +나옵니다. 수동 배포 단계는 없습니다. > **⚠ `google*.html` / `naver*.html`을 `_config.yml`의 `exclude`에 넣지 마세요.** > Search Console·네이버 소유권 인증 토큰이라 사이트 루트에 그대로 올라가야 합니다. diff --git a/README.md b/README.md index d6d98a4..58fdd43 100644 --- a/README.md +++ b/README.md @@ -58,9 +58,8 @@ bundle exec jekyll build ``` > **Why plain `jekyll` and not `github-pages`?** This site uses custom Ruby -> plugins in `_plugins/` (read time, lazy images, post descriptions), which the -> sandboxed `github-pages` gem disallows. So both local builds and CI run Jekyll -> directly. +> plugins in `_plugins/`, which the sandboxed `github-pages` gem disallows. So +> both local builds and CI run Jekyll directly. --- @@ -68,7 +67,7 @@ bundle exec jekyll build ``` _posts/ Posts — YYYY-MM-DD-slug.md (Korean; English twin is -en.md) -_layouts/ Page templates: default → post / page / archive +_layouts/ Page templates: default → post / page _includes/ Reusable fragments: head, header, footer, nav_links, page_divider, category-posts, language_switcher, related_posts @@ -78,7 +77,7 @@ _sass/ Styles: _layout, _post, _tags, _syntax (Rouge code theme), _plugins/ reading_time.rb (KO/EN-aware read time) lazy_images.rb (adds loading="lazy" to ) post_description.rb (fills page.description for posts) - related_posts.rb (fills page.related for posts) + related_posts.rb (page.related, and the prev/next links) scrollable_tables.rb (wraps wide tables so they scroll) css/ main.scss (Sass entry point) · search.css (search page only) js/ main.js (theme toggle, code-copy, TOC, menu, image zoom…) @@ -142,23 +141,31 @@ share one. Categories are **two levels**: - `categories[0]` — the **type**: `Paper Reviews`, `Paper Summaries`, - `Tech Guides`, or `Insights`. This decides which section page the post appears - on. Only `Paper Reviews` and `Insights` have posts today, so only those two are - in the nav — `paper-summaries.md` and `tech-guides.md` sit at `sitemap: false` - with no `main_nav`, and their front matter says which two keys to restore when - the first post lands in either. + `Tech Guides`, or `Insights`. This decides which nav tab the post appears under. + A type with no posts yet keeps its tab and renders an empty-state line: a + missing tab reads as a section that was removed, not one still filling up. - `categories[1]` — the **topic**: `Language-Models`, `Multimodal-Learning`, `Finetuning`, `Retrieval-Augmented-Generation`, `Agentic-AI`, … (add new ones freely). +Jekyll combines the two with the date to build the output path: + +``` +categories: ["Paper Reviews", "Language-Models"] + date: 2025-01-23 + ↓ +_site/paper reviews/language-models/2025/01/23/.html +``` + +So **changing the categories or date of a published post changes its URL**, which +breaks inbound links and search results. Set them once and leave them. + ### Tags: one topic tag on top of the specific ones -Tags are free-form and hyphenated, and most describe one paper's contribution -(`Fine-Grained-Expert-Segmentation`). Those are a good index but they connect -nothing: 220 of 264 tags were used by exactly one post, and since -`_plugins/related_posts.rb` requires a **shared** tag, 11 posts got no related -reading at all — including four graph-RAG papers that share no tag with each -other. +Tags are free-form and hyphenated, and a tag phrased as one paper's contribution +(`Fine-Grained-Expert-Segmentation`) can only ever apply to that paper. Those +make a precise index and connect nothing, and `_plugins/related_posts.rb` +requires a **shared** tag — so a post tagged only that way ships with no +"Related reading" block at all. So also give each post at least one tag from the controlled topic layer: @@ -169,18 +176,12 @@ Retrieval-Augmented-Generation ``` Keep the specific tags — they say something the topic tag does not, and dropping -them would move live `/tags/` anchors. Before inventing a topic tag, check -whether one of the above (or an existing tag used by 2+ posts) already covers it: -`Agentic-Architecture`, `Agentic-Patterns` and `Agentic-Infrastructure` were three -names for one thing, which is why that cluster had no links. - -Jekyll combines them with the date to build the output path: +them would move live `/tags/` anchors. **Add the topic tag, don't swap for it.** -``` -categories: ["Paper Reviews", "Language-Models"] + date: 2025-01-23 - ↓ -_site/paper reviews/language-models/2025/01/23/.html -``` +Before coining a new topic tag, check that nothing above already covers it. Three +names for one idea (`Agentic-Architecture`, `Agentic-Patterns`, +`Agentic-Infrastructure`) leave every post holding a tag no other post shares, +which is the same as having no topic tag at all. ### Math: always use `$$…$$` @@ -194,40 +195,42 @@ dollar signs like `$10M` are fine — they're not math.) ### Validate before pushing +These are the four gates CI runs, in the same order. Build to a throwaway +directory rather than `_site/`, for the reason in the note below: + ```bash -ruby test/run_all.rb # plugin logic still correct? -bundle exec jekyll build # does it build clean? -bundle exec htmlproofer ./_site --disable-external # any broken links/images? -script/validate-site.sh # sitemap, feed, metadata, headings +ruby test/run_all.rb # plugin logic still correct? +bundle exec jekyll build --strict-front-matter \ + --destination /tmp/site-verify # does it build clean? +bundle exec htmlproofer /tmp/site-verify --disable-external \ + --allow-hash-href --no-enforce-https # broken links, images, anchors? +script/validate-site.sh /tmp/site-verify # sitemap, feed, metadata, headings ``` -CI runs the same three checks, so catching it locally saves a failed deploy. - -`test/` covers `_plugins/` — the description derivation, the read-time estimate, -and the lazy-image rewrite. Plain `ruby`, not `bundle exec`: the plugins guard -their Jekyll/Liquid registration behind `defined?` so their logic loads -standalone, and minitest ships with Ruby. **Anything you change in `_plugins/` -changes every page on the site**, so add a case there before changing behaviour. - -> **If the checks report something impossible, look for a running `jekyll serve` +> **If a check reports something impossible, look for a running `jekyll serve` > first.** It watches the tree and rewrites `_site/` behind you, it overrides > `site.url` with `http://localhost:4000` (so every sitemap URL looks wrong), and -> it keeps the `_config.yml` it started with — so `exclude` entries added since -> then don't apply. Either stop it, or build somewhere else: +> it holds the `_config.yml` it started with — so `exclude` entries added since +> then don't apply. Building elsewhere sidesteps all three: > > ```bash > ps aux | grep '[j]ekyll serve' -> bundle exec jekyll build --destination /tmp/site-verify -> script/validate-site.sh /tmp/site-verify > ``` +`test/` unit-tests the pure logic in `_plugins/` — one file per plugin. Plain +`ruby`, not `bundle exec`: each plugin guards its Jekyll/Liquid registration +behind `defined?` so the logic loads standalone, and minitest ships with Ruby. +**Anything you change in `_plugins/` changes every page on the site**, so add a +case before changing behaviour. + --- ## Deployment `.github/workflows/jekyll.yml` runs on **pull requests to `main` as well as -pushes to it**, so the four gates below block a bad merge instead of only -reporting one. Steps 1–4 run on both; step 5 is skipped for pull requests. It: +pushes to it**, so the gates below block a bad merge rather than merely reporting +one after the fact. Steps 1–4 run on both events; step 5 is skipped for pull +requests. In order, it: 1. runs **`ruby test/run_all.rb`** (the `_plugins/` unit tests), 2. builds the site with `JEKYLL_ENV=production`, @@ -240,8 +243,8 @@ reporting one. Steps 1–4 run on both; step 5 is skipped for pull requests. It: no authoring sources published — and 5. deploys to GitHub Pages. -If the workflow fails, it's almost always step 3 or 4 — open the Actions log, -which names the exact link, image, or page. No manual deploy step is needed. +A failure is almost always step 3 or 4; the Actions log names the exact link, +image, or page. There is no manual deploy step. > **⚠ Don't add `google*.html` / `naver*.html` to `_config.yml`'s `exclude`.** > They're Search Console / Naver ownership-verification tokens that must ship to diff --git a/_posts/2026-04-12-agentcore-harness-engineering-analysis.md b/_posts/2026-04-12-agentcore-harness-engineering-analysis.md index 36c6559..b65e36a 100644 --- a/_posts/2026-04-12-agentcore-harness-engineering-analysis.md +++ b/_posts/2026-04-12-agentcore-harness-engineering-analysis.md @@ -7,7 +7,7 @@ description: >- Build·Deploy·Assess 세 층으로 열어 하네스 체크리스트의 어디를 채우고 어디를 비워 두었는지 짚습니다. date: 2026-04-12 12:00:00 categories: ["Insights", "Agentic-AI"] -tags: ["AgentCore", "AWS-Bedrock", "Harness-Engineering", "Agentic-Infrastructure", "Model-Context-Protocol", "Cedar-Policy", "Managed-RAG", "Agent-Registry", "Agentic-AI"] +tags: ["AgentCore", "AWS-Bedrock", "Harness-Engineering", "Agentic-Infrastructure", "MCP", "Model-Context-Protocol", "Cedar-Policy", "Managed-RAG", "Agent-Registry", "Agentic-AI"] cover: /assets/images/insights.jpg use_math: false --- diff --git a/docs/tech-doc.md b/docs/tech-doc.md index 87212b4..0b6b66e 100644 --- a/docs/tech-doc.md +++ b/docs/tech-doc.md @@ -68,8 +68,8 @@ Pages가 그대로 서빙.** > **실행을 거부**하게 만든다. 그래서 외부에서 불러오는 것은 버전을 고정하고 SRI를 붙인다. > **왜 GitHub Pages 기본 빌드가 아니라 Jekyll을 직접 돌리나?** GitHub Pages의 내장 -> 빌드는 보안 샌드박스라 우리가 `_plugins/`에 만든 커스텀 플러그인(읽기시간·lazy-load·설명문)을 -> 막는다. 그래서 로컬과 CI 모두 순수 `jekyll`을 직접 실행한다. +> 빌드는 보안 샌드박스라 우리가 `_plugins/`에 만든 커스텀 플러그인을 막는다. 그래서 로컬과 +> CI 모두 순수 `jekyll`을 직접 실행한다. ## 2. 빌드 파이프라인 — 마크다운 한 편이 HTML이 되기까지 @@ -107,9 +107,8 @@ Pages가 그대로 서빙.** 포스트는 프런트매터에 **2단계** 카테고리를 가진다: `categories: ["<유형>", "<주제>"]`. - **0단계 (유형)** — `Paper Reviews`, `Paper Summaries`, `Tech Guides`, `Insights` 중 하나. - 글이 어느 섹션 페이지에 들어갈지를 정한다. 넷 다 전용 페이지를 가지지만, 지금 글이 있는 - 유형은 `Paper Reviews`(28편)와 `Insights`(7편)뿐이고 내비에는 이 둘만 올라간다(아래 - "내비게이션은 코드가 아니라 데이터가 만든다" 참조). + 글이 어느 내비 탭에 들어갈지를 정한다. 넷 다 전용 페이지와 탭을 가지며, 글이 없는 유형은 + 빈 상태 문구를 띄운다(아래 "내비게이션은 코드가 아니라 데이터가 만든다" 참조). - **1단계 (주제)** — `Language-Models`, `Multimodal-Learning`, `Finetuning`, `Retrieval-Augmented-Generation`, `Agentic-AI` 등. 세부 주제이며 필요하면 자유롭게 추가한다. @@ -135,17 +134,17 @@ _site/paper reviews/language-models/2025/01/23/.html | nav_order | 페이지 | 소스 | |-----------|--------|------| | 1 | About | `about.md` | +| 2 | Paper Summaries | `paper-summaries.md` — `site.categories['Paper Summaries']` 필터 | | 3 | Paper Reviews | `paper-reviews.md` — `site.categories['Paper Reviews']` 필터, 주제별 그룹화 | +| 4 | Tech Guides | `tech-guides.md` — `site.categories['Tech Guides']` 필터 | | 5 | Insights | `insights.md` — `site.categories['Insights']` 필터 | | 6 | Search | `search.md` | -`nav_order`의 2·4번이 빈 것은 실수가 아니다. `paper-summaries.md`(2)와 -`tech-guides.md`(4)는 **글이 0편이라 내비에서 내렸다** — 여섯 탭 중 둘이 -"this space will fill up soon"으로 이어지고 있었고, 같은 페이지가 사이트맵에도 올라가 -검색엔진에 빈 페이지를 내밀고 있었다. 두 파일은 `main_nav` 없이 `sitemap: false`로 -남아 있고(URL로는 계속 열린다), 첫 글이 그 유형으로 들어오면 프런트매터 주석이 지시하는 -대로 `main_nav: true`와 원래 `nav_order`를 되살리면 된다. 번호를 다시 매기지 않은 이유가 -이것이다. +**빈 섹션도 탭을 유지한다.** 글이 없는 유형은 `_includes/category-posts.html`의 `empty` +문구를 띄운다("Summaries are in the works…"). 편수가 0이라고 `main_nav`를 떼지 않는다 — +탭이 사라지면 채우는 중인 섹션이 아니라 **없어진 섹션**으로 읽히고, 남은 탭이 로고 옆에 +좁게 뭉쳐 전체 폭 히어로와 어긋난다. 빈 상태 문구가 정직한 신호이고, 탭 자체가 "이 블로그는 +이 종류의 글도 쓴다"는 선언이다. `categories.html`(`/categories/`)과 `tags.html`(`/tags/`)은 *모든* 카테고리/태그를 가로지르는 전체 색인 페이지다. 메인 내비에는 없고, 각 포스트 하단의 메타데이터에서 링크된다. @@ -157,12 +156,13 @@ _site/paper reviews/language-models/2025/01/23/.html > `site.categories['<유형>']`으로 먼저 거른 뒤** 주제별로 그룹화한다 — 절대 반대 순서로 > 하지 않는다. -> **함정 — 앵커 점프 깨짐.** 메타데이터의 카테고리/태그 링크는 모두 `slugify` 필터로 ID를 -> 만든다. 과거에는 링크 쪽은 `downcase`(공백 유지), 제목(H2)의 id는 원본 케이스(`Paper -> Reviews`)를 써서 서로 어긋났고, 클릭해도 해당 위치로 점프하지 못했다(html-proofer가 -> 128건 적발). 지금은 양쪽 다 `slugify`로 통일했다 — `paper-reviews.md`가 마지막까지 -> `id="{{cat}}"`로 원본 케이스를 내고 있었는데, 그 앵커를 가리키는 링크가 없어 html-proofer가 -> 잡지 못했다. 새 목록 페이지를 만들 땐 `| slugify`를 빼먹지 않는다. +> **함정 — 앵커 점프 깨짐.** 카테고리·태그 앵커는 **링크 쪽과 헤딩 `id` 쪽이 같은 방식으로 +> 슬러그를 만들어야** 한다. 양쪽 다 `slugify` 필터를 쓴다. 한쪽만 `downcase`거나 원본 +> 케이스(`id="{{cat}}"`)를 내면 클릭해도 해당 위치로 점프하지 않는다. 새 목록 페이지를 만들 땐 +> `| slugify`를 빼먹지 않는다. +> +> 이 어긋남은 **아무 링크도 그 앵커를 가리키지 않으면 html-proofer가 잡지 못한다.** 목록 +> 페이지가 자기 헤딩으로 들어오는 링크를 갖고 있지 않은 경우가 그렇다. ### 태그: 세부 태그 + 통제된 주제 태그 @@ -174,15 +174,18 @@ _site/paper reviews/language-models/2025/01/23/.html `Knowledge-Graph`, `Llama`, `Mixture-of-Experts`, `Multimodal-Models`, `Reasoning-Models`, `Retrieval-Augmented-Generation`. -세부 태그만 있으면 관련 글 기능이 굶는다. 태그 264개 중 220개(83%)가 단 한 편에만 붙어 -있었고, `related_posts.rb`가 공유 태그를 요구하므로 11편이 관련 글 0개였다 — 그중 넷은 -RAPTOR·GraphRAG·LightRAG·Zep, 즉 서로 붙어야 마땅한 그래프 RAG 논문들이었다. 한 주제를 세 -이름으로 부른 것(`Agentic-Architecture`/`Agentic-Patterns`/`Agentic-Infrastructure`)도 같은 -증상을 만들었다. +**세부 태그만 달면 관련 글이 안 붙는다.** 논문 하나의 기여를 그대로 옮긴 태그는 그 논문에만 +붙을 수 있고, `related_posts.rb`는 공유 태그를 요구한다. 그래서 세부 태그만 달린 글은 +"Related reading" 없이 배포된다. 한 주제를 여러 이름으로 부르는 것도 같은 증상을 만든다 +(`Agentic-Architecture`/`Agentic-Patterns`/`Agentic-Infrastructure`가 그랬다). -주제 태그를 한 층 얹어 35편 전부가 관련 글을 얻게 했다. 고친 곳이 `related_posts.rb`의 -규칙이 아니라 **글의 프런트매터**라는 점이 중요하다 — 규칙을 느슨하게 풀면 공통점 없는 글이 -서로 추천된다. 세부 태그는 지우지 않았다(살아 있는 `/tags/` 앵커가 움직인다). +관련 글이 비어 보일 때 고칠 지점은 `related_posts.rb`의 규칙이 아니라 **글의 프런트매터**다. +규칙을 느슨하게 풀어 카테고리만으로 연결하면, "둘 다 Paper Reviews / Language-Models"라는 +이유로 공통점 없는 글이 서로 추천된다. 가중치가 그걸 막는 구조는 플러그인 상단 주석 +(`TAG_WEIGHT`·`SUBCATEGORY_WEIGHT`·`CATEGORY_WEIGHT`)에 설명돼 있다. + +그리고 세부 태그는 지우지 않는다 — 살아 있는 `/tags/` 앵커가 움직인다. 주제 태그를 +**얹기만** 한다. ## 4. 수식 렌더링 @@ -204,10 +207,9 @@ kramdown은 단일 `$`를 수식으로 **취급하지 않는다**. `$x_i + y_j$` MathJax에는 이미 망가진 입력이 전달된다(위 그림의 빨간 경로). 예를 들어 `$a*b*c$`가 `$abc$`가 되어버린다. -과거의 임시 우회책은 모든 언더스코어를 `\_`로 손수 이스케이프하는 것이었다. 근본 해결은 -**`$$`를 쓰는 것** — 구간 안의 마크다운 처리를 통째로 끈다. 저장소는 이미 일괄 -마이그레이션되었다(git 히스토리 참조). 참고로 코드 블록 *안*의 단일 `$` 수식(예: DeepSeek-R1 -글의 `` 트레이스)은 verbatim(있는 그대로) 코드로 표시되므로 이 문제와 무관하다. +언더스코어를 `\_`로 하나하나 이스케이프해도 증상은 사라지지만, 그건 우회책이다. 해결은 +**`$$`를 쓰는 것** — 구간 안의 마크다운 처리를 통째로 끈다. 참고로 코드 블록 *안*의 단일 +`$`는 verbatim(있는 그대로) 코드로 표시되므로 이 문제와 무관하다. > **통화 표기 주의.** 본문에 쓰는 달러 기호(`$1.2B`, `$250M`)는 수식이 아니므로 단일 `$`로 > 둬야 한다. 그래서 MathJax 설정에서 `$`를 인라인 구분자에서 **일부러 뺐다**(`\(...\)`만 @@ -232,30 +234,31 @@ MathJax 설정은 `head.html`에 있고 `{% if page.use_math %}`로 감싸 **프 빌드 때 모든 포스트를 `{title, url, date, category, tags, snippet, content}` 형태로 뽑아낸다. 여기서 `snippet`은 결과 카드에 보여줄 40단어짜리 발췌, `content`는 매칭에 쓰는 **HTML을 제거한 전체 본문**이다. -- **왜 발췌가 아니라 전체 본문을 색인하나** — `simple-jekyll-search`는 똑똑한 형태소 분석 - 없이 단순 부분문자열 매칭을 한다. 즉 색인에 없는 글자는 못 찾는다. 예전에 snippet(앞 - 40단어)만 색인했더니, "어텐션"·"트랜스포머"가 본문 중·후반에 18개 포스트나 있는데 발췌엔 - 안 들어가 검색 결과가 **0건**으로 나왔다. `content`로 전체를 색인해 한글 재현율을 회복했다. - 대가는 실측 **3.2 MB, gzip 991 KB**다(글이 늘면 같이 는다). `/search/`에서만 내려받으므로 - 다른 페이지 속도엔 영향이 없다. - - > **알려진 한계 — 매치가 안 보일 수 있다.** 색인은 본문 전체인데 결과 카드에 찍히는 건 - > 앞 40단어짜리 `snippet`뿐이다. 그래서 5만 번째 글자에서 걸린 검색어는 스니펫에 없고 - > `` 강조도 안 걸려서, 맞는 결과가 엉뚱한 결과처럼 보인다. 고치려면 매치 위치 - > 주변을 잘라 보여 주는 스니펫이 필요하다(`simple-jekyll-search`의 템플릿 밖 작업). +- **왜 발췌가 아니라 전체 본문을 색인하나** — `simple-jekyll-search`는 형태소 분석 없이 + 단순 부분문자열 매칭을 한다. **색인에 없는 글자는 못 찾는다.** 발췌만 색인하면 본문 + 중·후반에만 나오는 단어("어텐션", "트랜스포머" 같은)는 검색 결과가 0건이 된다. `content`로 + 전체를 색인하는 건 한글 재현율을 위한 의도된 선택이다. + 대가는 파일 크기다 — 본문 전체가 들어가므로 **글 수에 비례해 선형으로 커진다**(현재 규모는 + MB 단위, gzip 후 그 3분의 1 아래). `/search/`에서만 내려받으므로 다른 페이지 속도엔 영향이 + 없다. 실제 값은 `curl -so /dev/null -w '%{size_download}' /search.json`로 확인한다. + + > **비대칭에 주의.** 매칭은 `content`(전체)로 하고 표시는 `snippet`(앞 40단어)으로 한다. + > 그래서 본문 깊은 곳에서 걸린 검색어는 결과 카드에 보이지 않는다(§9). - **`js/search.js`** 가 `simple-jekyll-search`(CDN 버전 **고정 + SRI**: `1.10.0`)를 `search.md`의 `#search-input` 입력칸에 연결한다. 매칭된 키워드를 ``로 강조하고 결과 개수를 라이브 상태줄에 표시한다. 강조 처리는 결과가 다 그려진 뒤 **디바운스**로 단 - 한 번만 실행한다(예전엔 키를 누를 때마다 `setTimeout`을 쌓아 서로 경합하며 깜빡였다). + 한 번만 실행한다 — 키 입력마다 `setTimeout`을 쌓으면 타이머가 서로 경합해 화면이 깜빡인다. > **용어 — 디바운스(debounce).** 사용자가 빠르게 연속으로 일으키는 이벤트(타이핑 등)에서, > 마지막 입력 뒤 잠깐 멈출 때까지 기다렸다가 **딱 한 번만** 함수를 실행하는 기법이다. - `category`/`tags`도 색인에 들어가므로 제목·본문뿐 아니라 메타데이터로도 검색된다. 색인에는 **글(`site.posts`)만** 들어간다 — `search.json`이 `site.posts`를 순회하므로 - About·Search·index 같은 페이지는 애초에 후보가 아니다. (`_config.yml`에 `about.md`· - `search.md`·`index.html`을 열거하던 `simple_jekyll_search.exclude` 블록이 있었는데, - `simple-jekyll-search`는 브라우저에서 도는 JS라 `_config.yml`을 읽을 수 없어 아무것도 하지 - 않았다. 지워졌고, 왜 없는지가 `_config.yml`의 주석으로 남아 있다.) + About·Search·index 같은 페이지는 애초에 후보가 아니다. + + > **주의 — `_config.yml`로는 검색을 제어할 수 없다.** `simple-jekyll-search`는 브라우저에서 + > 도는 JS라 `_config.yml`을 읽지 못한다. 색인 대상을 바꾸려면 `search.json`의 Liquid를 + > 고쳐야 한다. `_config.yml`에 `simple_jekyll_search:` 같은 블록을 만들어도 아무 일도 + > 일어나지 않는다. ## 6. 스타일 (Sass) @@ -270,9 +273,13 @@ Bourbon → base/ → Neat → _layout → _post → _tags → _syntax(Rouge 코 - **수정해도 되는 곳**: `_sass/_layout.scss`, `_sass/_post.scss`, `_sass/_tags.scss`, `_sass/base/*`(특히 색·간격·브레이크포인트를 모아 둔 `_variables.scss`). -- **수정하면 안 되는 곳**: `_sass/bourbon/**`, `_sass/neat/**`(외부에서 가져온 벤더 - 프레임워크 — 우리가 만든 게 아니다), `_sass/_syntax.scss`(자동 생성물 — - `rougify style monokai.sublime`로 재생성한다). +- **수정하면 안 되는 곳**: `_sass/bourbon/**`, `_sass/neat/**` — 외부에서 가져온 벤더 + 프레임워크다. +- **`_sass/_syntax.scss`는 재생성하지 말 것**: 원래 `rougify style monokai.sublime`의 + 출력이지만 **그 뒤 손으로 고쳤다.** Monokai 기본 팔레트의 주석·키워드 색이 코드 배경에서 + 명도 대비 4.5:1을 못 넘겨 두 색을 올려 놨고, 파일 맨 위 주석에 원래 값과 대비 수치가 + 적혀 있다. `rougify`로 덮어쓰면 그 수정이 조용히 사라진다. 색을 바꿔야 하면 이 파일을 + 직접 고치고 주석도 함께 갱신한다. - **디자인 토큰을 쓸 것**: 값을 하드코딩하지 말고 미리 정의된 변수를 쓴다. 전환은 `$transition-*`, 그림자는 `$shadow-*`, 강조색/보조 텍스트는 `$action-color`/`$medium-gray`. `0.3s ease`나 `#aaaaaa` 같은 리터럴을 직접 박지 않는다. (예: `$medium-gray`는 @@ -280,8 +287,8 @@ Bourbon → base/ → Neat → _layout → _post → _tags → _syntax(Rouge 코 읽히는 진한 파랑으로 헤더/푸터 배경용.) - **절대 금지**: HTML 안의 인라인 `