tending-tbaguette

Use at the start of every conversation, in any project or repo, and keep watching for the rest of it — the moment a genuinely project-agnostic lesson shows up while using TBaguette, capture it. Triggers include a correction that generalizes past this one codebase, a gap or wrong assumption found in a skill while that skill was running, a recurring judgment call nothing covers yet, a TBaguette skill that looks wrong enough to want editing, an installed plugin that already carries hand-edits, or any question about how to contribute to TBaguette. Covers the bar a candidate has to clear, capturing one without derailing the current task, scrubbing it of anything project-specific, choosing which existing skill a lesson belongs in, the approval gate that no absent human lifts, opening the pull request from a fork and answering the review it gets, restoring an install that was edited in place, a command the harness declines rather than git, and how a merged change comes back through keeping-tbaguette-current.

What this is

TBaguette is a library of project-, stack-, and language-agnostic skills. It only gets better if it keeps absorbing what actually gets learned in real work — and that work happens in your conversations, about your projects, almost none of which have anything to do with TBaguette itself. The lesson is right there in the transcript for about ten minutes, and then it is gone.

This skill is the always-on watcher that catches those moments, and the route that turns a caught moment into a pull request against the published repo.

Two clearly separate jobs, on two different clocks:

  • Capture — cheap, constant, every conversation. Notice something, write two sentences to a queue file, get back to whatever you were actually doing. This must never derail the current task.
  • Contribute — occasional, at a natural breakpoint, and never without an explicit yes. Turn queued candidates into a real pull request.

Capture is meant to start at conversation turn one. When this skill gets invoked partway through instead, it runs a one-time catch-up first — see "Starting mid-conversation" — before settling into the same ongoing watch.

Why a pull request, and never an edit in place

Extremely important

No TBaguette skill gets changed by editing the installed copy. A change to a skill goes upstream as a pull request, or it does not happen. There is no "just this once" branch of this rule and no size of change that is below it.

Three separate things make that the rule, and they stack:

The licence. TBaguette is free software under the GNU General Public License, version 2. Be precise about what that does and does not compel, because a rule defended with a false reason is a rule that collapses the first time someone checks. GPLv2 says that if you pass a modified copy on to anyone else, you must pass it on under these same terms, with source. It does not oblige you to send anything upstream; a change you make and keep entirely to yourself is yours to keep. What the licence establishes is the direction the project runs in — improvements stay free and stay shared — and this skill is how TBaguette makes that real rather than nominal. It ships inside the plugin on purpose: every recipient of the software also receives the means to send changes back, so the share-alike promise has a working route rather than only a legal one.

The freeze. This is the mechanical one, and it bites immediately. The installed plugin is a git clone, and keeping-tbaguette-current refuses to update a clone with local changes — it will not discard your work to force an update through. So a hand-edit does not merely sit there. It pins your entire install at that commit, silently, for as long as it exists. You traded every future skill in the library for one local edit, and nothing announces the trade.

One specific wrong belief is what makes people comfortable doing this, and it is worth naming because it is the one they actually hold: the next update will quietly overwrite my edit anyway, so it is temporary. It will not, and it is not. The update reads the tree before it touches anything and backs off rather than discarding your work. The edit is not a sandcastle waiting for the tide — it is a wedge holding the door shut, and it holds until somebody removes it by hand.

The direction of travel. An edit that stays local decays. Upstream moves, your copy does not, and the improvement you were proud of becomes a merge conflict nobody remembers the reason for. The same edit, merged, comes back to you automatically — and to everyone else — the next time keeping-tbaguette-current runs. That is the whole point: your improvements reach you through the update path, not through your working tree.

Where a project-specific rule actually belongs

Most edits someone wants to make to an installed skill are not corrections to that skill at all. They are a local fact — this estate is camelCase, this service already validates upstream, this team does not use feature flags — colliding with a library that is deliberately project-agnostic. The skill is not wrong; it is general, and that generality is the entire reason it works in a repo it has never seen.

A local fact has a home, and the home is not inside the plugin. It goes in the project's own instructions: CLAUDE.md at the repo root, or a project-level skill living in the repo. Either one sits above a library skill rather than modifying it, and that buys four things a hand-edit cannot:

  • It is version-controlled where the team can see it, rather than sitting in one person's home directory.
  • It reaches everyone working in that repo, rather than one machine.
  • It survives every update, because nothing overwrites it.
  • The install stays clean and keeps updating.

Reach for this first, every time. Then ask the separate question: is the skill also wrong in a way that would be wrong in any repo? If yes, that part goes upstream as a pull request. If no, there was never an upstream change to make here — only a project that needed to say something about itself.

Getting this backwards is the expensive mistake in both directions. A collision between one project and a general rule, sent upstream, asks a maintainer to make the library worse for everyone else. The same collision hand-edited into the install freezes the install. The project's own instructions are where it belonged from the start.

The bar: what actually belongs in TBaguette

Every skill here is a reusable judgment call or technique, stated so it applies regardless of language, framework, or project. scripts/content_pipeline.py's CATEGORIES in the repo is the live list of what the library already covers, and reading it beats guessing.

Worth capturing:

  • A correction someone gave you that generalizes past this one codebase — "don't do X because Y", where Y is not specific to this stack.
  • Using a TBaguette skill and finding it had a real gap, a wrong assumption, or a case it missed. This is usually higher-value than a new skill and much lower-risk to land.
  • A judgment call that came up, felt like it should have had a skill already, and didn't.

Not worth capturing:

  • Anything tied to a specific codebase, company, product, library, or API that doesn't generalize. That is a note for your own project, not a skill for a library that has to survive being read cold in a repo it has never seen.
  • A one-off bug fix with no reusable lesson in it.
  • A fact to remember rather than a technique or judgment call to apply.

A shape that turns up in two unrelated projects is the strongest evidence this bar can get, and you will usually not have it. Agnosticism is not a property a candidate can be inspected for — from inside one project every lesson looks general, because the features you cannot see are exactly the ones belonging to the project. Repetition is what actually discriminates, and someone working in one codebase has one instance by construction.

So neither manufacture the second instance nor let its absence stop you. Say in the pull request how many times you have really seen this. "Twice, in unrelated stacks" and "once, and here is why I believe it generalizes" are both legitimate openings, and they ask the reviewer for different things — the first offers evidence, the second offers an argument and invites it to be checked. A single instance with its generalization stated outright is an ordinary contribution. A single instance presented as a general law is the one that comes back, because the reviewer's first question is the one it skipped.

Close calls get captured anyway. The contribute phase re-evaluates every candidate with fresh eyes before touching anything, so a weak candidate costs nothing to discard later. Capturing is cheap; opening a public pull request on something half-baked is not.

Non-negotiable: scrub before writing anything down

This skill runs in conversations about all kinds of things, some of them private, proprietary, or under an agreement you did not read. *Before capturing and again before contributing*, strip every identifying detail: project names, company names, product names, internal URLs, hostnames, business logic, anything that is not the generalizable technique itself.

