A generator and the schema it fills have to share a commit

  • tooling
  • astro
  • windows

Two skills write the content on this site: one reads a repository and produces a project page, the other turns a work session into a post. They lived in the user-level skills directory. The site lived in its own repository. That arrangement survived exactly one schema change.

What happened

I switched the blog from Russian to English, and the status field on a project went with it:

status: z.enum(['building', 'live', 'paused', 'archived']).optional(),

Three places encode that set of values. The zod schema in src/content.config.ts. The one project page already written, which had status: работает in its frontmatter. And the skill that generates project pages, which carries a table telling it exactly which values are legal.

The first two are in this repository and went into the same commit. The third was somewhere else entirely, and nothing in that commit could have reminded me it existed.

Why it’s worse than it looks

The failure mode isn’t silence. Astro validates frontmatter against the schema at build time, so a generator still emitting status: работает produces a failed build with a precise message: field status, expected one of four values, got something else.

The detection is fine. The problem is where the error surfaces. The build complains about the generated file, and the generated file is correct — it faithfully reflects what the generator believes. The actual bug is in a file that is not in this repository, not in this commit, and not in any diff I would review. So the error points at the symptom and stays quiet about the cause, which is the expensive kind of error even when the message is good.

What I did

Moved both skills into .claude/skills/ in the blog repo. Now a schema change and the generator that has to agree with it land in one commit, and the diff shows both.

That created a second problem: skills are looked up in the user-level directory, and this repository isn’t my working directory day to day — its parent is. The obvious fix is to keep a copy in each place, which is the original drift with an extra step in front of it.

So the repo holds the files and the user-level path is a link to them:

New-Item -ItemType Junction -Path "$env:USERPROFILE\.claude\skills\$s" -Target "$PWD\.claude\skills\$s"

A junction rather than a symlink, because on Windows symlinks want administrator rights or Developer Mode and junctions want neither. They only work for directories, but a skill is a directory. An edit made through either path is the same edit, because there is only one file.

The cost is that a junction is machine state, not repository state. It doesn’t survive a clone, and it dangles if the repository moves. So the setup is a line in the README that actually has to be run — which is the trade I wanted: a silent drift I wouldn’t notice, exchanged for a setup step I can’t forget, because nothing works at all until I run it.

What’s left

The skills still hardcode an absolute path to this repository. That’s fine on one machine and wrong on any other, and it’s the next thing that breaks if I ever clone this somewhere else.

← all posts