Imported from kw1jjang/academic-homepage-cv-sync (
AGENTS.md). Install upstream withnpx skills add kw1jjang/academic-homepage-cv-sync. Copyright stays with the author.
Project Notes
What this repo is
An academic homepage (Jekyll → GitHub Pages) and a LaTeX CV generated from one set of YAML files, so the two can't drift apart.
This repo ships with placeholder data. Until someone replaces it, every paper, talk, person, award, and photo belongs to "Bokdol Lee" — a Maltese from South Korea with three PhDs (Philosophy, Math, Kinesiology) who is now a student at the "International School for Elite Gifted Dogs", and who studies the purpose of life, the golden ratio of food/exercise/sleep, and the biomechanics of running and jumping. The persona is deliberately absurd so it can never be mistaken for a real user's data, and it exercises every supported field, so it doubles as documentation.
If the data still describes a dog, the user's first request is almost certainly "make this mine" — that means replacing the placeholder content, not appending to it. See Personalizing a fresh copy. Don't leave a half-Bokdol/half-user site behind.
Prerequisites
- Python 3.10+ with
pyyaml(pip install pyyaml) — required. - A TeX distribution with
pdflatex— required only for the PDF. Without it, runpython sync.py --no-compileand tell the user the PDF wasn't rebuilt; don't silently skip it.
Run sync.py from the repo root — its paths are relative.
CV ↔ Website Sync
Workflow
- Edit YAML files in
_data/(add/update papers, talks, people, etc.) - Run
python sync.py(orpython sync.py --no-compileto skip the PDF) - Both
index.mdandcv/CV.texare updated betweenSYNC:markers - The CV is compiled and copied to
CV.pdfat the repo root - Commit the regenerated files — see GitHub Pages
sync.py exits non-zero and writes nothing when it finds a data problem, and
names the offending entry. Trust it: if it exits 0 and prints Sync complete!, the
CV really did compile. Never paper over a non-zero exit — read the message.
Generated vs. hand-written
Generated by sync.py — never hand-edit |
Yours to edit | |
|---|---|---|
index.md |
between <!-- SYNC:NAME:BEGIN --> / <!-- SYNC:NAME:END --> |
everything else: the front matter, the inlined <script>, the hero header, the ## … Lab @ … heading |
cv/CV.tex |
between % SYNC:NAME:BEGIN / % SYNC:NAME:END |
the preamble, Research Interests, \extended{…} blocks, hand-written section titles |
Editing inside the markers is pointless — the next sync.py overwrites it. Edit the
YAML instead. Deleting a marker is worse: sync.py now refuses to run rather than
freezing that section silently, but you still have to restore the pair by hand.
When YAML can't express it
Not everything on the site or in the CV comes from _data/. Some things have no
field, and some have no section. Pick the smallest tier that actually fits —
and say which one you used, because tiers 2 and 3 change files the user may not
expect you to touch.
Tier 1 — data (default). It's another paper, talk, person, award, grant,
course, service entry, or job. Edit the YAML, run sync.py, done. If you find
yourself reaching for tier 2 or 3 to add one of those, re-read the file's schema
header first — the field usually exists.
Tier 2 — hand-written prose, edited directly in index.md / cv/CV.tex.
Content that is specific to this one person and will never repeat belongs
outside the SYNC markers, where sync.py will never touch it:
- Rewording the bio, hero header, email, or profile links (
index.md) Research Interests,\name{},\address{},pdfauthor, the webpage/Scholar list after\begin{document}(cv/CV.tex)- A whole new CV section. A
\begin{rSection}{Patents}…\end{rSection}block placed outside every marker pair survives every futuresync.pyrun. This is the escape hatch for one-off sections (patents, press, a mentoring statement, a teaching philosophy) that no YAML schema covers. - Long-form-only material: wrap it in
\extended{…}and it appears only when the preamble says\setboolean{extended}{true}
Inventing a new marker pair does nothing on its own — sync.py only fills
the names listed in main() (PUBLICATIONS, TALKS, PEOPLE, AWARDS,
SERVICE, GRANTS, TEACHING, BACKGROUND, TECH_REPORTS). Unrecognized
markers are inert comments. That's tier 3.
Tier 3 — change sync.py itself. Correct when the shape of the output is
wrong, not the content: a YAML field the generator ignores, a new publication
type or talk subsection, a new person role, a new CV section that should be
generated from data. Editing the generator is expected and fine — it is part of
the template, not a black box. When you do:
- Update the schema comment at the top of the affected
_data/*.yml. That header is the documentation the next agent reads; a field that works but isn't documented there will be deleted by someone who thinks it's a typo. - Change both generators, or neither. Website output comes from
gen_web_*, the CV fromgen_cv_*. Adding a field to one and not the other recreates the exact drift this repo exists to prevent. If a field is deliberately one-sided (cv_onlyon awards, enrollment numbers on courses), say so in the schema comment. - Add a
validate()check if a bad or missing value would degrade quietly — a dropped entry, an empty role, a?'25margin label. The rule here is that data mistakes fail loudly before anything is written, rather than shipping a half-wrong PDF. A silent fallback is a bug, not politeness. - Keep the config-block tuples in step with the branches you add:
TALK_TOPICS,CV_AWARD_TYPES,PEOPLE_ROLES.PEOPLE_ROLESmirrors the branches ingen_web_people/gen_cv_people— adding a name there without adding the branch just moves the silent failure. - Re-run and check both outputs, not just the one you were asked about.
git diff index.md cv/CV.texand read it; for the PDF,pdftotext CV.pdf -.
A useful test for tier 2 vs. tier 3: will this recur? "Add a patents
section listing my two patents" is prose — tier 2. "Every patent should render
with its number and filing date" is a schema — tier 3, with a patents.yml, a
gen_web_patents/gen_cv_patents pair, a marker pair in both files, and a
replace_section call for each.
Never edit index.md or cv/CV.tex inside the markers, in any tier. If the
generated text is wrong, the generator or the data is wrong.
Data files (_data/)
| File | Contents |
|---|---|
tech_reports.yml |
Tech reports (non-publication artifacts). Key fields: title, authors, year, month, url, arxiv, links |
publications.yml |
All papers. Key fields: title, authors, venue, year, type (preprint/conference/workshop/journal/abstract), selected, url, award, award_type, cv_label, venue_full, location, month |
talks.yml |
All talks. Key fields: title, venue, year, month, topic (must be a key in TALK_TOPICS), selected, video, links |
people.yml |
Lab members & alumni. Key fields: name, role, link, photo, graduation_year, current_position. Order is semantic: current members first, then alumni — don't sort the file |
awards.yml |
Awards and honors |
grants.yml |
Research grants |
teaching.yml |
Courses taught |
service.yml |
Professional service (reviewing, editing, etc.) |
background.yml |
Education, appointments, work experience |
Each YAML file begins with a comment documenting its full schema — read it before adding an entry. It gives the accepted values, not just the field names.
Configuration block in sync.py
Lines ~20–48, and it needs editing per user — it is not just a script to invoke:
| Constant | What it does |
|---|---|
PI_NAME |
Bolded in CV author lists via a plain substring replace, so it must match the author strings in publications.yml byte for byte. PI_NAME = "Jane Smith" will not bold J. Smith. |
HIRING_NOTE |
Sentence above the website's people list ('' to disable) |
PUB_OLD_YEAR_CUTOFF / TALK_OLD_YEAR_CUTOFF |
Older entries collapse into an "N and earlier" bucket on the website |
TALK_TOPICS |
CV talk subsections, in order. Every talk's topic must be a key here |
CV_AWARD_TYPES |
award_type values the CV can render |
Common tasks
- Add a paper: add to
publications.yml(selected: trueto feature it), runpython sync.py - Add a talk: add to
talks.ymlwith atopicfromTALK_TOPICS, runpython sync.py - Add a tech report: add to
tech_reports.yml, runpython sync.py - Add a lab member: add to
people.yml(current members before alumni) with a photo inassets/images/members/, runpython sync.py
Personalizing a fresh copy
All seven of these still say "Bokdol Lee" in a fresh clone:
_config.yml—title,description,url,baseurl(see below)index.md— the front-matterdescription:, the hero header above the firstSYNCmarker, and the## Bokdol Lab @ …heading. Keep the{: .people-list}line under that heading — it's the styling hookcv/CV.tex—pdfauthor,\name{},\address{}, and the webpage/Scholar list right after\begin{document}sync.py— the configuration block above,PI_NAMEfirst_data/*.yml— replace the placeholder entriesassets/images/profile.pngandassets/images/members/placeholder-*.png- Run
python sync.py, then commit
CV
- Source is
cv/CV.tex;sync.pycompiles it withpdflatex(two passes) - Both
CV.pdf(root, published) andcv/CV.pdfare tracked — commit both \setboolean{extended}{true}in the preamble includes the\extended{…}sections (media coverage, detailed grants) for a long-form CV- Paper titles, author lists, people names, and grant titles reach the CV as raw
LaTeX — deliberately, so a title can contain math like
$O(n^2)$. That means a literal%,#,_,$, or&in one of those fields must be escaped in the YAML (50\%,Chain\_of\_Thought). The build fails loudly rather than shipping a stale PDF, but the fix is yours. - Do not pre-escape
&in venue,venue_full,venue_tex, course, institution, company, or award fields —sync.pyescapes those itself, so writing\&there double-escapes and breaks the build
Website
- Jekyll site using the Just-the-Docs theme, vendored in-repo (
_layouts/,_includes/,_sass/). There is no theme gem and noremote_theme— don't add one, and don't assume upstream just-the-docs docs apply to files here. - Single-page layout: all content in
index.md, sidebar hidden via CSS - Custom JS is inlined in
index.md(theme doesn't auto-load_includes/js/custom.js) - Custom styles in
_sass/custom/custom.scss bundle exec jekyll serveto preview locally (optional; needs Ruby)- Known cosmetic gap: website talks are grouped by year but not sorted within a
year — they appear in file order. Reorder
talks.yml, notindex.md
How this repo becomes a live site (GitHub Pages)
This is a classic Pages site: GitHub runs Jekyll server-side on push.
_data/*.yml ──► python sync.py ──► index.md + cv/CV.tex + CV.pdf [locally, by you]
│
git push
▼
GitHub runs jekyll build ──► live site
sync.pynever runs on GitHub. Its output (index.md,cv/CV.tex,CV.pdf,cv/CV.pdf) is committed generated code. After editing_data/, always runsync.pyand commit the regenerated files, or the live site won't change.- GitHub ignores the
Gemfileand builds with its own pinned toolchain. TheGemfile/Dockerfileexist only for local preview. Any plugin added to_config.ymlmust be on the GitHub Pages allowlist — currently onlyjekyll-seo-tagis used. baseurlin_config.ymlmust match where the site is served.""for a user site (<user>.github.io),"/<repo>"for a project site (<user>.github.io/<repo>). Asset URLs go through Jekyll'srelative_urlfilter, sobaseurlis the only knob — but a wrong value silently 404s every stylesheet, icon font, and photo while the page itself still loads.- Never hard-code root-absolute asset paths (
/assets/...) inindex.md,_includes/, or in strings emitted bysync.py. Pipe them throughrelative_urlinstead (see themember-iconimages insync.pyfor the pattern). A local build with an emptybaseurlwill not catch this mistake. _config.yml'sexclude:list keeps repo-only files (sync.py,cv/,AGENTS.md, Docker files, …) out of the published site.CV.pdfat the repo root is published;cv/(the LaTeX source) is not.- Build failures appear in the repo's Actions tab and are emailed by GitHub; there is no workflow file to inspect in this repo.