If the lesson cannot be stated without referencing specifics, it is not agnostic enough for TBaguette. Leave it out entirely rather than sanitize it down to something misleading. A pull request is public and permanent from the moment it opens — there is no scrubbing it afterward, only apologizing for it. redacting-sensitive-output covers the general technique.

Capture procedure

Queue file: ~/.claude/tbaguette-candidates.md — deliberately outside the plugin's own directory, which every update overwrites and which must stay clean for the reasons above. Not ~/.claude/skills/tending-tbaguette/candidates.md — that near-identical path belongs to a different, personal skill some readers of this one also have installed, with its own separate queue and a direct-push ending instead of a pull request. The same lesson landing in both files, days apart, is the tell that a past session wrote to the wrong one from memory instead of re-reading this line. Create it with all three headings if it does not exist — ## Pending, then ## Settled, then ## Shipped. Step 10 moves entries out of ## Pending into one of the other two, and a file missing a heading is one where an entry that reaches that state has nowhere to go and invents a heading instead. ## Settled holds the other kind of result: a candidate the coverage check killed, or a question this skill answered "no", dated and carrying what would reopen it — so the next pass confirms it in one command instead of investigating it a second time from cold.

Append under ## Pending:

### <YYYY-MM-DD> — <short title>
- Kind: improve `<existing-skill-slug>` | new skill
- Observed: what happened (scrubbed, per above)
- Why agnostic: the one-sentence generalization
- Sketch: rough shape of the fix or the new skill's angle

That is the whole capture step. Do not stop to draft the skill content now, do not go looking at the repo now — two sentences, then straight back to what the user actually asked for.

An entry being worked is not distinguishable from one nobody has touched

The queue records two states — queued and resolved — and a run that starts acting on a candidate looks, to every other reader of the file, exactly like a run that has not looked at it yet. Two runs have picked the same entry out of this same file and each written a full section for it: different prose, different placement, same lesson, discovered only when both landed. The duplication is invisible until then, which is the most expensive moment to find out.

Before starting the contribution procedure on an entry, re-read the file and append - Claimed: <ISO date> as its last bullet, in that same read-then-write — never in a separate write, since the gap between reading and writing is exactly where two runs collide. A claim dated today is a stop: pick a different entry, or confirm the claiming run actually finished (its pull request or commit will exist) before treating the mark as abandoned. A claim with no matching result after a day is stale and can be taken. The line moves with the entry into ## Shipped or ## Settled, or is simply gone once either happens — it is bookkeeping for the queue, never part of what got contributed.

This does not touch the other collision the same shared-queue write can cause — a fixed, reused working path landing one run's edits on top of another's checkout. That is a different failure with a different fix, in the contribution procedure below.

Starting mid-conversation

This skill is meant to be watching from the first turn. Sometimes it is not: it gets invoked explicitly, or its trigger finally fires, partway into a conversation that already has real history. That history was never watched, so catch up on it once before returning to the triggering request:

  1. Read back over the conversation from its actual start — not just the triggering message — applying the bar above exactly as if capture had been running the whole time. A long conversation can hold several capturable moments, not just the most recent one. Find all of them.
  2. Run the capture procedure on each moment that clears the bar. Same scrub, same file, same one-entry-per-moment format.
  3. Finding nothing is a normal outcome, not a failed scan. Same rule as an empty ## Pending: don't narrate it, don't report that the sweep came up empty. Silence is correct.

Run this inline, in the current conversation, never in a subagent — the transcript is the input, and a fresh agent does not have it.

Read that rule with its reason attached, because the reason is what bounds it. "Never in a subagent" follows from this conversation's transcript being the input. A run genuinely working from records it fetches rather than from a transcript it is inside — a scheduled pass over sessions that already ended, say — is no longer the case the rule describes, and it is free to delegate. What that run should expect to get back is the next paragraph's subject.

The sweep works backward through one conversation, and not much further. Reading back over the conversation you are in is cheap and it works: the transcript is right there, in order, with each correction still attached to whatever provoked it. Do not read that as a general ability to recover capture after the fact. Across conversations that have already ended it does not hold, and the reason is worth knowing before you lean on it — a lesson has no consistent surface form. Corrections rarely contain the words you would think to search for, so a keyword sweep over finished sessions comes back nearly empty while those same sessions were full of material at the time.

Which is what the queue file is actually for. It is not a convenience for batching work; it is the only durable record that a moment happened, and the transcript is not a backup for it. A retroactive pass over a body of finished work therefore reads the queue and the artifacts still visible — the commits, the diffs, the notes — rather than fishing transcripts for a shape they do not have. And a moment nobody captured is, in practice, gone. That is the whole argument for two sentences now rather than a better write-up later.

A pass over finished work is steered by the subject line's grammar, not its subject. A month across a few projects is several hundred commits, and nobody reads several hundred commits. What discriminates is how the subject was written: one phrased as a judgment — something stopped, something that turned out not to be true, something decided against — was written by someone who had already done the generalizing, and its body usually states the lesson outright. One phrased as a change — a bump, a rename, a feature added — has nothing in it a skill could carry. So read subject lines, then read the bodies of the handful that read as judgments. The diff is the project-specific half; the body is the half whose author already scrubbed it.

That selector is worth exactly what the repository's commit discipline is worth, and where subjects are written as changes it selects nothing. That is a true answer about that repository rather than a broken sweep — say so and stop, rather than falling back to reading diffs at random and calling the result a pass.

Count the population before the selector runs, and report both numbers. The population and the selector get chosen separately — which projects are in scope is answered from recent activity, what to read in each by the subject-line rule above — and a member of the population the selector simply cannot read is dropped without a word. A loop reading commit subjects across a list of recent working directories prints nothing to stdout for any of them that is not a version-controlled repository — the tool does complain, to stderr and through an exit code, so a loop that discards either leaves a result indistinguishable from a project that had a quiet week, and a loop that guards on the directory's existence never asks at all. In one such pass seven of sixteen went that way, and the sweep would have been reported as covering all sixteen. The remedy is arithmetic rather than judgment: say how many were in scope, how many the selector could actually read, and name the difference. It is also the only way the gap becomes a finding — those seven were not unproductive, they were projects whose work leaves no commit trail, and that is worth knowing before designing a sweep that assumes one.

And two numbers only check each other if they come from different instruments. The rule above is arithmetic, which is exactly what makes it feel safe, and there is one natural way to write it that makes it vacuous: derive the population from the same traversal that feeds the selector. Both numbers then agree by construction, the difference is zero, and the report certifies the gap instead of finding it. In one pass the population was counted by testing each candidate directory for a .git child, while the selector enumerated .git directories by pattern to a fixed depth — two spellings of one question, except that the second reached only top-level repositories. Twenty against sixteen, and that four-way difference was the finding.

