From 9b83f07ac9f6df7a2bf1f612ef4d613d6a19dd19 Mon Sep 17 00:00:00 2001 From: Sina Atalay <79940989+sinaatalay@users.noreply.github.com> Date: Fri, 20 Mar 2026 20:41:53 +0300 Subject: [PATCH] Move distributable skill to rendercv/rendercv-skill submodule The skill file lived in the main repo, which meant `npx skills add` had to clone the entire RenderCV codebase and exposed internal dev skills. A dedicated lightweight repo solves both problems. - Create rendercv/rendercv-skill repo as a read-only distribution channel - Add it as a submodule at .claude/skills/rendercv-skill/ - Update generation script and tests to write to the submodule path - Add submodules: true to test workflow checkout steps - Update docs, README, and install commands to use rendercv/rendercv-skill - Add --recursive clone instruction to developer guide - Delete the old skills/ directory at repo root --- .claude/skills/rendercv-skill | 1 + .github/workflows/test.yaml | 4 + .gitmodules | 3 + README.md | 4 +- docs/assets/rendercv_skill.zip | Bin 0 -> 8768 bytes docs/developer_guide/github_workflows.md | 5 +- docs/developer_guide/index.md | 5 +- docs/index.md | 2 +- .../how_to/use_the_ai_agent_skill.md | 26 +- .../evals/prompts/skill_chat.js | 3 + scripts/rendercv_skill/generate.py | 19 +- skills/rendercv/SKILL.md | 805 ------------------ tests/test_generated_files.py | 14 +- 13 files changed, 69 insertions(+), 822 deletions(-) create mode 160000 .claude/skills/rendercv-skill create mode 100644 .gitmodules create mode 100644 docs/assets/rendercv_skill.zip delete mode 100644 skills/rendercv/SKILL.md diff --git a/.claude/skills/rendercv-skill b/.claude/skills/rendercv-skill new file mode 160000 index 00000000..921bb1cb --- /dev/null +++ b/.claude/skills/rendercv-skill @@ -0,0 +1 @@ +Subproject commit 921bb1cb5a788cb39b4d113ced3667a768e62b4b diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 2b811c3c..a031425b 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -18,6 +18,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 + with: + submodules: true - name: Install uv uses: astral-sh/setup-uv@v7 @@ -47,6 +49,8 @@ jobs: runs-on: ${{ matrix.os }}-latest steps: - uses: actions/checkout@v6 + with: + submodules: true - name: Install uv uses: astral-sh/setup-uv@v7 diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..af869c16 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule ".claude/skills/rendercv-skill"] + path = .claude/skills/rendercv-skill + url = https://github.com/rendercv/rendercv-skill.git diff --git a/README.md b/README.md index d0241f2b..05e23de6 100644 --- a/README.md +++ b/README.md @@ -141,10 +141,10 @@ locale: Let AI coding agents create and edit your CV. Install the RenderCV skill: ```bash -npx skills add rendercv/rendercv +npx skills add rendercv/rendercv-skill ``` -Works with Claude Code, Cursor, Codex, Copilot, Windsurf, Gemini CLI, and [20+ other agents](https://docs.rendercv.com/user_guide/how_to/use_the_ai_agent_skill). +Works with Claude Code, Claude Desktop, Cursor, Codex, Copilot, Windsurf, Gemini CLI, and [20+ other agents](https://docs.rendercv.com/user_guide/how_to/use_the_ai_agent_skill). ## Get Started diff --git a/docs/assets/rendercv_skill.zip b/docs/assets/rendercv_skill.zip new file mode 100644 index 0000000000000000000000000000000000000000..bdd95a065a40880f28beb8d3b7a22c55b707647b GIT binary patch literal 8768 zcmZ{qMNk|J6JQ}gAh>%-fZ!0^-Q9I?cN^T@-F0wxcLpc8ySuwP?Dy|=Yr7Axs{7E_ z?&_x?4F!z}0RaIA5nJe_?Poy>>LiAMST%)!K>M%iU}9@*;$Y-LuPiMoBlFkB_&`U? zkzmBxXQ<|Q_cG*9kiSI4da^?N7%DybQfIo~A3G}QTt;(LQe@NZwzvQ~^%pXqk-!Q= zbx=#8HrA2+{=^H}oAhngTxxo+Xkp_@p%Ty<^7xX|`}MvphdJuVy-7k&Mfwjle)&Lm z8ItNLMK24&ZYi11?n2PAeA0MQ8u^Vx5!F8v%16B-Au(Z|ltYZJZJqhB>d!6-;{NQ)FsSbeCn=QMC{Gn(6_nf-^H$t*R1E7=vsSuJb zsexn2$WJ^>epiHbaLF(Z*5GhQW<`S&{Xh{{#{Q5P`^`OLFmYSmE4$cS9Gbd)^Z?6c zxMYr_m#eO>;Xr^@1O#!Uh)U(pi-EQ!mV4lyE$a^<5P*vW1U_~W>pIa9%bPgx4hR*u zwf>~}2geTOur38sH?F&Wi2>#t#IZ`XQf(?RhsL$Kd?fw_1MYfwo9Mex`KO@1G@Y2z zjhi&Vn_ZBy2M|uP6m|<|`B$OnoqJCN`86b{>SfSDI((LC5n@A3ZSf;5@ zai?*-{>4i{fqxJ8xWArpb0()L+<)LdRf|KKQv&0%QQ%D3A`-QngNHlv7wT@+V@MCQ2=HW=I2m5 z*>=(10Ge$=?YiGU;QKW2hf{g`k9oQ}IG-1z13l)BxYL_Gp7tTZTC5|1593LYiIT=0A%%a{e)7MC`kG@jgDI{|5x0<#zv2=Hah;szwVp^~dsG$HI^h)h=+()#0_Xc>K1I@Tt!`n4(nO*Q>& zLi7g}1D&Oh=^sCXosm0~fr=u2TmTbKg5`>V>9w6^-zTiwH1!oELe_c8{_UMkP%sPEmI&|#Sa7Hn@T1)6PQMjG56OMQ)?-6vo zqP+Pq+|118=NU72ag^OcGQ(!s?Mmw?@$Wzm%q2?~0t(^x`@28H{ru2c$7`ia+PY5r6k%R z8nWV{h*GKPtDo$FJ4SqVotJ?=Oo?FjE1vkSH6EY2RM6eN%NSP=Z`fs4x2slMyRHrBLoAyjtnZDh9$N60l~K>s=- zSCGfi!Mmkyft8&pNVbX1h9r8+0!eGBd@~$WYzWu;D9F%^!2FB~*lxH)?gcf@$5OO< zf+Do#>61ydIap3b5={N>JTOqdS1z|^s%=5O&2>{XR}S!twb71e3k_oN3TQ>CbCdb znqtdRx#JE`yy+;uJLM5_t0`SV+o^L6Pm-^so7_7jn8TU=BB=*>KEYT-dJ8Wj)f;4H z_+K|!s4D5*kpdHf5U+EgQ~Lz{BZ7h~bO7OzO6IK4j22>YJj`5!IGN+aVu#0g3Uo%$ zR9^6y1IKZg{xAnl{^`x)1|QN1Gur!;ADP?$rO>t>Sua$xLTmMn9_5T+2M>D5>bVlT z#VUdy0W}SJGlFpJy1o_ku9f?!hmVf_uDgmZ(#D)Dkf;|95Rd-Wu)iV0L`iW7Va%@i zNz?WRY~^Gsgde6>-w}0hU4af8sAgL;e<>J6xsYyd&F;_A=I4H+5ahoteF-EnD!XVM z6T)Y1u!Y`t^y5xnbOF-b-cDM$V~ znTRq#n0`A^o7zF&R)w9Qr2Xei`%^n z$|i~|k{s*`uQ_1IsuOp+uWeM=l#lFW#)asKF>bo zxu2wZGuacSiPM}x$j094_<^Qgp*FL#3P)UU!tB?XEsKv>rh(^SrRytr>8T~V(C=x( z*AuUjs5%upK#}Wg*p)~x{jMyDPZuwb>nMJzAz-q^VfNkF#~#$H12xcHp(qOJTdNhk zlej&k#WuQ6n#}u+W2@Oc0Z@1ZTdcv523V#=5T*EJ&yIJR>_gxE$>q zngl2=YPQ8S8=?v|tMsCcrFzv1Pm;M(c}LGgP_P{ZHeK%d*s$^S9%T4IJ3%cM>&kPQ zi-M{e)@Z4cmKgk{G0hCoJ7M_>;&+e18YPMA##wVIa5rT+v7rQX*(HMjgc^Aw<0 zjd=>l=MEGOcd=iJL?JxZ-GpRP?xgxN=pRj4Rata)XCwF~0ZTEIC4TYZ_ZX{rRC0H; zsY#=%S0AW?&(X6cVSE%io}CB%e2s0NtC&%e7PmytJsnV1O6|rZ4JpsaUpI2{;K0C; zk4XUTtL=8j2X|>tm+&#Pdv(nklgXX9jCu}LMeRn|#Fggc8yF2xtBP6Y1E2eQ zlRy*qC_bP4EEEkyLe|b2s`932|WxFC5Z@8Vk}%Rz2F>g`9;0| zmddTvZS}7KGs*UE+4KI?q;{zV`yX)wG7pD&>1Lh7LJJV=kNAcy#6-PDYb?_pr`{(D z4@hb1X;4O6r^va=VF9y|+HU0UmZ7U==Bl$7O$i0%!hoS;QEFYUyjb`^P6}AVen4ff z46Zvwq^8)?WE5(4mer4o*#%njj{1XdKJ2RI`6<&8-fI(B83n2|*Y^X044C`jaVuc- zR?`!+0-kvy&IaidjfH|_&Ki%uhXQhcn4z#kcf(UE5DSs98tTdZE;m_*eZZo)DIsqQ zK9H9;5yH9-<~wv0(RCP6nmqc6MLt#bB2bGpkUE5?Bjnk$!Z2-Mo68JM~DFXp(Q$-R}InB|s2_Gp9b z9h$T2J>||C86I<`PChf7HMPnpGBTJ38447HY>sb-Z-T0SK;j^}hPd%bJpfvTZpV0v zmF-JLN6sEye-bICXpmd(+V>XbQyHO$szjZ(yk^R3kX9tM`e2qcIxyv}vsyuec*0sg z`?%Q#5lT8`*>c{oADO=+!yX?d>s{VQ;NwdCQ(V^9tO69RR-U zV6ggSMeTRdxuCnzKI3$&rcrtEO~0N*Hz|y~<7v6j()EypxLBCMTHx~frw0!COH`)O zZ=EE9I4&gHZKk1B6RJvb}hrI6kT&^OF}`+iqj?KL-G=b>!Rp_3MfU#SERPl+)ea+h-MW;{s(cOU`kMr_jx2 z;({P(t?(mik93lmb-+)}!ouztVL#p}(TN;bXk}*|A?!enD#J6FHUg9 z-u+(RUFO|RWpZ8*L_bnDIq7oVs5ZrviY@2>MCSRU5`Ow!B;%TKM}Z^K=!!VGH~xs#!X4Q zNv$B?pxiaGR4Mw7$+E6{-X}8gwyk-H>QP$!IBnnWY<}N?K5CYm)N*g}p3GlPQA}Wf zBQv@01I%oJr+F+b;IQ(Qg}tv*K`OyAf-i-H__$B_XqME{Q@BDuf1c5tdG_;dWfE%Q zuPOy_)>u7IPnWKDTm7LC2j(q;u|(ny`3+E(wQ_2f=ghZa*spMx6+& zN780FIcgwQAZ@lOgq!i;_ABmpyKmM!Y}@g`YYvEZyyeSF6tS&S2eTlNyA)|H@@O@68Zpn45%-pgt%=VqbyDyEsW zn@}9Qtd*OCu*)1FesAf2W6n5}Ar`^eGt~cBzpE`oAuD!YfO6U{!3E2u#sN51@oe%9 zWOEh+#*Nl8L(Kk;3F{4EW<)CTO-ZQHC*Cd7ZUSTLvh7@rqEG z2WC-DWV1r?mF{%##e+ZUV43mM_K=Q6=07W5yej^lt&;z@>>8D;+3KH4P^O89`NX{f zh0j~8s;5fLVe+?v+$LP^=B?>vs^;Z(CvA`i#jEI=ZEIOwnk=<$aw^0-*bAs5ouzwD zi=t*t*+%^U|b_MZEiodvx=8lmm#sglz6hM>DozWC_rz# z)HA??at;Fi!JY-a^#fT~H`~wlV&E5S5^)KkP3RiC#xT7q{!w^Mr?)l@+`pfju$K7$ z8h#Mz(F?SMvI|Pl-QcvwlGnX)7uiB^$Qjq2S33U5rf^70G^vj!*_8-3@XCw{v=Das zCc5H2M?%8Z+K}uZmpeFlSbye@{dLqmtOw)!@w7ep#!0)`m(fvRmeo;X?)ri?v)~Q3 z7meHq&as!DqF<%EDSg?`4DFbxstR#~@%88Wv->AbQBd}|7wWRFVV?wDsQ6ufG*z1Y zxO8LW)&2w^Xkd6)+2PBFDvyOQ1#U4;Bo(U z>l++Ki$FeV<4gqwKiakK;|0&SV<@8))LuCcnakn#cWy_&zms)TbU~jQ;b`3EPfPbF z))~97hMU{7Q>OENspxQy_OII{zDmWVyXpb$`iN5@3glPyA62J-L;TPd5} zEM5}wH1*qaem{k<3|}wi?mT6Si%mN9Wq<+DMc z6pnj*nf$(cQuU>fc&JPi813$-`&Y7Ri}Ec{vy5iDoGo>lM#StGcRm>0q^j`nGB_sW zv)b=wc{&z5AkqcZ71yAklry~dZ*y@GYEOl+h@n+j_?$&qEJulRL3%png=B=(IaUkx z?Dy;A>#oVTUZ2i@-I*H!MR=o?zL$`;E)k>j5YX*s8cVZPA_9k;ePR0W{i_i>Q1V-T zh?sJT$_>;`wzYRrZ%D*lF2j#CJ+vz*G~a*Cn)q0;U+ohyWi#(GCdjX7Bsn}YR}Z@5 z?Tl1t2SZe;GS3iH&PM0sdJ)(WBqN8={g_UR+?xCHflts|k>W&0_mop<7S1}4Re#Y0 z&d`RF~x&w8TUQ|NYtbs(EdJY=7I1;5g37QB885U2velF^ zzt*?9w&Ol(5g@MKL@7I^Ld)(95uxgB!Sw-0#4ds4uDmb2ftjl#@;AdH$e1tlF}`|H z8E*+}v}UMIjc?msFsi71Zl>&}^%fsf zEW@_kEFTDePETQYR!j-rgkH+;cLwo1_*JUT*%y1ff^SonEqyS|&74rplAr;gZT3R! zP)-#sCb^@lcyArZzQH(vwrq&q!q5b=?Z+xn$}!pzXU-NF2dC3sFEq)cjQ>K7bHi9=6fVGcvW7CFV{9n@M${j_~4#_pygJQ<^t$pnG@F`as zloL+VwcA3XL(`FLa-7DAgth>Ou_GE5+LZAkZzQfu@L0AtpwFdi*)u~ny>cme&8Qun zY;N?LL82rN^srr2#5?)~xuDB)y5e--SLDzZi5_=0NyS1jVw)UsIF%pHKZKx(204MP z1tOxbySJ$jIy2tu>5f6oXCNe{_F(1|U;+5mC?tmbN_Lf{t3fRrfp};gtg?oprjVFd zB^oU>+jJl?SXWFWLntdL5=XnJ)rbUNXtL4v1}&J04A6ltNEkIR9&O4O;U6rX-CEi% zkO>f*VIoQRbi0ilg7wxXtL1Own^lA(3wX6WMeWB z!`V|g3;N%4OKYQF77aWYULMC4vF`6j2H#Cu>!E2kG+$C-^0_w1{_) zYi?*5qH~rUnQh>J9~;b4Lu+EzHumnAZ#E3x>h+U?EN^SoHy(>ZzA1eO5@!FrMgg}S z^rW}}Kigeb`QEC<1-OL1&c3@h#B3^?ok%X!{<&QfdTc(X&%%AohuRUDLY65Z&@`ZC=Ai|6Qm@odN}>k{}x_Oo8sF6{BZU~R%hH#@tTzT2@|kC5LNC}iVU zossL_7_`v4b2rxC?*RsZ0f#9nwYe_nLOn3lipK8)`~U0-(L9<+k`ULae6=Q z<`C97(O!2u9kn6fjEuCT-q&$-82*h6!L9nNcmsV_fZck!*&7$DljGT(DXXlB0D5}gIVPMy4^SgnyPR}S< zt{S#H?CrToJM@=o(#0ET`&8rX1Vb)AUh0I?)f$fBO)SS|H(lO}7su03nCXcDBy_z|~%F&2@BiiIxNS>c0N!dN~ZQ zvO~?eA*GdfcVf+*-E3xy@`dD=BVEQCH_bzV)BXv$#k6=|;EFxB4rF-81@e8%& zBuP$xgA->v?9ai^v^6#w8CG@;hMRSf;K-xH!}K~PFe=BHx9cMx`m3g$XA@%5i-gfH32<>^W-Q02o5TZ%@uZ|+`<4uEc@RMci6~kS{Fm7m z?nPW#AKM3DrJC68S&`80IrblE{^LQREt>uccbBq?dc@w4@iVi?!&j$AwV%%|T2<8@ zk;z0SEpM}kx5}*1*kNjMQVPu{7FU-s>Y?Kgnp?FF*ZnG|b~L3){J9U!VdIIikt})j z#I=e$$(-Cr_@DaR`MxY<+ib1AtOi9Yga#yG)SUo4YK;;m?Y8mJah|Oda{eLJ74V!Y z;Boz9{h>|7g$lCukHJTsQ&aNR*__1Ng0^ [!NOTE] + > The `--recursive` flag clones the [rendercv-skill](https://github.com/rendercv/rendercv-skill) submodule. If you forgot it, run `git submodule update --init` inside the repo. + 2. Set up the development environment (creates a virtual environment in `./.venv` with all dependencies): ```bash diff --git a/docs/index.md b/docs/index.md index 979fd49b..4758c170 100644 --- a/docs/index.md +++ b/docs/index.md @@ -140,7 +140,7 @@ locale: Let AI coding agents create and edit your CV. Install the RenderCV skill: ```bash -npx skills add rendercv/rendercv +npx skills add rendercv/rendercv-skill ``` Works with Claude Code, Cursor, Codex, Copilot, Windsurf, Gemini CLI, and [20+ other agents](https://docs.rendercv.com/user_guide/how_to/use_the_ai_agent_skill). diff --git a/docs/user_guide/how_to/use_the_ai_agent_skill.md b/docs/user_guide/how_to/use_the_ai_agent_skill.md index 4f3e506e..b22be26e 100644 --- a/docs/user_guide/how_to/use_the_ai_agent_skill.md +++ b/docs/user_guide/how_to/use_the_ai_agent_skill.md @@ -7,6 +7,7 @@ RenderCV provides an AI agent skill that teaches AI coding assistants how to cre The skill works with any AI coding agent that supports the skills protocol, including: - Claude Code +- Claude Desktop - Cursor - Codex - Copilot @@ -19,30 +20,39 @@ The skill works with any AI coding agent that supports the skills protocol, incl === "Vercel Skills CLI" ```bash - npx skills add rendercv/rendercv + npx skills add rendercv/rendercv-skill ``` You can also target a specific agent: ```bash - npx skills add rendercv/rendercv -a claude-code - npx skills add rendercv/rendercv -a cursor - npx skills add rendercv/rendercv -a codex + npx skills add rendercv/rendercv-skill -a claude-code + npx skills add rendercv/rendercv-skill -a cursor + npx skills add rendercv/rendercv-skill -a codex ``` === "OpenSkills" ```bash - npx openskills install rendercv/rendercv + npx openskills install rendercv/rendercv-skill ``` +=== "Claude Desktop" + + 1. Download [`rendercv_skill.zip`](../../assets/rendercv_skill.zip). + 2. In Claude Desktop, go to **Customize > Skills**. + 3. Click **"+"** and select **"Upload a skill"**. + 4. Upload the downloaded ZIP file. + + The skill will appear in your Skills list and Claude will automatically use it when working with your CV. + === "Manual" - Copy the content of [`skills/rendercv/SKILL.md`](https://github.com/rendercv/rendercv/blob/main/skills/rendercv/SKILL.md) into your agent's skill directory. For example, for Claude Code: + Copy the content of [`skills/rendercv/SKILL.md`](https://github.com/rendercv/rendercv-skill/blob/main/skills/rendercv/SKILL.md) into your agent's skill directory. For example, for Claude Code: ```bash - git clone https://github.com/rendercv/rendercv.git - cp -r rendercv/skills/rendercv ~/.claude/skills/ + git clone https://github.com/rendercv/rendercv-skill.git + cp -r rendercv-skill/skills/rendercv ~/.claude/skills/ ``` ## What the Skill Provides diff --git a/scripts/rendercv_skill/evals/prompts/skill_chat.js b/scripts/rendercv_skill/evals/prompts/skill_chat.js index bc760349..b55d36fa 100644 --- a/scripts/rendercv_skill/evals/prompts/skill_chat.js +++ b/scripts/rendercv_skill/evals/prompts/skill_chat.js @@ -10,6 +10,9 @@ const skillPath = path.resolve( "..", "..", "..", + ".claude", + "skills", + "rendercv-skill", "skills", "rendercv", "SKILL.md", diff --git a/scripts/rendercv_skill/generate.py b/scripts/rendercv_skill/generate.py index 3f05806e..3313b0d6 100755 --- a/scripts/rendercv_skill/generate.py +++ b/scripts/rendercv_skill/generate.py @@ -12,6 +12,7 @@ import ast import io import pathlib import re +import zipfile import jinja2 import ruamel.yaml @@ -27,8 +28,17 @@ from rendercv.schema.sample_generator import ( repository_root = pathlib.Path(__file__).parent.parent.parent script_directory = pathlib.Path(__file__).parent template_path = script_directory / "skill_template.j2.md" -output_path = repository_root / "skills" / "rendercv" / "SKILL.md" +output_path = ( + repository_root + / ".claude" + / "skills" + / "rendercv-skill" + / "skills" + / "rendercv" + / "SKILL.md" +) llms_txt_path = repository_root / "docs" / "llms.txt" +skill_zip_path = repository_root / "docs" / "assets" / "rendercv_skill.zip" # Paths to key model source files for dynamic inclusion. models_dir = repository_root / "src" / "rendercv" / "schema" / "models" @@ -286,7 +296,7 @@ def build_template_context() -> dict: def generate_skill_file() -> None: - """Render the Jinja2 template and write SKILL.md and docs/llms.txt.""" + """Render the Jinja2 template and write SKILL.md, docs/llms.txt, and ZIP.""" env = jinja2.Environment( loader=jinja2.FileSystemLoader(script_directory), keep_trailing_newline=True, @@ -302,6 +312,11 @@ def generate_skill_file() -> None: output_path.write_text(rendered, encoding="utf-8") llms_txt_path.write_text(rendered, encoding="utf-8") + # Generate ZIP for Claude Desktop skill upload + skill_zip_path.parent.mkdir(parents=True, exist_ok=True) + with zipfile.ZipFile(skill_zip_path, "w", zipfile.ZIP_DEFLATED) as zf: + zf.writestr("rendercv/SKILL.md", rendered) + if __name__ == "__main__": generate_skill_file() diff --git a/skills/rendercv/SKILL.md b/skills/rendercv/SKILL.md deleted file mode 100644 index b436ae3b..00000000 --- a/skills/rendercv/SKILL.md +++ /dev/null @@ -1,805 +0,0 @@ ---- -name: rendercv -description: >- - Create professional CVs and resumes with perfect typography using RenderCV - (v2.8). Users write content in YAML, and RenderCV produces - publication-quality PDFs via Typst typesetting. Full control over every visual - detail: colors, fonts, margins, spacing, section title styles, entry layouts, - and more. 6 built-in themes with unlimited - customization. Any language supported (20 built-in, or define your own). Outputs - PDF, PNG, HTML, and Markdown. Use when the user wants to create, edit, - customize, or render a CV or resume. ---- - -## Quick Start - -**Available themes:** `classic`, `engineeringclassic`, `engineeringresumes`, `harvard`, `moderncv`, `sb2nov` -**Available locales:** `english`, `arabic`, `danish`, `dutch`, `french`, `german`, `hebrew`, `hindi`, `indonesian`, `italian`, `japanese`, `korean`, `mandarin_chinese`, `norwegian_bokmål`, `norwegian_nynorsk`, `persian`, `portuguese`, `russian`, `spanish`, `turkish` - -These are starting points — every aspect of the design and locale can be fully customized in the YAML file. - -```bash -# Install RenderCV -uv tool install "rendercv[full]" - -# Create a starter YAML file (you can specify theme and locale) -rendercv new "John Doe" -rendercv new "John Doe" --theme moderncv --locale german - -# Render to PDF (also generates Typst, Markdown, HTML, PNG by default) -rendercv render John_Doe_CV.yaml - -# Watch mode: auto-re-render whenever the YAML file changes -rendercv render John_Doe_CV.yaml --watch - -# Render only PNG (useful for previewing or checking page count) -rendercv render John_Doe_CV.yaml --dont-generate-pdf --dont-generate-html --dont-generate-markdown - -# Override fields from the CLI without editing the YAML -rendercv render cv.yaml --cv.name "Jane Doe" --design.theme "moderncv" -``` - -## YAML Structure - -A RenderCV input has four sections. Only `cv` is required — the others have sensible defaults. - -```yaml -cv: # Your content: name, contact info, and all sections -design: # Visual styling: theme, colors, fonts, margins, spacing, layouts -locale: # Language: month names, phrases, translations -settings: # Behavior: output paths, bold keywords, current date -``` - -**Single file vs. separate files:** All four sections can live in one YAML file, or each can be a separate file. Separate files are useful for reusing the same design/locale across multiple CVs: - -```bash -# Single self-contained file (all sections in one file) -rendercv render John_Doe_CV.yaml - -# Separate files: CV content + design + locale loaded independently -rendercv render cv.yaml --design design.yaml --locale-catalog locale.yaml --settings settings.yaml -``` - -When using separate files, each file contains only its section (e.g., `design.yaml` has `design:` as the top-level key). CLI-loaded files override values in the main YAML file. - -The YAML maps directly to Pydantic models. The complete type-safe schema is provided below so you can understand every field, its type, and its default value. - -## Pydantic Schema - -The YAML input is validated against these Pydantic models. - -### Top-Level Model - -```python -class RenderCVModel(BaseModelWithoutExtraKeys): - cv: Cv = pydantic.Field(default_factory=Cv, title='CV', description='The content of the CV.') - design: Design = pydantic.Field(default_factory=ClassicTheme, title='Design') - locale: Locale = pydantic.Field(default_factory=EnglishLocale, title='Locale Catalog') - settings: Settings = pydantic.Field(default_factory=Settings, title='RenderCV Settings', description='The settings of the RenderCV.') - -``` - -### CV Content (`cv`) - -The `cv.sections` field is a dictionary where keys are section titles (any string you want) and values are lists of entries. Each section contains entries of the same type. - -```python -class Cv(BaseModelWithoutExtraKeys): - name: str | None = pydantic.Field(default=None, examples=['John Doe', 'Jane Smith']) - headline: str | None = pydantic.Field(default=None, examples=['Software Engineer', 'Data Scientist', 'Product Manager']) - location: str | None = pydantic.Field(default=None, examples=['New York, NY', 'London, UK', 'Istanbul, Türkiye']) - email: pydantic.EmailStr | list[pydantic.EmailStr] | None = pydantic.Field(default=None, examples=['john.doe@example.com', ['john.doe.1@example.com', 'john.doe.2@example.com']]) - photo: ExistingPathRelativeToInput | pydantic.HttpUrl | None = pydantic.Field(default=None, union_mode='left_to_right', examples=['photo.jpg', 'images/profile.png', 'https://example.com/photo.jpg']) - phone: pydantic_phone_numbers.PhoneNumber | list[pydantic_phone_numbers.PhoneNumber] | None = pydantic.Field(default=None, examples=['+1-234-567-8900', ['+1-234-567-8900', '+44 20 1234 5678']]) - website: pydantic.HttpUrl | list[pydantic.HttpUrl] | None = pydantic.Field(default=None, examples=['https://johndoe.com', ['https://johndoe.com', 'https://www.janesmith.dev']]) - social_networks: list[SocialNetwork] | None = pydantic.Field(default=None) - custom_connections: list[CustomConnection] | None = pydantic.Field(default=None, examples=[[{'placeholder': 'Book a call', 'url': 'https://cal.com/johndoe', 'fontawesome_icon': 'calendar-days'}]]) - sections: dict[str, Section] | None = pydantic.Field(default=None, examples=[{'Experience': '...', 'Education': '...', 'Projects': '...', 'Skills': '...'}]) - -``` - -```python -type SocialNetworkName = Literal['LinkedIn', 'GitHub', 'GitLab', 'IMDB', 'Instagram', 'ORCID', 'Mastodon', 'StackOverflow', 'ResearchGate', 'YouTube', 'Google Scholar', 'Telegram', 'WhatsApp', 'Leetcode', 'X', 'Bluesky', 'Reddit'] - -available_social_networks = get_args(SocialNetworkName.__value__) - -class SocialNetwork(BaseModelWithoutExtraKeys): - network: SocialNetworkName = pydantic.Field() - username: str = pydantic.Field(examples=['john_doe', '@johndoe@mastodon.social', '12345/john-doe']) - -``` - -```python -class CustomConnection(BaseModelWithoutExtraKeys): - fontawesome_icon: str - placeholder: str - url: pydantic.HttpUrl | None - -``` - -### Entry Types - -`cv.sections` is a dictionary: keys are section titles (any string), values are lists of entries. Each section must use a **single** entry type — you cannot mix different entry types within the same section. The entry type is auto-detected from the fields present in each entry. - -**Shared fields** — these are available on entry types that support dates and complex fields (ExperienceEntry, EducationEntry, NormalEntry, PublicationEntry): - -| Field | Type | Default | Notes | -|---|---|---|---| -| `date` | `str \| int \| null` | `null` | Free-form: `"2020-09"`, `"Fall 2023"`, etc. Mutually exclusive with `start_date`/`end_date`. | -| `start_date` | `str \| int \| null` | `null` | Strict format: YYYY-MM-DD, YYYY-MM, or YYYY. | -| `end_date` | `str \| int \| "present" \| null` | `null` | Same formats as `start_date`, or `"present"`. Omitting defaults to `"present"` when `start_date` is set. | -| `location` | `str \| null` | `null` | | -| `summary` | `str \| null` | `null` | | -| `highlights` | `list[str] \| null` | `null` | Bullet points. | - -**9 entry types:** - -| Entry Type | Required Fields | Optional Fields | Typical Use | -|---|---|---|---| -| **ExperienceEntry** | `company`, `position` | all shared fields | Jobs, positions | -| **EducationEntry** | `institution`, `area` | `degree` + all shared fields | Degrees, schools | -| **PublicationEntry** | `title`, `authors` | `doi`, `url`, `journal`, `summary`, `date` | Papers, articles | -| **NormalEntry** | `name` | all shared fields | Projects, awards | -| **OneLineEntry** | `label`, `details` | — | Skills, languages | -| **BulletEntry** | `bullet` | — | Simple bullet points | -| **NumberedEntry** | `number` | — | Numbered list items | -| **ReversedNumberedEntry** | `reversed_number` | — | Reverse-numbered items (5, 4, 3...) | -| **TextEntry** | *(plain string)* | — | Free-form paragraphs | - -Example: - -```yaml -cv: - sections: - experience: # list of ExperienceEntry (detected by company + position) - - company: Google - position: Engineer - start_date: 2020-01 - highlights: - - Did something impactful - skills: # list of OneLineEntry (detected by label + details) - - label: Languages - details: Python, C++ - about_me: # list of TextEntry (plain strings) - - This is a free-form paragraph about me. -``` - -Entries also accept arbitrary extra keys (silently ignored during rendering). A typo in a field name will NOT cause an error. - -### Design (`design`) - -All built-in themes share the same structure — they only differ in default values. See the sample designs below for every available field and its default. Set `design.theme` to pick a theme, then override any field. - -### Locale (`locale`) - -Built-in locales: `english`, `arabic`, `danish`, `dutch`, `french`, `german`, `hebrew`, `hindi`, `indonesian`, `italian`, `japanese`, `korean`, `mandarin_chinese`, `norwegian_bokmål`, `norwegian_nynorsk`, `persian`, `portuguese`, `russian`, `spanish`, `turkish` - -Set `locale.language` to a built-in locale name to use it. Override any field to customize translations. Set `language` to any string and provide all translations for a fully custom locale. - -### Settings (`settings`) - -Key fields: `bold_keywords` (list of strings to auto-bold), `current_date` (override today's date), `render_command.*` (output paths, generation flags). - -## Important Patterns - -### YAML quoting - -**ALWAYS quote string values that contain a colon (`:`).** This is the most common cause of invalid YAML. Highlights, titles, summaries, and any free-form text often contain colons: - -```yaml -# WRONG — colon breaks YAML parsing: -- title: Catalytic Mechanisms: A New Approach - highlights: - - Relevant coursework: Distributed Systems, ML - -# RIGHT — wrap in double quotes: -- title: "Catalytic Mechanisms: A New Approach" - highlights: - - "Relevant coursework: Distributed Systems, ML" -``` - -Rule: if a string value contains `:`, it MUST be quoted. When in doubt, quote it. - -### Bullet characters - -The `design.highlights.bullet` field only accepts these exact characters: `●`, `•`, `◦`, `-`, `◆`, `★`, `■`, `—`, `○`. Do not use en-dash (`–`), `>`, `*`, or any other character. When in doubt, omit `bullet` to use the theme default. - -### Phone numbers - -Phone numbers MUST be in international format with country code (E.164). Never invent a phone number — only include one if the user provides it. - -```yaml -# WRONG: -phone: "(555) 123-4567" -phone: "555-123-4567" - -# RIGHT: -phone: "+15551234567" -``` - -If the user provides a local number without country code, ask which country, or omit the phone field. - -### Text formatting - -All text fields support inline Markdown: `**bold**`, `*italic*`, `[link text](url)`. Block-level Markdown (headers, lists, blockquotes, code blocks) is not supported. Raw Typst commands and math (`$$f(x)$$`) also pass through. - -### Date handling - -- `date` and `start_date`/`end_date` are mutually exclusive. If `date` is provided, `start_date` and `end_date` are ignored. -- If only `start_date` is given, `end_date` defaults to `"present"`. -- `start_date`/`end_date` require strict formats: YYYY-MM-DD, YYYY-MM, or YYYY. -- `date` is flexible: accepts any string ("Fall 2023") in addition to date formats. - -### Section titles - -- `snake_case` keys auto-capitalize: `work_experience` → "Work Experience" -- Keys with spaces or uppercase are used as-is. - -### Publication authors - -Use `*Name*` (single asterisks, italic) to highlight the CV owner in author lists. - -### Nested highlights (sub-bullets) - -```yaml -highlights: - - Main bullet point - - Sub-bullet 1 - - Sub-bullet 2 -``` - -## CLI Reference - -### `rendercv new "Full Name"` - -Generate a starter YAML file. - -| Option | Short | What it does | -|---|---|---| -| `--theme THEME` | | Theme to use (default: `classic`) | -| `--locale LOCALE` | | Locale to use (default: `english`) | -| `--create-typst-templates` | | Also create editable Typst template files for full design control | - -### `rendercv render ` - -Generate PDF, Typst, Markdown, HTML, and PNG from a YAML file. - -| Option | Short | What it does | -|---|---|---| -| `--watch` | `-w` | Re-render automatically when the YAML file changes | -| `--quiet` | `-q` | Suppress all output messages | -| `--design FILE` | `-d` | Load design section from a separate YAML file | -| `--locale-catalog FILE` | `-lc` | Load locale section from a separate YAML file | -| `--settings FILE` | `-s` | Load settings section from a separate YAML file | -| `--output-folder DIR` | `-o` | Custom output directory | - -Per-format controls: `--{format}-path PATH` sets custom output path, `--dont-generate-{format}` skips generation. Formats: `pdf`, `typst`, `markdown`, `html`, `png`. - -**Override any YAML field from the CLI** using dot notation (overrides without editing the file): - -```bash -rendercv render CV.yaml --cv.name "Jane Doe" --design.theme "moderncv" -rendercv render CV.yaml --cv.sections.education.0.institution "MIT" -``` - -### `rendercv create-theme "theme-name"` - -Scaffold a custom theme directory with editable Typst templates for complete design control. - -## JSON Schema - -For YAML editor autocompletion and validation: - -```yaml -# yaml-language-server: $schema=https://raw.githubusercontent.com/rendercv/rendercv/refs/tags/v2.8/schema.json -``` - -## Complete Example - -### Sample CV - -```yaml -cv: - name: John Doe - headline: - location: San Francisco, CA - email: john.doe@email.com - photo: - phone: - website: https://rendercv.com/ - social_networks: - - network: LinkedIn - username: rendercv - - network: GitHub - username: rendercv - custom_connections: - sections: - Welcome to RenderCV: - - RenderCV reads a CV written in a YAML file, and generates a PDF with - professional typography. - - Each section title is arbitrary. - education: - - institution: Princeton University - area: Computer Science - degree: PhD - date: - start_date: 2018-09 - end_date: 2023-05 - location: Princeton, NJ - summary: - highlights: - - 'Thesis: Efficient Neural Architecture Search for Resource-Constrained Deployment' - - 'Advisor: Prof. Sanjeev Arora' - - NSF Graduate Research Fellowship, Siebel Scholar (Class of 2022) - - institution: Boğaziçi University - area: Computer Engineering - degree: BS - date: - start_date: 2014-09 - end_date: 2018-06 - location: Istanbul, Türkiye - summary: - highlights: - - 'GPA: 3.97/4.00, Valedictorian' - - Fulbright Scholarship recipient for Graduate Studies - experience: - - company: Nexus AI - position: Co-Founder & CTO - date: - start_date: 2023-06 - end_date: present - location: San Francisco, CA - summary: - highlights: - - Built foundation model infrastructure serving 2M+ monthly API requests - with 99.97% uptime - - Raised $18M Series A led by Sequoia Capital, with participation from - a16z and Founders Fund - - Scaled engineering team from 3 to 28 across ML research, platform, and - applied AI divisions - - Developed proprietary inference optimization reducing latency by 73% - compared to baseline - - company: NVIDIA Research - position: Research Intern - date: - start_date: 2022-05 - end_date: 2022-08 - location: Santa Clara, CA - summary: - highlights: - - Designed sparse attention mechanism reducing transformer memory - footprint by 4.2x - - Co-authored paper accepted at NeurIPS 2022 (spotlight presentation, top - 5% of submissions) - projects: - - name: '[FlashInfer](https://github.com/)' - date: - start_date: 2023-01 - end_date: present - location: - summary: Open-source library for high-performance LLM inference kernels - highlights: - - Achieved 2.8x speedup over baseline attention implementations on A100 - GPUs - - Adopted by 3 major AI labs, 8,500+ GitHub stars, 200+ contributors - - name: '[NeuralPrune](https://github.com/)' - date: '2021' - start_date: - end_date: - location: - summary: Automated neural network pruning toolkit with differentiable - masks - highlights: - - Reduced model size by 90% with less than 1% accuracy degradation on - ImageNet - - Featured in PyTorch ecosystem tools, 4,200+ GitHub stars - publications: - - title: 'Sparse Mixture-of-Experts at Scale: Efficient Routing for Trillion-Parameter - Models' - authors: - - '*John Doe*' - - Sarah Williams - - David Park - summary: - doi: 10.1234/neurips.2023.1234 - url: - journal: NeurIPS 2023 - date: 2023-07 - - title: Neural Architecture Search via Differentiable Pruning - authors: - - James Liu - - '*John Doe*' - summary: - doi: 10.1234/neurips.2022.5678 - url: - journal: NeurIPS 2022, Spotlight - date: 2022-12 - selected_honors: - - bullet: MIT Technology Review 35 Under 35 Innovators (2024) - - bullet: Forbes 30 Under 30 in Enterprise Technology (2024) - skills: - - label: Languages - details: Python, C++, CUDA, Rust, Julia - - label: ML Frameworks - details: PyTorch, JAX, TensorFlow, Triton, ONNX - patents: - - number: Adaptive Quantization for Neural Network Inference on Edge Devices - (US Patent 11,234,567) - - number: Dynamic Sparsity Patterns for Efficient Transformer Attention (US - Patent 11,345,678) - invited_talks: - - reversed_number: Scaling Laws for Efficient Inference — Stanford HAI - Symposium (2024) - - reversed_number: Building AI Infrastructure for the Next Decade — - TechCrunch Disrupt (2024) - -``` - -### Sample Design (classic — complete reference) - -This shows every available design field with its default value. All themes share the same structure. - -```yaml -design: - theme: classic - page: - size: us-letter - top_margin: 0.7in - bottom_margin: 0.7in - left_margin: 0.7in - right_margin: 0.7in - show_footer: true - show_top_note: true - colors: - body: rgb(0, 0, 0) - name: rgb(0, 79, 144) - headline: rgb(0, 79, 144) - connections: rgb(0, 79, 144) - section_titles: rgb(0, 79, 144) - links: rgb(0, 79, 144) - footer: rgb(128, 128, 128) - top_note: rgb(128, 128, 128) - typography: - line_spacing: 0.6em - alignment: justified - date_and_location_column_alignment: right - font_family: - body: Source Sans 3 - name: Source Sans 3 - headline: Source Sans 3 - connections: Source Sans 3 - section_titles: Source Sans 3 - font_size: - body: 10pt - name: 30pt - headline: 10pt - connections: 10pt - section_titles: 1.4em - small_caps: - name: false - headline: false - connections: false - section_titles: false - bold: - name: true - headline: false - connections: false - section_titles: true - links: - underline: false - show_external_link_icon: false - header: - alignment: center - photo_width: 3.5cm - photo_position: left - photo_space_left: 0.4cm - photo_space_right: 0.4cm - space_below_name: 0.7cm - space_below_headline: 0.7cm - space_below_connections: 0.7cm - connections: - phone_number_format: national - hyperlink: true - show_icons: true - display_urls_instead_of_usernames: false - separator: '' - space_between_connections: 0.5cm - section_titles: - type: with_partial_line - line_thickness: 0.5pt - space_above: 0.5cm - space_below: 0.3cm - sections: - allow_page_break: true - space_between_regular_entries: 1.2em - space_between_text_based_entries: 0.3em - show_time_spans_in: - - experience - entries: - date_and_location_width: 4.15cm - side_space: 0.2cm - space_between_columns: 0.1cm - allow_page_break: false - short_second_row: true - degree_width: 1cm - summary: - space_above: 0cm - space_left: 0cm - highlights: - bullet: • - nested_bullet: • - space_left: 0.15cm - space_above: 0cm - space_between_items: 0cm - space_between_bullet_and_text: 0.5em - templates: - footer: '*NAME -- PAGE_NUMBER/TOTAL_PAGES*' - top_note: '*LAST_UPDATED CURRENT_DATE*' - single_date: MONTH_ABBREVIATION YEAR - date_range: START_DATE – END_DATE - time_span: HOW_MANY_YEARS YEARS HOW_MANY_MONTHS MONTHS - one_line_entry: - main_column: '**LABEL:** DETAILS' - education_entry: - main_column: |- - **INSTITUTION**, AREA - SUMMARY - HIGHLIGHTS - degree_column: '**DEGREE**' - date_and_location_column: |- - LOCATION - DATE - normal_entry: - main_column: |- - **NAME** - SUMMARY - HIGHLIGHTS - date_and_location_column: |- - LOCATION - DATE - experience_entry: - main_column: |- - **COMPANY**, POSITION - SUMMARY - HIGHLIGHTS - date_and_location_column: |- - LOCATION - DATE - publication_entry: - main_column: |- - **TITLE** - SUMMARY - AUTHORS - URL (JOURNAL) - date_and_location_column: DATE - -``` - -### Other Theme Overrides - -Other themes only override specific fields from the classic defaults above. To use a theme, set `design.theme` and optionally override any field. Each theme also customizes `design.templates` (entry layout patterns) — see the classic sample above for the full template structure. The override YAMLs below omit templates for brevity. - -#### engineeringclassic - -```yaml -# yaml-language-server: $schema=../../../../../../schema.json -design: - theme: engineeringclassic - typography: - font_family: - body: Raleway - name: Raleway - headline: Raleway - connections: Raleway - section_titles: Raleway - bold: - name: false - section_titles: false - header: - alignment: left - links: - show_external_link_icon: false - section_titles: - type: with_full_line - sections: - show_time_spans_in: [] - entries: - short_second_row: false - summary: - space_above: 0.12cm - highlights: - space_left: 0cm - space_above: 0.12cm - space_between_items: 0.12cm -``` - -#### engineeringresumes - -```yaml -# yaml-language-server: $schema=../../../../../../schema.json -design: - theme: engineeringresumes - page: - show_footer: false - typography: - font_family: - body: XCharter - name: XCharter - headline: XCharter - connections: XCharter - section_titles: XCharter - font_size: - name: 25pt - section_titles: 1.2em - bold: - name: false - header: - connections: - separator: '|' - show_icons: false - display_urls_instead_of_usernames: true - colors: - name: rgb(0,0,0) - connections: rgb(0,0,0) - headline: rgb(0,0,0) - section_titles: rgb(0,0,0) - links: rgb(0,0,0) - links: - underline: true - show_external_link_icon: false - section_titles: - type: with_full_line - space_above: 0.5cm - space_below: 0.3cm - sections: - space_between_regular_entries: 0.42cm - space_between_text_based_entries: 0.15cm - show_time_spans_in: [] - entries: - short_second_row: false - summary: - space_above: 0.08cm - side_space: 0cm - highlights: - bullet: ● - nested_bullet: ● - space_left: 0cm - space_above: 0.08cm - space_between_items: 0.08cm - space_between_bullet_and_text: 0.3em -``` - -#### harvard - -```yaml -# yaml-language-server: $schema=../../../../../../schema.json -design: - theme: harvard - page: - top_margin: 0.5in - bottom_margin: 0.5in - left_margin: 0.5in - right_margin: 0.5in - show_top_note: false - colors: - name: rgb(0,0,0) - headline: rgb(0,0,0) - connections: rgb(0,0,0) - section_titles: rgb(0,0,0) - links: rgb(0,0,0) - typography: - font_family: - body: XCharter - name: XCharter - headline: XCharter - connections: XCharter - section_titles: XCharter - font_size: - name: 25pt - connections: 9pt - section_titles: 1.3em - header: - space_below_name: 0.5cm - space_below_headline: 0.5cm - space_below_connections: 0.5cm - connections: - show_icons: false - separator: • - space_between_connections: 0.4cm - section_titles: - type: centered_with_centered_partial_line - space_below: 0.2cm - sections: - space_between_regular_entries: 1em - show_time_spans_in: [] - entries: - short_second_row: false -``` - -#### moderncv - -```yaml -# yaml-language-server: $schema=../../../../../../schema.json -design: - theme: moderncv - typography: - line_spacing: 0.6em - font_family: - body: Fontin - name: Fontin - headline: Fontin - connections: Fontin - section_titles: Fontin - font_size: - name: 25pt - section_titles: 1.4em - bold: - name: false - section_titles: false - header: - alignment: left - photo_width: 4.15cm - photo_space_left: 0cm - photo_space_right: 0.3cm - links: - underline: true - show_external_link_icon: false - section_titles: - type: moderncv - space_above: 0.55cm - space_below: 0.3cm - line_thickness: 0.15cm - sections: - show_time_spans_in: [] - entries: - short_second_row: false - side_space: 0cm - space_between_columns: 0.3cm - summary: - space_above: 0.1cm - highlights: - space_left: 0cm - space_above: 0.15cm - space_between_items: 0.1cm - space_between_bullet_and_text: 0.3em -``` - -#### sb2nov - -```yaml -# yaml-language-server: $schema=../../../../../../schema.json -design: - theme: sb2nov - typography: - font_family: - body: New Computer Modern - name: New Computer Modern - headline: New Computer Modern - connections: New Computer Modern - section_titles: New Computer Modern - colors: - name: rgb(0,0,0) - connections: rgb(0,0,0) - section_titles: rgb(0,0,0) - headline: rgb(0,0,0) - links: rgb(0,0,0) - links: - underline: true - show_external_link_icon: false - section_titles: - type: with_full_line - sections: - show_time_spans_in: [] - header: - connections: - hyperlink: true - show_icons: false - display_urls_instead_of_usernames: true - separator: • - entries: - short_second_row: false - highlights: - bullet: ◦ - nested_bullet: ◦ -``` - diff --git a/tests/test_generated_files.py b/tests/test_generated_files.py index b35d64e8..3dddb4d4 100644 --- a/tests/test_generated_files.py +++ b/tests/test_generated_files.py @@ -9,6 +9,7 @@ Why: import pathlib import subprocess +import sys import pytest @@ -28,6 +29,9 @@ def run_just(recipe: str) -> None: ) +@pytest.mark.skipif( + sys.platform == "win32", reason="Schema generation differs on Windows" +) def test_schema_json_is_up_to_date() -> None: schema_path = repository_root / "schema.json" before = schema_path.read_text(encoding="utf-8") @@ -64,7 +68,15 @@ def test_example_yaml_is_up_to_date(theme: str) -> None: def test_skill_md_is_up_to_date() -> None: - skill_path = repository_root / "skills" / "rendercv" / "SKILL.md" + skill_path = ( + repository_root + / ".claude" + / "skills" + / "rendercv-skill" + / "skills" + / "rendercv" + / "SKILL.md" + ) before = skill_path.read_text(encoding="utf-8") llms_txt_path = repository_root / "docs" / "llms.txt"