"""
def risks():
rows = ""
for r in master["riskRegister"]:
rows += f"""
{e(r['risk'])}
{badge_risk(r['likelihood'])}
{badge_risk(r['impact'])}
{e(r['mitigation'])}
"""
return f"""
리스크
발생가능
영향
완화책
{rows}
"""
def metrics():
out = ""
for m in master["successMetrics"]:
out += f"""
{e(m['metric'])}
{e(m['current'])}
→
{e(m['target'])}
"""
return out
def research_section():
out = ""
for r in researches:
libs = ""
for L in r.get("libraries", []):
libs += f"""
{e(L.get('name'))}
{e(L.get('purpose'))}
{e(L.get('license'))}
{e(L.get('maturity'))}
"""
libtbl = (f'
라이브러리 · 도구
이름
용도
라이선스
성숙도
{libs}
') if libs else ""
finds = "".join(f"
{e(k)}
" for k in r.get("keyFindings", []))
srcs = "".join(
f'{e(s.split("//")[-1][:46])}'
for s in r.get("sources", []) if str(s).startswith("http")
)
out += f"""{e(r['topic'])}▸
핵심 발견
{finds}
권장 접근 (.NET 9 / 단일 EXE)
{e(r.get('recommendedApproach'))}
{libtbl}
통합 노트
{e(r.get('integrationNotes'))}
{('
출처
'+srcs+'
') if srcs else ''}
"""
return out
def analysis_section():
out = ""
for a in analyses:
weak = "".join(f"
{e(w)}
" for w in a.get("weaknesses", []))
blk = "".join(f"
{e(b)}
" for b in a.get("extensibilityBlockers", []))
opp = "".join(
f'
'
for o in a.get("improvementOpportunities", [])
)
out += f"""{e(a['subsystem'])}▸
{e(a.get('summary'))}
설계 약점
{weak}
확장성 차단 요소 (file:line)
{blk}
개선 기회
{opp}
"""
return out
def designs_section():
rk = {r["angle"].split("(")[0].strip(): r for r in ranking["rankings"]}
# map by index hint in angle text
cards = ""
for idx, d in enumerate(designs):
# find matching ranking by 'idx N'
rinfo = next((r for r in ranking["rankings"] if f"idx {idx}" in r["angle"]), None)
score = rinfo["score"] if rinfo else "-"
is_win = (rinfo and rinfo["score"] == max(rr["score"] for rr in ranking["rankings"]))
pct = (score if isinstance(score, (int, float)) else 0)
moves = "".join(f"
{e(mv)}
" for mv in d.get("keyMoves", [])[:6])
win_html = 'SELECTED BASE' if is_win else ""
cards += f"""
{e(d['angle'])}
{win_html}
{e(score)}
{e(d['vision'])[:340]}…
핵심 변경
{moves}
최대 리스크 · {e(d.get('biggestRisk',''))[:170]}
"""
graft = "".join(f"
{e(g)}
" for g in ranking["bestIdeasToGraft"])
return f"""
{cards}
심사 종합 권고
{nl2br(ranking['recommendation'])}
마스터플랜에 흡수한 최고의 아이디어
{graft}
"""
# nav
NAV_ITEMS = [
("개요", [("vision", "비전"), ("principles", "설계 원칙"), ("architecture", "타깃 아키텍처")]),
("설계", [("adr", "핵심 결정 (ADR)"), ("matrix", "변환 매트릭스"), ("ai", "AI 통합"), ("media", "미디어 레이어")]),
("실행", [("roadmap", "로드맵 P1–P8"), ("metrics", "성공 지표"), ("risks", "리스크 레지스터"), ("handoff", "다음 세션 인계")]),
("근거", [("designs", "설계안 · 심사"), ("research", "인터넷 리서치"), ("analysis", "코드 분석")]),
]
def nav():
out = ""
for grp, items in NAV_ITEMS:
out += f'
{grp}
'
for hid, lab in items:
out += f'{lab}'
return out
u = meta["usage"]
metarow = f"""
생성{e(meta['generatedDate'])}
방법멀티에이전트 Workflow
에이전트{u['agents']}
서브에이전트 토큰{u['subagentTokens']:,}
도구 호출{u['toolUses']}
종합{e(meta['winningSynthesis'])}
"""
HTML = f"""
{e(meta['title'])}
MASTER PLAN · Single Source of Truth
모든 변환을 엣지로, 엔진이 경로를 합성하는 만능 변환기
{e(master['elevatorPitch'])}
{metarow}
00
북극성 비전
{e(master['vision'])}
01
설계 원칙
아키텍처를 관통하는 불변식. 모든 코드 변경은 이 원칙을 위배하지 않아야 한다.
{principles()}
02
타깃 아키텍처
4계층 변환 그래프 아키텍처. 핵심은 ProviderRegistry를 단일 홉 딕셔너리에서 ConversionGraph로 승격하는 것.
{architecture()}
03
핵심 아키텍처 결정 (ADR)
현재 코드의 구체적 한계를 직접 겨냥한 결정. 클릭하면 근거·대안·트레이드오프가 펼쳐진다.
{adrs()}
04
변환 매트릭스 · 그래프 라우팅
단일 홉 → 멀티홉 자동 합성. transitive closure로 "이 파일로 만들 수 있는 모든 포맷"이 폭발한다.
{matrix()}
05
AI 통합 — Codex OAuth + API
키가 없어도 모든 기존 변환은 100% 동작. AI는 ✨ 배지로만 opt-in 노출되는 부가가치 엣지.
{ai_section()}
06
미디어 레이어 — 영상·오디오·PDF 압축
FFmpeg(LGPL 분리 호출)·Ghostscript(AGPL 감지만)로 카테고리를 미디어 변환기로 점프.
{media_section()}
07
실행 로드맵 · P1 → P8
각 단계가 독립적으로 가치를 전달하고 이전 단계에 의존한다. P1은 그래프 코어 + 즉시 체감(PDF 압축)을 함께 심는다.
{roadmap()}
08
성공 지표
현재 → 목표. 각 지표가 로드맵 완료를 객관적으로 측정한다.
{metrics()}
09
리스크 레지스터
가장 큰 위협은 "보이지 않는 리팩터링의 함정"과 GPL/AGPL 라이선스 오염.
{risks()}
10
다음 세션 인계 노트
이 문서가 SSOT다. 다음 세션은 아래 순서대로 시작한다.
▶ Handoff — 어디서부터 시작하고 무엇을 먼저 검증할지
{nl2br(master['ssotNotes'])}
11
설계안 비교 · 심사
3개 독립 아키텍트가 서로 다른 각도에서 제안했고, 심사가 점수화·종합했다.
{designs_section()}
12
인터넷 리서치 (7개 토픽)
2026년 6월 기준 WebSearch로 조사한 라이브러리·방법론·라이선스. 클릭하면 출처까지 펼쳐진다.
{research_section()}
13
코드 심층 분석 (5개 서브시스템)
현재 코드를 직접 읽고 file:line으로 인용한 약점·확장 차단 요소·개선 기회.
{analysis_section()}
"""
with open(os.path.join(HERE, "index.html"), "w", encoding="utf-8") as f:
f.write(HTML)
# ------------------------------------------------------------------ Markdown SSOT
def md():
L = []
L.append(f"# {meta['title']}\n")
L.append(f"> {meta['subtitle']}\n")
L.append(f"**생성** {meta['generatedDate']} · **방법** {meta['method']} ")
L.append(f"**규모** {u['agents']} agents · {u['subagentTokens']:,} subagent tokens · {u['toolUses']} tool calls ")
L.append(f"**종합** {meta['winningSynthesis']}\n")
L.append("> 이 문서는 SSOT다. `docs/ssot/_data/*.json` 을 갱신하고 `python docs/ssot/build.py` 로 재생성한다. 웹 버전은 `docs/ssot/index.html`.\n")
L.append("---\n## 엘리베이터 피치\n")
L.append(master["elevatorPitch"] + "\n")
L.append("## 북극성 비전\n")
L.append(master["vision"] + "\n")
L.append("## 설계 원칙\n")
for i, p in enumerate(master["designPrinciples"], 1):
L.append(f"{i}. **{p['name']}** — {p['description']}")
L.append("\n## 타깃 아키텍처\n")
L.append(master["targetArchitecture"]["overview"] + "\n")
for Lr in master["targetArchitecture"]["layers"]:
L.append(f"- **{Lr['name']}** — {Lr['responsibility']} \n `{'`, `'.join(Lr['components'])}`")
L.append(f"\n**데이터 흐름:** {master['targetArchitecture']['dataFlow']}\n")
L.append("## 핵심 아키텍처 결정 (ADR)\n")
for a in master["coreDecisions"]:
L.append(f"### {a['id']} · {a['title']}")
L.append(f"- **결정:** {a['decision']}")
L.append(f"- **근거:** {a['rationale']}")
if a.get("alternatives"):
L.append(f"- **대안:** {a['alternatives']}")
L.append(f"- **트레이드오프:** {a['tradeoffs']}\n")
L.append("## 실행 로드맵\n")
for r in master["roadmap"]:
dep = f" · depends: {r['dependsOn']}" if r.get("dependsOn") else ""
st = STATUS.get(r["phase"], "planned")
L.append(f"### {r['phase']} · {r['title']} `effort:{r['effort']}` `risk:{r['risk']}` `status:{st}`{dep}")
L.append(f"**목표:** {r['goal']}\n")
L.append("**산출물:**")
for d in r.get("deliverables", []):
L.append(f"- {d}")
if r.get("keyChanges"):
L.append("\n**핵심 코드 변경:**")
for c in r["keyChanges"]:
L.append(f"- `{c['area']}` — {c['change']}")
if r.get("newProviders"):
L.append(f"\n**신규 Provider:** {', '.join(r['newProviders'])}")
L.append(f"\n**Exit Criteria:** {r['exitCriteria']}\n")
L.append("## 변환 매트릭스 · 그래프 라우팅\n")
m = master["conversionMatrix"]
L.append(f"- **현재:** {m['currentState']}")
L.append(f"- **목표:** {m['targetState']}\n")
L.append("**구조적 공백:**")
for g in m["gaps"]:
L.append(f"- {g}")
L.append(f"\n**그래프 라우팅 설계:** {m['graphRoutingPlan']}\n")
L.append("## AI 통합\n")
ai = master["aiIntegration"]
L.append(f"- **Codex OAuth:** {ai['codexOAuth']}")
L.append(f"- **API 모드:** {ai['apiMode']}")
L.append(f"- **아키텍처:** {ai['architecture']}")
L.append("- **활용 사례:**")
for x in ai["useCases"]:
L.append(f" - {x}")
L.append("\n## 미디어 레이어\n")
ml = master["mediaLayer"]
for k, lab in [("video", "영상"), ("audio", "오디오"), ("pdfCompression", "PDF 압축"), ("imageOptim", "이미지 최적화"), ("approach", "외부 바이너리·라이선스 전략")]:
L.append(f"- **{lab}:** {ml[k]}")
L.append("\n## 리스크 레지스터\n")
L.append("| 리스크 | 발생 | 영향 | 완화책 |\n|---|---|---|---|")
for r in master["riskRegister"]:
L.append(f"| {r['risk']} | {r['likelihood']} | {r['impact']} | {r['mitigation']} |")
L.append("\n## 성공 지표\n")
L.append("| 지표 | 현재 | 목표 |\n|---|---|---|")
for x in master["successMetrics"]:
L.append(f"| {x['metric']} | {x['current']} | {x['target']} |")
L.append("\n## 다음 세션 인계 노트 (Handoff)\n")
L.append(master["ssotNotes"].replace("\\n", "\n") + "\n")
L.append("---\n## 심사 종합 권고\n")
L.append(ranking["recommendation"].replace("\\n", "\n") + "\n")
L.append("### 마스터플랜에 흡수한 최고의 아이디어\n")
for g in ranking["bestIdeasToGraft"]:
L.append(f"- {g}")
return "\n".join(L)
with open(os.path.join(HERE, "PLAN.md"), "w", encoding="utf-8") as f:
f.write(md())
print("OK index.html", os.path.getsize(os.path.join(HERE, "index.html")), "bytes")
print("OK PLAN.md", os.path.getsize(os.path.join(HERE, "PLAN.md")), "bytes")