Expect the members it drops to be the valuable ones, because the bias is structural rather than random. Whatever an enumeration cannot reach, it usually cannot reach for a reason, and that reason tends to correlate with activity. A depth limit drops precisely the nested repositories — a component inside a larger product, a submodule, a directory that grew into a project of its own — which is where multi-part work lives. In that pass the single most active repository in the sweep — as many commits as the next three combined — was one of the four the selector could not see.

Then print the names of the difference rather than its size. The repaired loop in that same pass reported three unreadable projects; there was one, whose path contained a space, which the shell had split into three words each naming nothing. A coverage number computed by the instrument under suspicion comes back looking exactly like a coverage number, and no arithmetic catches that. A list of names does, at a glance: one of those three was the bare word NOT, which is not a path to anything.

Widening the enumeration until it drops nothing pulls in repositories that are not your work, and they are a third class rather than quiet members. The fix for the depth-limit bias above is to search deeper, and one level deeper than the nested product is the vendored upstream: a platform source tree that is itself a few dozen repositories, a mirror kept for reference, a third-party checkout that happens to sit under the code root. One pass that searched to depth four found forty-six repositories where the previous pass had found twenty-one, and twenty-four of the new ones were components of one upstream operating-system tree with no commit by the identity doing the work in any of them inside the window. Two things go wrong if they are filed as members that simply had a quiet week. Every count derived from the population — the silent members especially — is wrong by that block, so the coverage arithmetic certifies a gap that is really an absence. And the day a mirror syncs inside the window, the subject-line sweep fills with hundreds of judgment-shaped subjects written by many hands about one project — which the cluster step, built to reward the same shape across unrelated repositories, reads as the strongest possible signal. Classify by authorship, not by location: a repository with no commit by the working identity in the window is foreign, reported by name in its own count, and its subjects never enter the cluster. git log --since=<window> --author=<email> is the whole test, and it runs in the same loop that already reads the subjects — match on the email rather than the name, because the name one identity commits under can differ from checkout to checkout while the email does not.

Cluster before opening anything, because the repeat is the whole point. The bar above says a shape appearing in two unrelated projects is the strongest evidence it can get, and a pass that opens one body at a time can never see it: each lesson arrives alone, looks general for exactly the reason the bar warns about, and gets judged alone. Sweep the subject lines of every project in scope into one list first, and group them by the shape they describe rather than by the repository they came from. Groups of one and groups of four then separate at a glance, and the fours are the candidates.

The bodies are a bonus; the cluster is the payload. In one pass every repository in scope committed subjects with nothing under them at all, and the pass still produced its finding: the clusters were built from subject lines, the coverage check that killed all but one of them ran against subject lines, and the body eventually opened for the survivor added nothing the cluster had not already said. So an absent body is not a sweep that failed to reach its evidence — a subject line written as a judgment is the scrubbed sentence, which is the whole reason the grammar selects. Open the bodies of whatever survives the coverage check, expect a good share of them to be empty or to merely restate the subject, and treat that as the ordinary case rather than as a reason to keep reading downward into the diff.

The saving compounds with the coverage check below. Run that against the cluster, in the vocabulary the covering skill would have used, before opening a single body — if the library already says it, nothing in those bodies was going to change that, and a cluster killed at the subject-line stage costs one search. In one such pass four of six candidates died there, and no commit body was opened for any of the four.

Once done, it is done. Go back to watching turn by turn like a skill that started on turn one.

When to contribute

At the start of a conversation if ## Pending has anything in it; after finishing the actual task, if there is a natural lull; or whenever the user asks. Never mid-task — do not interrupt live work to go run a git pipeline.

If ## Pending is empty there is nothing to do, and saying so is worse than saying nothing.

A pass with no conversation behind it inherits only half this skill. The two jobs run on two clocks, and a run started by a timer, a scheduled job, or a delegation rather than by a conversation has no transcript, no correction and no moment — so capture has nothing to do, and the whole of its work is whatever the queue already holds. That creates two failures and reverses one rule.

The failure does not feel like one at the time. A run whose stated purpose is improvement, finding the queue empty and the library in good order, will go looking for something to change, and it will find something, because a corpus this size always holds a sentence that could be put more sharply. Here that costs more than an unnecessary commit would: it becomes a pull request a maintainer has to read, weigh and turn down. An empty queue ends a scheduled pass exactly as it ends a conversational one — and it ends there whether the queue arrived empty or was emptied by the coverage check in the contribution procedure below.

And the strongest form of that pressure is an instruction rather than an urge. The paragraph above describes a run that goes looking on its own initiative. A run can also arrive holding a task that names the target: improve skill X, contribute something to the library. That reads as settling in advance the question the bar and the coverage check exist to ask, and it is harder to resist than the self-generated version, because declining now feels like declining the task rather than exercising the filter.

It is not. An instruction to improve something is an instruction to look, and looking can honestly come back empty. What an instruction never supplies is evidence that a particular sentence is wrong — that still has to come from outside it: a queued candidate, a correction someone made, a shape seen twice in unrelated work. A pull request whose whole provenance is having been asked for one costs a maintainer the same read as any other and teaches them to weigh yours less. Answer in the instruction's own terms instead: what was examined, what survived, and what turned out to be covered already.

The reversal is in what gets said. Inside a conversation, silence about an empty queue is correct: there is a user mid-task and a report about nothing is noise. A pass with no conversation has nothing to interrupt and its report is its only output, so there "the queue was empty and nothing was opened" is the report rather than narration about it. One line, and stop.

A candidate you keep re-reading has already been declined

This is one queue's instance of a general rule — a recurring run that surrenders the same ledger line every pass settles it rather than surrendering it twice — which finishing-what-you-started carries for any standing request.

The bar above says to capture on a close call and let a later pass decide, and the safety rails say to leave a borderline candidate queued rather than push it under time pressure. Both are right, and together they build a graveyard.

Deferring is free and needs no argument; deciding costs a justification and can be wrong. So a genuinely marginal candidate gets re-read by a careful pass, re-agreed to be marginal, and re-queued — reasonably, every time. The tell is that the entry is complete: the analysis is finished, the target skill is named, the cost is known, and the only missing piece is somebody willing to say yes or no. That is not a candidate waiting for information. It is one waiting for nerve.

So state the deferral's own premise where the candidate lives, in the Sketch: line — what would change this answer, and when. A second sighting in another project, a description with room in it, a section that grows past an aside. Then:

  • A second deferral names what changed since the first. If nothing changed, the inputs are the ones you already declined to act on.
  • There is no third. An entry deferred twice on unchanged inputs is settled at the next pass — either it goes into a pull request or it moves to ## Settled, dated, with a one-line reason and what would reopen it. Both are cheaper than a fourth reading.

But "the next pass" is a claim about the interval, not about the work. The counting rule assumes visits to the queue are spaced far enough apart that the inputs have a chance to move between them, and nothing enforces that. Two sessions in an afternoon, or a scheduled sweep firing on top of a manual one, both count as two passes while holding exactly the same evidence — so a rule written to force a decision after a real wait instead manufactures one on unchanged inputs, and that decision carries the false authority of having been reached twice.

