Analysis report → Word (.docx)
Generate a CUBRID-house-style Korean Word report by writing a JSON spec and running the bundled generator (assets/build_report.js), which assembles the .docx with docx-js (the engine behind Anthropic's docx skill) and embeds matplotlib charts. The exact house design is reproduced:
- Centered cover: title (bold 23pt navy #1F3864), subtitle (12pt gray), meta line (9pt gray), bold conclusion abstract (10pt)
- Auto table of contents generated from the
h1 sections
- Numbered headings: Heading 1 = navy 15pt bold, Heading 2 = blue 12pt
- Bordered tables with row-level color coding: sky-blue (#D5E8F0) header row, green (#E2EFDA) pass rows, red (#F8D7DA) fail rows, gray (#F2F2F2) neutral-emphasis
- Running header set to the report title; footer page numbers
Step 0: Dependencies
assets/build.sh installs what is missing on its own (global docx, the matplotlib venv), so there is no separate setup step. The document is assembled with docx-js; chart/diagram blocks are rendered by assets/figures.py (matplotlib) and embedded as images. Extra tools, needed only when those blocks are used: a mermaid block needs curl + network to reach Kroki (or set KROKI_URL to a self-hosted instance; if Kroki is unreachable the block degrades to a code block and the build continues); svg blocks and the Step 4 visual verification need LibreOffice (soffice) and pdftoppm (poppler).
Step 1: Identify the report type and gather inputs
| Type |
Body skeleton |
| 에러 분석 |
증상 → 재현 → 원인 → 영향 → 해결 |
| 코드 분석 |
대상 → 구조/흐름 → 발견사항 → 개선안 |
| 이슈 분석 |
배경 → 현황 → 원인 → 해결방향 → 계획 |
| 비교 분석 |
기준/대상 → 항목별 비교표 → 권고 |
| 조사/현황 |
개요 → 방법 → 결과(표) → 해석 → 다음 단계 |
Collect (ask only for what is missing): title, subtitle (scope/version), meta (author/team · date: author defaults to CUBRID Dev1; do not ask for it unless the user names a different author), the headline conclusion, body content, and table data.
작성 원칙: write less, show more
Optimize for a reader who skims. Keep prose minimal and let structure carry the detail:
- Lead with the conclusion: the cover
conclusion states the answer first; 1. 개요 is one short paragraph.
- Core points only: short sentences, one idea per bullet; cut background the reader can infer.
- Prefer tables / charts / figures over paragraphs: turn comparisons, metrics, and status into a color-coded
table; turn trends or distributions into a chart embedded via an image; use a note box for the single most important caveat. Reserve p paragraphs for the few sentences that truly need prose.
- One fact per bullet, and no long inline lists: do not pack
현재 → 목표 into one sentence (make it two table columns), and never chain more than four names (classes, files, options) with commas. Split them into a table when the reader needs each name, or compress to 기준 + 개수 when they only need the scope (e.g. "Wrapper를 구현하는 7개 클래스").
- Emphasize only the few key terms with
**…** so the eye lands on them.
- No em-dash: never use the
— character in the document; use commas, colons, parentheses, or periods instead.
Step 2: Write the JSON spec
Write <topic>.json. See assets/example.json for a complete example. Schema:
title, subtitle, meta (작성자/팀 · 작성일 YYYY-MM-DD: author/team + date only; author defaults to CUBRID Dev1 unless the user specifies another; no org name, scope, or version on this line: those go in the subtitle or 부록), conclusion (bold cover abstract), header (optional; defaults to title), auto_number (optional: true → h1 sections auto-numbered 1., 2., … and TOC stays in sync; then give h1 bare titles without numbers).
blocks: an ordered list of:
{"t":"h1","text":"1. 개요"}: numbered section (TOC auto-built from these; number them 1., 2., …)
{"t":"h2","text":"6.1 …"}: subsection
{"t":"p","text":"…","bold":false}: paragraph (1.15 line spacing). Use **핵심어** anywhere to emphasize a phrase in navy bold.
{"t":"ul","items":["…","…"]}: bullet list ('•')
{"t":"table","header":[…],"aligns":["left","center", …],"rows":[{"cells":[…],"status":"good"}, …]}: default alignment: col0 left, other columns centered
{"t":"note","text":"강조할 핵심/주의","kind":"info|warn|bad"}: shaded callout box (참고/주의/경고)
{"t":"image","path":"figure.png","caption":"그림 1","width_in":6.0}: embed any existing image, centered + caption
{"t":"chart","kind":"bar","title":"…","subtitle":"…","note":"하단 주석","signed":false,"bars":[{"label":"…","value":N,"color":"base|blue|good|lightgreen|warn|bad","badge":"강조\n둘째 줄","note":"바 위 메모"}]}: vertical bars: bold-navy value labels, optional pill badge / colored note above a bar, subtle baseline (house style, no axes/grid)
{"t":"chart","kind":"hbar","title":"…","subtitle":"…","note":"…","bars":[{"label":"…","value":N,"color":"…","tag":"유지","tag_color":"good"}]}: horizontal ranked bars: label left (+ optional colored tag), proportional bar, bold-navy value at the end. Best for ranking magnitudes (signed defaults true)
- For trends or share:
{"t":"chart","kind":"line|pie","labels":[…],"series":[{"name":"…","data":[…]}]}
{"t":"mermaid","code":"flowchart LR\n A[…] --> B{…}","caption":"그림 1","width_in":6.4}: Mermaid diagram: PREFER THIS for every structured 도식 (flow/workflow, sequence, state, ER, class, architecture). Nodes auto-size to their text, so labels never clip. Rendered to PNG via Kroki (server-side headless browser) and embedded, so Word/LibreOffice show text + fills faithfully. Follow the Mermaid 도식 작성 규칙 below. (Needs curl + network, or a self-hosted KROKI_URL; if Kroki is unreachable the block degrades to a code block, so the build never fails on this.)
{"t":"svg","code":"<svg …>…</svg>","caption":"그림 2","width_in":6.4}: hand-authored SVG, embedded as a native vector image with a PNG fallback. Use only for bespoke visuals a standard Mermaid diagram can't express (custom geometry, annotated layouts, non-graph illustrations). Follow the SVG 도식 작성 규칙 below. (Needs LibreOffice for the fallback.)
{"t":"diagram","direction":"LR|TB","nodes":[…],"edges":[…],"caption":"그림 2"}: (legacy) matplotlib auto-layout flow; prone to text/shape overlap. Do not use for new reports: author a mermaid block instead.
{"t":"code","text":"..."}: monospace block (Consolas on light-gray)
{"t":"pagebreak"}: force a page break (cover→목차→본문 breaks are automatic)
- Table
status per row: good = green (pass), bad = red (fail), warn = gray, omitted = neutral. The header row is auto sky-blue and repeats across page breaks.
Follow the type skeleton from Step 1 and the 작성 원칙 above. Language: Korean, plain and direct.
도식 선택 (어떤 블록을 쓸까)
- 구조화된 도식(흐름/워크플로, 시퀀스, 상태, ER, 클래스, 아키텍처) →
mermaid. 노드가 글자에 맞춰 자동으로 커지므로 글자 짤림이 없고, 손으로 좌표를 잡을 필요가 없다. 기본값으로 삼는다.
- 정량 비교/추세/비율(막대·선·파이) →
chart.
- 표준 그래프로 표현 못 하는 맞춤 그림(특수 기하, 주석 레이아웃, 삽화) → **
svg**를 직접 작성.
Mermaid 도식 작성 규칙 (the mermaid block)
- 문법: 첫 줄에 다이어그램 종류(
flowchart LR|TB, sequenceDiagram, stateDiagram-v2, erDiagram, classDiagram)를 쓰고, 노드/엣지를 이어서 정의한다. 라벨은 한국어로 간결하게.
- 방향: 노드가 4개를 넘으면
flowchart LR(가로)보다 flowchart TB(세로)가 페이지 폭에 맞아 글자가 더 크게 나온다. 폭이 넘칠 것 같으면 세로로.
- 크기:
width_in으로 문서 내 폭을 정한다(기본 6.4). Kroki가 텍스트에 맞춰 렌더하므로 폭만 정하면 된다.
- 분기 라벨: 조건 분기는 엣지 라벨(
B -->|성공| C)로 표기한다.
- 금지:
%%{init}%% 로 htmlLabels를 끄지 말 것(불필요). 노드 라벨에 —(em-dash) 쓰지 말 것.
- Kroki가 각 도식을 서버에서 브라우저로 렌더해 PNG로 굽는다: 글자와 색이 항상 이미지에 박혀 Word/LibreOffice에서 그대로 보인다.
SVG 도식 작성 규칙 (the svg block)
Author the SVG yourself, the way the visualize tool would: deliberate layout, not auto-placed. These rules keep it on-brand and prevent the overlap/distortion that the old matplotlib diagram produced:
- Canvas: set
viewBox="0 0 W H" (this fixes the aspect ratio; width_in sizes it in the doc). No pixel width/height needed.
- Font: put
font-family="'맑은 고딕','Malgun Gothic','Apple SD Gothic Neo',sans-serif" on every <text>; titles font-weight="bold".
- Palette (match the report): text/heading navy
#1F3864; strokes & arrows blue #2E6DA4, good #2EA84F, bad #C0392B, warn #E8862E; light box fills #EAF1F8 (blue) / #E2EFDA (good) / #F8D7DA (bad) / #FFF2CC (warn); plain boxes on white.
- Boxes: rounded
rx="9", stroke-width="2". Size each box to its text: roughly width ≈ 9px × 글자수 + 32, height ≥ 48. Center the label with text-anchor="middle" and baseline ≈ box-center-y + 5.
- Arrows: draw edge-to-edge (start on the source box border, end on the target border: never center-to-center),
stroke-width="2", end with a <marker> arrowhead colored like the line. Put an edge label at the segment midpoint in the matching color.
- Spacing: leave ≥ 24px between boxes; never let text touch or overlap a border or another shape.
- Scope: use SVG for schematic diagrams (boxes + arrows + short labels). For quantitative comparison/trend/share, use a
chart block, not SVG.
Step 3: Generate the .docx
bash <skill-base-dir>/assets/build.sh <topic>.json <output>.docx
build.sh checks the toolchain and then runs build_report.js, so building is one call.
<skill-base-dir> is this skill's own directory (shown as its base directory when the skill runs). build_report.js calls figures.py for chart/diagram blocks (matplotlib), renders each mermaid block to PNG via Kroki (curl; override the endpoint with KROKI_URL), and rasterizes each svg block's PNG fallback via LibreOffice (soffice, resolved on PATH → macOS app bundle). Filename convention: CUBRID_<주제>_<유형>_YYYYMMDD.docx.
Step 4: Validate, visually verify, hand off
1) Check and render in one call:
bash <skill-base-dir>/assets/preview.sh <output>.docx
It verifies the .docx structure (zip integrity, required parts, well-formed XML in every part) and then renders every page to PNG, printing the image paths. Read those images. This is a structural check, not full OOXML schema validation.
2) Visual verification: render every page to an image and read them, to catch layout issues the schema can't (clipped chart labels, overlapping text, a note box merging into a table, broken page breaks, color/table problems). This is the PRIMARY defect-catcher; the structure check cannot see any of these. Do not skip it whenever soffice resolves: only skip if LibreOffice is genuinely absent (and then say so explicitly).
Per-diagram check (render → look → fix → repeat): when you read the rendered pages, inspect each figure specifically: is every node label fully inside its box, no text clipped or overlapping, no shape collision, arrows landing on borders, the whole figure within the page width? If a figure is wrong, fix its block (for mermaid: switch LR↔TB, shorten labels, or adjust width_in; for svg: resize the box or canvas) and re-run Step 3 + this render, then look again. Repeat until every diagram is clean. Do not hand off a report with a diagram you have not looked at.
preview.sh resolves LibreOffice itself (PATH, then the macOS app bundle) and tells you what to install if it is missing. After a fix, re-run build.sh then preview.sh: two calls per round.
Note: LibreOffice substitutes 맑은 고딕 if it is not installed locally: the user's Word (with the font) renders correctly; chart text uses AppleGothic baked into the PNGs.
Then ls -la <output>.docx, tell the user the path, and keep the .json (editable source).
1---2name: report3description: Generate a CUBRID-house-style Korean analysis report as a Word (.docx): error analyses, code analyses, issue write-ups, before/after comparisons, and status/investigation reports. Builds the .docx with docx-js (the engine behind Anthropic's docx skill) from a JSON spec, embedding matplotlib charts, reproducing the exact design: centered cover (navy 23pt title), auto table of contents, navy/blue numbered headings, and bordered tables with row-level color coding (sky-blue header, green pass rows, red fail rows). Use when the user wants a Word report or structured document summarizing analysis, findings, comparisons, or current status of Hibernate/JDBC/CUBRID work. Triggers on phrases like 'write a report', '보고서 만들어', 'Word 문서로 정리', 'docx로 작성', 'analysis report', '검증 보고서', '비교 분석 문서'.4---56# Analysis report → Word (.docx)78Generate a CUBRID-house-style Korean Word report by writing a JSON spec and running the bundled generator (`assets/build_report.js`), which assembles the .docx with **docx-js** (the engine behind Anthropic's docx skill) and embeds matplotlib charts. The exact house design is reproduced:910- **Centered cover**: title (bold 23pt navy #1F3864), subtitle (12pt gray), meta line (9pt gray), bold conclusion abstract (10pt)11- **Auto table of contents** generated from the `h1` sections12- **Numbered headings**: Heading 1 = navy 15pt bold, Heading 2 = blue 12pt13- **Bordered tables with row-level color coding**: sky-blue (#D5E8F0) header row, green (#E2EFDA) pass rows, red (#F8D7DA) fail rows, gray (#F2F2F2) neutral-emphasis14- **Running header** set to the report title; footer page numbers1516## Step 0: Dependencies1718`assets/build.sh` installs what is missing on its own (global `docx`, the matplotlib venv), so there is no separate setup step. The document is assembled with **docx-js**; `chart`/`diagram` blocks are rendered by `assets/figures.py` (matplotlib) and embedded as images. Extra tools, needed only when those blocks are used: a `mermaid` block needs `curl` + network to reach **Kroki** (or set `KROKI_URL` to a self-hosted instance; if Kroki is unreachable the block degrades to a code block and the build continues); `svg` blocks and the Step 4 visual verification need **LibreOffice** (`soffice`) and `pdftoppm` (poppler).1920## Step 1: Identify the report type and gather inputs2122| Type | Body skeleton |23|------|---------------|24| 에러 분석 | 증상 → 재현 → 원인 → 영향 → 해결 |25| 코드 분석 | 대상 → 구조/흐름 → 발견사항 → 개선안 |26| 이슈 분석 | 배경 → 현황 → 원인 → 해결방향 → 계획 |27| 비교 분석 | 기준/대상 → 항목별 비교표 → 권고 |28| 조사/현황 | 개요 → 방법 → 결과(표) → 해석 → 다음 단계 |2930Collect (ask only for what is missing): title, subtitle (scope/version), meta (author/team · date: **author defaults to `CUBRID Dev1`**; do not ask for it unless the user names a different author), the headline conclusion, body content, and table data.3132## 작성 원칙: write less, show more3334Optimize for a reader who skims. Keep prose minimal and let structure carry the detail:3536- **Lead with the conclusion**: the cover `conclusion` states the answer first; `1. 개요` is one short paragraph.37- **Core points only**: short sentences, one idea per bullet; cut background the reader can infer.38- **Prefer tables / charts / figures over paragraphs**: turn comparisons, metrics, and status into a color-coded `table`; turn trends or distributions into a chart embedded via an `image`; use a `note` box for the single most important caveat. Reserve `p` paragraphs for the few sentences that truly need prose.39- **One fact per bullet, and no long inline lists**: do not pack `현재 → 목표` into one sentence (make it two table columns), and never chain more than four names (classes, files, options) with commas. Split them into a `table` when the reader needs each name, or compress to **기준 + 개수** when they only need the scope (e.g. "`Wrapper`를 구현하는 7개 클래스").40- **Emphasize only the few key terms** with `**…**` so the eye lands on them.41- **No em-dash**: never use the `—` character in the document; use commas, colons, parentheses, or periods instead.4243## Step 2: Write the JSON spec4445Write `<topic>.json`. See `assets/example.json` for a complete example. Schema:4647- `title`, `subtitle`, `meta` (`작성자/팀 · 작성일 YYYY-MM-DD`: author/team + date **only**; **author defaults to `CUBRID Dev1`** unless the user specifies another; no org name, scope, or version on this line: those go in the subtitle or 부록), `conclusion` (bold cover abstract), `header` (optional; defaults to title), `auto_number` (optional: `true` → h1 sections auto-numbered `1.`, `2.`, … and TOC stays in sync; then give h1 bare titles without numbers).48- `blocks`: an ordered list of:49 - `{"t":"h1","text":"1. 개요"}`: numbered section (TOC auto-built from these; number them `1.`, `2.`, …)50 - `{"t":"h2","text":"6.1 …"}`: subsection51 - `{"t":"p","text":"…","bold":false}`: paragraph (1.15 line spacing). Use `**핵심어**` anywhere to emphasize a phrase in navy bold.52 - `{"t":"ul","items":["…","…"]}`: bullet list ('•')53 - `{"t":"table","header":[…],"aligns":["left","center", …],"rows":[{"cells":[…],"status":"good"}, …]}`: default alignment: col0 left, other columns centered54 - `{"t":"note","text":"강조할 핵심/주의","kind":"info|warn|bad"}`: shaded callout box (참고/주의/경고)55 - `{"t":"image","path":"figure.png","caption":"그림 1","width_in":6.0}`: embed any existing image, centered + caption56 - `{"t":"chart","kind":"bar","title":"…","subtitle":"…","note":"하단 주석","signed":false,"bars":[{"label":"…","value":N,"color":"base|blue|good|lightgreen|warn|bad","badge":"강조\n둘째 줄","note":"바 위 메모"}]}`: vertical bars: bold-navy value labels, optional pill `badge` / colored `note` above a bar, subtle baseline (house style, no axes/grid)57 - `{"t":"chart","kind":"hbar","title":"…","subtitle":"…","note":"…","bars":[{"label":"…","value":N,"color":"…","tag":"유지","tag_color":"good"}]}`: horizontal ranked bars: label left (+ optional colored `tag`), proportional bar, bold-navy value at the end. Best for ranking magnitudes (`signed` defaults true)58 - For trends or share: `{"t":"chart","kind":"line|pie","labels":[…],"series":[{"name":"…","data":[…]}]}`59 - `{"t":"mermaid","code":"flowchart LR\n A[…] --> B{…}","caption":"그림 1","width_in":6.4}`: **Mermaid diagram: PREFER THIS for every structured 도식 (flow/workflow, sequence, state, ER, class, architecture).** Nodes auto-size to their text, so labels never clip. Rendered to PNG via Kroki (server-side headless browser) and embedded, so Word/LibreOffice show text + fills faithfully. Follow the **Mermaid 도식 작성 규칙** below. (Needs `curl` + network, or a self-hosted `KROKI_URL`; if Kroki is unreachable the block degrades to a code block, so the build never fails on this.)60 - `{"t":"svg","code":"<svg …>…</svg>","caption":"그림 2","width_in":6.4}`: hand-authored SVG, embedded as a native vector image with a PNG fallback. Use **only for bespoke visuals** a standard Mermaid diagram can't express (custom geometry, annotated layouts, non-graph illustrations). Follow the **SVG 도식 작성 규칙** below. (Needs LibreOffice for the fallback.)61 - `{"t":"diagram","direction":"LR|TB","nodes":[…],"edges":[…],"caption":"그림 2"}`: *(legacy)* matplotlib auto-layout flow; prone to text/shape overlap. **Do not use for new reports: author a `mermaid` block instead.**62 - `{"t":"code","text":"..."}`: monospace block (Consolas on light-gray)63 - `{"t":"pagebreak"}`: force a page break (cover→목차→본문 breaks are automatic)64- Table `status` per row: `good` = green (pass), `bad` = red (fail), `warn` = gray, omitted = neutral. The header row is auto sky-blue and repeats across page breaks.6566Follow the type skeleton from Step 1 and the 작성 원칙 above. Language: Korean, plain and direct.6768### 도식 선택 (어떤 블록을 쓸까)69701. **구조화된 도식**(흐름/워크플로, 시퀀스, 상태, ER, 클래스, 아키텍처) → **`mermaid`**. 노드가 글자에 맞춰 자동으로 커지므로 글자 짤림이 없고, 손으로 좌표를 잡을 필요가 없다. 기본값으로 삼는다.712. **정량 비교/추세/비율**(막대·선·파이) → **`chart`**.723. **표준 그래프로 표현 못 하는 맞춤 그림**(특수 기하, 주석 레이아웃, 삽화) → **`svg`**를 직접 작성.7374### Mermaid 도식 작성 규칙 (the `mermaid` block)7576- **문법**: 첫 줄에 다이어그램 종류(`flowchart LR|TB`, `sequenceDiagram`, `stateDiagram-v2`, `erDiagram`, `classDiagram`)를 쓰고, 노드/엣지를 이어서 정의한다. 라벨은 한국어로 간결하게.77- **방향**: 노드가 4개를 넘으면 `flowchart LR`(가로)보다 `flowchart TB`(세로)가 페이지 폭에 맞아 글자가 더 크게 나온다. 폭이 넘칠 것 같으면 세로로.78- **크기**: `width_in`으로 문서 내 폭을 정한다(기본 6.4). Kroki가 텍스트에 맞춰 렌더하므로 폭만 정하면 된다.79- **분기 라벨**: 조건 분기는 엣지 라벨(`B -->|성공| C`)로 표기한다.80- **금지**: `%%{init}%%` 로 `htmlLabels`를 끄지 말 것(불필요). 노드 라벨에 `—`(em-dash) 쓰지 말 것.81- Kroki가 각 도식을 서버에서 브라우저로 렌더해 PNG로 굽는다: 글자와 색이 항상 이미지에 박혀 Word/LibreOffice에서 그대로 보인다.8283### SVG 도식 작성 규칙 (the `svg` block)8485Author the SVG yourself, the way the `visualize` tool would: deliberate layout, not auto-placed. These rules keep it on-brand and prevent the overlap/distortion that the old matplotlib `diagram` produced:8687- **Canvas**: set `viewBox="0 0 W H"` (this fixes the aspect ratio; `width_in` sizes it in the doc). No pixel `width`/`height` needed.88- **Font**: put `font-family="'맑은 고딕','Malgun Gothic','Apple SD Gothic Neo',sans-serif"` on every `<text>`; titles `font-weight="bold"`.89- **Palette (match the report)**: text/heading navy `#1F3864`; strokes & arrows blue `#2E6DA4`, good `#2EA84F`, bad `#C0392B`, warn `#E8862E`; light box fills `#EAF1F8` (blue) / `#E2EFDA` (good) / `#F8D7DA` (bad) / `#FFF2CC` (warn); plain boxes on white.90- **Boxes**: rounded `rx="9"`, `stroke-width="2"`. Size each box to its text: roughly `width ≈ 9px × 글자수 + 32`, `height ≥ 48`. Center the label with `text-anchor="middle"` and baseline ≈ box-center-y + 5.91- **Arrows**: draw **edge-to-edge** (start on the source box border, end on the target border: never center-to-center), `stroke-width="2"`, end with a `<marker>` arrowhead colored like the line. Put an edge label at the segment midpoint in the matching color.92- **Spacing**: leave ≥ 24px between boxes; never let text touch or overlap a border or another shape.93- **Scope**: use SVG for schematic diagrams (boxes + arrows + short labels). For quantitative comparison/trend/share, use a `chart` block, not SVG.9495## Step 3: Generate the .docx9697```bash98bash <skill-base-dir>/assets/build.sh <topic>.json <output>.docx99```100101`build.sh` checks the toolchain and then runs `build_report.js`, so building is one call.102103`<skill-base-dir>` is this skill's own directory (shown as its base directory when the skill runs). `build_report.js` calls `figures.py` for `chart`/`diagram` blocks (matplotlib), renders each `mermaid` block to PNG via Kroki (`curl`; override the endpoint with `KROKI_URL`), and rasterizes each `svg` block's PNG fallback via LibreOffice (`soffice`, resolved on PATH → macOS app bundle). Filename convention: `CUBRID_<주제>_<유형>_YYYYMMDD.docx`.104105## Step 4: Validate, visually verify, hand off106107**1) Check and render in one call**:108109```bash110bash <skill-base-dir>/assets/preview.sh <output>.docx111```112113It verifies the .docx structure (zip integrity, required parts, well-formed XML in every part) and then renders every page to PNG, printing the image paths. Read those images. This is a structural check, not full OOXML schema validation.114115**2) Visual verification**: render every page to an image and read them, to catch layout issues the schema can't (clipped chart labels, overlapping text, a `note` box merging into a table, broken page breaks, color/table problems). This is the PRIMARY defect-catcher; the structure check cannot see any of these. **Do not skip it whenever `soffice` resolves**: only skip if LibreOffice is genuinely absent (and then say so explicitly).116117**Per-diagram check (render → look → fix → repeat)**: when you read the rendered pages, inspect **each figure** specifically: is every node label fully inside its box, no text clipped or overlapping, no shape collision, arrows landing on borders, the whole figure within the page width? If a figure is wrong, fix its block (for `mermaid`: switch `LR`↔`TB`, shorten labels, or adjust `width_in`; for `svg`: resize the box or canvas) and re-run Step 3 + this render, then look again. Repeat until every diagram is clean. Do not hand off a report with a diagram you have not looked at.118119`preview.sh` resolves LibreOffice itself (PATH, then the macOS app bundle) and tells you what to install if it is missing. After a fix, re-run `build.sh` then `preview.sh`: two calls per round.120121Note: LibreOffice substitutes 맑은 고딕 if it is not installed locally: the user's Word (with the font) renders correctly; chart text uses AppleGothic baked into the PNGs.122123Then `ls -la <output>.docx`, tell the user the path, and keep the `.json` (editable source).