← Corpus / lossless-monorepo / agent-skill

Three Skills Shipped, Two Date Families Untangled, and a Repo That Can Keep a Secret

A skill library that publishes everything needs somewhere to put the things it can't. This session built that boundary — gitignored config read by instruction, an audit grep that has to come back empty, and a rule against injecting our conventions into repos we only have READ on — then shipped three skills through it and finally wrote down the frontmatter standard the tree had been running on tacitly for months.

Path
agent-skills/changelog/2026-08-15_01.md
Authors
Michael Staton
Augmented with
Claude Code on Claude Opus 5
Tags
Agent-Skills · Conventions · Frontmatter · Context-Vigilance

Why Care?

The through-line of this session was making tacit practice legible — and discovering, twice, that the thing standing in the way was a boundary nobody had drawn yet.

The first was privacy. A public skill library can’t hold operator particulars, but a skill stripped of them stops working. That got resolved earlier the same evening (2026-08-14_01) and immediately paid for itself: the expense-report skill shipped through the same audit, and the CRM skill’s own §1 was caught inlining exactly the values its example config existed to hold.

The second was dates. Two families of them — editorial and filesystem — had been coexisting across context-v/ and changelog/ for months with no document saying which answers what. Agents kept guessing, and kept guessing wrong in a specific direction: stamping date_modified as a proxy for “when did this change,” which Obsidian bumps merely for opening a file.

Both are the same failure mode. A convention that lives only in someone’s head is one an agent will violate confidently.

What’s New?

Two new skills

  • expense-report-generation — turning raw card and bank statement downloads into a claim a client’s finance team can approve without asking a question. The expensive part isn’t arithmetic, it’s identification: card descriptors name the billing entity, not the product, so Loom bills as ATLASSIAN and Trae as BYTEDANCE, and a reviewer sees a vendor the claimant never mentioned. The skill’s answer is timeline continuity — pull every charge for both descriptors in date order, and if A stops the exact period B starts on the same billing day with no overlap and no gap, it’s one subscription that changed descriptors. Paired with a hard never-invent-a-product-name rule, the BASE/EXTENDED split that lets a reviewer decline one section without unpicking the other, and the statement-close gap that silently truncates any trip spanning two billing periods.

  • prep-images-for-embed — screenshots to CDN-hosted, SEO-correct embeds in one call, with a shipped prep-images.mjs. Headline finding is measured rather than assumed: upload JPEG, never WebP. ImageKit converts to WebP for browsers that accept it and falls back for those that don’t — upload WebP and that fallback becomes a PNG up to 3× larger. Alt text is not optional; the script rejects alt="screenshot".

Conventions written down

  • The frontmatter standard, finally explicit. context-vigilance and changelog-conventions now both carry the editorial vs. filesystem date split: date_authored_initial_draft / date_authored_current_draft answer “when was this written and last meaningfully revised,” and are what timelines should read. date_created / date_modified answer “when did these bytes appear and change,” and lie in both directions — date_modified overstates recency (Obsidian bumps mtime on open), and date_created can overstate age-of-origin, because a machine recovery in this tree reset birthtime on a batch of files. Where frontmatter records an earlier creation date than the filesystem, the frontmatter wins.

    Two rules with teeth came out of it. Deleting an unrecognized key is always wrong — the extended date vocabulary is applied unevenly on purpose, different surfaces read different keys, and an empty date_authored_final_draft: means “not final yet,” not “unused.” And renaming a key can break a consumer: date: → date_authored_initial_draft silently broke two changelog ingesters, one that read date as its metadata field and one that used it as a temporal anchor. Renaming a key across 85 files is easy; noticing what read it is the actual work.

    The directed-sweep carve-out is now spelled out too — “show, don’t enforce” governs incidental edits and was never meant to forbid an operator asking for a repo to be brought up to standard. Under a sweep: additive only, no YAML round-trips (they collapse multi-line lede: blocks and reorder hand-authored keys), never touch the body, edit originals rather than rollups, and judge publish by reading the document — word counts get it backwards in both directions.

  • changelog-conventions composes with prep-images-for-embed. An entry that wants images is itself the trigger; the user shouldn’t have to name both skills. With it, the rule that relative image paths are not a style preference — they break: changelog entries get rolled up into parent splash sites, so ![](./images/foo.png) resolves locally and resolves to nothing once rendered elsewhere. A CDN URL survives every aggregation.

  • gh-cli Convention 3b — git worktrees make HEAD ambiguous. The link recipe resolves both repo root and branch from wherever the shell is standing, which is per-worktree, not per-repository. With a development worktree and a main worktree side by side, the directories are indistinguishable at a glance and running the recipe from the wrong one yields a well-formed URL that 404s only for the reader. Pin BRANCH explicitly when scripting; audit with git worktree list --porcelain, because branches checked out in another worktree are marked + rather than * and quietly break anything grepping for *.

Repo hygiene that turned out to be load-bearing

  • CLAUDE.md for the skills repo, which had none. It carries the symlink-sync habit plus the two failure modes that silently break discovery: a directory name disagreeing with the frontmatter name: loads a skill without its description, so it never triggers; and renaming a skill directory strands the old symlink, because the sync script adds but never removes. Both were live — the CRM skill shipped as guidlines against a frontmatter reading guidelines.

  • Lossless conventions no longer get injected into vendored repos. The tree-wide browser-drive rollout wrote its sentinel block into 16 CLAUDE.md files, one of which was chroma-agent-skills/ — pinned from chroma-core/agent-skills, where we have READ permission only. That copy could never be committed: a commit inside it would be unpushable and would leave this repo’s gitlink pointing at a SHA existing on no remote, which is the loss scenario the pseudomonorepos HARD STOP exists to prevent. Block moved into this repo’s own CLAUDE.md, vendored file restored, submodule clean.

  • .gitignore grew two depth-anywhere rules — **/config/private.json and **/private-data/ — because ~/.claude/skills/<skill> symlinks into this repo, so anything a skill creates at its documented paths lands inside the repo and is seen by git.

Notes

The convention list at the top of gh-cli-projects-tasks-conventions is ordered by priority to adhere to, not chronologically. Per-item (added YYYY-MM-DD) tags made it read as a timeline, so out-of-order dates looked like a defect and invited a renumber that would have demoted a deliberately higher-ranked convention. Tags stripped, principle stated inline.

Upstream chroma-core/agent-skills was verified current against the GitHub API rather than a local fetch: main is at 5669a50, last pushed 2026-04-20. The skill-split branch that looks like unreleased work is refs/pull/6/head — already merged.

  • 2026-08-14_01 — the public-skills/private-config pattern and lossless-crm-interface-guidelines, the first skill built to it. This entry picks up where that one ends.