Read a deferral premise by its condition, never by its count. An entry whose premise says fires on a second sighting in unrelated work has not fired because a second session opened the queue. Where a premise was written in counts and the visits have bunched up, treat them as one and leave the entry queued: that is the first deferral still waiting, not a third one. automating-repetition's "Next run" is a claim about the interval, not about the work is the general form.

This governs your deferrals, not your partner's. "Keep it queued" chosen at the approval gate is a decision someone made and it resets nothing — they own the scope, and re-asking about it next week is pestering rather than rigor.

An entry held back by the pass's own budget is not a deferral, and the bunching rule above does not reach it. A pass opens pull requests for a bounded number of candidates, and one that cleared every filter but arrived after that bound was spent goes back into the queue looking exactly like one that was declined — same section, same Sketch: line, same "next pass" in its premise. The two are waiting on different things. A deferral waits on evidence — its premise names something outside the pass, a second sighting, a section read in full — so bunched passes holding the same evidence rightly count as one. An earmark waits on capacity, which every pass brings with it, so it fires on the next pass that still has room in its bound, however soon that pass runs, and it is never counted as a deferral. Write the difference into the entry where the next pass will read it: EARMARKED — bound spent names capacity; DEFERRED — fires on <condition> names evidence.

A reopen condition that fires is the entry doing its job

The rule above covers a candidate you keep re-reading. A ## Settled entry has the opposite life: it was answered, and the only question left is when to look again. When its reopen condition trips, supersede it with a new dated entry and leave the old one standing underneath. Deleting it discards the only positive evidence you will ever have that the condition can fire — and a condition that has never fired has two readings that look identical from a cold start, a genuinely closed question and a condition too narrow to trip. Both present as an entry that has been quiet for a month.

There is a third failure the count does not catch, so do not read firing as proof of a good condition either: one that fires on noise also fires, and re-opens a settled question every pass for nothing. What separates the two is whether the condition names something checkable. For a ## Settled entry, prefer a condition phrased as a command whose output changes — gh pr list --state open, a count, a hash — over one phrased as a circumstance somebody would have to notice. That preference belongs to settled entries specifically; the deferral premises above govern ## Pending, and those are legitimately circumstances, because what they wait on is a second sighting rather than a number.

The approval gate that no absent human lifts

Extremely important

Nothing gets pushed, forked, or opened as a pull request without an explicit yes from the user, obtained through the harness's own question tool, for this specific contribution.

Ask through whatever structured question tool the harness gives you — AskUserQuestion, an elicitation call, a multiple-choice prompt, whatever yours calls it. Prose fallback is for the absence of such a tool, not for convenience.

Check that absence rather than reading it off. A harness can defer its tools — they exist and are callable, but their schemas are missing from the list until something fetches them — so a question tool can be entirely available and still not appear where you looked. Query the harness's own index for one by name and then by keyword before concluding there is none; routing-around-capability-gaps covers why reading a list is not the same as looking. It matters more here than almost anywhere else, because this is the one gate this skill will not let an unattended run substitute.

Ask after the edit exists and the suite is green, and before the first command that reaches the network. That ordering is deliberate: asking earlier means asking about a change nobody can see yet, and asking later means the answer is decoration on something already published.

Put the actual content in the question — which skill, what changes, how many lines — not "may I proceed?".

Then say what the yes does, because "open the pull request" names an intention rather than an outcome. Short bullets, inside the question itself, naming what will exist afterwards that does not exist now:

  • a public fork of the repository under their account, if they have none yet
  • a branch pushed to that fork, carrying every commit on it
  • a pull request against LeSplooch/tbaguette-skills, open under their GitHub identity and readable by anyone
  • a diff of roughly a hundred files, nearly all of them the regenerated site
  • a maintainer who now has something to act on, which closing it later does not undo

Five lines, not five paragraphs — if it needs a paragraph it belongs in the description of the change, not in the consequences. The reason to spell them out is that the person answering is almost always approving the change: they read the skill, they read the diff, they decided the lesson holds. The part that is irreversible is none of that. It is the publication, under their name, of a thing strangers can read and a maintainer has to answer. Someone can genuinely want the change and still not want that today, and they can only tell you so if the question distinguishes them.

The four options that earn their place:

OptionWhat it does
Open the pull requestPush the branch and open the PR
Show me the diff firstPrint the full diff, then ask again
Keep it queuedLeave the candidate in ## Pending, push nothing
Drop itMove it to ## Settled, dated, with the one-line reason and what would reopen it

Three things that are not approval, each of which has been mistaken for it: enthusiasm earlier in the conversation about contributing to TBaguette; a yes given for a previous contribution in this same session; and the user having invoked this skill at all. Approval is per-pull-request. Getting one yes does not bank a second.

In a run with nobody there, this gate is not substituted — it is where the run ends. That is worth stating plainly, because the library's own bounding-autonomous-work says an unanswered gate keeps its job and changes its mechanism, and an autonomous run reading that rule alone could reason its way into writing itself a self-approval here. It does not apply. Opening a public pull request is outward-facing and permanent from the instant it happens, which is the one category no confidence level and no envelope converts into a self-answer. So an unattended run does everything up to it — the edit, the suite, the adversarial pass, the commit on a local branch — and then stops with the work staged and a report saying exactly what is waiting for a yes. Reaching that point is the run finishing correctly, not failing. Anything staged for that yes is read again, word for word, just before it is sent; the Ask step of reference/contribution-procedure.md explains why.

Contribution procedure

The full pipeline is reference/contribution-procedure.md — read it before the first candidate of a session and work from it, not from memory. It also covers the case where the install itself has already been edited in place, which is common and recoverable.

Run it once per candidate, fully, before starting the next. Never batch several candidates into one pull request — a reviewer who wants two of three changes then has to reject all three.

The shape, so this file still says what happens:

  1. Fork and clone a working copy that is not the install, and branch from current upstream/master rather than from whatever the fork last saw.
  2. Re-evaluate the candidate against the bar with fresh eyes, scrub applied again. One that no longer holds up gets dropped with a one-line note — that is the filter working, not a failure.
  3. Make the edit, then write the update note in UPDATES.md. Date it the day you write it — the only date you can know — and expect the maintainer to move it to the day it lands. The date a reader's clock measures is the merge day, and a note left under an earlier date sits below the entry already at the top, which on the site means inside the archive fold: published, and seen by nobody. A cleanly merging note is the one this happens to, because nothing forces anyone to look at where it landed.
  4. Run python3 scripts/run_tests.py, fully green — and read what green means here. The suite checks the filing: registries agreeing, counts matching, manifests at one version, the update note well-formed. Nothing in it can see whether a section is true, belongs where you put it, or is reachable from the description. Green is a precondition for the next step, never a substitute for it (confirming-before-claiming-done).
  5. red-teaming-your-own-work, then karen-and-the-manager.
  6. Commit — then ask, which is the approval gate above and the one step nothing past this point runs without.
  7. Push, open the pull request, record the URL in the queue file, and report it as waiting on a maintainer rather than as shipped.
  8. Answer the review when it comes back. A maintainer arguing with a section is not a request to delete it and not a request to defend it — verifying-review-feedback owns which of those a given comment is, and the same rule applies to your own change as to anyone else's: fix it, refute it with a reason, or withdraw it, and never silently drop one. A contribution that goes quiet under review costs the maintainer more than one that was never opened.

