Skip to content

fix(cline): compile to lazy skills instead of an always-on rule - #94

Merged
singhharsh1708 merged 1 commit into
mainfrom
fix/cline-lazy-skills
Jul 28, 2026
Merged

fix(cline): compile to lazy skills instead of an always-on rule#94
singhharsh1708 merged 1 commit into
mainfrom
fix/cline-lazy-skills

Conversation

@singhharsh1708

Copy link
Copy Markdown
Owner

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:

→ .agents/skills/prereview/SKILL.md     ← lazy, ~40 tok standing
→ .clinerules/prereview.md              ← always-on rule, ~539 tok standing
IDENTICAL BODY

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 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. apps/vscode/src/core/storage/skill-directories.ts:

const SKILL_DIRECTORY_NAMES = {
	clineruleSkillsDir: ".clinerules/skills",
	clineSkillsDir: ".cline/skills",
	claudeSkillsDir: ".claude/skills",
	agentsSkillsDir: ".agents/skills",

and their docs are explicit about progressive loading — metadata (~100 tokens) at startup, the body only when use_skill fires. "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

cline now compiles to .agents/skills/<name>/SKILL.md, the same vendor-neutral file agents and zed emit.

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/skills would have halved the harm and left a second copy behind; this removes the failure mode.

Detection gains .cline/. Stale .clinerules/<name>.md prunes on the next compile — .clinerules was 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:

⚠ always-on → cline: authored disclosure = "eager", but Cline loads skills on demand
  — the body stays out of context until the model invokes it

The headline number survives

The benchmark reads loading modes from ADAPTERS rather than restating them, so the tax moved on its own:

before after
manifested skill 13× 14×
unmanifested SKILL.md 45× 47×

Cline leaving the eager set costs a row in a table, not the argument — aider and agentsmd still 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 .clinerules file surviving that prune, no standing-cost warning any more, the shared path emitted exactly once with no conflict, no second copy anywhere, the disclosure = "eager" warning firing, that warning failing --strict, and a lazy-authored skill drawing no such warning.

npm run check clean · full suite green · npm run bench deterministic · node site/build.mjs --check current. 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, explain reporting 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.

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.
@vercel

vercel Bot commented Jul 28, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
kitbash Ready Ready Preview, Comment Jul 28, 2026 6:44pm

@github-actions github-actions Bot added documentation Docs, spec, RFCs, README, site dependencies Dependency or action version bumps benchmark Benchmark script or measured numbers labels Jul 28, 2026
@singhharsh1708
singhharsh1708 merged commit f067dc2 into main Jul 28, 2026
8 checks passed
@singhharsh1708 singhharsh1708 self-assigned this Jul 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

benchmark Benchmark script or measured numbers dependencies Dependency or action version bumps documentation Docs, spec, RFCs, README, site

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant