<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Blog on Codegirl</title><link>https://www.codegirl007.com/blog/</link><description>Notes, essays, and updates from Codegirl.</description><generator>Hugo</generator><language>en-us</language><lastBuildDate>Sun, 18 Oct 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://www.codegirl007.com/blog/index.xml" rel="self" type="application/rss+xml"/><item><title>I built a code-taste linter for coding agents</title><link>https://www.codegirl007.com/blog/i-built-a-code-taste-linter-for-coding-agents/</link><pubDate>Sun, 18 Oct 2026 00:00:00 +0000</pubDate><guid>https://www.codegirl007.com/blog/i-built-a-code-taste-linter-for-coding-agents/</guid><description>&lt;p&gt;I like coding, and I still review the code my agents write. The frustrating part isn&amp;rsquo;t always that the code is wrong. Often it works, but it&amp;rsquo;s not how I would have written it.&lt;/p&gt;&#10;&lt;p&gt;I find it introduces an abstraction that doesn&amp;rsquo;t need to exist or spreads a simple operation across several helpers. After enough rounds of giving the same feedback, I started wondering if I could turn those preferences into rules.&lt;/p&gt;&#10;&lt;p&gt;That&amp;rsquo;s where Jevlint came from.&lt;/p&gt;&#10;&lt;h2 id="not-your-normal-linter"&gt;Not your normal linter&lt;/h2&gt;&#10;&lt;p&gt;Traditional linters are good at questions with deterministic answers. Code taste is different. Two implementations can be correct, and I can still prefer one over the other.&lt;/p&gt;&#10;&lt;p&gt;Jevlint lets me describe a preference in plain language and check code against it. The rule belongs to the repository, rather than living in a prompt I have to repeat every time I use an agent.&lt;/p&gt;&#10;&lt;p&gt;Here is the kind of thing I mean. One of my rules is &lt;a href="https://github.com/codegirl-007/jevlint/blob/86a3e9b4d998efaaa47a9177974b42ef5cd92bf5/jevlint.json#L113-L123"&gt;&lt;code&gt;wrapper-without-value&lt;/code&gt;&lt;/a&gt;. The fixture I use to test it is a function that only forwards to another function:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;GetUserPoints&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userID&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;fetchPoints&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;That compiles, and nothing is technically wrong with it. It&amp;rsquo;s just a layer that doesn&amp;rsquo;t earn its keep. The passing version of the same fixture adds a real guard, so the wrapper is now doing something:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;GetUserPoints&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userID&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;userID&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;fetchPoints&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;No AST rule is going to tell those two apart on taste. A plain-language rule can.&lt;/p&gt;&#10;&lt;h2 id="what-it-does"&gt;What it does&lt;/h2&gt;&#10;&lt;p&gt;Jevlint uses Tree-sitter to parse source files and extract functions, types, and other code units. It sends relevant units and applicable rules to a configured decision provider (currently support Jev, Cloudflare&amp;rsquo;s decisioning models, OpenAI decisioning models with more to come). It can also gather deterministic repository evidence, including direct callers, callees, related types, and imports.&lt;/p&gt;&#10;&lt;p&gt;It is conservative about resolution: when it can&amp;rsquo;t connect a relationship confidently, it omits it.&lt;/p&gt;&#10;&lt;p&gt;That evidence used to be a narrower &amp;ldquo;callee context&amp;rdquo; feature. I replaced it with a repository-wide index that is built once per run and resolves callees, callers, related types, and imports into evidence with provenance (path, lines, symbol, source). Being conservative mattered in practice. In review I found that a non-relative import from an unrelated module whose path happened to end in a local directory name (something like &lt;code&gt;github.com/other/project/internal/auth&lt;/code&gt;) could bind to my local code. Now a non-relative Go import only resolves if it is inside the repository&amp;rsquo;s own module, read from &lt;code&gt;go.mod&lt;/code&gt;. Wrong evidence is worse than no evidence, because the model will trust it.&lt;/p&gt;&#10;&lt;h2 id="using-jevlint-on-jevlint"&gt;Using Jevlint on Jevlint&lt;/h2&gt;&#10;&lt;p&gt;Jevlint&amp;rsquo;s own repository has a &lt;a href="https://github.com/codegirl-007/jevlint/blob/master/jevlint.json"&gt;&lt;code&gt;jevlint.json&lt;/code&gt;&lt;/a&gt; at the root, and it applies my rules to Jevlint&amp;rsquo;s own source. It currently lists 26 rules (config format is in the &lt;a href="https://github.com/codegirl-007/jevlint/blob/master/README.md#rules"&gt;README Rules section&lt;/a&gt;), things like &lt;a href="https://github.com/codegirl-007/jevlint/blob/86a3e9b4d998efaaa47a9177974b42ef5cd92bf5/jevlint.json#L91-L101"&gt;&lt;code&gt;unnecessary-abstraction&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://github.com/codegirl-007/jevlint/blob/86a3e9b4d998efaaa47a9177974b42ef5cd92bf5/jevlint.json#L102-L112"&gt;&lt;code&gt;speculative-generalization&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://github.com/codegirl-007/jevlint/blob/86a3e9b4d998efaaa47a9177974b42ef5cd92bf5/jevlint.json#L113-L123"&gt;&lt;code&gt;wrapper-without-value&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://github.com/codegirl-007/jevlint/blob/86a3e9b4d998efaaa47a9177974b42ef5cd92bf5/jevlint.json#L167-L177"&gt;&lt;code&gt;boolean-parameter&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://github.com/codegirl-007/jevlint/blob/86a3e9b4d998efaaa47a9177974b42ef5cd92bf5/jevlint.json#L232-L243"&gt;&lt;code&gt;swallowed-errors&lt;/code&gt;&lt;/a&gt;, and &lt;a href="https://github.com/codegirl-007/jevlint/blob/86a3e9b4d998efaaa47a9177974b42ef5cd92bf5/jevlint.json#L43-L50"&gt;&lt;code&gt;comment-quality&lt;/code&gt;&lt;/a&gt;, with &lt;code&gt;include: [&amp;quot;**/*&amp;quot;]&lt;/code&gt; and the example fixtures and Go tests excluded. The README tells contributors to run &lt;code&gt;go run ./cmd/jevlint check .&lt;/code&gt; from the repo root when working on Jevlint itself.&lt;/p&gt;&#10;&lt;p&gt;A rule like &lt;a href="https://github.com/codegirl-007/jevlint/blob/86a3e9b4d998efaaa47a9177974b42ef5cd92bf5/jevlint.json#L43-L50"&gt;&lt;code&gt;comment-quality&lt;/code&gt;&lt;/a&gt; is a good example of why I wanted this on my own code. It fails comments that just paraphrase the code, and passes ones that explain intent or constraints. Agents love writing the first kind.&lt;/p&gt;&#10;&lt;h2 id="testing-the-rules"&gt;Testing the rules&lt;/h2&gt;&#10;&lt;p&gt;I added evals because a rule can sound clear and still produce surprising results. Each fixture declares an expected pass or fail. The eval runner reports whether the result matches, and distinguishes inconclusive decisions from actual passes.&lt;/p&gt;&#10;&lt;p&gt;The repo ships its own eval file, &lt;a href="https://github.com/codegirl-007/jevlint/blob/master/jevlint-evals.json"&gt;&lt;code&gt;jevlint-evals.json&lt;/code&gt;&lt;/a&gt;, with 674 cases over the fixtures in &lt;code&gt;examples/rules&lt;/code&gt;. Each rule has good and bad fixtures in a dozen or so languages, so I can check that a rule like &lt;a href="https://github.com/codegirl-007/jevlint/blob/86a3e9b4d998efaaa47a9177974b42ef5cd92bf5/jevlint.json#L167-L177"&gt;&lt;code&gt;boolean-parameter&lt;/code&gt;&lt;/a&gt; behaves the same in Go, TypeScript, Python, Rust, and the rest. A case looks like this:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;rule&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;database-joins&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;file&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;examples/rules/database-joins/bad/example.go&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;expect&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;fail&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;My loop is to let an agent write code, run Jevlint, review the code myself, and update rules or fixtures when the feedback isn&amp;rsquo;t what I intended. Having evals allows me to create test cases that helps build my own confidences that my rules hold up to the test of time over a lot of code.&lt;/p&gt;&#10;&lt;h2 id="limitations"&gt;Limitations&lt;/h2&gt;&#10;&lt;p&gt;Jevlint isn&amp;rsquo;t a guarantee that code is good. The model can be wrong, a rule can be vague, and repository context can be incomplete. Confidence scores don&amp;rsquo;t replace human review. Provider latency and costs matter, too.&lt;/p&gt;&#10;&lt;p&gt;I want to catch the recurring review comments before I have to repeat them. The goal isn&amp;rsquo;t to replace my judgment; it&amp;rsquo;s to make my preferences easier to apply consistently so that when a coding agent uses jevlint, they get immediate feedback before my eyes ever look at the code.&lt;/p&gt;&#10;&lt;p&gt;Source: &lt;a href="https://github.com/codegirl-007/jevlint"&gt;https://github.com/codegirl-007/jevlint&lt;/a&gt;&lt;/p&gt;&#10;</description></item></channel></rss>