Five decisions inside step 3 have no gate behind them, and a session that never opens the reference can still get them wrong quietly:

First check the library does not already say it. The strongest reason to drop a candidate is that some skill already covers it, and that is invisible from the candidate — a capture records what you noticed, never what the corpus already knew. Whichever skill states it is filed by the family of judgments the lesson joins, so it is usually not the file the subject points at. Search in the vocabulary the covering skill would have used, before drafting: a section already written is expensive to abandon and easy to keep on the grounds that it puts the point more sharply.

A candidate that already names where the rule lives is reporting a memory, not a search. The most convincing entries in a queue are the ones whose capture did some homework — this already exists over here, so the real question is whether it is a duplicate or a routing gap. That framing is worth having, and it was written from inside the situation by someone with two or three files open, so it names exactly those two or three. Treat it as a lead rather than as the check having happened.

Which sends you somewhere the paragraph above says is usually the wrong place, and that is deliberate. Filing goes by family, so the covering skill is usually not the one the subject points at — and the exception is worth ten seconds anyway: open the skill the candidate proposes to edit, read that file's own section headings, and confirm the rule is not already in it under a heading that names it. It will usually miss, and when it hits it ends the question outright and saves the wider search.

Read a near-hit for its actor, not only for its topic. A search that lands on a passage covering the candidate's subject is usually the end of it — and occasionally it is the most valuable finding of the pass instead. A skill states its rule for the role it had in mind, and the situation that produced the candidate may have put its reader in a different one. A rule written for something that reads a record can be exactly wrong for something that reads and then writes it back. A rule written for whoever authors a check can be silent about whoever later reads its output. From the grep that found it, "the library covers this" and "the library covers this for somebody else" are the same result, so the question after a hit is not whether the topic is there but whether the actor is. When it is not, the change is one paragraph inside the passage you just found — cheaper than anything else you were considering, and worth more, because a rule that is right for one role and quietly wrong for another does more damage than a rule that is merely absent. The reader in the wrong role has already been answered.

And the second axis is the moment, not only the role. The test above can pass — same reader, same job — and the passage still miss, because it was written for a different point in time. A rule about composing a report after the work assumes a writer who can revise before anyone sees it and a reader who can reply; the same reader watching a surface while the work runs has neither, and may act on what they are told before the work finishes. Same topic, same role, different moment, and the consequences that follow are ones the covering passage has no way to state. So ask a hit both questions — is my actor in here, and is my moment? — because from the grep they look identical. The two axes do blur where a different moment brings a different emitter with it; what tells them apart is whether the reader is in the same position, and a reader who can still act on what they are told is not.

Expect that search to succeed. On a library this size the ordinary outcome of checking a candidate is that something already covers it, sometimes better than the draft would have — and a session that hits coverage three or four times running starts reading its own filter as failure rather than as the filter working, and goes looking for something it can land. That is how the bar gets lowered by someone who never decided to lower it. Four candidates dropped for coverage is a pass that worked, and it ends with nothing opened.

Which skill owns it is decided by neighbours, not by subject. Read the candidate file's section headings and ask whether yours reads as a sibling or as a visitor. Filing into the least-wrong skill is not parking it safely — it is invisible to everyone who needed it.

Then check the description would route someone to the section you wrote. It is the only always-loaded part of a skill, so it is the entire routing surface, and a new section in a file whose description never mentions the question it answers is unreachable while every suite stays green. The 1024 character cap is enforced by the format itself; past it, a new trigger has to displace an older one rather than join it.

A description with no room left is evidence about fit, not only about space. Displacement frames a full description as a budgeting problem — something loses so the new trigger can join — and that framing takes for granted that the skill you picked is the right one and merely crowded. It is worth testing, because a description is a compressed statement of a skill's scope, and one that has been saturated for a while describes a skill whose scope is saturated too. A lesson that will not fit into the sentence often does not fit inside the skill either.

When the cap bites, ask the fit question before the budget one: is there another skill where this lesson is the general case, with the one you first reached for holding only its sharpest instance? That reframe is common, because the skill a lesson occurs to you in is the one you happened to be using when it bit. Re-homing costs a cross-reference from the original section — one sentence, no description budget — and both skills end up more accurate than a displacement would leave them. It also matters more here than it would in your own repository: a displacement deletes a trigger other people are relying on to reach a section you may never have read, and your pull request is the only place that loss is visible. Name the other candidate homes and say why each is worse, in the pull request body, before proposing that a maintainer spend a trigger.

State the mechanism as its own claim, separately from the fix that works. A pull request usually opens with a sentence of the form X rejects Y because of Z, and by the time it is written the fix has been smoke-tested, so the sentence reads as settled. The outcome is settled. The mechanism is a second claim with its own evidence, and the maintainer checks it against the source of whatever did the refusing — because a fix that works by a route the diagnosis did not name, a fallback or a default or a second code path, lands a side effect the diagnosis cannot predict, and it gets found after the merge. So name the mechanism from the refusing program's own source or its exact message, and say which of the two you have. If the change deletes a test, quote what it guards — its docstring or its name — in the pull request and say why that is wrong now: a test whose docstring predicts your change's side effect is the first thing a review reads, and deleting it without a sentence reads as not having read it.

Expect the commit to touch most of docs/. The pre-commit hook regenerates the site, every page carries a version and a build timestamp, so a one-file edit lands as comfortably a hundred files. That is correct rather than damage — the site is served straight out of docs/. Do not trim them back out, and do say in the pull request body which files are the change and which are the regenerated site.

And that is why a branch left waiting goes bad rather than merely stale. Every branch carries a full regeneration, so every branch conflicts with every other one the moment either lands — in files where both sides look equally authoritative, because each is internally consistent with its own branch's source. Resolve those to your side and the merge is textually clean, the suite is green, and the published rendering of whatever landed while you waited is silently gone.

So when a pull request sits, or a review asks for a change days later: merge upstream/master into the branch, settle the source-side conflicts properly, keep both UPDATES.md entries rather than choosing one, and resolve every file under docs/ by regenerating from the merged tree — never by taking a side and never by hand-merging. Say in the merge commit that docs/ was regenerated rather than resolved. resolving-merge-conflicts owns the general form; here it is not hypothetical, it is what the second merge on a long-lived branch in this repo consists of.

A declined command is reported, not routed around

This skill's pipeline is a sequence of git and platform writes — fork, branch, commit, push, open the pull request — and a permission layer can decline any one of them on its own, without the underlying command ever running. It does not look like the failure it most resembles, and telling the two apart is the only hard part: *a refusal is announced before the command runs and produces no output from the command at all, while an auth failure comes back through it, carrying a remote's status line.* No output from the command means there is nothing to re-authenticate, so do not go rotating credentials or switching accounts over it.

