The anti-over-engineering skill that fought itself
Flight Deck 1.10.0 shipped a fourth skill, code-craft, whose whole job is to stop a coding agent from over-engineering. Writing it kept producing the exact failure it was written to prevent: a premise that wanted to add what already existed, an orchestrator that quietly cancelled its central clause, and a minimalism rule that told the model to delete a guard on a destructive path. Here is each one and the fix it forced.
Key takeaways
- The package already stated the doctrine in four places, so a fifth statement would dilute it, not strengthen it.
- A skill that hunts for problems will find some, so finding nothing has to be a valid outcome.
- Two loaded files gave opposite orders on the same step, and the direct order wins unless precedence is written down.
- Minimalism stops at the trust boundary, not at a line count, and irreversible paths gate the test.
- Prohibitions work against rule-skipping under pressure; for shaping an output a positive recipe beats them.
Flight Deck 1.10.0 went out with a fourth skill in it, called code-craft. Its job is narrow: when a coding agent is writing a change or about to call one done, ask whether the change is the right size, the right shape, and whether it costs what it should at runtime. An anti-over-engineering skill, in other words. The package that carries it is on the Flight Deck page if you want the context.
Writing it was not the smooth part. Almost every step surfaced a problem where the skill was quietly becoming the thing it was written to prevent, and each one had a fix that was more interesting than the feature. That tension is the whole post. It is not a changelog, and you do not need to install anything for the failures to be useful.
The premise was wrong
The plan was to add anti-over-engineering doctrine to the package. Before writing a line I audited what was already there, and found the doctrine stated in four separate places, near word for word: the always-loaded activation block in the project instructions, the verify phase of the orchestration skill, the proportionality rule in the testing skill, and a row in the README table. Writing a fifth statement would not have strengthened any of them. It would have diluted all four, because a rule that appears everywhere reads as filler.
That is the first lesson, and it is the one I keep having to relearn: the reflex to add is the failure mode. It is the failure mode in code, obviously, that is what the skill is about. It is exactly as much the failure mode in prose. A skill file is a context cost paid on every session that loads it, and restating a rule the model has already read three times buys nothing at all.
So I went looking for what was genuinely missing rather than what sounded on-theme. Grep confirmed two real gaps. Nothing anywhere in the package told the agent to notice a query sitting inside a loop, or a quadratic scan over a collection the code does not bound. And nothing told it to match the idiom of the code it was editing, which is the difference between a change that disappears into a file and one you can spot from across the room. The skill that shipped absorbed the scattered doctrine into a single home and added those two gaps. Net lines added across the package were close to nothing, which was the correct outcome and not the one I had planned.
The trap is documented, which did not help me avoid it
Anthropic's own best-practices guidance for Claude Code makes a point that lands uncomfortably close to home here: a reviewer prompted to find gaps will report some even when the work is sound, and chasing every finding is precisely what produces over-engineering. Extra abstraction layers. Defensive code nobody needs. Tests for cases that cannot happen.
Read that back with the skill in mind. A skill whose entire job is to look at a diff and hunt for problems is, structurally, that reviewer. Firing it on every change means firing a gap-finder on every change, and a gap-finder that reports nothing feels, to a model, like a gap-finder that failed. Left alone it would generate its own workload, which is a strange way to build a tool against excess.
So code-craft carries an explicit clause about when to stop talking. Raise what affects correctness or the stated requirements, and fix that. Name anything else once, as an observation, and move on without acting on it. And, said out loud because it needs saying, finding nothing is a valid and expected outcome: the change is the right size, stop there. That last sentence exists only because without it the model has no permission to be satisfied.
Then the orchestrator quietly cancelled that clause
The package's orchestration skill, mission-control, has a verify step that hunts over-delivery: a lot can satisfy every done criterion and still have grown an abstraction with exactly one caller. Wiring the new skill into that step was a two-line change, and I wrote it the obvious way. Run code-craft against the diff, it said, and trim what it flags.
Put the two files side by side. code-craft says do not act on the non-critical findings, name them once and leave them. The calling step says trim what it flags, all of it. Both files are loaded into the same session at the same time, they give opposite instructions about the same list of findings, and if you ask which one wins, the answer is not the one with the better reasoning. It is the one giving the direct order at the moment of action. My careful clause about restraint would have been read, understood, and then overridden by four words in another file.
An adversarial review pass caught it, not me. The fix was small and boring: state the precedence explicitly in the calling step, so the orchestrator says which findings it wants acted on and defers to the skill for the rest. What I take from it is broader than the bug. When two instruction files can be loaded together, the contradiction between them is not resolved by the model weighing intent. It is resolved by whichever line is most imperative and most local, so if a precedence matters you write it down rather than hoping.
Minimalism has a floor, and it is not line count
Any instruction to keep a diff small has a predictable counter-risk: it gets over-applied to the code where small is the wrong goal. Untrusted input, authentication, payments, irreversible actions, responses from external APIs that time out and lie and change shape without warning. Tell a model to minimize and it will happily minimize there too, because those guards look like exactly the kind of ceremony a minimalism rule is aimed at.
So the skill draws its line at the trust boundary rather than at a line count. Those paths are simply never subject to minimalism. Everywhere else, it runs one mechanical test on each remaining guard: does this guard protect against a state my own code can produce, or against a state arriving from outside my control? Own code means delete the guard and make the bad state impossible instead, through the type, the constructor, or the single call path. Outside means keep the guard, and hand it to the testing skill as a named proof, because a check that exists but is never exercised is not protection.
The review found that my first draft of that section contradicted itself, in a way I would never have caught by rereading. Consider an internal destructive path: a migration runner, a batch delete. It is your own code, so the mechanical test says delete the guard. It is also an irreversible action, so the never-list says keep it. Two rules, one piece of code, opposite verdicts. And because I had presented the test as mechanical, the model would do the mechanical thing: run it, get own code, remove the guard from the batch delete.
The fix was structural rather than verbal. Instead of listing the exceptions after the test and hoping they are applied backwards, the never-list now gates the test: it comes first, and code on those paths never reaches the mechanical step at all. Same two rules, same words more or less, but the order now makes the wrong reading unavailable.
How the rules are phrased turned out to be load-bearing
I went into this assuming that the way to stop unwanted behavior is to prohibit it, which is what most instruction files do. That assumption holds in one specific case: a prohibition works when the failure is a rule being skipped under pressure, where you need a line the model cannot negotiate its way around. It does not hold for shaping the form of an output. In a controlled comparison, the arm that prohibited the unwanted content produced more of it than the arm that gave a positive recipe for what to write instead, and it trended worse than giving no guidance at all. Naming a thing you do not want puts the thing in the context.
So the skill spends its single hard requirement, the one written as a must, on the trust boundary. That is the one place where the failure mode really is a rule getting skipped under pressure from an instruction to keep the diff minimal. Everything else is phrased as what to do: write the operation inline the first time and extract it when a second real call site exists, read the neighboring files before writing, use the framework's own construct. Not a list of sins.
One related detail, equally load-bearing and easy to get wrong. A skill's description field has exactly one job: say when to fire. It must never summarize the procedure in the body. There is a documented case of agents reading a process summary in a description and executing that shortened version instead of opening the file and following the real steps, which turns your carefully written procedure into decorative text. So the description lists triggers and phrases and nothing else.
Every check had to be executable
The same guidance that warns about gap-hunting reviewers also lists non-actionable advice as something to keep out of instructions, with “write clean code” as the example. That is a fair description of most of what gets written about over-engineering, including several sentences in my own early drafts. Advice that cannot fail a check does not change behavior.
So each of the four cuts in the skill carries something you can actually run. For scope, read the diff hunk by hunk and trace each one back to a line of the request; a hunk that traces to nothing comes out. For a guard, name the caller that can reach it with the bad value, and if there is no such caller the guard goes. For an abstraction, grep the repo for the call sites of the helper you want to add: two that exist today earn it, a future one does not. For a comment, it has to say a why that the code itself cannot show. None of these are judgment calls you can talk yourself past, which is the entire point.
The part I got wrong and did not catch
One last thing, for honesty. The package ships an install verifier, a shell script that checks an installation is complete. It enumerates the skill folders in a hardcoded loop, and I did not update it. It would have reported a clean bill of health on an install missing the new skill entirely. The adversarial review found that too, and the reason I did not is petty and instructive: I had run the right grep, and truncated the output before the line that mattered. The discipline this skill encodes is not a thing you install once and then own. The source is at github.com/CaseReed/flight-deck, verifier now fixed.