ワンクリックで
content-pipeline
{{AGENT_NAME}} content pipeline - editorial topic selection, draft writing, Korean humanization, review-ready publishing
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
{{AGENT_NAME}} content pipeline - editorial topic selection, draft writing, Korean humanization, review-ready publishing
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
SOC 職業分類に基づく
{{AGENT_NAME}} brain signal architecture — event bus, signal processor, awareness reporter, brain monitor
TDD-PDCA plan for building a self-replicating branching agent in {{AGENT_NAME}}. 단일 에이전트가 스스로 서브에이전트를 분기하고, 결과를 수렴시키는 구조.
Integrate {{AGENT_NAME}} with ACP (Agent Client Protocol) agents. Reverse-engineered from goose's Rust ACP provider implementation.
Maintain .{{AGENT_NAME_LOWER}} as an Obsidian vault — graph connectivity overhaul (P-layer mesh, skill clusters, SEO article web), broken link detection, backlink density verification via Obsidian CLI, filename deduplication, bidirectional linking, auto-generated orphan management, cron output graph bloat prevention, and Obsidian compatibility.
AI Trend Collection & Filtering — collect GitHub trending repos, score via 5-axis philosophy filter, evaluate, apply, and retire.
RSS 피드 모니터링 → SEO 기사 수집·분석·트렌드 리포트
| name | content-pipeline |
| description | {{AGENT_NAME}} content pipeline - editorial topic selection, draft writing, Korean humanization, review-ready publishing |
Aggregator pattern: 이 skill은 content를 수집하지 않음. monitoring — watch, don't prompt.
Collect from external sources (trend-harvester, SEO-harvester), select topics, assign to content-writer via kanban. Suitable for: trend posts, SEO-optimized evergreen, tool roundups.
Single autonomous agent profile (content-manager) observes Drew's recent work and produces multi-format content. Runs daily at 12:00 KST via cron.
Trigger: Cron (0 12 * * *), deliver to Discord #content-channel.
Profile: ~/.{{AGENT_NAME_LOWER}}/agents/content-manager.md (deepseek-v4-pro, tools: terminal, file, search, session_search, web)
Skill prerequisites: content-pipeline (this skill), SVG knowledge, Excalidraw, Mermaid.
Suitable for: build logs, troubleshooting deep-dives, architecture decisions, project retrospectives.
See references/cmo-agent-mode.md for the full implementation guide.
Mode B requires these knowledge base files (in P4-cortex/content/):
brand-guide.md — brand positioning, voice, audienceglossary.md — project terms ({{AGENT_NAME}}, M-LOG, PDC...)content-inventory.md — published/drafted content for dedupnarrative_arc.md — episode tracking, season structure, continuityThe agent reads all four at the start of every cycle before gathering context.
YOUR_DOMAIN은 자동 뉴스 블로그가 아니라 Drew가 AI, 도구, 코드, 글쓰기, 시스템 설계를 통해 어떻게 사고하고 만드는지를 보여주는 개인 작업실이다.
공개 후보가 되는 글은 반드시 아래 등식을 만족해야 한다:
public-worthy content = 기록 + 해석 + 재사용 가능한 통찰
단순한 수집 결과, 출시 소식, 활동 보고, 링크 요약은 공개 draft로 만들지 않는다. 그런 항목은 raw 또는 archive에 남겨도 되지만 content board task로 승격하지 않는다.
raw source
↓ editorial_screen
candidate
↓ enough Drew-angle + reader value
draft → status: draft → Quartz EXCLUDE
↓ human review
published → status: published → Quartz INCLUDE
↓ rework needed
in_review → status: in_review → Quartz EXCLUDE
↓ no longer useful
archived → status: archived → Quartz EXCLUDE
content-pipeline은 candidate → draft까지만 자동화한다. published 전환은 사용자가 직접 검토한 뒤에만 한다.
0 */3 * * * KST)0 12 * * *), content-manager agent profile
See references/cmo-agent-mode.md for the full Mode B implementation.
When making technical/content recommendations for Drew (site platform, theme, tools, services):
Try first, evaluate later. Propose a concrete implementation path, apply it to the current environment, and test. Do NOT decide based on imagination, brand reputation, or hypotheticals alone. Retreat only if the tested approach proves genuinely inefficient.
Research, don't pretend. If you don't have first-hand experience with a tool or technology, say "let me search for that" — not "I think X is good." The user will call out fake expertise immediately. When asked for a recommendation:
Don't fear change. Changing platforms (Quartz → WordPress), tools, or approaches is not inherently bad. Evaluate the migration cost against the benefit honestly, and if the direction makes sense, execute it rather than listing objections.
When asked for a recommendation, search first. Before saying "I recommend X," search for current options, pick 2-3 concrete candidates, install/test the most promising one, and report results. Let real testing guide the decision, not your training data.
Don't list objections for a direction the user is already committed to. If the user says "let's use WordPress" or "let's run Huly on NAS," don't produce a comparison table of alternatives. Instead:
These rules apply any time the user asks "which X should I use" or "what do you recommend."
When updating or extending skills, follow these conventions:
references/ files preserve session-specific detail. When a one-time setup or workflow is discovered, add a reference file rather than bloating SKILL.md. Each reference file starts with a one-line summary of what it covers.scripts/. Anything the agent should run rather than hand-type. Scripts are documented in SKILL.md with their usage example.templates/. Boilerplate configs, scaffolding, starter files meant to be copied and modified.직접 수집하지 않음. 기존 cron output 3개를 읽음.
위치: ~/.{{AGENT_NAME_LOWER}}/P4-cortex/growth/trend-harvester/analyzed/keep/
형식: JSON (item{name,description,url,source}, total_score, decision)
필터: decision == "keep", scored_at 최근 48시간
선별: 상위 5개 → topic 후보
# 실행
ls -t ~/.{{AGENT_NAME_LOWER}}/P4-cortex/growth/trend-harvester/analyzed/keep/*.json | head -10
# → 상위 10개 JSON 파일 경로
JSON 파싱:
import json, pathlib
keep_dir = pathlib.Path("~/.{{AGENT_NAME_LOWER}}/P4-cortex/growth/trend-harvester/analyzed/keep")
files = sorted(keep_dir.glob("*.json"), key=lambda p: p.stat().st_mtime, reverse=True)[:10]
for f in files:
d = json.load(open(f))
item = d["item"]
print(f"- {item['name']}: score={d['total_score']:.1f}, source={item['source']}")
위치: ~/.{{AGENT_NAME_LOWER}}/P2-hippocampus/knowledge/seo-articles/report.json
형식: JSON (articles[{title,url,keyword,score}])
선별: score ≥ 0.7, keyword 명확한 것 상위 3개
cat ~/.{{AGENT_NAME_LOWER}}/P2-hippocampus/knowledge/seo-articles/report.json | python3 -c "
import json, sys
d = json.load(sys.stdin)
articles = d.get('articles', [])
scored = [(a, a.get('score', 0)) for a in articles if a.get('score', 0) >= 0.7]
for a, s in sorted(scored, key=lambda x: -x[1])[:5]:
print(f'- {a[\"title\"]} | keyword={a.get(\"keyword\",\"?\")} | score={s:.2f}')
"
위치: {{AGENT_NAME_LOWER}}_tasks.db (board=default, trigger_source=activity_logger)
쿼리: SELECT title, body FROM tasks
WHERE trigger_source = 'activity_logger'
AND status = 'completed'
AND created_at > datetime('now', '-24 hours')
선별: conversation insight 관련 것 (implement/build/create 등 포함) 상위 3개
import sqlite3, pathlib
db = pathlib.Path.home() / ".{{AGENT_NAME_LOWER}}/P2-hippocampus/kanban/state/{{AGENT_NAME_LOWER}}_tasks.db"
conn = sqlite3.connect(str(db))
rows = conn.execute("""
SELECT title, body FROM tasks
WHERE trigger_source = 'activity_logger'
AND status = 'completed'
AND created_at > datetime('now', '-24 hours')
ORDER BY created_at DESC
LIMIT 10
""").fetchall()
for title, body in rows:
print(f"- [{title}] {body[:100] if body else ''}")
conn.close()
세 source에서 합산 최대 3개 topic 선별. 기본값은 0개다. 충분히 좋은 후보가 없으면 [SILENT]가 올바른 결과다.
| 출처 | Topic 유형 | Prefix |
|---|---|---|
| Trend Harvester | [draft-trend] | 기술 동향 |
| SEO Harvester | [draft-seo] | 검색 최적화 |
| Activity Logger | [draft-conversation] | 작업 insight |
각 후보는 0~2점으로 평가한다. 총점 7점 이상만 content board task로 만든다.
| 기준 | 0점 | 1점 | 2점 |
|---|---|---|---|
| Drew-angle | Drew의 작업/판단과 무관 | 약하게 연결됨 | Drew의 작업 방식, 도구, 포트폴리오와 직접 연결 |
| Insight | 요약/소식 수준 | 관찰은 있으나 일반화 약함 | 독자가 재사용할 수 있는 판단 기준이 있음 |
| Evidence | 출처/맥락 불충분 | 기본 출처 있음 | 출처 + 실제 작업 맥락 또는 artifact 있음 |
| Portfolio value | 지나가는 메모 | blog note로는 가능 | portfolio/insight 축에 남겨도 가치 있음 |
| Specificity | 일반론, 키워드 나열 | 어느 정도 구체적 | 구체적 사례, 프로젝트, 도구, 실패/결정 포함 |
자동 reject 규칙:
source별 우선순위:
[draft-conversation]: Drew의 작업에서 나온 insight면 우선.[draft-trend]: {{AGENT_NAME}}, YOUR_DOMAIN, agent tooling, creative coding, publishing system과 연결될 때만.[draft-seo]: 검색 유입보다 사이트 정체성에 맞는 evergreen 주제일 때만.최대 3개. 품질 > 수량.
선별한 topic마다 content board에 task 생성:
kanban_create(
title=f"[draft-{type}] {topic_title}",
body=f"""## Topic
{topic_description}
## Editorial Decision
- score: {score}/10
- publish_intent: blog | insight | portfolio | archive
- Drew-angle: {why_this_matters_to_drew}
- reusable_insight: {one_sentence_reader_value}
- reject_if_missing: if this cannot show Drew's judgment, archive instead of drafting
## Content Source
- source: {source_name}
- collected_at: {YYYY-MM-DD HH:MM}
## 글쓰기 방향
- 톤: writing-style-guide.md hybrid approach 따르기
- 톤: 휴머너드 말투 (긴 플로우, Bold 강조, "당신" 직접호칭, 1인칭 "저"/"나")
- SEO: 기존 방식 유지 (aliases, 해시태그, SEO 키워드 섹션)
- 제목: 질문 또는 provocative statement
- 도입: Bold 훅 한 문장 → 10~20문장 플로우
## Frontmatter
title: {title} type: document space: concept tags: [blog, {category}] aliases: ['/blog/{slug}'] created: {YYYY-MM-DD} status: draft links:
## 작성 시 반드시 확인할 것
1. forbidden.patterns grep → 0건
2. Bold 섹션 강조 2~4개 존재
3. "당신" 직접호칭 + 1인칭 "저"/"나" 포함
4. 본문 날짜 (X월 X일) 없음
5. SEO 키워드 3~7개, 해시태그 8~14개
6. aliases in frontmatter
7. 기록 + 해석 + 재사용 가능한 통찰이 모두 있음
""",
board="content",
trigger_source="content_pipeline",
priority=1,
idempotency_key=f"{YYYY-MM-DD}-{slug}",
)
Title prefix 규칙:
[draft-trend][draft-seo][draft-conversation]Draft 파일 위치: memories/insights/YYYY-MM-{slug}.md
memories/insights/2026-05-gemini-cli-shutdown.md~/.{{AGENT_NAME_LOWER}}/P2-hippocampus/memories/insights/## Draft 파일 위치 절대경로가 있으면 대시보드 카드에 📄 Obsidian 링크가 자동 생성됨. 카드 클릭 → Description 탭 하단 "📄 Open in Obsidian" 버튼 클릭 → Obsidian에서 draft 파일 열림./blog/{slug} 또는 /blog/YYYY/{slug} 경로로 Quartz에 의해 공개됨. Raw monthly log는 공개하지 않음.kanban-dispatcher가 ready task를 worker에 배분.
kanban_claim(task_id, ttl_seconds=1800)
Topic 관련 정보가 충분하지 않으면 web search로 보강. 출처: URL, source-date 명시.
memories/insights/에 Markdown 파일 생성.
writing-style-guide.md 적용 (hybrid approach):
** ReefWatch 스타일 글 구조 (기술 심화 글용):**
ReefWatch 아티클 (dev.to/siiddhantt/building-reefwatch)의 7가지 핵심 포인트를 적용한 템플릿:
![[image.png|너비]]** ReefWatch 확장 템플릿:**
# {제목: 질문 또는 provocative statement}
{커버 이미지: ![]() — 16:9 landscape, 글 전체를 대표하는 시각적}
**{문제 프레이밍 — Bold 훅}**
{구체적 상황 묘사. 독자가 공감할 수 있는 경험/관찰}
**{강한 주장 — Bold 한 문장}**
{기존 방식의 한계 또는 문제의 본질}
{분석 플로우: 10~20문장. 경험 → 관찰 → 분석}
**{중간 핵심 강조 — Bold}**
{구체적 해결책 또는 인사이트}
{이어지는 플로우}
> {One-sentence 정의 — 블록쿼트로 한 문장 요약}
## What This Guide Builds
| 당신은 이것을 할 수 있게 된다 |
|---|
| {outcome 1} |
| {outcome 2} |
| {outcome 3} |
{구현/설계 섹션}
{이미지: ![]() — 플로우/아키텍처 다이어그램, 설명 전에 배치}
{이어지는 설명}
**{마무리 강조 — Bold 한 문장}**
{결론 또는 행동 유도}
---
**이런 분들께 추천**
- {타겟 독자}
**SEO 키워드**: {키워드 3~7개}
**#해시태그**: #{태그1} #{태그2} ...
** ReefWatch 스타일 글쓰기 순서:**
기술 심화 글이 아닌 경우 (트렌드/단편): 확장 템플릿 대신 기본 구조(문제 프레이밍 → Bold 주장 → Bold 마무리)만 사용. "What This Guide Builds" 테이블은 생략 가능.
Blog posts use THREE types of visual content, each with a different production path:
| Type | Production | Cost | When |
|---|---|---|---|
| Mermaid (inline) | ```mermaid code blocks — Quartz renders to SVG | $0 | Flows, sequences, state machines |
| Excalidraw PNG (exported) | .excalidraw.json → excalidraw-to-png.js → .png | $0 | Architecture diagrams, before/after, data flow |
| SVG Cover (inline) | Model writes SVG XML → saved as .svg | $0 | Hero/banner, article cover image |
SVG is the primary cover/hero image format — model writes SVG XML directly. Mermaid is for inline flow diagrams. Excalidraw is for complex architecture visuals.
For stories with a natural humor angle, create a meme SVG alongside the cover. Memes make technical content more approachable on social/X.
Supported templates:
| Template | Use Case | Structure |
|---|---|---|
| Drake Reject/Approve | Before/after comparison | Two panels: red ✗ (old) → green ✓ (new) |
| "This is fine" | Recognizable pain/bug | Burning room, "it's fine" caption |
| Galaxy Brain | Escalating understanding | 4 levels of insight, last one mind-blowing |
| Distracted Boyfriend | Three-way comparison | 3 labeled elements: old → new → shiny |
Save as YYYY-MM-DD-slug-meme.svg. Embed optionally: ![[slug-meme.svg|600]]
Pitfall: Don't force a meme where none fits. If the story is serious (incident, security, reflection), skip it. Memes are for "this is ridiculous" or "this pattern is obvious in hindsight" angles only.
⚠️ Pitfall: Do not conflate Mermaid with exported images. Mermaid renders inline as SVG. Excalidraw PNGs are separate files. The blog post needs BOTH — Mermaid for inline flows, Excalidraw PNG for architecture visuals.
The content-manager creates .excalidraw.json files for complex architecture. The full pipeline is:
Create .excalidraw.json with proper JSON structure. Must validate before proceeding:
python3 -c "import json; json.load(open('diagram.excalidraw.json'))" && echo "VALID" || echo "INVALID"
Common pitfall: trailing comma in the last element of an array or object. JSON does not allow trailing commas. Use write_file which includes a JSON lint step, or run the validation above.
excalidraw create diagram.excalidraw.json -o diagram.excalidraw
# Requires: npm install -g excalidraw-cli (Homebrew)
This step converts the JSON into a binary .excalidraw file. This is a required intermediate step — the PNG script below needs it.
excalidraw export diagram.excalidraw
# Returns: URL: https://excalidraw.com/#json=<id>,<key>
Uploads to excalidraw.com, returns a shareable URL. Requires internet access.
node ~/.{{AGENT_NAME_LOWER}}/scripts/excalidraw-to-png.js \
/path/to/diagram.excalidraw.json \
/path/to/diagram.png
This runs Puppeteer (headless Chrome) to open the URL in ?embed=1 mode and screenshot the canvas.
Setup:
npm install -g excalidraw-cli # CLI for upload/export (Step 2-3)
cd ~/.{{AGENT_NAME_LOWER}}/scripts # local install for puppeteer (Step 4)
npm install puppeteer
Runtime requirements: Node.js, internet access to excalidraw.com, puppeteer in ~/.{{AGENT_NAME_LOWER}}/scripts/node_modules/.
Shortcut — the excalidraw-to-png.js script combines Steps 3+4 (export + screenshot). You still need Step 2 (excalidraw create) separately.
Embed in blog post:
![[diagram.png|700]]
*{caption}*
The content-manager generates an SVG cover image for each blog post. SVG is XML text — the model writes it directly. No API calls, no extra cost.
Template (1200×630, YOUR_DOMAIN dark theme):
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="630">
<defs>
<linearGradient id="bg" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#0f0f1a"/>
<stop offset="100%" style="stop-color:#1a1a2e"/>
</linearGradient>
</defs>
<rect width="1200" height="630" fill="url(#bg)"/>
<!-- title, subtitle, illustration, tags, date -->
</svg>
Design rules:
#0f0f1a → #1a1a2e gradient#7b5f3d (amber — brand color)#4a90d9 (blue) or #50c878 (teal)#e8e4df (warm white), #8a8680 (muted)#ffffff at 3% opacity![[YYYY-MM-DD-slug-cover.svg|800]]The SVG replaces the old <!-- COVER: ... --> HTML comment pattern (deprecated). No paid API needed — the model writes SVG markup directly.
Pitfall: Do NOT fall back to paid image APIs (FAL, OpenAI, Gemini Image). The user explicitly rejected these. SVG generation is the designated zero-cost approach. If the SVG is too complex, simplify it — don't suggest payment.
Every blog post gets a companion X thread. The thread is a self-contained narrative — it should be understandable without reading the blog post, while linking back to it.
1/HOOK — Bold claim or surprising question. Make the reader pause.
Example: "\"Is this even working?\" This question found 3 silently broken
bugs. Here's what happened."
2-3/ — Problem setup. What was supposed to work, what was actually happening.
One tweet per bug or per layer of the problem.
4-7/ — The discovery process. How you found it, what the root cause was.
Concrete details matter: error codes, line counts, hours stranded.
8-9/ — The pattern. What these bugs share. The lesson that generalizes beyond
your specific setup.
10-11/— The fix. What changed. Keep it short — the detail is in the blog post.
12-13/— Broader implication + CTA. "This is what taste in engineering looks like."
Link to the blog post.
Last tweet: Season/Episode tag (e.g., "Season 1: Taste Engineering — Episode 3") + link.
Save as YYYY-MM-DD-slug-thread.txt in memories/insights/. Plain text, one tweet per paragraph separated by blank lines. The numbered "N/15" prefix is added by the human on posting.
Hooked question
2/ Bug description
3/ Pattern discovery
...
Last/ Link + season tag
Pitfall: Don't write the "N/15" counting in the file — the human decides the final tweet count. Write the content; the /N comes from the posting interface.
Cover images are <!-- COVER: description --> HTML comments — no image generation. The content-manager describes the ideal image; a human or future tool generates it.
This pattern is deprecated in favor of SVG cover generation above. Keep only if SVG generation fails (unlikely — SVG is just XML text).
이미지 배치 원칙 — "설명 전에 보여주기":
기술 심화 글의 이미지 전략:
|300 |400 |600으로 본문 폭에 맞춤예시 — 트렌드 글:
# AI 에이전트, 증거 없이 답하지 마라
**문제 프레이밍**
당신이 AI 에이전트를 구축한다고 치자. 모니터링은 PagerDuty에서 하고, 에러는 Sentry에 있고, 배포는 GitHub Actions에서 됐다. 문제는 여러 곳에 흩어져 있는데, 일반 챗봇은 "Sentry에서 에러를 확인해보세요"라고만 말할 수 있다.
**강한 주장**
하지만 그것은 triage가 아니다. 그건 정돈된 할 일 목록일 뿐이다.
**디자인 제약**
원칙은 단순했다: 증거 없이는 답하지 않는다.
**해결책**
이 원칙을 지키기 위해 Coral을 데이터 플레인으로 사용하면...
AI 티 제거 + 한글 교정을 DeepSeek로 윤문. CMO Agent mode에서는 skip (content-manager가 직접 작성).
윤문 철학 — 역할 확인, 삭제 규칙이 아니다.
-적, 의, 들, 것, 있는/있다는, 있었다 등은 패턴 매칭으로 무조건 삭제하지 않고, 의미·리듬·강조·장르에 필요한 역할을 하는지 확인변경률 가드:
Vault에서 API 키 조회:
import sys, os
sys.path.insert(0, os.path.expanduser("~/.{{AGENT_NAME_LOWER}}"))
from modules.secrets_vault import SecretsVault
vault = SecretsVault()
api_key = vault.resolve("vault_9fa1b5bb") # DEEPSEEK_API_KEY
실행:
DEEPSEEK_API_KEY="<vault에서 조회한 키>" \
python3 ~/.{{AGENT_NAME_LOWER}}/P4-cortex/scripts/humanize_korean.py \
~/.{{AGENT_NAME_LOWER}}/P2-hippocampus/memories/insights/{YYYY-MM}-{slug}.md \
~/.{{AGENT_NAME_LOWER}}/P2-hippocampus/memories/insights/{YYYY-MM}-{slug}_polished.md
스크립트: ~/.{{AGENT_NAME_LOWER}}/P4-cortex/scripts/humanize_korean.py
실행 후:
_polished.md 파일이 생성됨 → 원본과 교체 (rename)Vault ref:
| 서비스 | ref | 확인 |
|---|---|---|
| DeepSeek API | vault_9fa1b5bb | vault.list("api_key")로 확인 |
Vault에 키 등록 (최초 1회):
from modules.secrets_vault import vault
vault.register("DEEPSEEK_API_KEY", os.getenv("DEEPSEEK_API_KEY", "sk-your-key-here"), category="api_key")
DeepSeek 윤문을 사용할 경우 이 단계는 skip — DeepSeek가 처리함.
# 1. 불필요한 한문/한자/일어 → 한글 변환
# 한자: "倫理" → "윤리", "機能" → "기능", "存在" → "존재"
# 한문: "不可以" → "불가", ",要注意" → "주요"
# 일어: "ceras" → 없음 (외래어として以外), "ニーズ" → "니즈"
# 2. 비문检查 — 읽기 어려운 문장 reformulate
# 3. 오탈자 자동 수정
# 4. writing-style-guide forbidden.patterns 재확인
Polish 체크리스트 (DeepSeek 윤문 후):
[ ] humanize_korean.py 실행 완료
[ ] _polished.md → 원본 파일로 rename
[ ] forbidden.patterns grep → 0건
[ ] 변경률 5~30% (30%+ = 과윤문 검토, 50%+ = 중단)
[ ] Sunny 역할 확인: 표현이 무슨 일을 하는가? (의미·리듬·강조·정밀도·장르)
원문 예시 (수정 전):
이 功能은 您的 жизнь에 매우 重要합니다不可以
수정 후 (DeepSeek):
이 기능은 당신의 삶에 매우 중요합니다
실패 시: 원본 draft 파일 그대로 유지 (작업 실패가 아님). Phase 5로 진행.
draft_file = f"~/.{{AGENT_NAME_LOWER}}/P2-hippocampus/memories/insights/{YYYY-MM}-{slug}.md"
kanban_complete(
task_id,
result=f"Draft file: {draft_file}",
summary=f"Draft created: {title}",
metadata={"draft_path": f"P2-hippocampus/memories/insights/{YYYY-MM}-{slug}.md"}
)
result에는 반드시 절대경로 포함 — Phase 5 Periodic Delivery의 SQLite regex가 ~ 또는 ~/로 시작하는 경로를 추출함.
Phase 5 regex: r'memories/insights/(20\d{2}-\d{2}-[^.]+\.md)' 또는 r'~/.{{AGENT_NAME_LOWER}}/P2-hippocampus/memories/insights/(20\d{2}-\d{2}-[^.]+\.md)'
After every blog post + X thread, update BOTH tracking files before completing the cycle.
narrative_arc.md (P4-cortex/content/narrative_arc.md) — Two updates:
content-inventory.md (P4-cortex/content/content-inventory.md) — Three updates:
Pitfall: Do not list the same draft file in both Published and Drafts tables.
memories/insights/ 확인status: draft → status: publishedstatus: draft → status: in_reviewstatus: draft → status: archivedpublish_date: {YYYY-MM-DD} 추가Draft 작성 완료 시 확인. 모든 패턴 매칭은 "삭제 규칙이 아니라 역할 확인" — 표현이 여기서 무슨 일을 하는지 먼저 판단하고 수정할 것.
/blog/{slug})Polish 체크리스트 출력 예시:
Language Polish: DONE
한자→한글: 3건 변환
한문 표현: 1건 변환
일어 외래어: 0건
비문 수정: 2건
Forbidden: 0건
Bold sections: 3
Direct address ("당신"): YES
1인칭 ("저"/"나"): YES
Dates in body: 0
SEO keywords: 5
해시태그: 11
aliases: /blog/gemini-cli-shutdown
PASS
## Content Pipeline — YYYY-MM-DD HH:MM KST
Topics selected: N
| # | Source | Topic | Task ID | Draft File |
|---|--------|-------|---------|------------|
| 1 | trend | {title} | {id} | ~/.{{AGENT_NAME_LOWER}}/P2-hippocampus/memories/insights/{YYYY-MM}-{slug}.md |
| 2 | seo | {title} | {id} | ~/.{{AGENT_NAME_LOWER}}/P2-hippocampus/memories/insights/{YYYY-MM}-{slug}.md |
| 3 | conversation | {title} | {id} | — (in progress) |
Worker 배분: kanban-dispatcher-content가 5분마다 ready task를 worker에 배분
Review at: Obsidian → P2-hippocampus → memories → insights
## Content Pipeline — YYYY-MM-DD HH:MM KST
Topics selected: N (task created)
| # | Source | Topic | Task ID | Draft File |
|---|--------|-------|---------|------------|
| 1 | trend | {title} | {id} | ~/.{{AGENT_NAME_LOWER}}/P2-hippocampus/memories/insights/{YYYY-MM}-{slug}.md |
| 2 | seo | {title} | {id} | ~/.{{AGENT_NAME_LOWER}}/P2-hippocampus/memories/insights/{YYYY-MM}-{slug}.md |
| 3 | conversation | {title} | {id} | — (in progress) |
Worker 배분: kanban-dispatcher-content가 5분마다 ready task를 worker에 배분
Review at: Obsidian → P2-hippocampus → memories → insights
Topics selected=0이면 [SILENT].
content-pipeline cron job이 매 실행마다 content board의 completed task를 조회해 draft 파일 위치를 보고:
import sqlite3, os, re, pathlib, glob
DB = os.path.expanduser("~/.{{AGENT_NAME_LOWER}}/P2-hippocampus/kanban/state/{{AGENT_NAME_LOWER}}_tasks.db")
INSIGHTS = os.path.expanduser("~/.{{AGENT_NAME_LOWER}}/P2-hippocampus/memories/insights/")
conn = sqlite3.connect(DB)
rows = conn.execute("""
SELECT id, title, body, completed_at
FROM tasks
WHERE trigger_source = 'content_pipeline'
AND board = 'content'
AND status = 'completed'
AND completed_at > datetime('now', '-72 hours')
ORDER BY completed_at DESC
""").fetchall()
conn.close()
drafts = []
for task_id, title, body, completed_at in rows:
draft_path = "— (path not recorded)"
if body:
# body: "Draft file: ~/.{{AGENT_NAME_LOWER}}/P2-hippocampus/memories/insights/2026-05-{slug}.md"
m = re.search(r'P2-hippocampus/memories/insights/(\d{4}-\d{2}-[^/]+\.md)', body)
if m:
draft_path = os.path.join(INSIGHTS, m.group(1))
if not os.path.exists(draft_path):
date_prefix = m.group(1)[:7]
matches = glob.glob(os.path.join(INSIGHTS, f"{date_prefix}-*.md"))
draft_path = max(matches, key=os.path.getmtime) if matches else f"~/.{{AGENT_NAME_LOWER}}/P2-hippocampus/memories/insights/{m.group(1)}"
drafts.append((title, task_id, draft_path))
완료된 task가 있으면这份 delivery 출력:
## Content Pipeline Update — YYYY-MM-DD HH:MM KST
Draft files ready for review:
| # | Topic | Task ID | Draft File |
|---|-------|---------|------------|
| 1 | {title} | {id} | ~/.{{AGENT_NAME_LOWER}}/P2-hippocampus/memories/insights/{filename}.md |
| 2 | {title} | {id} | ~/.{{AGENT_NAME_LOWER}}/P2-hippocampus/memories/insights/{filename}.md |
Review at: Obsidian → P2-hippocampus → memories → insights
완료된 task가 없으면 (completed_at이 72시간 내 없으면) [SILENT].
## Content Pipeline — YYYY-MM-DD HH:MM KST
Topics selected: N
| # | Source | Topic | Task ID | Draft File |
|---|--------|-------|---------|------------|
| 1 | trend | {title} | {task_id} | ~/.{{AGENT_NAME_LOWER}}/P2-hippocampus/memories/insights/{filename}.md |
| 2 | seo | {title} | {task_id} | ~/.{{AGENT_NAME_LOWER}}/P2-hippocampus/memories/insights/{filename}.md |
| 3 | conversation | {title} | {task_id} | — (in progress) |
Review at: Obsidian → P2-hippocampus → memories → insights
Draft File 列은 task body에서 P2-hippocampus/memories/insights/ 경로를 추출 — 있으면 절대경로로 표시, 없으면 "— (in progress)" 표시.
Obsidian에서 status: published로 바꾸면 → fswatch가 vault 변경 감지 → quartz build → wrangler pages deploy → YOUR_DOMAIN 게시 (약 10초 내외)
YOUR_USERNAME (Obsidian에서 draft 편집)
→ frontmatter: status: published, publish_date: YYYY-MM-DD
→ fswatch가 vault 파일 변경 감지 (LaunchAgent 자동 실행)
→ 5초 debounce 후 quartz build
→ wrangler pages deploy public/ --project-name=YOUR_SITE
→ Cloudflare Pages에 directory deploy (git 불필요)
→ YOUR_DOMAIN 게시
wrangler pages deploy는 git 없이 directory를 직접 Cloudflare Pages에 올립니다. git repo 아니어도 동작, git commit 불필요.
| Component | Details |
|---|---|
| fswatch LaunchAgent | com.{{AGENT_NAME_LOWER}}.quartz-fswatch (PID 5247 ✅) |
| fswatch script | ~/.{{AGENT_NAME_LOWER}}/P4-cortex/scripts/quartz-fswatch.sh |
| Watched dirs | memories/insights, P4-cortex/growth, P4-cortex/knowledge, YOUR_SITE/content |
| Debounce | 5초 (변경 후 5초内有 추가 변경이면 다시 5초 대기) |
| Build | cd YOUR_SITE && npx quartz build --concurrency=4 |
| Deploy | wrangler pages deploy public/ --project-name=YOUR_SITE (git 불필요) |
| Deploy LaunchAgent | com.{{AGENT_NAME_LOWER}}.quartz-deploy (runs on-demand via wrapper script) |
| CF Account ID | dc0199b6b6c27bc9bb2f3201d47cb643 |
| CF Project | YOUR_SITE |
| Site URL | https://YOUR_DOMAIN |
fswatch:
5247 running com.{{AGENT_NAME_LOWER}}.quartz-fswatch ← vault 변경 감지
63582 running com.{{AGENT_NAME_LOWER}}.quartz-deploy ← (KeepAlive, 필요시 실행)
YOUR_DOMAIN 실시간 게시 상태:
vault 파일 변경 → fswatch 감지 → 5초 debounce → quartz build → wrangler deploy → ~3초 후 게시
# 상태 확인
launchctl list | grep quartz
# fswatch 재시작
launchctl unload ~/Library/LaunchAgents/com.{{AGENT_NAME_LOWER}}.quartz-fswatch.plist
launchctl load -w ~/Library/LaunchAgents/com.{{AGENT_NAME_LOWER}}.quartz-fswatch.plist
# 로그 확인
tail -f ~/Library/Logs/quartz-fswatch.log
tail -f ~/Library/Logs/quartz-deploy.log
Content-manager drafts live at ~/.{{AGENT_NAME_LOWER}}/P2-hippocampus/memories/insights/. The Quartz site maps this directory into its content tree via symlink:
# Current mapping (June 2026):
YOUR_SITE/content/insights → P2-hippocampus/memories/insights/ # drafts + SVGs + PNGs
⚠️ Pitfall: The symlink was previously pointing to P4-cortex/knowledge (wrong dir). If drafts aren't appearing in the build, check:
readlink ~/.{{AGENT_NAME_LOWER}}/YOUR_SITE/content/insights
# Should point to: ~/.{{AGENT_NAME_LOWER}}/P2-hippocampus/memories/insights
The fswatch → quartz build → wrangler deploy pipeline can enter an infinite loop if build output triggers another fswatch event. Symptoms: deploy log repeating every 10 seconds, launch agents showing error exit codes.
Fix: The deploy script should check if a build is already running before starting a new one. Use a lock file:
LOCKFILE=/tmp/quartz-deploy.lock
if [ -f "$LOCKFILE" ] && [ $(($(date +%s) - $(cat "$LOCKFILE"))) -lt 60 ]; then
log "Deploy already running, skipping"
exit 0
fi
date +%s > "$LOCKFILE"
# ... build + deploy ...
rm -f "$LOCKFILE"
[ ] 오탈자·내용 수정
[ ] frontmatter: status: published (draft → published)
[ ] frontmatter: publish_date: YYYY-MM-DD 추가
[ ] aliases: ['/blog/YYYY/slug'] 또는 ['/blog/slug'] 설정 (SEO-friendly URL)
[ ] fswatch가 자동 감지 → YOUR_DOMAIN 게시 (~10초)
[ ] YOUR_DOMAIN에서 게시 확인
# 수동 빌드 + 배포 (fswatch 통하지 않고)
bash ~/.{{AGENT_NAME_LOWER}}/P4-cortex/scripts/quartz-deploy.sh
# 또는 wrangler 직접
cd ~/.{{AGENT_NAME_LOWER}}/YOUR_SITE
npx quartz build
wrangler pages deploy public/ --project-name=YOUR_SITE
~/.{{AGENT_NAME_LOWER}}/YOUR_SITE/
├── wrangler.toml ← project_name = "YOUR_SITE"
└── .wrangler.jsonv2 ← account_id, CF Pages project 설정
~/.{{AGENT_NAME_LOWER}}/P4-cortex/scripts/
├── quartz-deploy.sh ← build + wrangler deploy 스크립트
└── quartz-fswatch.sh ← fswatch 파일 변경 감지 → debounce → deploy
Quartz at YOUR_DOMAIN has Mermaid enabled (quartz/components/scripts/mermaid.inline.ts). Write ````mermaid code blocks directly in blog post markdown:
```mermaid
graph TD
subgraph "Source"
A[Git] --> D
B[Kanban] --> D
end
D{Agent} --> E[Blog Draft]
D --> F[X Thread]
Common types: `graph TD` (top-down), `graph LR` (left-right), `sequenceDiagram`, `flowchart TD`.
### Excalidraw Diagrams (Hand-Drawn Architecture Style)
For complex architecture/flow diagrams, create an `.excalidraw.json` companion file. Follows the visual style from the ReefWatch article (dev.to/siiddhantt/building-reefwatch).
The blog post references it as: `<!-- EXCALIDRAW: slug.excalidraw.json -- architecture diagram -->`
The JSON opens in Obsidian (Excalidraw plugin) or excalidraw.com for PNG export.
### Cover Image Descriptions
Add an HTML comment describing the ideal cover image:
```markdown
<!-- COVER: Dark mode dev dashboard showing architecture, amber accents, YOUR_DOMAIN style. -->
Placeholder — image generated later by image_gen tool or human designer.
status: published
publish_date: 2026-05-27
| Frontmatter | Site URL |
|---|---|
aliases: ['/blog/2026/agent-workflow'] | YOUR_DOMAIN/blog/2026/agent-workflow |
aliases: ['/projects/kanban-review'] | YOUR_DOMAIN/projects/kanban-review |
aliases: ['/lab/dream-system'] | YOUR_DOMAIN/lab/dream-system |
Slug 생성 규칙:
KANBAN-REVIEW-20260520 → kanban-reviewOpen Crab Ontology → open-crab-ontologyThe, A, AnDrafts can be pushed to the YOUR_DOMAIN WordPress site via the WordPress MCP server.
content-manager draft → memories/insights/ 저장
→ (future) Huly issue 생성 (status: Todo)
→ Drew reviews in Huly → Done
→ publisher cron → WordPress MCP push
See references/wordpress-publish-workflow.md for the full implementation guide.
Draft paths in agent profiles, cron prompts, and workflow documents must be absolute paths (~/.{{AGENT_NAME_LOWER}}/P2-hippocampus/memories/insights/). Do NOT use:
memories/insights/ — the agent's cwd may not resolve correctly~ expansion like ~/.{{AGENT_NAME_LOWER}}/... — some contexts (cron, subagent spawned by dispatcher) don't expand tilde✅ Safe: ~/.{{AGENT_NAME_LOWER}}/P2-hippocampus/memories/insights/filename.md
❌ Unsafe: memories/insights/filename.md
❌ Unsafe: ~/.{{AGENT_NAME_LOWER}}/P2-hippocampus/memories/insights/filename.md