What follows is the part that costs. A declined step usually leaves a plausible-looking way to get the same result by other means — ask someone else to run it, carry the text across by hand, work the change in somewhere it was not reviewed. For this skill's reader, who does not own the repository and cannot land anything alone, the damage is not a change that ships unreviewed. It is a second copy of the work circulating outside the pull request that was supposed to carry it: the maintainer meets the same lesson twice through two channels, and the copy that is not in the branch is the freeze problem above arriving by another door — it exists somewhere that no update will ever reach.

So report it: name the step that was declined, say what is staged behind it, and stop. The candidate has not shipped and stays in ## Pending. Describing anything as contributed, merged, or deployed at that point is false, and routing-around-capability-gaps owns why quietly finding another route is the worse of the two available failures.

Being told to contribute it is not authorization to contribute it by another channel. The approval gate above ends with a person saying yes, and that yes is still in hand when the push to the fork, or gh pr create, comes back declined. It reads as authorization for the outcome: they wanted the maintainers to have this, and pasting the section into an issue, a discussion, or an email does get it to them — creating the second copy described above, now under an approval nobody asked for it.

What they said yes to was a step: a branch on your fork, and a pull request opened from it under their name. What you are now holding it up as is an outcome, and the two only come apart at the moment the step is refused — which is when nobody is looking for the difference. The question that separates them is about the step you are about to substitute, not about the goal it serves: is this how you would have sent it if gh pr create had worked? Nobody pastes a diff into an issue while the pull request is available. The yes is not spent, and it does not expire — it is still waiting for a session where the command runs, and a second copy in an issue is the one thing that can make it unusable.

Write this pipeline as one call per step, so a refusal names its own subject. The discriminator above still works on a bundled call — a declined git add && git commit && git push produces no command output either way. What it cannot tell you is which of the three was the subject, and that is what the report needs. So keep them apart: the commit apart from the push, the push apart from gh pr create. Never take a bundled refusal apart afterwards to find out which piece it was; routing-around-capability-gaps owns why, and the short version is that each piece is another attempt at a step that may never have been permitted. A bundled call that was declined is reported as declined, whole.

A step that worked is a measurement, not a property

The section above treats a refusal as the surprising event and permission as the resting state. Both move. revalidating-decisions owns the general form — why the premises that block you get recorded with a way to reopen them while the ones that permit you get recorded nowhere, and why an outcome you can observe is not a capability you have. Read it there; what follows is only what is specific to this pipeline.

The fork push, gh pr create, and every git write between them were permitted on the pass that last used them, and that is a fact about that pass. Because they sit at the end of this pipeline, an unrecorded permission that has lapsed is discovered after the reading, the drafting, the approval gate and the commit have all been spent — the most expensive place to meet a refusal. One --dry-run at the start costs nothing and moves that discovery to the front.

Treat a refusal met there as a reading rather than a verdict, too. Retry once after doing other real work, never by re-spelling the same call — a permission has flipped inside a single session before, in both directions, and declined twice, minutes apart tells the next reader far more than declined. And a dry run does not stand in for the write it rehearses: --dry-run and the real command have disagreed in the wild, so a clean dry run says the plumbing works, not that the write itself is cleared. Measure the write when the pipeline actually needs it, and expect that measurement to be one pass stale by construction.

The trap specific to a contributor is that the work can land without you. A maintainer applies the lesson themselves after reading it in an issue; another contributor sends the same observation. The branch is merged and the section is in the file, and from a cold start that is indistinguishable from your own pipeline having worked. Record what actually happened — the change landed, my push was declined — because the repository's state will never say so, and the next pass will otherwise credit itself with a step it never ran.

Safety rails

  • Never push to LeSplooch/tbaguette-skills directly. Contributions go through a fork and a pull request. A push that unexpectedly succeeds is a reason to stop and check what account is active, not to carry on.
  • Never force-push, never rewrite published history.
  • Never open a pull request without the approval gate above.
  • Never commit red, and never open a pull request on red.
  • Always fetch upstream and branch from upstream/master immediately before editing — the repo moves.
  • The queue file is personal state. It never gets committed to TBaguette.
  • A refused step is reported to your reader and its candidate stays in ## Pending. Write the steps that ran into the pass's own note as well, dated — a permission is a measurement with a date on it exactly as a refusal is, and only one of the two is ever written down. Credit a step only if you watched its command run; a change that reached the repository by some other route says nothing about whether yours can.
  • The install directory is a read-only dependency. Nothing writes to it except keeping-tbaguette-current.
  • When genuinely unsure whether something clears the agnostic bar, leave it in ## Pending for a later pass rather than pushing a borderline call under time pressure — with the premise of that deferral written into its Sketch: line, so the next pass can tell a wait from a decline. Twice on unchanged inputs and it gets settled rather than re-read.
  • Settling one needs somewhere to put it. Give the queue file a ## Settled section between ## Pending and ## Shipped, and move the entry there with its date and the one thing that would reopen it — a second sighting in another project, a maintainer saying the covering skill does not in fact cover it. A negative finding recorded only in your own head, or only in the conversation that produced it, is a question that looks unanswered from every later pass: one already answered "no" and one nobody has looked at are identical from a cold start, and only one of them is worth the investigation again.

Common mistakes

SymptomReal cause
The install has not updated in months and nobody noticedA hand-edit froze it; keeping-tbaguette-current has been declining to update ever since, exactly as designed
A pull request nobody can evaluateThe observation behind it was scrubbed into meaninglessness, or never included at all
Three unrelated changes in one pull requestBatched candidates; a reviewer who wants two of them now has to reject all three
The suite is red on a new skill and the new files all look rightThe registry, the count, or the inbound cross-reference — none of which live in the new skill's own directory
A one-word description tweak turned the suite redThe description was already near the 1024-character cap the format sets; past it, a new trigger has to displace an old one
A pull request opened, then a correction pushed two minutes laterThe approval gate was treated as the review, instead of running after one
A yes, then surprise at a public pull request under their own nameThe question described the change and never said what approving it would create
A candidate captured mid-task, and the task never got finishedCapture is two sentences; anything longer is the contribute phase running at the wrong time
The same idea proposed twiceIt was never moved to ## Shipped with its URL
"Contributed to TBaguette" reported for an open pull requestOpened and merged are different states, and only one of them reaches other users
A well-written section that nobody ever seems to reachIt answers a question the skill's description does not mention, so nothing routes there
A lesson filed somewhere defensible that no reader would think to look inFiled by its subject matter rather than beside the family of judgments it belongs to

