fix(cline): compile to lazy skills instead of an always-on rule - #94
Merged
Conversation
The cline adapter wrote `.clinerules/<name>.md` — a rule: always active, whole body in context, every session — and declared the target eager. Cline reads Agent Skills, and has for a while. Its loader (apps/vscode/src/core/storage/skill-directories.ts) scans `.cline/skills`, `.clinerules/skills`, `.claude/skills` and `.agents/skills`, and loads them progressively: name and description at startup, the body only when `use_skill` fires. So two things were wrong, and the second is the serious one. Kitbash billed Cline ~539 standing tokens for a skill Cline was willing to lazy-load for ~40. And in any repo that also had `.agents/`, it emitted the identical body to both paths — Cline scans both, so the skill loaded twice and the always-on copy defeated the lazy one. For those users Kitbash was not merely mislabelling Cline; it was making their context worse than installing the skill by hand would have, while billing itself as the tool that measures standing cost. cline now compiles to `.agents/skills/<name>/SKILL.md`, the same vendor-neutral file `agents` and `zed` emit. Sharing the path is what makes the duplicate structurally impossible rather than merely smaller: one file serves Cline, Codex, Cursor, Copilot and Zed at once, and a repo carrying several of their marker directories still compiles that skill exactly once. Detection gains `.cline/`. Stale `.clinerules/<name>.md` output prunes on the next compile — `.clinerules` was already a managed directory — and the user's own rule files there are left alone. A skill authored `disclosure = "eager"` now warns on this target. Cline loads every skill on demand, so such a skill does not get the always-resident body it asked for, and compiling that silently would fix one spec §2 conformance violation by introducing its mirror image. It warns, and fails --strict. The measured tax moves with the adapter, since the benchmark reads loading modes from ADAPTERS rather than restating them: 14x for a manifested skill (~40 lazy vs ~560 eager) and 47x for an unmanifested one, up from 13x and 45x. Cline leaving the eager set costs a row in a table, not the argument — aider and agentsmd still carry the whole body every session. Eight of ten targets are now lazy.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Kitbash was making Cline users' context worse than doing it by hand
In any repo with both
.clinerules/and.agents/, Kitbash emitted the same skill body to both paths — and Cline scans both:The always-on copy defeats the lazy one entirely. A tool that bills itself on measuring standing cost was silently adding ~539 tokens of it, per skill, for anyone using Cline alongside any other agent. Installing the skill by hand into
.agents/skills/would have been strictly better than running the compiler.Why it happened
The
clineadapter wrote.clinerules/<name>.md— a rule: always active, whole body in context, every session — and declared the targeteager.Cline reads Agent Skills.
apps/vscode/src/core/storage/skill-directories.ts:and their docs are explicit about progressive loading — metadata (~100 tokens) at startup, the body only when
use_skillfires. "Unlike rules (which are always active), skills load on-demand." So Kitbash was billing ~539 standing tokens for a skill Cline was willing to load for ~40, on the strength of a claim —cline is eager and cannot lazy-load— that had stopped being true.The fix
clinenow compiles to.agents/skills/<name>/SKILL.md, the same vendor-neutral fileagentsandzedemit.Sharing the path is the point: it makes the duplicate structurally impossible rather than merely smaller. One file serves Cline, Codex, Cursor, Copilot and Zed at once, and a repo carrying several of their marker directories still compiles that skill exactly once. Emitting to Cline's nominated
.cline/skillswould have halved the harm and left a second copy behind; this removes the failure mode.Detection gains
.cline/. Stale.clinerules/<name>.mdprunes on the next compile —.clineruleswas already a managed directory, so the upgrade path came free — and a user's own rule files there are untouched (tested).The mirror-image bug, avoided
Cline loads every skill on demand, so a skill authored
disclosure = "eager"does not get the always-resident body its author asked for. Compiling that silently would have fixed one spec §2 conformance violation by introducing its reflection. It warns, and fails--strict:The headline number survives
The benchmark reads loading modes from
ADAPTERSrather than restating them, so the tax moved on its own:Cline leaving the eager set costs a row in a table, not the argument —
aiderandagentsmdstill carry the whole body every session, ~560 against ~40. Eight of ten targets are now lazy. README, benchmark page, adapters reference, config table and the homepage target pane all updated to match.Tests
9 new assertions: lazy emission, the stale rule pruned on upgrade, a user's own
.clinerulesfile surviving that prune, no standing-cost warning any more, the shared path emitted exactly once with no conflict, no second copy anywhere, thedisclosure = "eager"warning firing, that warning failing--strict, and a lazy-authored skill drawing no such warning.npm run checkclean · full suite green ·npm run benchdeterministic ·node site/build.mjs --checkcurrent. Version 0.13.0 with a CHANGELOG section.The pattern worth naming
This is the third target-drift bug in two days — Zed's detection gap,
explainreporting no degradation for skills a target silently discards, and now Cline. All the same shape: Kitbash asserting something confident about a target that had stopped being true. Three point fixes are fine, but there will be a fourth, and the real question is how adapter claims get re-verified against upstream rather than frozen at the moment they were written. That belongs in the MANIFESTO as its own section, and it's the honest version of the pitch: not "we are right about every target" but "we find out when we are wrong, and say so in the output." Not in this PR — it deserves its own.