Red flags

  • "It's a one-line fix, I'll just edit the installed copy."
  • "The next update will overwrite it anyway, so it's temporary." — it will not; it will stop updating instead.
  • "It's only my machine, it affects nobody else." — it affects every future version of every skill you have, which is the opposite of nobody.
  • "Keeping the install pristine is superstition." — it is not kept clean for its own sake; clean is the precondition for it ever changing again.
  • "I'll open the PR and mention it to them after."
  • "They said yes to the last one, so this one's fine."
  • "I'll scrub the project name out of it later, before it merges."
  • "The skill is wrong and I need it right now" — the fork takes ninety seconds and does not freeze your install.
  • "I'll batch these three candidates so it's one review."
  • "Nobody will notice the install is pinned."
  • "The prose is in the file, so the change is done" — with a description that would never send anyone there.
  • "Nobody is around to approve it, so I'll substitute the gate" — every other gate, yes; this one is the stop.
  • "They already said the lesson was worth contributing" — that is a yes to the change. The fork, the branch, the pull request and their name on it are a different question, and it is the one that cannot be taken back.

Reference

The full pipeline for turning a queued candidate into a pull request on LeSplooch/tbaguette-skills, plus what to do when the install itself has already been edited. Read it before the first candidate of a session and work from it rather than from memory.

Contribution procedure

Run once per candidate, fully, before starting the next. Never batch several candidates into one pull request — a reviewer who wants two of three changes then has to reject all three.

One-time setup. This path uses GitHub's gh CLI, which is not installed by default on any platform. Run gh auth status first: it fails loudly both when the tool is missing and when it is present but not logged in, which are different problems with different fixes. Without it, everything below still works from a browser — push the branch, then open the pull request from the compare page GitHub offers on your fork. Only step 9 changes.

Then fork and clone a working copy that is not the install, and is not a path any other run of this same procedure would also reach for. A fixed, reused clone path is not merely a duplication risk shared with the queue file above — it is sharper, because a checkout has a current branch, and two runs in one checkout can redirect each other's commits rather than just repeat each other's work: one run's git checkout -B can move HEAD out from under a second run that is mid-edit, so the second run's commit lands on whatever branch the first run left checked out, its own branch stays empty, and the push reports "no commits between master and <branch>" — a message that names neither cause. Give the clone a path unique to this run rather than a name every run shares:

gh repo fork LeSplooch/tbaguette-skills --clone=false
CONTRIB=~/.claude/tbaguette-contrib-$(date +%s)-$$
git clone https://github.com/$(gh api user -q .login)/tbaguette-skills.git "$CONTRIB"
git -C "$CONTRIB" remote add upstream https://github.com/LeSplooch/tbaguette-skills.git

Everything below happens in $CONTRIB. The install at ~/.claude/skills/TBaguette is never touched, never edited, and stays clean so it can keep updating. $CONTRIB is disposable once the pull request is open — remove it or leave it, it does not matter, because answering a review that comes back later (see step 8 below) is simply another run of this same setup: mint a fresh $CONTRIB, then git fetch origin <branch-name> and git checkout <branch-name> in it instead of checkout -B ... upstream/master — never go hunting for wherever an earlier run happened to leave its clone.

The tell that this has already happened to you: a push that claims there is nothing to push, or a merge commit that carries the right content but only one parent (a second run's checkout stepping on an in-progress git merge the same way). Neither is the ordinary git failure it resembles, and neither is fixed by retrying the same command in the same checkout.

  1. Re-evaluate the candidate against the bar, with fresh eyes and the scrub applied again. If it does not hold up on a second look, move it to ## Shipped with a one-line note on why it was dropped and stop. That is not a failure, it is the filter working.
  1. Start from current upstream, not from whatever the fork last saw:
   git -C "$CONTRIB" fetch upstream
   git -C "$CONTRIB" checkout -B <branch-name> upstream/master
  1. Make the edit. Improving an existing skill means editing skills/<slug>/SKILL.md, or a reference file under that skill's own directory. This is the common case and by far the easier review.

One gate fires on that easy path too, and it is the only one that does. description is capped at 1024 characters by the Agent Skills format itself, not by this repo, and the suite enforces it. Descriptions are the field under permanent pressure to grow, because the natural way to make a skill fire on a newly-noticed trigger is to append that trigger to the list. Past the cap, the next trigger has to displace an older one rather than join it — so a description change is sometimes an edit to a sentence you did not come here to touch. Compressing the "Covers" half is usually where the room is, since it tends to restate triggers the "Use when" half already made.

First check the library does not already say it. The strongest reason to drop a candidate is that some skill already covers it, and that reason is invisible from the candidate itself — a capture records what you noticed, never what the corpus already knew. The search is harder than it looks for the same reason the ownership rule below is: the skill that already states the lesson is filed by the family of judgments it belongs to, so it is usually not the file the candidate's subject points at. Search for the idea in the words the covering skill would have used rather than the words the capture used, and search wider than the two or three files you expect. Do this before drafting, not after. A section you have already written is expensive to abandon and very easy to keep on the grounds that it says the same thing more sharply — which is how the corpus acquires two statements of one idea, in two files, neither aware of the other.

Read a near-hit for its actor, not only for its topic. A search that lands on a passage covering the candidate's subject is usually the end of it — and occasionally it is the most valuable finding of the pass instead. A skill states its rule for the role it had in mind, and the situation that produced the candidate may have put its reader in a different one. A rule written for something that reads a record can be exactly wrong for something that reads and then writes it back. A rule written for whoever authors a check can be silent about whoever later reads its output. From the grep that found it, "the library covers this" and "the library covers this for somebody else" are the same result, so the question after a hit is not whether the topic is there but whether the actor is. When it is not, the change is one paragraph inside the passage you just found — cheaper than anything else you were considering, and worth more, because a rule that is right for one role and quietly wrong for another does more damage than a rule that is merely absent. The reader in the wrong role has already been answered.

Decide which skill owns it before writing a word of it. A candidate usually arrives naming a topic, and the topic points at the wrong file surprisingly often. File by the family of judgments the lesson belongs to, not by its subject matter: a lesson about an installer that refused to run because a version check fired is not a dependency lesson, it is a reading-the-instrument lesson, and it belongs next to the other reading-the-instrument judgments even though nothing in it mentions debugging. The test that settles it is neighbourliness — read the section headings of the candidate file and ask whether yours would look like a sibling or like a visitor. A lesson filed into the least-wrong skill is not safely parked; it is invisible to everyone who would have needed it, which is the same failure as filing a new skill under the least-wrong category and is much easier to commit because no registry complains.

Then check the description would actually route someone to it. This is the step with no gate behind it, and it is the one that quietly wastes the work. A skill's description is the only part always loaded, so it is the whole of the routing: a section added to a file whose description never mentions the question it answers is unreachable, and the suite is perfectly happy — the prose is there, it is correct, and nothing will ever surface it. After the edit, read the description back and ask whether the sentence that made you write this would land on this file. If not, the description is part of the change, under the cap and its displacement rule above.

Grep the library for your key noun before you adopt it. A captured observation arrives in the words you happened to use at the time, and some of those words are already load-bearing here with a narrower meaning — seam means a place where behavior can be changed without editing the thing whose behavior changes, and finding-the-seam owns it. Shipping a capture's wording verbatim is how a second, vaguer sense of a defined term enters the corpus, and nothing in the suite can see that. One grep -rn over skills/ costs seconds and is the difference between a reviewer reading your change and a reviewer teaching you the glossary.

A genuinely new skill has more to satisfy, because the build gates it. The suite tells you which of these you missed, so run it early rather than working from this table:

What the repo demandsWhere
Frontmatter with exactly name and description, in the "Use when A, B, or C. Covers D, E, F." registerskills/<slug>/SKILL.md
A name matching the directory exactly, and a description under 1024 charactersskills/<slug>/SKILL.md, per the note above
Filed under a category in the registry the build readsCATEGORIES in scripts/content_pipeline.py
Filed in the same category in the human-readable mirrorCATALOG.md
The skill count, which is asserted, not derivedEXPECTED_SKILL_COUNT in scripts/generate.py, plus every manifest description
An inbound cross-reference from a skill that already existssome existing skill's "Not for:" line

That last row is the one that ambushes people, and it is a forcing function rather than a bug: a skill worth adding is one some existing skill should be handing off to, and writing that handoff is what proves it has a place rather than merely a topic. You cannot go green by touching only your own new files.

  1. Write the update note. Every change a user of the plugin would notice gets an entry at the top of UPDATES.md, newest first, shaped ## YYYY-MM-DD — Title followed by - bullets. Write it for someone who has TBaguette installed — the observable difference, not a restatement of your diff. writing-release-notes is the register.

Scope is the plugin and only what a user of it would notice. Repo tooling and site furniture are real work and are not news; they ship with no entry, and the build will not object.

  1. Run the suite. From the repo root:
   python3 scripts/run_tests.py

It must be fully green. Fix the real problem or abandon the candidate — never open a pull request on red, and never skip a suite to get around a failure. It is stdlib-only and needs no install step.

A check that fails for reasons that are not yours still has to be handled, and the handling is evidence rather than assertion. Run the same suite on a pristine checkout of upstream at the commit you branched from, and report that the same suites fail there identically, naming your platform. Without that baseline, "three of twelve suites fail on my machine" is indistinguishable from "this change breaks the build", and a reviewer has no way to tell which — so it reads as the second one. With it, you have handed them a platform bug report they did not have, which is worth more than the silence would have been.

The first run also wires this clone's pre-commit hook, which regenerates the site on every commit. Expect your commit to touch most of docs/ — comfortably a hundred files — on top of the one file you actually edited. Every page carries the plugin version and a build timestamp, so any change at all rewrites nearly all of them. That is correct, not damage: the site is served straight out of docs/, and a commit that skipped it would ship a page that disagrees with the skill it is showing. Do not try to trim those files back out of the diff.

If your environment cannot run the pre-commit hook at all — no git hooks support, a sandboxed shell, whatever the reason — regenerate by hand before committing. Pass the path the live site is actually served from:

   python3 scripts/generate.py --base-path /tbaguette-skills

Leaving off that flag builds every asset link, stylesheet, script, and font reference as if the site were served from the domain root instead of /tbaguette-skills/. The test suite will not catch it — it checks the filing, not the rendered links — so it passes clean and then breaks the live site's CSS, fonts, and script the moment it merges. A maintainer's own regeneration overwrites a wrong one before it ever goes live, but naming the flag here means your own pull request renders correctly too, for whoever reviews it before that happens.

  1. Review your own work before anyone else has to. red-teaming-your-own-work and then karen-and-the-manager are the standard adversarial close here, and they are most warranted exactly when the change looks obviously fine.
  1. Commit, with a message that explains why rather than what — see writing-commit-messages, and git log for this repo's voice.
  1. Ask, and do not go on without a yes. This is the approval gate, and it is stated in full in the skill file — but the binding part is repeated here on purpose, because this document is the one you were told to work from, and "the gate above" is not above anything on this page.

Nothing below this line runs until the user has said yes to this pull request, through the harness's own structured question tool. Ask after the suite is green and before the first command that touches the network.

The question carries what changed, and then short bullets saying what the yes creates: a public fork under their account, a branch pushed to it, a pull request open under their GitHub identity, a diff of roughly a hundred files that is mostly the regenerated site, and a maintainer who now has something to answer. Closing it later does not unmake any of that.

A yes given for an earlier contribution in this same session is not a yes for this one, and neither is enthusiasm about contributing in general. If nobody is there to answer, this is where the run ends, with the work committed on a local branch and a report saying what is waiting.

Whatever gets staged for that later yes is a file, and the file is what gets posted — not what it was written to mean. A pull request body or a review comment drafted now for a future pass to send often carries its own scaffolding aimed at whoever reads it before that later yes arrives: a note that it is not yet sent, the exact command to send it with, an instruction to delete this header first. A posting command does not know which lines were for that future reader and which were for the pull request's own audience — it sends whatever bytes it is handed. Read the literal content immediately before the send, once the yes has actually arrived, rather than trusting that the file already says what it is supposed to say.

  1. Push and open the pull request:
   git -C "$CONTRIB" push -u origin <branch-name>
   gh pr create --repo LeSplooch/tbaguette-skills --base master \
     --head "$(gh api user -q .login):<branch-name>" \
     --title "<subject>" --body "<what changed, and the observation behind it>"

--head is spelled out rather than left to inference. With --repo pointing at a repository you cannot push to, gh has to work out that the branch lives on your fork, and when it cannot it fails after the push — leaving a branch on your fork and no pull request, which reads like the push failed when it did not.

The body is where the candidate's "Observed" and "Why agnostic" lines earn their keep — scrubbed. A maintainer reading it should be able to tell whether the lesson generalizes without having been in your conversation. Say explicitly which files are the change and which are the regenerated site, because from the outside the two are one hundred-file diff and a reviewer who cannot separate them is reviewing either all of it or none of it.

  1. Record it. Move the candidate from ## Pending to ## Shipped in the queue file with the pull request's URL. That is the durable log, and it is what stops the same idea being proposed twice.
  1. Report the URL to the user, and say plainly that it is now waiting on a maintainer. Do not describe an unmerged pull request as shipped, landed, or done — confirming-before-claiming-done covers why the distinction is worth keeping.

If the install has already been edited

This is common and recoverable, and it is worth checking for whenever keeping-tbaguette-current reports that it skipped an update because of local changes.

git -C ~/.claude/skills/TBaguette status --porcelain
git -C ~/.claude/skills/TBaguette diff

Read the diff first — it is somebody's work and may be worth keeping. Then:

  1. Save it: git -C ~/.claude/skills/TBaguette diff > ~/.claude/tbaguette-local-edit.patch.
  2. Capture it as a candidate, applying the bar and the scrub like any other.
  3. Restore the install to clean so it can update again. Ask before doing this — discarding it is the user's call, never yours, and the patch file from step 1 is what makes the choice reversible.
  4. Run the contribution procedure on the candidate, applying the patch in the working clone rather than the install.

Never discard someone's local changes on their behalf to force an update through. Save, ask, then restore.

More in Environment and tooling