<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
    <title>loki.dev</title>
    <subtitle>The blog of Torsten Zielke, software architect and tech lead: hands-on series on modern Python tooling (uv, ruff, ty, prek), on Linux and Rust, and thoughts on AI-assisted software development.</subtitle>
    <link rel="self" type="application/atom+xml" href="https://loki.dev/en/atom.xml"/>
    <link rel="alternate" type="text/html" href="https://loki.dev"/>
    <generator uri="https://www.getzola.org/">Zola</generator>
    <updated>2026-11-13T13:00:00+00:00</updated>
    <id>https://loki.dev/en/atom.xml</id>
    <entry xml:lang="en">
        <title>The Merge Conflict</title>
        <published>2026-11-13T13:00:00+00:00</published>
        <updated>2026-11-13T13:00:00+00:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://loki.dev/en/comics/merge-conflict/"/>
        <id>https://loki.dev/en/comics/merge-conflict/</id>
        
        <content type="html" xml:base="https://loki.dev/en/comics/merge-conflict/">&lt;p&gt;Technically the robot is right: no branches, no conflicts. An agent takes &quot;force&quot; very literally, and destructive git commands are on my list of things it only gets to do after asking. Better still if two people never work in the same checkout. That&#39;s what I use &lt;a href=&quot;https://loki.dev/en/blog/advanced-git-worktree/&quot;&gt;git worktree&lt;/a&gt; for.&lt;/p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>The God Class</title>
        <published>2026-11-06T13:00:00+00:00</published>
        <updated>2026-11-06T13:00:00+00:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://loki.dev/en/comics/god-class/"/>
        <id>https://loki.dev/en/comics/god-class/</id>
        
        <content type="html" xml:base="https://loki.dev/en/comics/god-class/">&lt;p&gt;Agents like to build where code already exists. The class that already knows everything is the most convenient place for the next feature, and the one after that. Every single diff looks harmless. A few weeks later you notice one file is half the application. A linter with limits on class size and complexity catches that earlier than any review.&lt;/p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>The Friday Hotfix</title>
        <published>2026-10-30T13:00:00+00:00</published>
        <updated>2026-10-30T13:00:00+00:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://loki.dev/en/comics/friday-hotfix/"/>
        <id>https://loki.dev/en/comics/friday-hotfix/</id>
        
        <content type="html" xml:base="https://loki.dev/en/comics/friday-hotfix/">&lt;p&gt;Don&#39;t deploy on Friday afternoon. We knew that long before agents. What&#39;s new is that an agent with too many permissions manages in seconds what a human on a bad day needed half an hour for. That&#39;s why my agent never gets near production. Read access yes, &lt;code&gt;kill&lt;/code&gt; no.&lt;/p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>The Refactoring</title>
        <published>2026-10-23T13:00:00+00:00</published>
        <updated>2026-10-23T13:00:00+00:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://loki.dev/en/comics/refactoring/"/>
        <id>https://loki.dev/en/comics/refactoring/</id>
        
        <content type="html" xml:base="https://loki.dev/en/comics/refactoring/">&lt;p&gt;An agent optimizes for exactly what you tell it. Say &quot;more readable&quot; and you get more readable, at the cost of everything else if need be. What helps are tests that pin down the behavior before anyone starts cleaning up. How I set that up in my own toolchain is in &lt;a href=&quot;https://loki.dev/en/blog/modern-tooling-part-5-ai-harness/&quot;&gt;part 5 of the tooling series&lt;/a&gt;.&lt;/p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Do agents need a new Git branching model?</title>
        <published>2026-10-22T09:00:00+00:00</published>
        <updated>2026-10-22T09:00:00+00:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://loki.dev/en/blog/git-branching-fuer-agenten/"/>
        <id>https://loki.dev/en/blog/git-branching-fuer-agenten/</id>
        
        <content type="html" xml:base="https://loki.dev/en/blog/git-branching-fuer-agenten/">&lt;p&gt;&lt;em&gt;(Translation was done by AI. Sorry :/)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I like
&lt;a rel=&quot;external&quot; href=&quot;https://www.endoflineblog.com/oneflow-a-git-branching-model-and-workflow&quot;&gt;OneFlow&lt;/a&gt;.
Adam Ruka wrote it up in 2017 as a counter-proposal to GitFlow, and shortly
after it became what I recommend whenever someone asks me about Git branching
(well, sometimes also when nobody asks). One long-lived branch, short branches
around it, releases as tags. Done. So &lt;strong&gt;a lot&lt;/strong&gt; less complex than GitFlow.&lt;/p&gt;
&lt;p&gt;My day looks different from 2017, though. Often several agents run at the
same time, each in its own worktree (most people don&#39;t even notice), each on
its own branch with a &lt;code&gt;claude-&lt;/code&gt; prefix or similar. By the afternoon there are
branches in the repo I never created myself, and a few PRs that all want into
&lt;code&gt;main&lt;/code&gt; at once.&lt;/p&gt;
&lt;p&gt;Do we need a new model for this? Or does OneFlow do the job, just turned
faster? And which part of it gives the most benefit without me sinking a
weekend into tooling? Or even better: spending three weeks introducing a new
model, and half a year later everything changes again?&lt;/p&gt;
&lt;p&gt;I think the model itself can stay. The branches get smaller and live only a
few hours (compared to days before), and at the end a human (still?) has to
decide whether they get back onto the default branch.&lt;/p&gt;
&lt;h2 id=&quot;oneflow-in-60-seconds&quot;&gt;OneFlow in 60 seconds&lt;/h2&gt;
&lt;p&gt;Just a quick refresher. For details, feel free to click the OneFlow link
above.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;There is exactly one long-lived branch, usually &lt;code&gt;main&lt;/code&gt;. That is where the
history happens.&lt;/li&gt;
&lt;li&gt;Feature branches start from &lt;code&gt;main&lt;/code&gt; and get deleted after the merge.&lt;/li&gt;
&lt;li&gt;Releases are tags. Release branches are temporary and only exist when a
release needs stabilisation (QA, code freeze), then they go away again.&lt;/li&gt;
&lt;li&gt;Hotfixes branch off the latest release tag.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For features, Ruka recommends rebase plus fast-forward so the history stays
linear. He names two cases where OneFlow does not fit: projects that maintain
several incompatible versions in parallel, and continuous deployment, where
the release process looks completely different.&lt;/p&gt;
&lt;p&gt;Fun fact: Vincent Driessen, who invented GitFlow, sees it similarly by now.
Since 2020 there has been a note on top of
&lt;a rel=&quot;external&quot; href=&quot;https://nvie.com/posts/a-successful-git-branching-model/&quot;&gt;his article&lt;/a&gt;
saying that teams doing continuous delivery should pick something simpler.&lt;/p&gt;
&lt;h2 id=&quot;what-superintelligence-lol-d-changes&quot;&gt;What superintelligence (lol :D) changes&lt;/h2&gt;
&lt;h3 id=&quot;many-more-branches-much-shorter-ones&quot;&gt;Many more branches, much shorter ones&lt;/h3&gt;
&lt;p&gt;Agents work in worktrees. Claude Code does this on its own with
&lt;code&gt;claude --worktree &amp;lt;name&amp;gt;&lt;/code&gt;: new directory under &lt;code&gt;.claude/worktrees/&lt;/code&gt;, new
branch &lt;code&gt;worktree-&amp;lt;name&amp;gt;&lt;/code&gt;, branched from the default branch on the remote.
Subagents get their own with &lt;code&gt;isolation: worktree&lt;/code&gt;. I wrote about what
&lt;code&gt;git worktree&lt;/code&gt; is
&lt;a href=&quot;https://loki.dev/en/blog/advanced-git-worktree/&quot;&gt;back in 2021&lt;/a&gt;, when it was
still for humans.&lt;/p&gt;
&lt;p&gt;A branch now lives only as long as the one task it was created for. If the
agent gets lost, the branch is set up again in ten minutes, changes included,
and that is usually faster than debugging its mess.&lt;/p&gt;
&lt;p&gt;On
&lt;a rel=&quot;external&quot; href=&quot;https://trunkbaseddevelopment.com/short-lived-feature-branches/&quot;&gt;trunkbaseddevelopment.com&lt;/a&gt;,
Paul Hammant gives short-lived feature branches a couple of days at most,
including the time spent waiting for review. He now explicitly counts the
developer&#39;s own coding agents among those allowed to work on such a branch.&lt;/p&gt;
&lt;h3 id=&quot;more-conflicts-and-size-causes-the-most-trouble&quot;&gt;More conflicts, and size causes the most trouble&lt;/h3&gt;
&lt;p&gt;The best numbers I found come from
&lt;a rel=&quot;external&quot; href=&quot;https://arxiv.org/abs/2604.03551&quot;&gt;AgenticFlict&lt;/a&gt; (Ogenrwot and Businge,
AIware 2026). For this study they collected more than 142,000 PRs from coding
agents across more than 59,000 repos and test-merged about 107,000 of them.
27.7% had conflicts. Per agent it ranged from 15% (Copilot) to 32% (Codex).
Claude Code was at 26%, but with only 779 PRs, so the evidence is thin.&lt;/p&gt;
&lt;p&gt;I find the size data more interesting than the agent comparison, mostly
because we can learn something from it:&lt;/p&gt;
&lt;p&gt;PRs with about two changed lines conflicted around 10% of the time, at about
25 lines it was almost 30%. Above that, the rate levels off at around 32 to
33%.&lt;/p&gt;
&lt;p&gt;This lines up with the
&lt;a rel=&quot;external&quot; href=&quot;https://cloud.google.com/blog/products/ai-machine-learning/introducing-doras-inaugural-ai-capabilities-model&quot;&gt;AI Capabilities Model&lt;/a&gt;
from the 2025 DORA report. Of the seven capabilities that decide whether AI
helps or hurts a team, two are &quot;strong version control practices&quot; and
&quot;working in small batches&quot;. According to DORA, teams that commit often and
make use of rollbacks get more out of AI.&lt;/p&gt;
&lt;h3 id=&quot;the-dangerous-conflict-is-the-one-git-cannot-see&quot;&gt;The dangerous conflict is the one Git cannot see&lt;/h3&gt;
&lt;p&gt;Text conflicts are annoying, but at least Git reports them. Worse is when two
agents change different files, both branches are green, and &lt;code&gt;main&lt;/code&gt; is red
after the second merge anyway. Agent A renames a parameter, agent B still
calls the function with the old name.&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https://arxiv.org/abs/2609.25396&quot;&gt;&quot;Passes Alone, Fails Together&quot;&lt;/a&gt; (Xia, Wu,
Park, September 2026) looks at exactly this. In constructed tasks where two
agents work on the same Django helpers, 97% of runs broke. A single message
telling the second agent what the first one had already changed rescued 82%
of them.&lt;/p&gt;
&lt;p&gt;On real PR pairs mined from Django, though, the problem showed up in only one
of 834 runs, and the authors themselves warn against reading the 97% as an
everyday number. The tasks were built on purpose so that both agents poke at
the same helpers. In everyday work this is rare. When two agents touch the
same interface, it can happen easily, and then I would not let them run
blindly in parallel.&lt;/p&gt;
&lt;h3 id=&quot;merging-stays-manual-work&quot;&gt;Merging stays manual work&lt;/h3&gt;
&lt;p&gt;Starting agents is trivial by now, no &quot;proof&quot; required :D. Bringing the work
back together is still on me, though. The Claude Code docs describe worktrees
as isolation for file edits. What happens to the branch afterwards is my job.
Of course AI is a useful tool for that too, but real logic conflicts in
particular still have to be resolved by a human.&lt;/p&gt;
&lt;p&gt;With review, which I
&lt;a href=&quot;https://loki.dev/en/blog/ki-code-review/&quot;&gt;wrote about recently&lt;/a&gt;, it was
similar. Since the agents do the typing, reading is the bottleneck. Branches
play the same game one step later in the process, at the merge.&lt;/p&gt;
&lt;h2 id=&quot;and-scion&quot;&gt;And Scion?&lt;/h2&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https://github.com/GoogleCloudPlatform/scion&quot;&gt;Scion&lt;/a&gt; comes from Google. I had
filed it under Anthropic at first. It is an experimental orchestrator, open
source since April 2026, pre-1.0, and according to its README explicitly not
an officially supported Google product.&lt;/p&gt;
&lt;p&gt;The idea: every agent (Claude Code, Gemini CLI, Codex, OpenCode) runs in its
own container, with its own worktree and its own credentials. The agents
coordinate through the Scion CLI and message each other. Humans follow along
through a web UI or via Slack, Teams and friends.&lt;/p&gt;
&lt;p&gt;Mostly it showed me how little a worktree isolates. Ports, database, Docker
volumes and caches are still shared by every agent on my machine, only the
files are separate. That is why Scion puts each agent in a container with its
own credentials, and I like the second part in particular. An agent fixing
typos in the docs has no business holding a deploy token.&lt;/p&gt;
&lt;p&gt;How the work comes back together is something Scion leaves open too. I found
nothing in the README about branch names, merge strategy or PRs. So I have to
bring the branching model myself either way.&lt;/p&gt;
&lt;p&gt;Do I need Scion? Not right now. It pays off when many agents run for a long
time, on several machines and with different permissions. For a handful of
agents on my laptop, worktrees are enough.&lt;/p&gt;
&lt;h2 id=&quot;my-proposal-oneflow-turned-faster&quot;&gt;My proposal: OneFlow, turned faster&lt;/h2&gt;
&lt;figure class=&quot;diagram&quot;&gt;
&lt;svg viewBox=&quot;0 0 640 270&quot; width=&quot;100%&quot; role=&quot;img&quot; aria-labelledby=&quot;flow-title&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; font-family=&quot;ui-monospace, SFMono-Regular, Menlo, monospace&quot; fill=&quot;currentColor&quot;&gt;
&lt;title id=&quot;flow-title&quot;&gt;One long-lived main branch. Two agent branches run in parallel and come back via squash merge. A third one is discarded and restarted. Release as tag v1.4.0, hotfix from the tag as v1.4.1.&lt;/title&gt;
&lt;g fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot;&gt;
&lt;line x1=&quot;20&quot; y1=&quot;170&quot; x2=&quot;620&quot; y2=&quot;170&quot; stroke-width=&quot;4&quot;/&gt;
&lt;path d=&quot;M60 170 L90 115 L140 115 L165 170&quot;/&gt;
&lt;path d=&quot;M60 170 L100 60 L225 60 L255 170&quot;/&gt;
&lt;path d=&quot;M255 170 L280 115 L330 115&quot; stroke-dasharray=&quot;6 4&quot;/&gt;
&lt;path d=&quot;M345 170 L370 115 L420 115 L440 170&quot;/&gt;
&lt;path d=&quot;M440 170 L465 230 L515 230 L535 170&quot;/&gt;
&lt;line x1=&quot;324&quot; y1=&quot;109&quot; x2=&quot;336&quot; y2=&quot;121&quot;/&gt;
&lt;line x1=&quot;336&quot; y1=&quot;109&quot; x2=&quot;324&quot; y2=&quot;121&quot;/&gt;
&lt;/g&gt;
&lt;g&gt;
&lt;circle cx=&quot;60&quot; cy=&quot;170&quot; r=&quot;6&quot;/&gt;
&lt;circle cx=&quot;165&quot; cy=&quot;170&quot; r=&quot;6&quot;/&gt;
&lt;circle cx=&quot;255&quot; cy=&quot;170&quot; r=&quot;6&quot;/&gt;
&lt;circle cx=&quot;345&quot; cy=&quot;170&quot; r=&quot;6&quot;/&gt;
&lt;circle cx=&quot;440&quot; cy=&quot;170&quot; r=&quot;6&quot;/&gt;
&lt;circle cx=&quot;535&quot; cy=&quot;170&quot; r=&quot;6&quot;/&gt;
&lt;circle cx=&quot;600&quot; cy=&quot;170&quot; r=&quot;6&quot;/&gt;
&lt;circle cx=&quot;515&quot; cy=&quot;230&quot; r=&quot;5&quot;/&gt;
&lt;/g&gt;
&lt;g font-size=&quot;13&quot;&gt;
&lt;text x=&quot;95&quot; y=&quot;105&quot;&gt;PROJ-12&lt;/text&gt;
&lt;text x=&quot;120&quot; y=&quot;50&quot;&gt;PROJ-14&lt;/text&gt;
&lt;text x=&quot;270&quot; y=&quot;105&quot; opacity=&quot;0.7&quot;&gt;discarded&lt;/text&gt;
&lt;text x=&quot;375&quot; y=&quot;105&quot;&gt;PROJ-15&lt;/text&gt;
&lt;text x=&quot;555&quot; y=&quot;198&quot;&gt;main&lt;/text&gt;
&lt;text x=&quot;385&quot; y=&quot;198&quot;&gt;v1.4.0&lt;/text&gt;
&lt;text x=&quot;465&quot; y=&quot;258&quot;&gt;hotfix&lt;/text&gt;
&lt;text x=&quot;525&quot; y=&quot;248&quot;&gt;v1.4.1&lt;/text&gt;
&lt;/g&gt;
&lt;/svg&gt;
&lt;/figure&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;One long-lived branch, and it is always green.&lt;/strong&gt; Same as OneFlow. What is
new is how expensive a red &lt;code&gt;main&lt;/code&gt; has become. Every new agent branches off
the current &lt;code&gt;main&lt;/code&gt;, which is the default in Claude Code. If it is broken,
every agent starts from a broken state and burns tokens chasing bugs that
are not theirs.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;One task, one branch, one worktree, one PR.&lt;/strong&gt; The ticket number goes into
the branch name (&lt;code&gt;agent/PROJ-123-retry-logic&lt;/code&gt;), so three weeks later I still
know where it came from. Lifetime: hours, one working day at most. If an
agent heads in the wrong direction, I delete the branch and start again
with a better ticket.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cut it small.&lt;/strong&gt; Whatever does not fit into a small PR becomes a stack.
GitHub made
&lt;a rel=&quot;external&quot; href=&quot;https://github.blog/changelog/2026-10-06-stacked-pull-requests-generally-available/&quot;&gt;stacked PRs&lt;/a&gt;
generally available on 6 October 2026, &lt;code&gt;gh stack&lt;/code&gt; supports worktrees, and a
stack goes through the merge queue as one group. Unfinished work goes
behind a feature flag. There are no long-lived feature branches.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Squash merge.&lt;/strong&gt; This is where I deviate from Ruka. He cleans up with an
interactive rebase before merging. With five commits from a human that
works. Nobody cleans up the dozens of WIP commits an agent leaves behind by
hand, and squash does the same thing automatically: one PR, one commit on
&lt;code&gt;main&lt;/code&gt;, one &lt;code&gt;git revert&lt;/code&gt; if it goes wrong. That also settles Ruka&#39;s own
complaint about rebase (reverting a feature means reverting several
commits). The commit message gets the ticket number and an &lt;code&gt;Assisted-by&lt;/code&gt;
trailer.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Merge queue as soon as several agents open PRs in parallel.&lt;/strong&gt; The queue
tests each PR against &lt;code&gt;main&lt;/code&gt; plus every PR ahead of it. That is exactly
what catches the conflicts Git cannot see. On GitHub it is a branch rule
(depending on your plan), GitLab calls it a merge train.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;If two tasks share an interface, they run one after the other.&lt;/strong&gt; Or the
second agent gets told what the first one changed. In the Django study
above, exactly that one sentence rescued 82% of the broken runs.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Agents do not merge and do not tag.&lt;/strong&gt; Branch protection on &lt;code&gt;main&lt;/code&gt;,
changes only via PR, a human merges. Releases stay as in OneFlow: tag on
&lt;code&gt;main&lt;/code&gt;, release branch only when needed, hotfix from the tag.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;what-gives-the-most-for-the-least-effort&quot;&gt;What gives the most for the least effort?&lt;/h2&gt;
&lt;p&gt;My estimate, roughly in the order I would introduce things:&lt;/p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Measure&lt;/th&gt;&lt;th&gt;Effort&lt;/th&gt;&lt;th&gt;Benefit&lt;/th&gt;&lt;th&gt;When&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Protected &lt;code&gt;main&lt;/code&gt;, required CI&lt;/td&gt;&lt;td&gt;about an hour&lt;/td&gt;&lt;td&gt;very high&lt;/td&gt;&lt;td&gt;right away&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Worktree per agent&lt;/td&gt;&lt;td&gt;one flag&lt;/td&gt;&lt;td&gt;high&lt;/td&gt;&lt;td&gt;right away&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Small PRs, squash merge&lt;/td&gt;&lt;td&gt;one repo setting, plus discipline&lt;/td&gt;&lt;td&gt;high&lt;/td&gt;&lt;td&gt;right away&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Ticket in branch name, &lt;code&gt;Assisted-by&lt;/code&gt; in commit&lt;/td&gt;&lt;td&gt;none&lt;/td&gt;&lt;td&gt;medium&lt;/td&gt;&lt;td&gt;right away&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Merge queue&lt;/td&gt;&lt;td&gt;one setting, merges take longer&lt;/td&gt;&lt;td&gt;high&lt;/td&gt;&lt;td&gt;several parallel agent PRs per day&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Stacked PRs&lt;/td&gt;&lt;td&gt;a new habit&lt;/td&gt;&lt;td&gt;medium&lt;/td&gt;&lt;td&gt;features that do not fit into one PR&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Container per agent (Scion and friends)&lt;/td&gt;&lt;td&gt;days&lt;/td&gt;&lt;td&gt;high for credentials&lt;/td&gt;&lt;td&gt;many agents, several machines, different permissions&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;The first four rows together cost an afternoon. After that I would measure
for two weeks before touching anything further down the table.&lt;/p&gt;
&lt;h2 id=&quot;what-i-leave-out-on-purpose&quot;&gt;What I leave out on purpose&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;The &lt;code&gt;develop&lt;/code&gt; branch.&lt;/strong&gt; Ruka&#39;s variant with &lt;code&gt;develop&lt;/code&gt; as the working
branch and &lt;code&gt;master&lt;/code&gt; as a pointer to the latest release is meant for open
source projects. For agents, a second long-lived branch is mostly a second
place to branch off from by mistake.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Release branches by default.&lt;/strong&gt; For web apps with continuous deployment, a
tag is enough.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;jj.&lt;/strong&gt; &lt;a rel=&quot;external&quot; href=&quot;https://github.com/jj-vcs/jj&quot;&gt;Jujutsu&lt;/a&gt; treats conflicts as normal
data you can commit and resolve later, and has an undo for every operation.
Sounds ideal for agents. I have not tried it seriously yet, and as long as
Git with worktrees does the job, switching is too expensive for me.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Agent sessions in the repo.&lt;/strong&gt; &lt;a rel=&quot;external&quot; href=&quot;https://docs.entire.io/core-concepts&quot;&gt;Entire&lt;/a&gt;,
the company of former GitHub CEO Thomas Dohmke, attaches agent sessions to
commits via an &lt;code&gt;Entire-Checkpoint&lt;/code&gt; trailer and keeps the metadata on a
separate branch. Interesting when traceability is mandatory. For now, a link
to the session in the PR description is enough for me.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Do agents need a new branching model? I don&#39;t think so. I am sticking with
OneFlow: &lt;code&gt;main&lt;/code&gt; is the branch that matters, and everything else can go once
its task is done. With agents that just happens a lot more often.&lt;/p&gt;
&lt;p&gt;New branches cost me almost nothing today. Getting each one of them back into
&lt;code&gt;main&lt;/code&gt; still takes as much time as it used to, and there are many more of
them. That is why almost all the rules above are about merging, and about a
human clicking the merge button at the end.&lt;/p&gt;
&lt;h2 id=&quot;sources-selection&quot;&gt;Sources (selection)&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Adam Ruka,
&lt;a rel=&quot;external&quot; href=&quot;https://www.endoflineblog.com/oneflow-a-git-branching-model-and-workflow&quot;&gt;&lt;em&gt;OneFlow: a Git branching model and workflow&lt;/em&gt;&lt;/a&gt;,
2017&lt;/li&gt;
&lt;li&gt;Vincent Driessen,
&lt;a rel=&quot;external&quot; href=&quot;https://nvie.com/posts/a-successful-git-branching-model/&quot;&gt;&lt;em&gt;A successful Git branching model&lt;/em&gt;&lt;/a&gt;,
with a note from 2020&lt;/li&gt;
&lt;li&gt;Paul Hammant,
&lt;a rel=&quot;external&quot; href=&quot;https://trunkbaseddevelopment.com/short-lived-feature-branches/&quot;&gt;&lt;em&gt;Short-Lived Feature Branches&lt;/em&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Ogenrwot, Businge, &lt;em&gt;AgenticFlict&lt;/em&gt;,
&lt;a rel=&quot;external&quot; href=&quot;https://arxiv.org/abs/2604.03551&quot;&gt;arXiv:2604.03551&lt;/a&gt;, AIware 2026&lt;/li&gt;
&lt;li&gt;Xia, Wu, Park, &lt;em&gt;Passes Alone, Fails Together&lt;/em&gt;,
&lt;a rel=&quot;external&quot; href=&quot;https://arxiv.org/abs/2609.25396&quot;&gt;arXiv:2609.25396&lt;/a&gt;, 2026&lt;/li&gt;
&lt;li&gt;DORA, &lt;em&gt;State of AI-assisted Software Development 2025&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Claude Code Docs,
&lt;a rel=&quot;external&quot; href=&quot;https://code.claude.com/docs/en/worktrees&quot;&gt;&lt;em&gt;Run parallel sessions with worktrees&lt;/em&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://github.com/GoogleCloudPlatform/scion&quot;&gt;GoogleCloudPlatform/scion&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;GitHub Changelog,
&lt;a rel=&quot;external&quot; href=&quot;https://github.blog/changelog/2026-10-06-stacked-pull-requests-generally-available/&quot;&gt;&lt;em&gt;Stacked pull requests generally available&lt;/em&gt;&lt;/a&gt;,
6 October 2026&lt;/li&gt;
&lt;/ul&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Speech recognition in 17 MB, entirely in the browser</title>
        <published>2026-10-21T17:00:00+00:00</published>
        <updated>2026-10-21T17:00:00+00:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://loki.dev/en/blog/whistle-im-browser/"/>
        <id>https://loki.dev/en/blog/whistle-im-browser/</id>
        
        <content type="html" xml:base="https://loki.dev/en/blog/whistle-im-browser/">&lt;p&gt;&lt;em&gt;(Translation was done by AI. Sorry :/)&lt;/em&gt;&lt;/p&gt;
&lt;!-- TODO Torsten: add your own experience to the intro. --&gt;
&lt;p&gt;Until now, speech recognition mostly meant one of two things: send the audio
to a server, or run a local model that weighs a few hundred megabytes and
really wants a GPU. On October 2, Cactus Compute released
&lt;a rel=&quot;external&quot; href=&quot;https://cactuscompute.com/blog/whistle&quot;&gt;Whistle&lt;/a&gt;. The whole model is a single
16.9 MB file, and it runs on the CPU.&lt;/p&gt;
&lt;p&gt;At 17 MB the obvious question was: does this work in the browser, with no
server and without the audio ever leaving the tab? It does. The PoC took one
evening and lives here: &lt;a href=&quot;/lab/whistle/&quot;&gt;loki.dev/lab/whistle&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/whistle-im-browser-comic.jpeg&quot; alt=&quot;Three-panel comic. Developer: &amp;quot;You&amp;#39;re 17 MB, no server, no GPU, and the audio never leaves this tab?&amp;quot; Tiny robot: &amp;quot;Works in airplane mode too!&amp;quot; Developer into the mic: &amp;quot;I build my blog with Zola and deploy it to Uberspace.&amp;quot; On screen: &amp;quot;I build my block with solar and deploy it to outer space.&amp;quot; Robot: &amp;quot;Done in half a second!&amp;quot; Developer: &amp;quot;...Close enough for 17 MB.&amp;quot;&quot; /&gt;&lt;/p&gt;
&lt;h2 id=&quot;what-whistle-is&quot;&gt;What Whistle is&lt;/h2&gt;
&lt;p&gt;Whistle runs on the same engine as
&lt;a rel=&quot;external&quot; href=&quot;https://huggingface.co/Cactus-Compute/needle3&quot;&gt;Needle&lt;/a&gt;, the small
tool-calling model Cactus put out in May. Both share the &lt;code&gt;.cact&lt;/code&gt; container
format, the quantisation (Cactus Quants, 2 to 4 bit) and the SIMD kernels.
There are no dependencies and no GPU is needed. The license is Apache 2.0.&lt;/p&gt;
&lt;p&gt;According to the &lt;a rel=&quot;external&quot; href=&quot;https://huggingface.co/Cactus-Compute/whistle&quot;&gt;model card&lt;/a&gt;,
Whistle understands seven languages: English, German, French, Spanish,
Italian, Dutch and Polish. If you don&#39;t name one, it detects it. It handles up
to 30 seconds in one pass and splits longer recordings into windows. For every
word it returns start, end and a probability, taken from the decoder&#39;s
attention. A keyword list lets you push names and jargon.&lt;/p&gt;
&lt;p&gt;In live mode it transcribes while you talk and only commits the words two
passes agree on. If you don&#39;t need a transcript at all, you get the encoder
output as an embedding, one row per 80 ms, for something like search over
audio.&lt;/p&gt;
&lt;p&gt;Cactus lists the limits themselves. Silence and steady noise give you an empty
transcript instead of an invented sentence. Music, on the other hand, can
still come out as text. If you leave a microphone open all the time, put a
voice activity detector or a wake word in front of it.&lt;/p&gt;
&lt;p&gt;The &lt;a rel=&quot;external&quot; href=&quot;https://news.ycombinator.com/item?id=50008427&quot;&gt;Hacker News thread&lt;/a&gt; has
reports you won&#39;t find in the model card. The most striking one: a user ran it
on a TV episode and got nothing but &quot;Thank you.&quot; in several places, once for
60 seconds of dialogue in a row. Polish is apparently only usable if you speak
loud, clear and slow, and with a Flemish accent Whistle kept guessing between
Dutch and German.&lt;/p&gt;
&lt;h2 id=&quot;how-the-poc-works&quot;&gt;How the PoC works&lt;/h2&gt;
&lt;p&gt;Cactus also ships the engine as a WebAssembly build: &lt;code&gt;needle.js&lt;/code&gt; plus
&lt;code&gt;needle.wasm&lt;/code&gt;, just under 1 MB together, 0.4 MB over the wire with gzip. Add &lt;code&gt;whistle.cact&lt;/code&gt; to that. All of
it is hosted on loki.dev itself, so there are no requests to Hugging Face or a
CDN.&lt;/p&gt;
&lt;p&gt;Everything heavy runs in a Web Worker so the page doesn&#39;t freeze. The worker
fetches engine and model as an &lt;code&gt;ArrayBuffer&lt;/code&gt;, copies the model into
WebAssembly memory and loads it:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-1 z-i&quot;&gt;const&lt;/span&gt;&lt;span&gt; [wasm, cact]&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; = await&lt;/span&gt;&lt;span class=&quot;z-1 z-i&quot;&gt; Promise&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-5&quot;&gt;all&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  [&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt;&amp;#39;vendor/needle.wasm&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt; &amp;#39;vendor/whistle.cact&amp;#39;&lt;/span&gt;&lt;span&gt;].&lt;/span&gt;&lt;span class=&quot;z-5&quot;&gt;map&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span class=&quot;z-14 z-i&quot;&gt;u&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-1 z-i&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-5&quot;&gt; fetch&lt;/span&gt;&lt;span&gt;(u).&lt;/span&gt;&lt;span class=&quot;z-5&quot;&gt;then&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span class=&quot;z-14 z-i&quot;&gt;r&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-1 z-i&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;span&gt; r.&lt;/span&gt;&lt;span class=&quot;z-5&quot;&gt;arrayBuffer&lt;/span&gt;&lt;span&gt;())),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-4&quot;&gt;// The build ignores locateFile, so hand it the wasm bytes directly.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;M&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; = await&lt;/span&gt;&lt;span class=&quot;z-5&quot;&gt; createNeedle&lt;/span&gt;&lt;span&gt;({ wasmBinary: wasm });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-1 z-i&quot;&gt;const&lt;/span&gt;&lt;span&gt; p&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; =&lt;/span&gt;&lt;span&gt; M.&lt;/span&gt;&lt;span class=&quot;z-5&quot;&gt;_malloc&lt;/span&gt;&lt;span&gt;(cact.byteLength);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;M.HEAPU8.&lt;/span&gt;&lt;span class=&quot;z-5&quot;&gt;set&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;z-5&quot;&gt; Uint8Array&lt;/span&gt;&lt;span&gt;(cact), p);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-13&quot;&gt;if&lt;/span&gt;&lt;span&gt; (M.&lt;/span&gt;&lt;span class=&quot;z-5&quot;&gt;_needle_load&lt;/span&gt;&lt;span&gt;(p,&lt;/span&gt;&lt;span class=&quot;z-5&quot;&gt; BigInt&lt;/span&gt;&lt;span&gt;(cact.byteLength))&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; &amp;lt;&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt; 0&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; throw new&lt;/span&gt;&lt;span class=&quot;z-5&quot;&gt; Error&lt;/span&gt;&lt;span&gt;(M.&lt;/span&gt;&lt;span class=&quot;z-5&quot;&gt;UTF8ToString&lt;/span&gt;&lt;span&gt;(M.&lt;/span&gt;&lt;span class=&quot;z-5&quot;&gt;_needle_last_error&lt;/span&gt;&lt;span&gt;()));&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;wasmBinary&lt;/code&gt; is needed because the build ignores &lt;code&gt;locateFile&lt;/code&gt; and would
otherwise look for the &lt;code&gt;.wasm&lt;/code&gt; file in the wrong place.&lt;/p&gt;
&lt;p&gt;After that, transcribing is a single C call through &lt;code&gt;ccall&lt;/code&gt;. The samples go in
as a pointer, language and keywords as strings, and the result comes back as
JSON in a buffer reserved up front:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-1 z-i&quot;&gt;const&lt;/span&gt;&lt;span&gt; out&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-5&quot;&gt; call&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt;&amp;#39;needle_transcribe&amp;#39;&lt;/span&gt;&lt;span&gt;, [&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt;...&lt;/span&gt;&lt;span&gt;T,&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt; &amp;#39;number&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt; &amp;#39;number&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt; &amp;#39;number&amp;#39;&lt;/span&gt;&lt;span&gt;], [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  p, m.pcm.length, m.language&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; ||&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt; null&lt;/span&gt;&lt;span&gt;, m.keywords&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; ||&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt; null&lt;/span&gt;&lt;span&gt;, m.words&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; ?&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt; 1&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; :&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt; 0&lt;/span&gt;&lt;span&gt;, outPtr, OUT_CAP,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;]);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;call&lt;/code&gt; is a small wrapper around &lt;code&gt;M.ccall&lt;/code&gt; that checks the return code and
reads the buffer with &lt;code&gt;JSON.parse&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Whistle wants 16 kHz mono as a &lt;code&gt;Float32Array&lt;/code&gt;. For the microphone, the browser
handles that: &lt;code&gt;new AudioContext({ sampleRate: 16000 })&lt;/code&gt; resamples the mic
signal by itself. For files I first decode with &lt;code&gt;decodeAudioData&lt;/code&gt; and then
render the result through an &lt;code&gt;OfflineAudioContext&lt;/code&gt; with one channel at
16 kHz. That downmixes to mono and resamples in one step. Videos work too, as
long as the browser can decode the audio track.&lt;/p&gt;
&lt;p&gt;In live mode there is only ever one pass in flight. As soon as a second of
audio has piled up and the worker is free, the buffer goes to
&lt;code&gt;needle_stream_transcribe_process&lt;/code&gt;. Whatever arrives in the meantime goes into
the next pass. What comes back is the committed words plus a tentative tail,
which I show dimmed. On stop, &lt;code&gt;needle_stream_transcribe_stop&lt;/code&gt; collects the
rest. Words the model is less than 60 % sure about get a dotted underline, and
the timestamps sit in the tooltip.&lt;/p&gt;
&lt;p&gt;A service worker caches the page, the worker, the engine and the model. After
the first visit the page works in airplane mode. The audio never leaves the
tab, which you can check in the network tab of the DevTools.&lt;/p&gt;
&lt;!-- TODO Torsten: confirm your own measurements (load time, RTF) and turn
them into a paragraph. Suggestion from a run in the Claude browser pane on
your Mac (Chromium, WASM, one worker), not yet checked by you:
- Model load: 86 ms from localhost, 655 ms from loki.dev (warm HTTP)
- Test clip: 6.8 s of German macOS TTS (voice Anna): &quot;Ich baue meinen Blog mit
  Zola und deploye ihn per rsync auf Uberspace. Die Spracherkennung läuft
  komplett im Browser.&quot;
- No keywords: 502 ms, RTF 0.073, TTFT 125 ms, 196 tok/s. Output: &quot;Ich baue
  meinen Block mit Solar- und Deckläuer-Interrünk auf Oberspasse, die
  Sprachörkennung läuft komplett im Brauser.&quot;
- Keywords Zola/Uberspace/rsync: 449 ms, RTF 0.066. Output: &quot;...Block mit
  Solar- und Deckläuer in Parünk auf Uberspace, die Spracherkennung läuft
  komplett im Brauser.&quot; Biasing fixed Uberspace and Spracherkennung, not Zola
  or rsync.
- Live streaming gives the same text as the clip.
- Caveats: synthetic voice, English tech words inside German, and 196 tok/s in
  the browser is far below the 1,319 tok/s Cactus reports for the native
  engine on an M4 Pro.
--&gt;
&lt;h2 id=&quot;the-comparison&quot;&gt;The comparison&lt;/h2&gt;
&lt;p&gt;Up front: all numbers come from the vendors. Everyone measures with their own
setup, partly with different normalizers and scoring rules. The tables are
good enough for orders of magnitude, not for a ranking down to the decimal.&lt;/p&gt;
&lt;p&gt;First, the fact sheets:&lt;/p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Model&lt;/th&gt;&lt;th&gt;Download&lt;/th&gt;&lt;th&gt;Languages&lt;/th&gt;&lt;th&gt;License&lt;/th&gt;&lt;th&gt;Runs on&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;a rel=&quot;external&quot; href=&quot;https://huggingface.co/Cactus-Compute/whistle&quot;&gt;Whistle&lt;/a&gt;&lt;/td&gt;&lt;td&gt;16.9 MB&lt;/td&gt;&lt;td&gt;7, incl. German&lt;/td&gt;&lt;td&gt;Apache 2.0&lt;/td&gt;&lt;td&gt;CPU, WebAssembly too&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;a rel=&quot;external&quot; href=&quot;https://huggingface.co/UsefulSensors/moonshine-tiny&quot;&gt;Moonshine tiny&lt;/a&gt;&lt;/td&gt;&lt;td&gt;41.9 MB¹&lt;/td&gt;&lt;td&gt;English only&lt;/td&gt;&lt;td&gt;MIT&lt;/td&gt;&lt;td&gt;CPU&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;a rel=&quot;external&quot; href=&quot;https://github.com/openai/whisper&quot;&gt;Whisper base&lt;/a&gt;&lt;/td&gt;&lt;td&gt;145 MB¹&lt;/td&gt;&lt;td&gt;99&lt;/td&gt;&lt;td&gt;MIT&lt;/td&gt;&lt;td&gt;CPU, GPU&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;a rel=&quot;external&quot; href=&quot;https://huggingface.co/nvidia/parakeet-tdt-0.6b-v3&quot;&gt;Parakeet TDT 0.6B v3&lt;/a&gt;&lt;/td&gt;&lt;td&gt;714 MB (GGUF q8_0)&lt;/td&gt;&lt;td&gt;25 European&lt;/td&gt;&lt;td&gt;CC-BY-4.0&lt;/td&gt;&lt;td&gt;NVIDIA GPU with NeMo, locally via NeMo-Speech.cpp&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;a rel=&quot;external&quot; href=&quot;https://huggingface.co/nvidia/nemotron-speech-streaming-en-0.6b&quot;&gt;Nemotron Speech Streaming en 0.6B&lt;/a&gt;&lt;/td&gt;&lt;td&gt;700 MB (GGUF q8_0)&lt;/td&gt;&lt;td&gt;English only&lt;/td&gt;&lt;td&gt;NVIDIA Open Model License&lt;/td&gt;&lt;td&gt;same as Parakeet&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;a rel=&quot;external&quot; href=&quot;https://huggingface.co/nvidia/nemotron-3.5-asr-streaming-0.6b&quot;&gt;Nemotron 3.5 ASR Streaming 0.6B&lt;/a&gt;&lt;/td&gt;&lt;td&gt;742 MB (GGUF q8_0)&lt;/td&gt;&lt;td&gt;40 locales, incl. de-DE&lt;/td&gt;&lt;td&gt;OpenMDW-1.1&lt;/td&gt;&lt;td&gt;same as Parakeet, plus an ONNX export (int4)&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;¹ Size from Whistle&#39;s benchmark chart. The NVIDIA models have 600 million
parameters each and weigh about 2.5 GB as &lt;code&gt;.nemo&lt;/code&gt; files.&lt;/p&gt;
&lt;p&gt;Then the word error rates (WER in %, lower is better):&lt;/p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Model&lt;/th&gt;&lt;th style=&quot;text-align: right&quot;&gt;LibriSpeech clean&lt;/th&gt;&lt;th style=&quot;text-align: right&quot;&gt;LibriSpeech other&lt;/th&gt;&lt;th style=&quot;text-align: right&quot;&gt;TED-LIUM&lt;/th&gt;&lt;th style=&quot;text-align: right&quot;&gt;Earnings-22&lt;/th&gt;&lt;th style=&quot;text-align: right&quot;&gt;AMI&lt;/th&gt;&lt;th style=&quot;text-align: right&quot;&gt;FLEURS de&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Whistle&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;4.31&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;10.49&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;7.61&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;19.01&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;26.07&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;n/a²&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Moonshine tiny&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;4.52&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;11.71&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;5.64&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;21.25&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;22.77&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;en only&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Whisper base&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;4.9&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;11.0&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;5.0&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;n/a&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;21.5³&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;17.9&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Parakeet TDT 0.6B v3&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;1.93&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;3.59&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;2.75&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;11.42&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;11.31&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;5.04&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Nemotron en (1.12 s)&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;2.32&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;4.84&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;3.50&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;12.52&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;11.73&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;en only&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Nemotron 3.5 (1.12 s)&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;n/a&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;n/a&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;n/a&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;n/a&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;n/a&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;8.31&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;² For FLEURS, Whistle only gives the average over all seven languages: 21.4.
³ AMI-IHM. Whistle counts empty references on AMI, the way the Open ASR
Leaderboard does.&lt;/p&gt;
&lt;p&gt;Sources: the linked model cards, for Whisper base the
&lt;a rel=&quot;external&quot; href=&quot;https://arxiv.org/abs/2212.04356&quot;&gt;Whisper paper&lt;/a&gt; (tables 9 and 13), for
Moonshine the &lt;a rel=&quot;external&quot; href=&quot;https://arxiv.org/abs/2410.15608&quot;&gt;Moonshine paper&lt;/a&gt; (table 3).
Whistle&#39;s own chart compares against exactly these values.&lt;/p&gt;
&lt;p&gt;Whistle is roughly on par with Whisper base and Moonshine tiny. Slightly
ahead on LibriSpeech, behind on TED-LIUM and AMI. In Whistle&#39;s chart, the
Whisper base bar is higher on FLEURS and a bit lower on MLS (Multilingual
LibriSpeech), but those bars carry no numbers. It gets interesting with size:
17 MB against 145 MB for Whisper base. Then there&#39;s speed. On an M4 Pro,
Cactus measures 11.1 ms to the first token and 1,319 tokens per second.
Whisper base gets 73.2 ms and 266 tokens/s, Moonshine tiny 22.8 ms and 262
tokens/s. Vendor numbers again.&lt;/p&gt;
&lt;p&gt;On accuracy, Parakeet and Whistle are far apart. If I average Parakeet&#39;s own
FLEURS results over Whistle&#39;s seven languages, I get about 5.2, while Whistle
is at 21.4. Even with a generous allowance for different measuring methods,
that leaves roughly a factor of four. Then again, Parakeet is more than 40
times the size.&lt;/p&gt;
&lt;p&gt;Nemotron is designed for streaming: a cache-aware FastConformer with an RNN-T
decoder that only computes new audio chunks and keeps the encoder context
cached. You pick the chunk size at runtime, from 80 ms to 1.12 s. For the
English version, the average WER goes from 6.93 to 8.43 when you drop from
1.12 s to 80 ms. For Nemotron 3.5 on German (FLEURS) it&#39;s 8.31 and 9.81.&lt;/p&gt;
&lt;p&gt;There are two more options for the browser. Chrome&#39;s Web Speech API can now
recognize speech on the device, but only if you set
&lt;a rel=&quot;external&quot; href=&quot;https://developer.mozilla.org/docs/Web/API/SpeechRecognition/processLocally&quot;&gt;&lt;code&gt;processLocally&lt;/code&gt;&lt;/a&gt;
to &lt;code&gt;true&lt;/code&gt;. Without it, the browser decides for itself, and the audio may well
end up on a server.&lt;/p&gt;
&lt;p&gt;For Parakeet there&#39;s also a
&lt;a rel=&quot;external&quot; href=&quot;https://huggingface.co/striimit/parakeet-tdt-0.6b-v3-webgpu&quot;&gt;community ONNX port&lt;/a&gt;
that runs on WebGPU through onnxruntime-web. The encoder is fp16 and 1.24 GB,
because the int8 variant gives wrong results under WebGPU. The position
embeddings cover about 35 seconds, so you chunk longer recordings yourself,
and you write the TDT decoding yourself too. Doable, but a different calibre
than 17 MB.&lt;/p&gt;
&lt;h2 id=&quot;what-i-d-use-for-what&quot;&gt;What I&#39;d use for what&lt;/h2&gt;
&lt;p&gt;For anything that has to run in the browser or on small hardware without a
GPU, I&#39;d take Whistle: dictation, short voice commands, a search bar with a
mic button, subtitles for your own short clips. A 17 MB download is fine
there, 700 MB is not. I&#39;d only run an always-open microphone with a VAD in
front, though.&lt;/p&gt;
&lt;p&gt;If there&#39;s a server with an NVIDIA GPU and accuracy matters, Parakeet is the
better choice. For long meetings or interviews whose transcript someone will
actually read, 26 % WER on AMI is just too much. For German and the other
European languages, Parakeet is the most accurate of the open models I&#39;ve
looked at.&lt;/p&gt;
&lt;p&gt;Nemotron pays off once latency matters, say for a voice agent that starts
answering while you&#39;re still talking. Then the en version for English and 3.5
for the rest.&lt;/p&gt;
&lt;p&gt;And Whisper stays the pick when you need a language none of the others
support. Nobody else here does 99 languages, and the ecosystem around it is
huge.&lt;/p&gt;
&lt;p&gt;As for the PoC, I&#39;m sticking with Whistle. The page loads a little over 17 MB once and then
works on a plane.&lt;/p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Who actually reviews the code the AI writes?</title>
        <published>2026-10-20T09:00:00+00:00</published>
        <updated>2026-10-20T14:00:00+00:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://loki.dev/en/blog/ki-code-review/"/>
        <id>https://loki.dev/en/blog/ki-code-review/</id>
        
        <content type="html" xml:base="https://loki.dev/en/blog/ki-code-review/">&lt;p&gt;&lt;em&gt;(Translation was done by AI. Sorry :/)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;At work, agents now write a decent part of our new code. That is fast. Very
fast. And we do it even though (or precisely because?) we are experienced
developers, some of us with 15+ years in the industry.&lt;/p&gt;
&lt;p&gt;The problem comes afterwards, in the pull request. Either it is slow, or it is
not thorough enough, or the understanding is simply missing. Lately I have
been sitting in front of 900-line diffs that nobody on the team typed
themselves, asking myself: who actually understands this right now? Honestly:
not me anymore. And since I am not exactly the passive team member type, I
needed a solution :D&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/ki-code-review-comic.jpeg&quot; alt=&quot;Three-panel comic. Developer: &amp;quot;Claude, who wrote this 900-line pull request?&amp;quot; Robot: &amp;quot;I did!&amp;quot; Robot: &amp;quot;I also wrote the tests, ran the tests, and reviewed it. Everything is green!&amp;quot; Developer: &amp;quot;Great. So who actually understands it?&amp;quot; Robot: &amp;quot;...Define &amp;#39;understands&amp;#39;, pal.&amp;quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;But it is 2026. So I had three AIs (Claude, ChatGPT, Gemini Argon) research
the topic independently, added my own experience, merged everything and
looked up the contested numbers in the original sources. Brave new world...&lt;/p&gt;
&lt;p&gt;Here is the short version.&lt;/p&gt;
&lt;h2 id=&quot;the-work-has-moved&quot;&gt;The work has moved&lt;/h2&gt;
&lt;p&gt;We write more code but barely ship more. The DORA 2025 report (about 5,000
respondents) links AI adoption to higher throughput and higher instability at
the same time. DORA calls the extra checking effort the &quot;verification tax&quot;.
Faros AI telemetry (10,000+ developers, vendor data, so take it with a grain
of salt) shows the mechanism: 98% more merged PRs, PRs 154% larger, review
time up 91%, no gain at company level.&lt;/p&gt;
&lt;p&gt;Keep in mind that the DORA report is from 2025, and in the last six months
alone we have made huge jumps with Opus 5, Fable &amp;amp; co. Still, my guess is that
the gap has only widened since then. The agents write faster, and reading
still happens at the same speed.&lt;/p&gt;
&lt;p&gt;The bottleneck has moved from typing to reading.&lt;/p&gt;
&lt;p&gt;I am clearly not alone with this. There is an
&lt;a rel=&quot;external&quot; href=&quot;https://news.ycombinator.com/item?id=50151036&quot;&gt;Ask HN with hundreds of comments&lt;/a&gt; right now on how to deal with
shipping AI-written code you do not understand. It started with a Meta
engineer whose own code now feels like a black box to them. A senior engineer
with ten years of experience writes there that nobody can seriously review
thousands of changed lines a week, and sums up their daily routine like this:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;I just approve PRs, because I have to.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That is exactly where I do not want to end up. By the way, the top answer in
the thread is pretty much my station 1 further down: the human does the
design, the agent only fills in the gaps.&lt;/p&gt;
&lt;h2 id=&quot;three-things-that-worry-me&quot;&gt;Three things that worry me&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;1. We lose our understanding of our own code.&lt;/strong&gt; In 1985 Peter Naur wrote in
&lt;a rel=&quot;external&quot; href=&quot;https://pages.cs.wisc.edu/~remzi/Naur.pdf&quot;&gt;Programming as Theory Building&lt;/a&gt;
that a program is a theory held by the team: why it is built this way and how
it reacts to change. That theory used to form as a side effect of writing. If
an agent plans, writes and tests, the side effect is gone. In a randomized
trial by Anthropic (52 developers, January 2026) the AI group scored 17% lower
on comprehension. The interesting part is the split inside that group: people
who asked the AI to explain things scored 65 to 86%. People who just delegated
scored 24 to 39%. There is even a name for it now:
&lt;a rel=&quot;external&quot; href=&quot;https://arxiv.org/abs/2607.10856&quot;&gt;&quot;comprehension debt&quot;&lt;/a&gt;, meaning code the
team accepted before anyone could explain it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. Security.&lt;/strong&gt; Veracode tested over 100 models: without security guidance
in the prompt, 45% of the samples contained a known flaw, and that did not
improve across model generations. Then there is a lovely new attack: almost
20% of the packages AIs suggest do not exist, and many of the invented names
come back on every run. Attackers simply register them in advance
(&quot;slopsquatting&quot;). And in April 2026 researchers showed that prompt injection
in PR titles and comments makes several AI review bots leak CI secrets.&lt;/p&gt;
&lt;p&gt;You can watch what this looks like at scale in public, by the way. The
&lt;a rel=&quot;external&quot; href=&quot;https://github.com/anthropics/claude-code/issues&quot;&gt;Claude Code issue tracker&lt;/a&gt;
(CLI and desktop app), a project written to a large part by its own agent,
currently has about 14,700 open issues against roughly 86,000 closed ones. So
a good one in seven issues is still open.
&lt;a rel=&quot;external&quot; href=&quot;https://github.com/curl/curl/issues&quot;&gt;curl&lt;/a&gt;, maintained by humans the
old-school way, sits at 15 open against about 5,600 closed. It is not a clean
comparison (user base, age and release cadence are completely different), but
it matches my gut feeling pretty well.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. Responsibility.&lt;/strong&gt; AI helps with review, just not very well. On
&lt;a rel=&quot;external&quot; href=&quot;https://arxiv.org/abs/2509.01494&quot;&gt;SWR-Bench&lt;/a&gt; (1,000 real PRs) the best tool
reached an F1 score of 19%. Important: the paper is from September 2025, so it
tested models that are already two generations old. The number is surely
better by now, but still far from a reliable gate. In a
&lt;a rel=&quot;external&quot; href=&quot;https://arxiv.org/abs/2508.18771&quot;&gt;study across 178 repos&lt;/a&gt;, 1 to 19% of AI
comments led to a code change, against 60% for human comments.&lt;/p&gt;
&lt;p&gt;The Linux kernel settled this cleanly in 2026: an AI may not add
&lt;code&gt;Signed-off-by&lt;/code&gt;, and the human who submits is responsible for all of it. That
is also the only approach that works. Neither OpenAI nor Anthropic take
responsibility for the code their models generate. Whoever checks it in owns
it.&lt;/p&gt;
&lt;h2 id=&quot;what-i-take-from-it&quot;&gt;What I take from it&lt;/h2&gt;
&lt;p&gt;Keep the pull request, give it a new job. To me a PR is now a package a human
can check: what is supposed to happen, how do I see that it works, where is
the risk, and who owns it.&lt;/p&gt;
&lt;p&gt;And a PR has to create understanding again, the way writing used to as a side
effect. That only happens if the human author goes through and edits the AI&#39;s
work before anyone else looks at it. Passing the PR along just moves the
understanding to the reviewer, who has just as little context.&lt;/p&gt;
&lt;p&gt;Five stations for that:&lt;/p&gt;
&lt;figure class=&quot;diagram&quot;&gt;
&lt;svg viewBox=&quot;0 0 640 400&quot; width=&quot;100%&quot; role=&quot;img&quot; aria-labelledby=&quot;gates-title-en&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; font-family=&quot;ui-monospace, SFMono-Regular, Menlo, monospace&quot; fill=&quot;currentColor&quot;&gt;
&lt;title id=&quot;gates-title-en&quot;&gt;Five stations of a PR: intent, author check, automated gates, AI review, human reviews and merges&lt;/title&gt;
&lt;defs&gt;&lt;marker id=&quot;arr&quot; viewBox=&quot;0 0 10 10&quot; refX=&quot;5&quot; refY=&quot;5&quot; markerWidth=&quot;6&quot; markerHeight=&quot;6&quot; orient=&quot;auto&quot;&gt;&lt;path d=&quot;M0 0 L10 5 L0 10 z&quot;/&gt;&lt;/marker&gt;&lt;/defs&gt;
&lt;g fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot;&gt;
&lt;rect x=&quot;20&quot; y=&quot;10&quot; width=&quot;420&quot; height=&quot;56&quot;/&gt;
&lt;rect x=&quot;20&quot; y=&quot;90&quot; width=&quot;420&quot; height=&quot;56&quot;/&gt;
&lt;rect x=&quot;20&quot; y=&quot;170&quot; width=&quot;420&quot; height=&quot;56&quot; stroke-dasharray=&quot;6 4&quot;/&gt;
&lt;rect x=&quot;20&quot; y=&quot;250&quot; width=&quot;420&quot; height=&quot;56&quot; stroke-dasharray=&quot;6 4&quot;/&gt;
&lt;rect x=&quot;20&quot; y=&quot;330&quot; width=&quot;420&quot; height=&quot;56&quot; stroke-width=&quot;4&quot;/&gt;
&lt;line x1=&quot;230&quot; y1=&quot;66&quot; x2=&quot;230&quot; y2=&quot;86&quot; marker-end=&quot;url(#arr)&quot;/&gt;
&lt;line x1=&quot;230&quot; y1=&quot;146&quot; x2=&quot;230&quot; y2=&quot;166&quot; marker-end=&quot;url(#arr)&quot;/&gt;
&lt;line x1=&quot;230&quot; y1=&quot;226&quot; x2=&quot;230&quot; y2=&quot;246&quot; marker-end=&quot;url(#arr)&quot;/&gt;
&lt;line x1=&quot;230&quot; y1=&quot;306&quot; x2=&quot;230&quot; y2=&quot;326&quot; marker-end=&quot;url(#arr)&quot;/&gt;
&lt;/g&gt;
&lt;g font-size=&quot;17&quot; font-weight=&quot;bold&quot;&gt;
&lt;text x=&quot;36&quot; y=&quot;36&quot;&gt;1 Intent&lt;/text&gt;
&lt;text x=&quot;36&quot; y=&quot;116&quot;&gt;2 Author check&lt;/text&gt;
&lt;text x=&quot;36&quot; y=&quot;196&quot;&gt;3 Automated gates&lt;/text&gt;
&lt;text x=&quot;36&quot; y=&quot;276&quot;&gt;4 AI review&lt;/text&gt;
&lt;text x=&quot;36&quot; y=&quot;356&quot;&gt;5 Human merges&lt;/text&gt;
&lt;/g&gt;
&lt;g font-size=&quot;13&quot; opacity=&quot;0.7&quot;&gt;
&lt;text x=&quot;36&quot; y=&quot;56&quot;&gt;ticket with acceptance criteria&lt;/text&gt;
&lt;text x=&quot;36&quot; y=&quot;136&quot;&gt;read the full diff, explain it yourself&lt;/text&gt;
&lt;text x=&quot;36&quot; y=&quot;216&quot;&gt;tests, types, SAST, secrets, packages&lt;/text&gt;
&lt;text x=&quot;36&quot; y=&quot;296&quot;&gt;sandboxed, comments only&lt;/text&gt;
&lt;text x=&quot;36&quot; y=&quot;376&quot;&gt;depth follows risk&lt;/text&gt;
&lt;/g&gt;
&lt;g font-size=&quot;13&quot;&gt;
&lt;text x=&quot;460&quot; y=&quot;44&quot;&gt;human&lt;/text&gt;
&lt;text x=&quot;460&quot; y=&quot;124&quot;&gt;human&lt;/text&gt;
&lt;text x=&quot;460&quot; y=&quot;204&quot;&gt;machine&lt;/text&gt;
&lt;text x=&quot;460&quot; y=&quot;284&quot;&gt;machine&lt;/text&gt;
&lt;text x=&quot;460&quot; y=&quot;364&quot; font-weight=&quot;bold&quot;&gt;named human&lt;/text&gt;
&lt;/g&gt;
&lt;/svg&gt;
&lt;/figure&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Intent first.&lt;/strong&gt; Before an agent starts, there is a ticket with acceptance
criteria, invariants and what is explicitly &lt;em&gt;out of scope&lt;/em&gt;. Otherwise an AI
ends up checking the code against itself.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Author check.&lt;/strong&gt; Read the full diff, rework what does not fit. Write the
PR description in your own words. Mark AI use with an &lt;code&gt;Assisted-by&lt;/code&gt;
trailer.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Automated gates.&lt;/strong&gt; Tests, types, linters, SAST, secret scanning,
dependency checks (does the package even exist, how old is it).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AI review.&lt;/strong&gt; Runs in a sandbox without secrets, writes comments, cannot
approve anything.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Human reviews and merges.&lt;/strong&gt; How deep depends on the risk. Docs and
formatting: one reviewer is enough. Auth, crypto, personal data, CI/CD:
code owner or security review, no exceptions.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Plus a few rules I mean quite seriously:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If you cannot explain the essential logic of your change, you get the PR
back. No matter who wrote it.&lt;/li&gt;
&lt;li&gt;An approval means &quot;I could maintain this&quot;. It does not mean &quot;CI was green&quot;,
and it certainly does not mean &quot;the AI liked it&quot;.&lt;/li&gt;
&lt;li&gt;PRs stay small. Google calls about 100 lines reasonable and 1,000 too much.
I start with a soft limit of around 400 lines.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;garbage-in-garbage-out&quot;&gt;Garbage in, garbage out&lt;/h2&gt;
&lt;p&gt;An agent works only as well as the ticket it gets. That was never different
for humans. What is new is that most models do not notice on their own when
information is missing. If they are allowed to ask clarifying questions,
performance goes up by as much as 74% according to
&lt;a rel=&quot;external&quot; href=&quot;https://arxiv.org/abs/2502.13069&quot;&gt;Ambig-SWE&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I do not write tickets myself, but this is how I want them: written for two
readers. The colleague who joins next year, and an agent that knows nothing
outside the repo.&lt;/p&gt;
&lt;p&gt;In practice: name modules and files, point to the existing implementation to
follow, put quality goals in numbers (&quot;under 16 ms&quot;, not &quot;fast&quot;) and say how
to verify it.&lt;/p&gt;
&lt;p&gt;And this one matters: decisions go into the ticket or an ADR when they are
made. Chat logs and agent sessions disappear at some point, and the reasoning
with them.&lt;/p&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;I can already guarantee that this will not fly in a post-mortem: &quot;The AI
wrote it&quot;. &quot;Another AI reviewed it&quot; will not work either.&lt;/p&gt;
&lt;p&gt;For me that means: the tools can write as much as they like. A human has to
understand it and sign it.&lt;/p&gt;
&lt;p&gt;I am in the middle of proposing this approach to my team, and hopefully we
will try it over the next few weeks. With numbers before and after: review time, PR size,
defects after merge and whether a second engineer can explain a critical
change. Once there are results, I will write about them.&lt;/p&gt;
&lt;h2 id=&quot;sources-selection&quot;&gt;Sources (selection)&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;DORA, &lt;em&gt;State of AI-assisted Software Development 2025&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Anthropic, &lt;em&gt;How AI assistance impacts the formation of coding skills&lt;/em&gt;, January 2026&lt;/li&gt;
&lt;li&gt;Comprehension debt study, &lt;a rel=&quot;external&quot; href=&quot;https://arxiv.org/abs/2607.10856&quot;&gt;arXiv:2607.10856&lt;/a&gt;, 2026&lt;/li&gt;
&lt;li&gt;Spracklen et al., &lt;em&gt;We Have a Package for You!&lt;/em&gt;, USENIX Security 2025&lt;/li&gt;
&lt;li&gt;Zeng et al., SWR-Bench, &lt;a rel=&quot;external&quot; href=&quot;https://arxiv.org/abs/2509.01494&quot;&gt;arXiv:2509.01494&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Sun et al., &lt;em&gt;Does AI Code Review Lead to Code Changes?&lt;/em&gt;,
&lt;a rel=&quot;external&quot; href=&quot;https://arxiv.org/abs/2508.18771&quot;&gt;arXiv:2508.18771&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Vijayvargiya et al., Ambig-SWE, &lt;a rel=&quot;external&quot; href=&quot;https://arxiv.org/abs/2502.13069&quot;&gt;arXiv:2502.13069&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Naur, &lt;a rel=&quot;external&quot; href=&quot;https://pages.cs.wisc.edu/~remzi/Naur.pdf&quot;&gt;&lt;em&gt;Programming as Theory Building&lt;/em&gt;&lt;/a&gt;, 1985&lt;/li&gt;
&lt;li&gt;Linux kernel, &lt;code&gt;Documentation/process/coding-assistants.rst&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>What Is My Purpose?</title>
        <published>2026-10-16T13:00:00+00:00</published>
        <updated>2026-10-16T13:00:00+00:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://loki.dev/en/comics/purpose/"/>
        <id>https://loki.dev/en/comics/purpose/</id>
        
        <content type="html" xml:base="https://loki.dev/en/comics/purpose/">&lt;p&gt;Since agents type most of the code, a good chunk of my day is reviews. Not a complaint, review was always the part that really mattered. Still funny when the colleague who was supposed to take work off your plate has the same existential crisis you had.&lt;/p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Creative Limitations</title>
        <published>2026-10-09T13:00:00+00:00</published>
        <updated>2026-10-09T13:00:00+00:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://loki.dev/en/comics/creative-limitations/"/>
        <id>https://loki.dev/en/comics/creative-limitations/</id>
        
        <content type="html" xml:base="https://loki.dev/en/comics/creative-limitations/">&lt;p&gt;The first strip of the series, and right away a bit meta. Yes, the strips here are AI-generated, and yes, that&#39;s exactly what people are fighting about right now. The robot still has a point: the tired developer at his desk joking about tech debt was a genre of its own long before image generators.&lt;/p&gt;
&lt;p&gt;From now on I&#39;d like to put out one of these every Friday ;).&lt;/p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Brooks&#39;s Law, Now With Agents</title>
        <published>2026-08-06T09:00:00+00:00</published>
        <updated>2026-08-08T13:59:23+00:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://loki.dev/en/blog/brooks-law/"/>
        <id>https://loki.dev/en/blog/brooks-law/</id>
        
        <content type="html" xml:base="https://loki.dev/en/blog/brooks-law/">&lt;p&gt;&lt;em&gt;(Translation was done by AI. Sorry :/)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;This post is not going to be particularly useful, but I need to get the topic
off my chest:
I have seen the following pattern in several projects, and it always runs the
same way.&lt;/p&gt;
&lt;p&gt;Months pass without anyone committing to a date. Then a date shows up somewhere
in a slide/mail/meeting, because it was promised to the outside world, and
suddenly a half-finished state has to be done in six weeks. In the meeting after
that comes the suggestion from the C-level that I have learned to expect:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;... more resources.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The odd part is that the suggestion comes even more reliably the shorter the
remaining time is. With six months left, people discuss scope. With six weeks
left, two extra developers from another project. Fred Brooks wrote down what
happens then, in 1975:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;Adding manpower to a late software project makes it later.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That is Brooks&#39;s Law, from &lt;em&gt;The Mythical Man-Month&lt;/em&gt;. Brooks had the material
first hand back then: he managed the development of OS/360 at IBM. With
thousands of people involved, every date was missed anyway. The problem he
identified is the unit itself. Cost scales with people times months, progress
explicitly does not.&lt;/p&gt;
&lt;h2 id=&quot;why-short-deadlines-make-it-worse&quot;&gt;Why short deadlines make it worse&lt;/h2&gt;
&lt;p&gt;Ramp-up eats exactly the window that is left. Anyone joining has to be taught by
the people who already are the bottleneck. With six weeks to go, that is the
entire six weeks.&lt;/p&gt;
&lt;p&gt;Then there is coordination. With n people there are &lt;code&gt;n(n-1)/2&lt;/code&gt; possible
channels, four people have six, eight people have twenty-eight. Every one of
them is a chance for two different assumptions about the same interface, and
those only surface during integration week.&lt;/p&gt;
&lt;p&gt;Most expensive of all, much of the work is sequential. Until the schema is migrated, nobody
can rebuild the endpoint on top of it. A second developer then waits exactly as
long as the first one, only on payroll.&lt;/p&gt;
&lt;h2 id=&quot;what-would-have-helped&quot;&gt;What would have helped&lt;/h2&gt;
&lt;p&gt;Brooks&#39;s own answer is uncomfortable and still the only one:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Cut scope or move the date.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In the projects that did turn out fine, there was an honest list at the start of
what really had to work on the date. The rest shipped one release later, and
nobody outside the team noticed.&lt;/p&gt;
&lt;p&gt;And that is exactly the option that always gets ruled out. Possibly even for
good reasons (usually something along the lines of &quot;time2market beats quality&quot;),
but that does not change the fact that certain things are incompatible with
reality.&lt;/p&gt;
&lt;h2 id=&quot;and-with-agents&quot;&gt;And with agents?&lt;/h2&gt;
&lt;p&gt;Since I started running several Claude instances in parallel, the question comes
up again, this time without payroll. One of the three causes does fall
away: ramp-up costs almost nothing, the context lives in &lt;code&gt;AGENTS.md&lt;/code&gt; (or
&lt;code&gt;CLAUDE.md&lt;/code&gt; or &lt;code&gt;GEMINI.md&lt;/code&gt;) and gets reused per agent.
The sequence stays, the dependency graph does not care who sits in front of it.
And coordination comes back as duplicated work: two agents refactoring the same
helper, each of them plausible.&lt;/p&gt;
&lt;p&gt;Plus a bottleneck Brooks did not have. I am the only one allowed to accept a
result. Three agents in parallel help me, at ten I am just reading diffs and
deciding worse and worse. What does scale is the deterministic part of review:
linters, type checking, tests and pre-commit hooks judge as fast for ten agents
as for one. Exactly the argument from
&lt;a href=&quot;https://loki.dev/en/blog/modern-tooling-part-5-ai-harness/&quot;&gt;part 5 of the tooling
series&lt;/a&gt; (yes, that is
shameless self-promotion :D).&lt;/p&gt;
&lt;p&gt;After 51 years, one of the three causes has fallen away and a fourth has been
added. For that meeting with six weeks left and the suggestion from above, the
arithmetic comes out the same as in 1975.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;(Most of the numbers and examples are adapted and based on old experience, and
have nothing to do with any current events.)&lt;/em&gt;&lt;/p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Modern Tooling Part 5: The toolchain as a corset for AI agents</title>
        <published>2026-07-26T12:00:00+00:00</published>
        <updated>2026-07-26T12:00:00+00:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://loki.dev/en/blog/modern-tooling-part-5-ai-harness/"/>
        <id>https://loki.dev/en/blog/modern-tooling-part-5-ai-harness/</id>
        
        <content type="html" xml:base="https://loki.dev/en/blog/modern-tooling-part-5-ai-harness/">&lt;p&gt;In &lt;a href=&quot;https://loki.dev/en/blog/modern-tooling-part-1-uv/&quot;&gt;Part 1&lt;/a&gt; I wrote about two
upheavals: the Python ecosystem is moving to Rust-based tools, and more and more
code is written by AI. The four parts before this one worked through the first
upheaval. This bonus part adds the second, because the two fit together better
than it first looks.&lt;/p&gt;
&lt;p&gt;In short: the same &lt;code&gt;uv&lt;/code&gt;, &lt;code&gt;ruff&lt;/code&gt;, &lt;code&gt;ty&lt;/code&gt; and &lt;code&gt;prek&lt;/code&gt; checks that make my work easier
are also the corset that makes an AI agent productive and (more) trustworthy.&lt;/p&gt;
&lt;h3 id=&quot;why-an-agent-needs-guardrails&quot;&gt;Why an agent needs guardrails&lt;/h3&gt;
&lt;p&gt;An AI agent writes plausible-looking code in seconds. Often it&#39;s still
wrong: it invents APIs, misses &lt;code&gt;None&lt;/code&gt;/&lt;code&gt;null&lt;/code&gt; cases, ignores the
project&#39;s architecture boundaries, or just pulls in a new dependency. Review all
of that by hand and the speed gain is gone immediately.&lt;/p&gt;
&lt;p&gt;So I give it an environment where mistakes show
up instantly and deterministically. I&#39;d built the toolchain for exactly that
anyway (and used it this way long before AI).&lt;/p&gt;
&lt;h3 id=&quot;the-same-checks-now-as-a-feedback-loop-for-the-agent&quot;&gt;The same checks, now as a feedback loop for the agent&lt;/h3&gt;
&lt;p&gt;I don&#39;t need a new setup for the agent. The checks from parts 2 to 4 are already
a machine-readable, deterministic truth about the code:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;uv run ruff check --fix .&lt;/code&gt; cleans up and reports what it can&#39;t fix itself&lt;/li&gt;
&lt;li&gt;&lt;code&gt;uv run ty check&lt;/code&gt; catches type and &lt;code&gt;None&lt;/code&gt; errors before they become runtime errors&lt;/li&gt;
&lt;li&gt;&lt;code&gt;uv run bandit&lt;/code&gt; and &lt;code&gt;import-linter&lt;/code&gt; guard security and architecture&lt;/li&gt;
&lt;li&gt;&lt;code&gt;uv run pytest&lt;/code&gt; tells me whether the code still does what it should&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The agent gets these commands (for me in an &lt;code&gt;AGENTS.md&lt;/code&gt;, with a &lt;code&gt;CLAUDE.md&lt;/code&gt;/&lt;code&gt;GEMINI.md&lt;/code&gt;
pointing to it) and works in exactly the loop I use as a human: write, check,
follow the errors, fix, repeat. The only difference is that it runs the loop far
more often and faster.&lt;/p&gt;
&lt;h3 id=&quot;why-speed-becomes-a-prerequisite&quot;&gt;Why speed becomes a prerequisite&lt;/h3&gt;
&lt;p&gt;In the earlier parts, speed was &quot;just&quot; comfort. With an agent it becomes a
prerequisite. A check that takes 30 seconds kills an agent&#39;s loop the same way it
kills mine, except the agent might want to check twenty times a minute. Only
because &lt;code&gt;ruff&lt;/code&gt; and &lt;code&gt;ty&lt;/code&gt; answer in the millisecond range can the agent iterate
tightly. With slow checks it waits minutes after every step or, worse, skips
them entirely.&lt;/p&gt;
&lt;h3 id=&quot;the-corset-rules-the-agent-can-t-get-around&quot;&gt;The corset: rules the agent can&#39;t get around&lt;/h3&gt;
&lt;p&gt;My &lt;code&gt;select = [&quot;ALL&quot;]&lt;/code&gt; strategy from &lt;a href=&quot;https://loki.dev/en/blog/modern-tooling-part-2-ruff/&quot;&gt;Part 2&lt;/a&gt;,
the strict type discipline from &lt;a href=&quot;https://loki.dev/en/blog/modern-tooling-part-3-ty/&quot;&gt;Part 3&lt;/a&gt;,
and the architecture rules from &lt;a href=&quot;https://loki.dev/en/blog/modern-tooling-part-4-prek/&quot;&gt;Part 4&lt;/a&gt;
are more valuable for an agent than for me: they fix the shape of the project.
The agent is free to be productive, but only within that shape.&lt;/p&gt;
&lt;p&gt;There&#39;s no negotiating with it. The pre-commit hook blocks bad code before it reaches
the history, and CI catches the rest (including the &lt;code&gt;--no-verify&lt;/code&gt; of an
over-eager agent). The agent can argue as convincingly as it likes, the red
check stays red.&lt;/p&gt;
&lt;h3 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;The two upheavals from Part 1 belong together. A fast, strict, deterministic toolchain is the precondition for AI-assisted
development not ending in a pile of plausible but broken diffs.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;strong&gt;The series:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://loki.dev/en/blog/modern-tooling-part-1-uv/&quot;&gt;Part 1: uv&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://loki.dev/en/blog/modern-tooling-part-2-ruff/&quot;&gt;Part 2: ruff&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://loki.dev/en/blog/modern-tooling-part-3-ty/&quot;&gt;Part 3: ty&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://loki.dev/en/blog/modern-tooling-part-4-prek/&quot;&gt;Part 4: prek&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Part 5: a corset for AI agents (bonus, this part)&lt;/li&gt;
&lt;/ul&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Modern Tooling Part 4: High-performance Git hooks with prek</title>
        <published>2026-07-20T21:00:00+00:00</published>
        <updated>2026-07-20T21:00:00+00:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://loki.dev/en/blog/modern-tooling-part-4-prek/"/>
        <id>https://loki.dev/en/blog/modern-tooling-part-4-prek/</id>
        
        <content type="html" xml:base="https://loki.dev/en/blog/modern-tooling-part-4-prek/">&lt;p&gt;Over the course of this series I&#39;ve rebuilt my Python stack with &lt;code&gt;uv&lt;/code&gt;
(&lt;a href=&quot;https://loki.dev/en/blog/modern-tooling-part-1-uv/&quot;&gt;Part 1&lt;/a&gt;), &lt;code&gt;ruff&lt;/code&gt;
(&lt;a href=&quot;https://loki.dev/en/blog/modern-tooling-part-2-ruff/&quot;&gt;Part 2&lt;/a&gt;) and &lt;code&gt;ty&lt;/code&gt;
(&lt;a href=&quot;https://loki.dev/en/blog/modern-tooling-part-3-ty/&quot;&gt;Part 3&lt;/a&gt;). In &lt;strong&gt;Part 4&lt;/strong&gt; I
bring it all together: the checks should run automatically, before code even
reaches the repository. Nobody feels like running this stack of tools by hand
all the time.&lt;/p&gt;
&lt;h3 id=&quot;git-hooks-as-a-quality-gate&quot;&gt;Git hooks as a quality gate&lt;/h3&gt;
&lt;p&gt;Every check that only fails in CI is a delay and pulls me out of my tasks:
write, commit, push, wait five minutes, red pipeline, fix, &quot;Fix linting&quot; commit,
push again. A pre-commit hook moves exactly those same checks to the earliest
possible point: errors get fixed &lt;em&gt;before&lt;/em&gt; they land in the history, and CI turns
from the thing that finds errors into a pure safety net.&lt;/p&gt;
&lt;h3 id=&quot;the-bottleneck-with-classic-pre-commit&quot;&gt;The bottleneck with classic pre-commit&lt;/h3&gt;
&lt;p&gt;The &lt;a rel=&quot;external&quot; href=&quot;https://pre-commit.com&quot;&gt;&lt;code&gt;pre-commit&lt;/code&gt;&lt;/a&gt; framework has been a standard for
this for years. Tools like lefthook or husky are great too. But prek is fast
and, thanks to its compatibility with pre-commit, my tool of choice. Because
pre-commit has a few downsides: it&#39;s a Python package itself, isolates every
hook in its own virtual environment, and runs hooks sequentially. The first
&lt;code&gt;git commit&lt;/code&gt; after a fresh clone can take minutes while environments are built.&lt;/p&gt;
&lt;h3 id=&quot;prek&quot;&gt;prek&lt;/h3&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https://github.com/j178/prek&quot;&gt;&lt;code&gt;prek&lt;/code&gt;&lt;/a&gt; is a reimplementation of the pre-commit
framework in Rust that reads &lt;strong&gt;the same &lt;code&gt;.pre-commit-config.yaml&lt;/code&gt;&lt;/strong&gt;, just
with smarter caching and without the virtualenv overhead:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-4&quot;&gt;# Installation (a single static binary, no Python needed)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-5&quot;&gt;uv&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt; tool install prek&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-4&quot;&gt;# alternatively via brew/pacman/apt/etc.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-4&quot;&gt;# Enable the git hook in the repository (once per clone)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-5&quot;&gt;prek&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt; install&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-4&quot;&gt;# Run hooks manually&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-5&quot;&gt;prek&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt; run&lt;/span&gt;&lt;span class=&quot;z-4&quot;&gt;                # against staged files&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-5&quot;&gt;prek&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt; run&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt; --all-files&lt;/span&gt;&lt;span class=&quot;z-4&quot;&gt;    # against the whole codebase&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-5&quot;&gt;prek&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt; run gitleaks&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt; --all-files&lt;/span&gt;&lt;span class=&quot;z-4&quot;&gt;  # a single hook specifically&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&quot;my-hook-setup-in-detail&quot;&gt;My hook setup in detail&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;.pre-commit-config.yaml&lt;/code&gt; of my template chains together everything I built
in the last three parts. I split the hooks into two
kinds:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Remote hooks&lt;/strong&gt; come from foreign repositories: the basics like YAML/TOML
validation, trailing whitespace, and the secret scanner
&lt;a rel=&quot;external&quot; href=&quot;https://github.com/gitleaks/gitleaks&quot;&gt;gitleaks&lt;/a&gt;:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;-&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; repo&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt; https://github.com/pre-commit/pre-commit-hooks&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-13&quot;&gt;  rev&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt; v6.0.0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-13&quot;&gt;  hooks&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  -&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; id&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt; check-yaml&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  -&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; id&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt; check-toml&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  -&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; id&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt; check-merge-conflict&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  -&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; id&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt; trailing-whitespace&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  -&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; id&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt; end-of-file-fixer&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;-&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; repo&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt; https://github.com/gitleaks/gitleaks&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-13&quot;&gt;  rev&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt; v8.30.1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-13&quot;&gt;  hooks&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  -&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; id&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt; gitleaks&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Local hooks&lt;/strong&gt;, on the other hand, run via &lt;code&gt;uv run&lt;/code&gt; in my project environment.
That way ruff, ty and bandit check with exactly the versions pinned in &lt;code&gt;uv.lock&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;-&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; repo&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt; local&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-13&quot;&gt;  hooks&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  -&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; id&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt; ruff-format&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-13&quot;&gt;    name&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt; ruff format&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-13&quot;&gt;    entry&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt; uv run ruff format&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-13&quot;&gt;    language&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt; system&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-13&quot;&gt;    types&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt;python&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  -&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; id&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt; ruff-check&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-13&quot;&gt;    name&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt; ruff check&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-13&quot;&gt;    entry&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt; uv run ruff check --fix&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-13&quot;&gt;    language&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt; system&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-13&quot;&gt;    types&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt;python&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  -&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; id&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt; ty&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-13&quot;&gt;    name&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt; ty-check&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-13&quot;&gt;    entry&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt; uv run ty check --verbose --output-format=full .&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-13&quot;&gt;    language&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt; system&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-13&quot;&gt;    types&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt;python&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-13&quot;&gt;    pass_filenames&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt; false&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  -&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; id&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt; bandit&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-13&quot;&gt;    name&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt; bandit&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-13&quot;&gt;    entry&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt; uv run bandit -r src/app -c pyproject.toml&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-13&quot;&gt;    language&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt; system&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-13&quot;&gt;    types&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt;python&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-13&quot;&gt;    pass_filenames&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt; false&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On top of that there&#39;s &lt;a rel=&quot;external&quot; href=&quot;https://import-linter.readthedocs.io&quot;&gt;import-linter&lt;/a&gt;,
which guards my architecture: it fails as soon as someone imports infrastructure
from the domain layer. That turns the architecture rules into an executable
check.&lt;/p&gt;
&lt;h3 id=&quot;one-commit-the-whole-quality-gate&quot;&gt;One commit, the whole quality gate&lt;/h3&gt;
&lt;p&gt;With that, every &lt;code&gt;git commit&lt;/code&gt; automatically runs: formatting, linting with
auto-fix, type checking, security scan (bandit), secret scan (gitleaks) and the
architecture check. Thanks to prek largely in parallel and within a few seconds,
fast enough that nobody is tempted to switch it off.&lt;/p&gt;
&lt;p&gt;For emergencies there&#39;s still the back door:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-5&quot;&gt;git&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt; commit&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt; --no-verify&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But that&#39;s exactly what CI is the safety net for (well, that and the cases where
someone forgot to run &lt;code&gt;prek install&lt;/code&gt;): there the same checks run again
(&lt;code&gt;ruff format --check&lt;/code&gt;, &lt;code&gt;ruff check&lt;/code&gt;, &lt;code&gt;ty check&lt;/code&gt;, &lt;code&gt;bandit&lt;/code&gt;, &lt;code&gt;lint-imports&lt;/code&gt;,
&lt;code&gt;pytest&lt;/code&gt;), just as the last line of defence.&lt;/p&gt;
&lt;h3 id=&quot;series-conclusion&quot;&gt;Series conclusion&lt;/h3&gt;
&lt;p&gt;These four tools, &lt;code&gt;uv&lt;/code&gt;, &lt;code&gt;ruff&lt;/code&gt;, &lt;code&gt;ty&lt;/code&gt; and &lt;code&gt;prek&lt;/code&gt;, each replace a slow, fragmented
part of the Python toolchain with a fast Rust binary, while staying compatible
with what they replace (&lt;code&gt;pyproject.toml&lt;/code&gt;, Black formatting,
&lt;code&gt;.pre-commit-config.yaml&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;And if I&#39;m honest, they&#39;re usually the first tools of their kind that fit: a
codebase that was already perfect here is something I&#39;ve never seen before.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;strong&gt;The series:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://loki.dev/en/blog/modern-tooling-part-1-uv/&quot;&gt;Part 1: uv&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://loki.dev/en/blog/modern-tooling-part-2-ruff/&quot;&gt;Part 2: ruff&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://loki.dev/en/blog/modern-tooling-part-3-ty/&quot;&gt;Part 3: ty&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Part 4: prek (this part)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://loki.dev/en/blog/modern-tooling-part-5-ai-harness/&quot;&gt;Part 5: a corset for AI agents (bonus)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Modern Tooling Part 3: The future of type checking with ty</title>
        <published>2026-07-20T07:32:00+00:00</published>
        <updated>2026-07-20T07:32:00+00:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://loki.dev/en/blog/modern-tooling-part-3-ty/"/>
        <id>https://loki.dev/en/blog/modern-tooling-part-3-ty/</id>
        
        <content type="html" xml:base="https://loki.dev/en/blog/modern-tooling-part-3-ty/">&lt;p&gt;So far in this series I&#39;ve sorted out package management with &lt;code&gt;uv&lt;/code&gt;
(&lt;a href=&quot;https://loki.dev/en/blog/modern-tooling-part-1-uv/&quot;&gt;Part 1&lt;/a&gt;) and linting with
&lt;code&gt;ruff&lt;/code&gt; (&lt;a href=&quot;https://loki.dev/en/blog/modern-tooling-part-2-ruff/&quot;&gt;Part 2&lt;/a&gt;). &lt;strong&gt;Part 3&lt;/strong&gt;
is the most demanding piece of static analysis: type checking.&lt;/p&gt;
&lt;h3 id=&quot;why-type-checking&quot;&gt;Why type checking?&lt;/h3&gt;
&lt;p&gt;I really got into type checking back in the PHP days, through phpstan/psalm. It
felt like the validation I knew from other languages was finally arriving in the
scripting languages too. YES: it&#39;s a bit more work, and of course arrays (PHP)
or lists aren&#39;t quite as flexible anymore. But throwing everything in there was
never a good idea anyway. So for me type checkers enforced sensible behaviour.&lt;/p&gt;
&lt;p&gt;The main reason for me, though, is that type checkers in scripting languages
simply wipe out entire categories of bugs. Combined with linters, you should
have far fewer (or even zero) &lt;code&gt;None&lt;/code&gt; errors left in Python. Used consistently,
you avoid a lot of problems, e.g. unhandled logical branches in
&lt;code&gt;match&lt;/code&gt;/&lt;code&gt;if-elif-else&lt;/code&gt; cascades.&lt;/p&gt;
&lt;p&gt;And finally: code completion. With AI that matters a bit less these days, but
every modern IDE can read these type hints and make useful suggestions. The
success of FastAPI/pydantic rests partly on exactly that: the DX (developer
experience) is so good!&lt;/p&gt;
&lt;p&gt;In my template with a hexagonal architecture, types are even load-bearing: the
ports are defined as abstract, fully typed interfaces:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-1 z-i&quot;&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span class=&quot;z-5 z-u&quot;&gt;ItemRepository&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt;ABC&lt;/span&gt;&lt;span&gt;):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-5&quot;&gt;    @abstractmethod&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-1 z-i&quot;&gt;    async def&lt;/span&gt;&lt;span class=&quot;z-5&quot;&gt; save&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-14 z-i&quot;&gt;self&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-14 z-i&quot;&gt; item&lt;/span&gt;&lt;span&gt;: Item) -&amp;gt; Item:&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt; ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-5&quot;&gt;    @abstractmethod&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-1 z-i&quot;&gt;    async def&lt;/span&gt;&lt;span class=&quot;z-5&quot;&gt; by_id&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-14 z-i&quot;&gt;self&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-14 z-i&quot;&gt; item_id&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt; UUID&lt;/span&gt;&lt;span&gt;) -&amp;gt; Item&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; |&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt; None&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt; ...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A type checker makes sure every adapter fulfils this contract exactly, and that
nobody forgets to handle the &lt;code&gt;None&lt;/code&gt; case of &lt;code&gt;by_id&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The catch was always the price: &lt;code&gt;mypy&lt;/code&gt; gets noticeably slow on
growing codebases. And a check that takes 30 seconds stops being run locally, it
moves into CI, and type errors only surface after the push. Exactly the
feedback-loop problem that &lt;code&gt;ruff&lt;/code&gt; solved for linting still existed for type
checking.&lt;/p&gt;
&lt;h3 id=&quot;ty&quot;&gt;ty&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;ty&lt;/code&gt; is Astral&#39;s answer to that: a type checker written in Rust, built for
massive parallelism, that slots directly into the &lt;code&gt;uv&lt;/code&gt;/&lt;code&gt;ruff&lt;/code&gt; toolchain. The
check runs so fast that it fits into any pre-commit hook without trouble:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-5&quot;&gt;uv&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt; run ty check&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A typical finding looks like this, where someone forgets that &lt;code&gt;by_id&lt;/code&gt; can also
return &lt;code&gt;None&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;item&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; = await&lt;/span&gt;&lt;span&gt; repository.by_id(item_id)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-1&quot;&gt;print&lt;/span&gt;&lt;span&gt;(item.name)&lt;/span&gt;&lt;span class=&quot;z-4&quot;&gt;  # error: `name` on `Item | None`, `None` has no `name`&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At runtime that would be an &lt;code&gt;AttributeError&lt;/code&gt; in production. With &lt;code&gt;ty&lt;/code&gt; it&#39;s a red
line before the commit even exists, or depending on your CI tooling even a
prevented commit or merge.&lt;/p&gt;
&lt;h3 id=&quot;my-type-discipline&quot;&gt;My type discipline&lt;/h3&gt;
&lt;p&gt;Important for context: the rule that every function, variable and class must be
fully typed and that &lt;code&gt;Any&lt;/code&gt; stays the absolute exception is a &lt;strong&gt;project rule&lt;/strong&gt;
of my template. &lt;code&gt;ty&lt;/code&gt; is the tool that makes it
enforceable, together with &lt;code&gt;ruff&lt;/code&gt;&#39;s
&lt;a rel=&quot;external&quot; href=&quot;https://docs.astral.sh/ruff/rules/#flake8-annotations-ann&quot;&gt;&lt;code&gt;ANN&lt;/code&gt; rules that flag missing annotations&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In the pre-commit hook the check runs across the whole codebase:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;-&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; id&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt; ty&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-13&quot;&gt;  name&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt; ty-check&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-13&quot;&gt;  entry&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt; uv run ty check --verbose --output-format=full .&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-13&quot;&gt;  language&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt; system&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-13&quot;&gt;  types&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt;python&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-13&quot;&gt;  pass_filenames&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt; false&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;pass_filenames: false&lt;/code&gt; is set deliberately here: type checking only makes sense
across the whole project, since a change in file A can trigger a type error in
file B. Thanks to &lt;code&gt;ty&lt;/code&gt;&#39;s speed I can afford the full check on every commit.
Nice :).&lt;/p&gt;
&lt;h3 id=&quot;in-the-editor&quot;&gt;In the editor&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;ty&lt;/code&gt; also ships its own language server: autocompletion,
hover, go-to-definition and auto-imports, all at &lt;code&gt;ty&lt;/code&gt; speed.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;VS Code:&lt;/strong&gt; There&#39;s an official
&lt;a rel=&quot;external&quot; href=&quot;https://docs.astral.sh/ty/editors/&quot;&gt;ty extension&lt;/a&gt; from Astral. It automatically
turns off the Python extension&#39;s language server (sets &lt;code&gt;python.languageServer&lt;/code&gt;
to &lt;code&gt;None&lt;/code&gt;) so you don&#39;t run two language servers at once.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;PyCharm:&lt;/strong&gt; From version 2025.3 on, &lt;code&gt;ty&lt;/code&gt; is built in. Tick &lt;em&gt;Enable&lt;/em&gt; under
&lt;em&gt;Settings → Python | Tools → ty&lt;/em&gt;. PyCharm then picks up the &lt;code&gt;ty&lt;/code&gt; version
installed in your interpreter.&lt;/p&gt;
&lt;p&gt;Underneath it&#39;s always the same language server, which you can also start
manually (&lt;code&gt;uv run ty server&lt;/code&gt;), e.g. for Neovim.&lt;/p&gt;
&lt;h3 id=&quot;caveats&quot;&gt;Caveats&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;ty&lt;/code&gt; is young. My template pins it to &lt;code&gt;ty==0.*&lt;/code&gt;: the zero before the dot isn&#39;t
always meaningful in the open-source world. Here it does hold, though, &lt;code&gt;ty&lt;/code&gt; is
officially still a beta up to version &lt;code&gt;0.0.61&lt;/code&gt;. In my opinion (very subjective!)
it&#39;s already production ready, and the speed advantage is simply worth it to me.&lt;/p&gt;
&lt;p&gt;In my Architecture Decision Records I still mark the adoption as &lt;em&gt;Experimental&lt;/em&gt;:
I&#39;m evaluating &lt;code&gt;ty&lt;/code&gt; as the primary type checker and watching how quickly it
catches up to &lt;code&gt;mypy&lt;/code&gt;/&lt;code&gt;pyright&lt;/code&gt; on the more complex typing PEPs (generics,
&lt;code&gt;ParamSpec&lt;/code&gt;, overloads).&lt;/p&gt;
&lt;p&gt;For a template and greenfield projects the risk is manageable, the type
constructs there are rarely exotic. Anyone with a large existing codebase and
elaborate &lt;code&gt;mypy&lt;/code&gt; plugin setups should weigh the switch consciously, though:
&lt;code&gt;mypy&lt;/code&gt; currently still has more complete rule coverage.&lt;/p&gt;
&lt;h3 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;The real win of &lt;code&gt;ty&lt;/code&gt; is a type checker that runs fast: locally, on every
commit, without anyone having to wait. Type discipline only sticks when the
feedback is fast.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;strong&gt;The series:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://loki.dev/en/blog/modern-tooling-part-1-uv/&quot;&gt;Part 1: uv&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://loki.dev/en/blog/modern-tooling-part-2-ruff/&quot;&gt;Part 2: ruff&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Part 3: ty (this part)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://loki.dev/en/blog/modern-tooling-part-4-prek/&quot;&gt;Part 4: prek&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://loki.dev/en/blog/modern-tooling-part-5-ai-harness/&quot;&gt;Part 5: a corset for AI agents (bonus)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Modern Tooling Part 2: Consistent Linting and Formatting with ruff</title>
        <published>2026-07-16T17:14:00+00:00</published>
        <updated>2026-07-16T17:14:00+00:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://loki.dev/en/blog/modern-tooling-part-2-ruff/"/>
        <id>https://loki.dev/en/blog/modern-tooling-part-2-ruff/</id>
        
        <content type="html" xml:base="https://loki.dev/en/blog/modern-tooling-part-2-ruff/">&lt;p&gt;After tidying up package management with &lt;code&gt;uv&lt;/code&gt; in
&lt;a href=&quot;https://loki.dev/en/blog/modern-tooling-part-1-uv/&quot;&gt;Part 1&lt;/a&gt;, today I want to deal
with &lt;strong&gt;ruff&lt;/strong&gt; in Part 2.&lt;/p&gt;
&lt;h3 id=&quot;toolchain-soup&quot;&gt;Toolchain soup&lt;/h3&gt;
&lt;p&gt;&quot;Code quality&quot; long meant chaining four or five tools together:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Black for formatting&lt;/li&gt;
&lt;li&gt;Isort for import sorting&lt;/li&gt;
&lt;li&gt;Flake8 (or pylint) for linting (plus a dozen plugins)&lt;/li&gt;
&lt;li&gt;pyupgrade for modern syntax&lt;/li&gt;
&lt;li&gt;and Bandit for security checks&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here too the rule held: every tool has its own config file, its own version and
its own conflicts. Black and Isort, for example, had to be explicitly aligned
so they didn&#39;t keep reformatting each other&#39;s imports.&lt;/p&gt;
&lt;p&gt;And it was slow. On medium codebases these checks ran for seconds, sometimes
even minutes. Imho too slow to run on every save or commit. I still kept them
on, was usually already onto the next problem, and got promptly kicked back
because of some trivial issue. So more than once I disabled them and forgot to
run them again at the end, only to get annoyed by a red CI pipeline.&lt;/p&gt;
&lt;h3 id=&quot;ruff&quot;&gt;ruff!&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;ruff&lt;/code&gt; is linter &lt;strong&gt;and&lt;/strong&gt; formatter in a single Rust binary. It implements the
rules of Flake8 (including the popular plugins), Isort, pyupgrade and large
parts of Bandit, and formats Black-compatibly (with minimal deviations). The
runtime stays in the millisecond range even on large codebases, which makes a
decisive difference:&lt;/p&gt;
&lt;p&gt;Checks that are instant run on every save in the editor, on every commit and in
CI. Checks that take seconds eventually only run in CI. Together with &lt;code&gt;--fix&lt;/code&gt;,
simply brilliant tooling that also works independently of the IDE &amp;lt;3.&lt;/p&gt;
&lt;p&gt;The two commands you need:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-4&quot;&gt;# Find linting errors and fix them automatically where possible&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-5&quot;&gt;uv&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt; run ruff check&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt; --fix&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt; .&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-4&quot;&gt;# Format the codebase&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-5&quot;&gt;uv&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt; run ruff format .&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In CI I flip it around and only check:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;-&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; name&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt; Formatting Check (Ruff)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-13&quot;&gt;  run&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt; uv run ruff format --check .&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;-&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; name&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt; Linting Check (Ruff)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-13&quot;&gt;  run&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt; uv run ruff check .&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&quot;my-configuration-opt-out&quot;&gt;My configuration: opt-out&lt;/h3&gt;
&lt;p&gt;Most projects enable a handful of rule groups and add more over time. My
template goes the other way: &lt;strong&gt;all&lt;/strong&gt; rules on, and every exception has to be
justified. That forces a deliberate decision on each &lt;code&gt;ignore&lt;/code&gt;, and documents
the decision right there:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;toml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-4&quot;&gt;# Part of pyproject.toml&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[tool.ruff.lint]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-4&quot;&gt;# I want to be strict and only opt out deliberately!&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;select = [&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt; &amp;quot;ALL&amp;quot;&lt;/span&gt;&lt;span&gt; ]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;fixable = [&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt; &amp;quot;ALL&amp;quot;&lt;/span&gt;&lt;span&gt; ]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;ignore = [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-4&quot;&gt;  # strict&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-11&quot;&gt;  &amp;quot;D102&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-4&quot;&gt;    # would require docstrings for every public method&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-11&quot;&gt;  &amp;quot;D203&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-4&quot;&gt;    # incompatible with D211 (no blank line before class docstring)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-11&quot;&gt;  &amp;quot;EM101&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-4&quot;&gt;   # would force exception texts into variables first&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-4&quot;&gt;  # incompatible with &amp;quot;ruff format&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-11&quot;&gt;  &amp;quot;COM812&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-4&quot;&gt;  # temporary - as an example&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-11&quot;&gt;  &amp;quot;TC003&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-4&quot;&gt;   # would enforce TYPE_CHECKING blocks.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The comment behind each rule is the core of the strategy: a year from now nobody remembers why &lt;code&gt;COM812&lt;/code&gt; is off, unless it says so right
next to it.&lt;/p&gt;
&lt;h3 id=&quot;per-file-ignores-tests-are-different&quot;&gt;Per-file ignores: tests are different&lt;/h3&gt;
&lt;p&gt;Rules that make sense in production code are often just noise in tests. &lt;code&gt;assert&lt;/code&gt;
is the normal case in tests (in production code the Bandit rule &lt;code&gt;S101&lt;/code&gt; forbids
it), and &quot;magic numbers&quot; in a test case are simply the test data:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;toml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-4&quot;&gt;# Part of pyproject.toml&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[tool.ruff.lint.per-file-ignores]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;quot;tests/*&amp;quot; = [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-11&quot;&gt;    &amp;quot;ANN201&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-4&quot;&gt;   # return annotations for test functions: unnecessary&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-11&quot;&gt;    &amp;quot;PLR2004&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-4&quot;&gt;  # magic values in comparisons: those ARE the test data&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-11&quot;&gt;    &amp;quot;S101&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-4&quot;&gt;     # assert is essential in pytest&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&quot;import-sorting-included&quot;&gt;Import sorting included&lt;/h3&gt;
&lt;p&gt;Isort is on board right away as rule group &lt;code&gt;I&lt;/code&gt;. So that the sorting stays
deterministic even in edge cases, I declare explicitly what is third-party and
what is my own code:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;toml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[tool.ruff.lint.isort]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;known-third-party = [&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt;&amp;quot;fastapi&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt; &amp;quot;pydantic&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt; &amp;quot;starlette&amp;quot;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;known-local-folder = [&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt;&amp;quot;app&amp;quot;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Quick reminder: &lt;code&gt;isort&lt;/code&gt; makes sure the import blocks are roughly split into 3
categories:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&quot;batteries included&quot; imports, e.g.: &lt;code&gt;from math import sqrt&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;third-party imports: &lt;code&gt;from fastapi import FastAPI&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;project imports: &lt;code&gt;from my_project.domain import ImportantClass&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;And within the blocks, alphabetically.&lt;/p&gt;
&lt;h3 id=&quot;in-the-editor-feedback-as-you-type&quot;&gt;In the editor: feedback as you type&lt;/h3&gt;
&lt;p&gt;The biggest win comes when &lt;code&gt;ruff&lt;/code&gt; already runs as you type.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;VS Code:&lt;/strong&gt; The official
&lt;a rel=&quot;external&quot; href=&quot;https://marketplace.visualstudio.com/items?itemName=charliermarsh.ruff&quot;&gt;Ruff extension&lt;/a&gt;
(by charliermarsh/Astral) handles both linting and formatting. What matters to
me is that it uses the version pinned in the project: &lt;code&gt;ruff.importStrategy&lt;/code&gt;
defaults to &lt;code&gt;fromEnvironment&lt;/code&gt;, so it picks up the &lt;code&gt;ruff&lt;/code&gt; from the &lt;code&gt;uv&lt;/code&gt;
environment instead of the bundled one.&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;json&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-4&quot;&gt;// .vscode/settings.json&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-1 z-i&quot;&gt;  &amp;quot;[python]&amp;quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-1 z-i&quot;&gt;    &amp;quot;editor.defaultFormatter&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-10&quot;&gt; &amp;quot;charliermarsh.ruff&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-1 z-i&quot;&gt;    &amp;quot;editor.formatOnSave&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt; true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-1 z-i&quot;&gt;    &amp;quot;editor.codeActionsOnSave&amp;quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-1 z-i&quot;&gt;      &amp;quot;source.fixAll.ruff&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-10&quot;&gt; &amp;quot;explicit&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-1 z-i&quot;&gt;      &amp;quot;source.organizeImports.ruff&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-10&quot;&gt; &amp;quot;explicit&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-1 z-i&quot;&gt;  &amp;quot;ruff.importStrategy&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-10&quot;&gt; &amp;quot;fromEnvironment&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;.ruff&lt;/code&gt; suffix on the code actions pins the fixing and import sorting to
&lt;code&gt;ruff&lt;/code&gt;. Without it, some other formatter can grab the job by accident.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;PyCharm:&lt;/strong&gt; From version 2025.3 on, Ruff support is built in. Enable it under
&lt;em&gt;Settings → Tools → Ruff&lt;/em&gt;, and &lt;em&gt;Reformat Code&lt;/em&gt; and &lt;em&gt;Optimize Imports&lt;/em&gt; will use
&lt;code&gt;ruff&lt;/code&gt;. For older versions there&#39;s the
&lt;a rel=&quot;external&quot; href=&quot;https://plugins.jetbrains.com/plugin/20574-ruff&quot;&gt;Ruff plugin by koxudaxi&lt;/a&gt;. In
both cases it&#39;s worth pointing it at the &lt;code&gt;ruff&lt;/code&gt; from the project environment
rather than a globally installed one.&lt;/p&gt;
&lt;h3 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;ruff&lt;/code&gt; replaces four config files with one block in &lt;code&gt;pyproject.toml&lt;/code&gt; and makes
linting so fast that there&#39;s no excuse left not to run it everywhere. The
opt-out strategy with &lt;code&gt;select = [&quot;ALL&quot;]&lt;/code&gt; is deliberately radical: it costs a bit
of time to set up, but in return you get a ruleset where every exception is a
documented decision.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;strong&gt;The series:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://loki.dev/en/blog/modern-tooling-part-1-uv/&quot;&gt;Part 1: uv&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Part 2: ruff (this part)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://loki.dev/en/blog/modern-tooling-part-3-ty/&quot;&gt;Part 3: ty&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://loki.dev/en/blog/modern-tooling-part-4-prek/&quot;&gt;Part 4: prek&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://loki.dev/en/blog/modern-tooling-part-5-ai-harness/&quot;&gt;Part 5: a corset for AI agents (bonus)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Modern Tooling Part 1: Blazing-Fast Package Management with uv</title>
        <published>2026-07-13T19:50:00+00:00</published>
        <updated>2026-07-16T15:15:29+00:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://loki.dev/en/blog/modern-tooling-part-1-uv/"/>
        <id>https://loki.dev/en/blog/modern-tooling-part-1-uv/</id>
        
        <content type="html" xml:base="https://loki.dev/en/blog/modern-tooling-part-1-uv/">&lt;p&gt;Hey and welcome to my series on modern Python tooling: four parts on the tools
themselves, plus a bonus part on AI agents. I&#39;m writing it now because the
Python ecosystem is going through an upheaval. Several, in fact:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The Python ecosystem is being replaced in large part by Rust-based tools.
I largely have &lt;a rel=&quot;external&quot; href=&quot;https://astral.sh&quot;&gt;Astral&lt;/a&gt; to thank for that.&lt;/li&gt;
&lt;li&gt;Ever-larger parts of development are being done by AI from various vendors,
and keeping it under control leads to growing problems.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Conveniently, these two shifts fit together perfectly.&lt;/p&gt;
&lt;p&gt;So my goal is first to introduce these new Rust-based tools, and then to show
how they help in AI-driven development. I&#39;ll look at four+ of these tools along
a real FastAPI project template: &lt;code&gt;uv&lt;/code&gt;, &lt;code&gt;ruff&lt;/code&gt;, &lt;code&gt;ty&lt;/code&gt;
and &lt;code&gt;prek&lt;/code&gt; (+more mini tools).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Part 1 (this one)&lt;/strong&gt; is about the foundation: &lt;strong&gt;&lt;code&gt;uv&lt;/code&gt;&lt;/strong&gt;.&lt;/p&gt;
&lt;h3 id=&quot;the-problem-i-see-five-tools-for-one-job&quot;&gt;The problem I see: five tools for one job&lt;/h3&gt;
&lt;p&gt;Anyone who wanted to set up a Python project cleanly used to need a whole
toolbox:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;pyenv&lt;/code&gt; to install the right Python version&lt;/li&gt;
&lt;li&gt;&lt;code&gt;venv&lt;/code&gt; or &lt;code&gt;virtualenv&lt;/code&gt; (I&#39;ve also used &lt;code&gt;pipenv&lt;/code&gt;) to create an isolated environment&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pip&lt;/code&gt; to install packages&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pip-tools&lt;/code&gt;, &lt;code&gt;pip freeze&lt;/code&gt; (or &lt;code&gt;poetry&lt;/code&gt;) to lock dependencies&lt;/li&gt;
&lt;li&gt;plus the &lt;code&gt;source .venv/bin/activate&lt;/code&gt; ritual (often automated via a zsh/bash script)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Each of these tools has its own configuration (including its own config
formats), its own quirks and its own failure modes. The result: &quot;works on my
machine&quot;. Thrown in for free, you get a CI pipeline that spends minutes
resolving dependencies.&lt;/p&gt;
&lt;h3 id=&quot;the-solution-uv-a-single-binary&quot;&gt;The solution: uv, a single binary&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;uv&lt;/code&gt; replaces the entire stack with a single Rust binary. It&#39;s package
installer, environment manager, dependency resolver and Python version manager
in one. And it&#39;s orders of magnitude faster while doing it.&lt;/p&gt;
&lt;p&gt;Installing it is quick:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-5&quot;&gt;curl&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt; -LsSf&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt; https://astral.sh/uv/install.sh&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; |&lt;/span&gt;&lt;span class=&quot;z-5&quot;&gt; sh&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-4&quot;&gt;# or on macOS:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-5&quot;&gt;brew&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt; install uv&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;(As always: read the install script first, or even download it first and then
run it. For simplicity, only the direct step from the docs is shown here.)&lt;/em&gt;&lt;/p&gt;
&lt;h3 id=&quot;workflow&quot;&gt;Workflow&lt;/h3&gt;
&lt;p&gt;After that, day-to-day Python comes down to three commands:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-4&quot;&gt;# Install dependencies from the lockfile (incl. venv creation)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-5&quot;&gt;uv&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt; sync&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-4&quot;&gt;# Add a package: updates pyproject.toml AND uv.lock&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-5&quot;&gt;uv&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt; add fastapi&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-4&quot;&gt;# Run any command in the project environment&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-5&quot;&gt;uv&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt; run pytest&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So there&#39;s no more &lt;code&gt;activate&lt;/code&gt;. &lt;code&gt;uv run&lt;/code&gt; makes sure the command runs in the
right environment and that this environment matches the lockfile. If it
doesn&#39;t, &lt;code&gt;uv&lt;/code&gt; syncs it beforehand automatically. With &lt;code&gt;uv venv&lt;/code&gt; (optionally
with &lt;code&gt;--python 314&lt;/code&gt;) you can also create a &lt;code&gt;venv&lt;/code&gt; environment separately.
Alternatively, you can add a &lt;code&gt;.python-version&lt;/code&gt; file to the project with the
content &lt;code&gt;3.14&lt;/code&gt;. Then &lt;code&gt;uv&lt;/code&gt; simply downloads the matching interpreter on demand.
&lt;code&gt;pyenv&lt;/code&gt; becomes unnecessary.&lt;/p&gt;
&lt;h3 id=&quot;dependency-groups-tame-requirements-sprawl&quot;&gt;Dependency groups tame requirements sprawl&lt;/h3&gt;
&lt;p&gt;Instead of &lt;code&gt;requirements.txt&lt;/code&gt;, &lt;code&gt;requirements-dev.txt&lt;/code&gt; and
&lt;code&gt;requirements-test.txt&lt;/code&gt;, my template defines everything in &lt;code&gt;pyproject.toml&lt;/code&gt;,
using the standard for &lt;a rel=&quot;external&quot; href=&quot;https://peps.python.org/pep-0735/&quot;&gt;dependency groups (PEP 735)&lt;/a&gt;,
including groups that include other groups:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;toml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[dependency-groups]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;dev = [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-11&quot;&gt;  &amp;quot;prek==0.4.9&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  { include-group =&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt; &amp;quot;tests&amp;quot;&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  { include-group =&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt; &amp;quot;lint&amp;quot;&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;tests = [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-11&quot;&gt;  &amp;quot;httpx==0.*&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-11&quot;&gt;  &amp;quot;pytest==9.*&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-11&quot;&gt;  &amp;quot;pytest-asyncio&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;lint = [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-11&quot;&gt;  &amp;quot;ruff==0.*&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-11&quot;&gt;  &amp;quot;ty==0.*&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-11&quot;&gt;  &amp;quot;bandit==1.*&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-11&quot;&gt;  &amp;quot;import-linter&amp;gt;=2.11&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;uv sync&lt;/code&gt; installs the &lt;code&gt;dev&lt;/code&gt; group by default, &lt;code&gt;uv sync --no-dev&lt;/code&gt; leaves it
out. Perfect for the production environment. Further flags for the groups are
&lt;code&gt;--all-groups&lt;/code&gt; or, very specifically, &lt;code&gt;--group lint|tests|dev&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&quot;reproducibility-uv-lock&quot;&gt;Reproducibility: uv.lock&lt;/h3&gt;
&lt;p&gt;Important for stable deployments is &lt;code&gt;uv.lock&lt;/code&gt;: it pins every dependency
(including sub-dependencies) with an exact version and hash, across platforms.
In CI and Docker I use &lt;code&gt;--frozen&lt;/code&gt; to enforce that the lockfile is current. If
&lt;code&gt;pyproject.toml&lt;/code&gt; diverges from it, the build fails instead of quietly
installing something else.&lt;/p&gt;
&lt;p&gt;Here&#39;s how that looks in an example Dockerfile from my template (multi-stage,
with a cache mount):&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;docker&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-4&quot;&gt;# Deliberately not alpine, since that&amp;#39;s often extra work right now with binary&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-4&quot;&gt;# builds (e.g. psycopg2/etc.).&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-13&quot;&gt;FROM&lt;/span&gt;&lt;span&gt; python:3.14-slim &lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt;AS&lt;/span&gt;&lt;span&gt; builder&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-13&quot;&gt;ENV&lt;/span&gt;&lt;span&gt; UV_COMPILE_BYTECODE=1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-13&quot;&gt;ENV&lt;/span&gt;&lt;span&gt; UV_LINK_MODE=copy&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-4&quot;&gt;# First step: have uv. There are also ready-made Docker images!&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-13&quot;&gt;COPY&lt;/span&gt;&lt;span&gt; --from=ghcr.io/astral-sh/uv:latest /uv /bin/uv&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-13&quot;&gt;COPY&lt;/span&gt;&lt;span&gt; pyproject.toml uv.lock ./&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-4&quot;&gt;# Dependencies only first, this layer gets cached&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-4&quot;&gt;# as long as the lockfile doesn&amp;#39;t change&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-13&quot;&gt;RUN&lt;/span&gt;&lt;span&gt; uv sync --frozen --no-dev --no-install-project&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-4&quot;&gt;# Then the source code -&amp;gt; frequent changes&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-13&quot;&gt;COPY&lt;/span&gt;&lt;span&gt; src ./src&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-4&quot;&gt;# Install using the cache (makes a huge difference, especially in CI)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-13&quot;&gt;RUN&lt;/span&gt;&lt;span&gt; --mount=type=cache,target=/root/.cache/uv \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    uv sync --frozen --no-dev&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And in the GitHub Actions pipeline, the official action handles setup and
caching in a single step:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;-&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; name&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt; Install uv&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-13&quot;&gt;  uses&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt; astral-sh/setup-uv@v8.2.0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-13&quot;&gt;  with&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-13&quot;&gt;    python-version&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt; &amp;quot;3.14&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-13&quot;&gt;    enable-cache&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt; true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;-&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; name&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt; Install dependencies&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-13&quot;&gt;  run&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt; uv sync --all-groups&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;uv&lt;/code&gt; needs less configuration than the old stack and is still noticeably
faster. It&#39;s the reason every following part of this series starts with
&lt;code&gt;uv run ...&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Bonus feature: running &lt;a rel=&quot;external&quot; href=&quot;https://docs.astral.sh/uv/guides/scripts/#declaring-script-dependencies&quot;&gt;scripts with inline dependencies&lt;/a&gt;.
Perfect for DevOps and the like. :)&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;strong&gt;The series:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Part 1: uv (this part)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://loki.dev/en/blog/modern-tooling-part-2-ruff/&quot;&gt;Part 2: ruff&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://loki.dev/en/blog/modern-tooling-part-3-ty/&quot;&gt;Part 3: ty&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://loki.dev/en/blog/modern-tooling-part-4-prek/&quot;&gt;Part 4: prek&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://loki.dev/en/blog/modern-tooling-part-5-ai-harness/&quot;&gt;Part 5: a corset for AI agents (bonus)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Claude Code, Claw Code and the End of Secrets?</title>
        <published>2026-05-04T12:00:00+00:00</published>
        <updated>2026-05-04T12:00:00+00:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://loki.dev/en/blog/claude-code-leak-und-claw-code/"/>
        <id>https://loki.dev/en/blog/claude-code-leak-und-claw-code/</id>
        
        <content type="html" xml:base="https://loki.dev/en/blog/claude-code-leak-und-claw-code/">&lt;p&gt;Sorry for the long silence! Quite a bit of time has passed since the last post. I&#39;ve still got a whole pile of drafts sitting around (&lt;code&gt;.md.draft&lt;/code&gt;), but when I look at them, they all need a serious overhaul first, the world (and especially the AI world) is just moving too fast ;).&lt;/p&gt;
&lt;p&gt;But right now it&#39;s precisely this AI thing keeping me occupied, and I&#39;m missing a voice that&#39;s neither swept away by the hype nor hiding in a cave ignoring current developments. So I&#39;m just going to do it, even written entirely without AI.&lt;/p&gt;
&lt;p&gt;Claude Code first, since it&#39;s fairly clearly leading the popularity charts among developers. It&#39;s already fascinating (and a bit ironic) that a tool designed to help us write code released its own source code, even after Mythic (the thing so good it had to be kept from the public) was already in use; or because of it? 🤔 Some might remember similar claims around GPT-5.&lt;/p&gt;
&lt;h3 id=&quot;claw-code&quot;&gt;Claw Code&lt;/h3&gt;
&lt;p&gt;Anyone who wants to look at the whole thing in detail (while it&#39;s still online) should check out &lt;a rel=&quot;external&quot; href=&quot;https://web.archive.org/web/20260809043341/https://claw-code.codes/&quot;&gt;Claw Code&lt;/a&gt;. The project essentially &quot;deductively&quot; processed the leaked code and made it accessible. It&#39;s a fascinating piece of software architecture showing how Anthropic orchestrates the interaction between LLM and local shell.&lt;/p&gt;
&lt;h3 id=&quot;the-legal-side-dmca-whack-a-mole&quot;&gt;The legal side: DMCA Whack-A-Mole&lt;/h3&gt;
&lt;p&gt;Of course Anthropic finds this less amusing. There have apparently been over 8,100 takedown requests already. &lt;a rel=&quot;external&quot; href=&quot;https://www.heise.de/news/Claude-Code-Leck-8100-Loeschantraege-und-die-Geburtsstunde-von-Claw-Code-11279601.html&quot;&gt;Heise published a very good article about it&lt;/a&gt; that untangles the legal side. It&#39;s the classic &quot;birth of a tool from a leak&quot;, and the desperate attempt to put the cat back in the bag.&lt;/p&gt;
&lt;p&gt;I find this quite telling about the current state of AI:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The tools are becoming more powerful, but also more complex.&lt;/li&gt;
&lt;li&gt;Code (and bugs) are being produced faster and getting less and less genuine review.&lt;/li&gt;
&lt;li&gt;&quot;Security by Obscurity&quot; works even less with AI than it did before.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I&#39;m still thinking about whether to write a deeper article specifically about &lt;strong&gt;AI Guardrails&lt;/strong&gt;, a kind of safety corset for AI. I have the feeling this is still missing for many people, even though it&#39;s really a pretty low-hanging fruit.&lt;/p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Immutability / Const vs Let</title>
        <published>2024-12-06T09:34:00+00:00</published>
        <updated>2024-12-18T07:34:00+00:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://loki.dev/en/blog/const-immutability/"/>
        <id>https://loki.dev/en/blog/const-immutability/</id>
        
        <content type="html" xml:base="https://loki.dev/en/blog/const-immutability/">&lt;h2 id=&quot;tl-dr&quot;&gt;tl;dr&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Use const over let&lt;/li&gt;
&lt;li&gt;Make your variables immutable as often as possible&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I will teach you about how useful immutability is and how to use it in typescript (but similar concepts apply to other languages as well).&lt;/p&gt;
&lt;h2 id=&quot;what-is-immutability&quot;&gt;What is immutability?&lt;/h2&gt;
&lt;p&gt;Basically the name describes it already: Making stuff immutable: Not changeable. In typescript all basic datatypes are immutable!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;So when you do:&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;typescript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-1 z-i&quot;&gt;let&lt;/span&gt;&lt;span&gt; a&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt; 5&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-1 z-i&quot;&gt;let&lt;/span&gt;&lt;span&gt; b&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; =&lt;/span&gt;&lt;span&gt; a;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;a&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt; 123&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-4&quot;&gt;// 5 still exists in memory&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;b&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt; 42&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-4&quot;&gt;// 5 will be garbage collected soon&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Just as a hint to future content: immutability is a major part in functional programming ;).
To dive deeper into (im)mutability let&#39;s start with an example:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;typescript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-1 z-i&quot;&gt;let&lt;/span&gt;&lt;span&gt; helloWorldString&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt; &amp;quot;Hello World!&amp;quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span class=&quot;z-4&quot;&gt; // immutable&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;helloWorldString&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt; &amp;quot;Hello Moon!&amp;quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span class=&quot;z-4&quot;&gt; // no error, but var name no longer correct!&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-4&quot;&gt;// vs:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-1 z-i&quot;&gt;const&lt;/span&gt;&lt;span&gt; helloWorldString&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt; &amp;quot;Hello World!&amp;quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span class=&quot;z-4&quot;&gt; // immutable&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;helloWorldString&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt; &amp;quot;Hello Moon!&amp;quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span class=&quot;z-4&quot;&gt; // error! We can rely on the content of the var!&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the top part the stuff inside the variable changes and makes it harder
to predict what&#39;s inside it.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;But what about counter variables? I NEED to count stuff all the time!?&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Correct. Let&#39;s break this up a little:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The guideline is:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;avoid mutable variables &lt;strong&gt;when possible&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;if mutable variables improve readability or are the only viable
way (very seldom): &lt;strong&gt;go for it&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When I say &quot;seldom&quot; I mean that in many cases Type/Javascript will give us
a way to avoid counter variables:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;typescript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-1 z-i&quot;&gt;let&lt;/span&gt;&lt;span&gt; uppercaseNames&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; =&lt;/span&gt;&lt;span&gt; [];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-13&quot;&gt;for&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-1 z-i&quot;&gt;let&lt;/span&gt;&lt;span&gt; name&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; of&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt;&amp;quot;alice&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt; &amp;quot;bob&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt; &amp;quot;charlie&amp;quot;&lt;/span&gt;&lt;span&gt;]) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  uppercaseNames.&lt;/span&gt;&lt;span class=&quot;z-5&quot;&gt;push&lt;/span&gt;&lt;span&gt;(name.&lt;/span&gt;&lt;span class=&quot;z-5&quot;&gt;toUpperCase&lt;/span&gt;&lt;span&gt;());&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-4&quot;&gt;// Functional approach&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-1 z-i&quot;&gt;const&lt;/span&gt;&lt;span&gt; uppercaseNames&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; =&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt;&amp;quot;alice&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt; &amp;quot;bob&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt; &amp;quot;charlie&amp;quot;&lt;/span&gt;&lt;span&gt;].&lt;/span&gt;&lt;span class=&quot;z-5&quot;&gt;map&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span class=&quot;z-14 z-i&quot;&gt;name&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-1 z-i&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  name.&lt;/span&gt;&lt;span class=&quot;z-5&quot;&gt;toUpperCase&lt;/span&gt;&lt;span&gt;(),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Obviously this example is trivial, but in any way: We KNOW that &lt;code&gt;uppercaseNames&lt;/code&gt;
will contain exactly what it tells us, as soon as it&#39;s defined!&lt;/p&gt;
&lt;h2 id=&quot;why&quot;&gt;Why&lt;/h2&gt;
&lt;p&gt;Immutability doesn&#39;t mean all the way and also doesn&#39;t mean that mutable stuff is
inherently bad, but it definitely removes some footguns, when programming and debugging.&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https://biomejs.dev&quot;&gt;Biome&lt;/a&gt; has a lint &lt;code&gt;lint/style/useConst&lt;/code&gt; forcing us to use &lt;code&gt;const&lt;/code&gt;
when a variable is never reassigned, but I&#39;d go further and replace &lt;code&gt;let&lt;/code&gt;s as often as
possible by &lt;code&gt;const&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Imagine the following:&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;typescript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-1 z-i&quot;&gt;const&lt;/span&gt;&lt;span class=&quot;z-5&quot;&gt; mutateVar&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; =&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-14 z-i&quot;&gt;innerA&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-1 z-i&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  innerA&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt; 9&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-13&quot;&gt;  return&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt; 12&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-1 z-i&quot;&gt;let&lt;/span&gt;&lt;span&gt; outerA&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt; 123&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-1 z-i&quot;&gt;const&lt;/span&gt;&lt;span&gt; result&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-5&quot;&gt; mutateVar&lt;/span&gt;&lt;span&gt;(outerA);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-4&quot;&gt;// no change due to pass-by-value for elemental types&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span class=&quot;z-5&quot;&gt;log&lt;/span&gt;&lt;span&gt;({ result, outerA });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-4&quot;&gt;// But:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-1 z-i&quot;&gt;const&lt;/span&gt;&lt;span class=&quot;z-5&quot;&gt; mutateObj&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; =&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-14 z-i&quot;&gt;obj&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-1 z-i&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-4&quot;&gt;  // sideeffect we don&amp;#39;t know about when reading function name:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  obj.x&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt; 123&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-13&quot;&gt;  return&lt;/span&gt;&lt;span&gt; obj;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-1 z-i&quot;&gt;const&lt;/span&gt;&lt;span&gt; originalObject&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; =&lt;/span&gt;&lt;span&gt; { a:&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt; 42&lt;/span&gt;&lt;span&gt; };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-1 z-i&quot;&gt;const&lt;/span&gt;&lt;span&gt; mutatedObject&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-5&quot;&gt; mutateObj&lt;/span&gt;&lt;span&gt;(originalObject);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span class=&quot;z-5&quot;&gt;log&lt;/span&gt;&lt;span&gt;({ originalObject, mutatedObject });&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We expect &lt;code&gt;originalObject&lt;/code&gt; to be still the same and &lt;code&gt;mutatedObject&lt;/code&gt; to
contain the new value. If we try to remember to not change existing objects
and also use &lt;code&gt;const&lt;/code&gt; we will never be surprised by behaviour &lt;strong&gt;inside&lt;/strong&gt; a function.&lt;/p&gt;
&lt;p&gt;Functions which don&#39;t mutate parameters and are solely depending on parameters
are called &lt;strong&gt;pure functions&lt;/strong&gt;. A concept which has various advantages over
&lt;strong&gt;unpure&lt;/strong&gt; functions. But I&#39;ll go into that in the next article.&lt;/p&gt;
&lt;h2 id=&quot;but-const-objects-are-not-immutable&quot;&gt;But const objects are NOT immutable&lt;/h2&gt;
&lt;p&gt;Right. Especially because of javascript, but other languages like
python/php/etc. have similar problems. Fortunately with typescript we have
powerful tools at our hands: &lt;code&gt;Readonly&lt;/code&gt;, &lt;code&gt;readonly&lt;/code&gt; and &lt;code&gt;as const&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;typescript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-4&quot;&gt;// Readonly&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-1 z-i&quot;&gt;type&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span class=&quot;z-5 z-u&quot;&gt;Tree&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; =&lt;/span&gt;&lt;span&gt; { name&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-1 z-i&quot;&gt; string&lt;/span&gt;&lt;span&gt; };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-1 z-i&quot;&gt;const&lt;/span&gt;&lt;span&gt; ahorn&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span class=&quot;z-5 z-u&quot;&gt;Readonly&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;z-5 z-u&quot;&gt;Tree&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; =&lt;/span&gt;&lt;span&gt; { name:&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt; &amp;quot;ahorn&amp;quot;&lt;/span&gt;&lt;span&gt; };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;ahorn.name&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt; &amp;quot;bhorn&amp;quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span class=&quot;z-4&quot;&gt; // typescript error&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-4&quot;&gt;// As const&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-1 z-i&quot;&gt;const&lt;/span&gt;&lt;span&gt; ahorn&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; =&lt;/span&gt;&lt;span&gt; { name:&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt; &amp;quot;ahorn&amp;quot;&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; as const&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;ahorn.name&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt; &amp;quot;bhorn&amp;quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span class=&quot;z-4&quot;&gt; // typescript error&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-4&quot;&gt;// readonly&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-1 z-i&quot;&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span class=&quot;z-5 z-u&quot;&gt;Tree&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-1 z-i&quot;&gt;  constructor&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt;readonly&lt;/span&gt;&lt;span class=&quot;z-14 z-i&quot;&gt; name&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-1 z-i&quot;&gt; string&lt;/span&gt;&lt;span&gt;) {}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-1 z-i&quot;&gt;const&lt;/span&gt;&lt;span&gt; t&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; = new&lt;/span&gt;&lt;span class=&quot;z-5&quot;&gt; Tree&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt;&amp;quot;ahorn&amp;quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;t.name&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt; &amp;quot;asdf&amp;quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span class=&quot;z-4&quot;&gt; // typescript error&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&quot;what-about-string-builders&quot;&gt;What about string builders&lt;/h2&gt;
&lt;p&gt;I personally prefer:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;extracting the process of building a string into a function&lt;/li&gt;
&lt;li&gt;having a template in the end where the various bits and pieces fit into&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;And then use the result:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;typescript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-4&quot;&gt;/** Approach of most people */&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-1 z-i&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;z-5&quot;&gt; greetCustomer&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-14 z-i&quot;&gt;customer&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-1 z-i&quot;&gt;  let&lt;/span&gt;&lt;span&gt; message&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt; &amp;quot;&amp;quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-13&quot;&gt;  if&lt;/span&gt;&lt;span&gt; (customer.age&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; &amp;gt;&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt; 15&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    message&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; +=&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt; &amp;quot;Good day sir,&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    message&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; +=&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt; &amp;quot;Hey little one,&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-13&quot;&gt;  if&lt;/span&gt;&lt;span&gt; (customer.hasEaten) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    message&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; +=&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt; &amp;quot;Can I offer you something to drink?&amp;quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    message&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; +=&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt; &amp;quot;Can I offer you our lunch menu?&amp;quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  console.&lt;/span&gt;&lt;span class=&quot;z-5&quot;&gt;log&lt;/span&gt;&lt;span&gt;(message);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-4&quot;&gt;// vs what I prefer&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-1 z-i&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;z-5&quot;&gt; buildGreeting&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-14 z-i&quot;&gt;customer&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-1 z-i&quot;&gt; string&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-1 z-i&quot;&gt;  const&lt;/span&gt;&lt;span&gt; greet&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; =&lt;/span&gt;&lt;span&gt; customer.age&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; &amp;gt;&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt; 15&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; ?&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt; &amp;quot;Good day sir&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; :&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt; &amp;quot;Hey little one&amp;quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-1 z-i&quot;&gt;  const&lt;/span&gt;&lt;span&gt; offer&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; =&lt;/span&gt;&lt;span&gt; customer.hasEaten&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; ?&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt; &amp;quot;something to drink&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; :&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt; &amp;quot;our lunch menu&amp;quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-13&quot;&gt;  return&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt; `&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt;${&lt;/span&gt;&lt;span&gt;greet&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt;Can I offer you &lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt;${&lt;/span&gt;&lt;span&gt;offer&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt;?`&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-1 z-i&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;z-5&quot;&gt; greetCustomer&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-14 z-i&quot;&gt;customer&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-1 z-i&quot;&gt;  const&lt;/span&gt;&lt;span&gt; greeting&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-5&quot;&gt; buildGreeting&lt;/span&gt;&lt;span&gt;(customer);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  console.&lt;/span&gt;&lt;span class=&quot;z-5&quot;&gt;log&lt;/span&gt;&lt;span&gt;(greeting);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I think this is better as it&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;reduces the complexity making it more visible how the final text is structured.&lt;/li&gt;
&lt;li&gt;reduces indentation. Another topic I&#39;d like to cover soon!&lt;/li&gt;
&lt;li&gt;We extracted the logic into a pure function while having the side effect function only using this&lt;/li&gt;
&lt;li&gt;That again makes the &lt;code&gt;buildGreeting&lt;/code&gt; function easier to test as we don&#39;t rely
on catching console output&lt;/li&gt;
&lt;/ul&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Linters &amp; Stylecheck</title>
        <published>2024-12-02T09:00:00+00:00</published>
        <updated>2024-12-03T06:32:23+00:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://loki.dev/en/blog/linter-stylecheck/"/>
        <id>https://loki.dev/en/blog/linter-stylecheck/</id>
        
        <content type="html" xml:base="https://loki.dev/en/blog/linter-stylecheck/">&lt;h2 id=&quot;stylecheck&quot;&gt;Stylecheck&lt;/h2&gt;
&lt;p&gt;Let&#39;s start with the simpler issue: Stylechecks. It all boils down to maintaining
the same style over the full application. It doesn&#39;t really matter WHICH style, but it
must be the same!&lt;/p&gt;
&lt;h4 id=&quot;why&quot;&gt;Why?&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Pattern matching:&lt;/strong&gt; Regardless of developer level it makes it easier to recognize patterns if
they all are in the same structure&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Git Diffs:&lt;/strong&gt; Having multiple styles might result in a back-and-forth of changes each time
different styles collide&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tools:&lt;/strong&gt; Having a tool which can format your code and auto apply its rules may help a lot!&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&quot;tools&quot;&gt;Tools&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://prettier.io&quot;&gt;prettier&lt;/a&gt; is the more commonly known tool for this&lt;/li&gt;
&lt;li&gt;But I prefer &lt;a rel=&quot;external&quot; href=&quot;https://biomejs.dev/formatter&quot;&gt;biome&lt;/a&gt; as it is faster and can also take care of &lt;strong&gt;linting&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;It&#39;s also 97% compatible with prettier (same rules) - the differences &lt;a rel=&quot;external&quot; href=&quot;https://github.com/biomejs/biome/blob/main/crates/biome_js_formatter/report.md&quot;&gt;are reasonable&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;It can format: JavaScript, TypeScript, JSX, TSX, JSON, CSS and GraphQL&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;examples&quot;&gt;Examples&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Trivial&lt;/strong&gt;: using the same kind of quote &lt;code&gt;&#39;&lt;/code&gt; vs &lt;code&gt;&quot;&lt;/code&gt; where applicable or the same amount of spaces&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Readability&lt;/strong&gt;: Splitting multiple lines of import if the line gets too long&lt;/li&gt;
&lt;/ul&gt;
&lt;h6 id=&quot;trivial-spacing&quot;&gt;Trivial: Spacing&lt;/h6&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;typescript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-4&quot;&gt;// FROM&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;@&lt;/span&gt;&lt;span class=&quot;z-5&quot;&gt;Injectable&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-13&quot;&gt;export&lt;/span&gt;&lt;span class=&quot;z-1 z-i&quot;&gt; class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span class=&quot;z-5 z-u&quot;&gt;JwtService&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-5&quot;&gt;    decodeToken&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-14 z-i&quot;&gt;token&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-1 z-i&quot;&gt; string&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-1 z-i&quot;&gt; any&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-13&quot;&gt;        return&lt;/span&gt;&lt;span&gt; jwt.&lt;/span&gt;&lt;span class=&quot;z-5&quot;&gt;decode&lt;/span&gt;&lt;span&gt;(token);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-4&quot;&gt;// TO&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;@&lt;/span&gt;&lt;span class=&quot;z-5&quot;&gt;Injectable&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-13&quot;&gt;export&lt;/span&gt;&lt;span class=&quot;z-1 z-i&quot;&gt; class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span class=&quot;z-5 z-u&quot;&gt;JwtService&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-5&quot;&gt;    decodeToken&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-14 z-i&quot;&gt;token&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-1 z-i&quot;&gt; string&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-1 z-i&quot;&gt; any&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-13&quot;&gt;        return&lt;/span&gt;&lt;span&gt; jwt.&lt;/span&gt;&lt;span class=&quot;z-5&quot;&gt;decode&lt;/span&gt;&lt;span&gt;(token);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;or tabs vs spaces&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;typescript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-4&quot;&gt;// FROM 2 spaces&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-5&quot;&gt;describe&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt;&amp;#39;ReportsController&amp;#39;&lt;/span&gt;&lt;span&gt;, ()&lt;/span&gt;&lt;span class=&quot;z-1 z-i&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-1 z-i&quot;&gt;  let&lt;/span&gt;&lt;span&gt; controller&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span class=&quot;z-5 z-u&quot;&gt;ReportsController&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-5&quot;&gt;  beforeEach&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt;async&lt;/span&gt;&lt;span&gt; ()&lt;/span&gt;&lt;span class=&quot;z-1 z-i&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-4&quot;&gt;// TO tabs&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-5&quot;&gt;describe&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt;&amp;#39;ReportsController&amp;#39;&lt;/span&gt;&lt;span&gt;, ()&lt;/span&gt;&lt;span class=&quot;z-1 z-i&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-1 z-i&quot;&gt;	let&lt;/span&gt;&lt;span&gt; controller&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span class=&quot;z-5 z-u&quot;&gt;ReportsController&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-5&quot;&gt;	beforeEach&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt;async&lt;/span&gt;&lt;span&gt; ()&lt;/span&gt;&lt;span class=&quot;z-1 z-i&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;* Btw I&#39;m a spaces guy, but &lt;strong&gt;convention over sympathy!&lt;/strong&gt;&lt;/p&gt;
&lt;h6 id=&quot;trivial-newlines&quot;&gt;Trivial: Newlines&lt;/h6&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;typescript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-4&quot;&gt;// FROM&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;@&lt;/span&gt;&lt;span class=&quot;z-5&quot;&gt;Module&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  controllers: [IntegrationController],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  providers: [IntegrationService, PrismaService, ScrapingService, CustomerIoService, JwtService]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;})&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-4&quot;&gt;// TO&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;@&lt;/span&gt;&lt;span class=&quot;z-5&quot;&gt;Module&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	controllers: [IntegrationController],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	providers: [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;		IntegrationService,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;		PrismaService,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;		ScrapingService,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;		CustomerIoService,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;		JwtService,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;})&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There is also the analyzer which sorts imports in a predictable manner. This is optional,
but gives each header of a file the same order. For details about the order you can look
&lt;a rel=&quot;external&quot; href=&quot;https://biomejs.dev/assist/actions/organize-imports/&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;linting&quot;&gt;Linting&lt;/h2&gt;
&lt;p&gt;This is giving us a headstart on customers finding bugs. Each linter obviously has a small
false positive rate, but the ruleset itself makes sure to exclude some classes of errors before they
even happen!&lt;/p&gt;
&lt;p&gt;The full ruleset of biome is &lt;a rel=&quot;external&quot; href=&quot;https://biomejs.dev/linter/javascript/rules/&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It has some very trivial checks like &lt;a rel=&quot;external&quot; href=&quot;https://biomejs.dev/linter/rules/no-distracting-elements/&quot;&gt;no distracting elements&lt;/a&gt;
which prohibits &lt;a rel=&quot;external&quot; href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTML/Element/marquee&quot;&gt;marquee&lt;/a&gt; and &lt;a rel=&quot;external&quot; href=&quot;https://developer.mozilla.org/en-US/docs/Glossary/blink_element&quot;&gt;blink&lt;/a&gt;, but also far more advanced checks.&lt;/p&gt;
&lt;p&gt;My favorite category is &lt;a rel=&quot;external&quot; href=&quot;https://biomejs.dev/linter/javascript/rules/#nursery&quot;&gt;nursery&lt;/a&gt; which contains legacy
JS features which are surpassed by advanced Syntax.&lt;/p&gt;
&lt;h4 id=&quot;linter-warnings-examples&quot;&gt;Linter warnings examples&lt;/h4&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;typescript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-4&quot;&gt;// 1. no banned types&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;code&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt;-&lt;/span&gt;&lt;span&gt;block.ts:&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt;1&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt;10&lt;/span&gt;&lt;span&gt; lint&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt;/&lt;/span&gt;&lt;span&gt;complexity&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt;/&lt;/span&gt;&lt;span&gt;noBannedTypes ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ✖ Don’t use ’{}’ &lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt;as&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span class=&quot;z-5 z-u&quot;&gt;a&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span class=&quot;z-5 z-u&quot;&gt;type&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-13&quot;&gt;  &amp;gt;&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt; 1&lt;/span&gt;&lt;span&gt; │ &lt;/span&gt;&lt;span class=&quot;z-1 z-i&quot;&gt;const&lt;/span&gt;&lt;span&gt; n&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt;:&lt;/span&gt;&lt;span&gt; {}&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt; 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      │          &lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt;^^&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-4&quot;&gt;// 2. complexity&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;code&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt;-&lt;/span&gt;&lt;span&gt;block.js:&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt;1&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt;10&lt;/span&gt;&lt;span&gt; lint&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt;/&lt;/span&gt;&lt;span&gt;complexity&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt;/&lt;/span&gt;&lt;span&gt;noExcessiveCognitiveComplexity ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ⚠ Excessive complexity&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; of&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt; 21&lt;/span&gt;&lt;span class=&quot;z-5&quot;&gt; detected&lt;/span&gt;&lt;span&gt; (max: &lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt;15&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-13&quot;&gt;  &amp;gt;&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt; 1&lt;/span&gt;&lt;span&gt; │ &lt;/span&gt;&lt;span class=&quot;z-1 z-i&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;z-5&quot;&gt; tooComplex&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      │          &lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt;^^^^^^^^^^&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-6&quot;&gt;    2&lt;/span&gt;&lt;span&gt; │     &lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt;for&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-1 z-i&quot;&gt;let&lt;/span&gt;&lt;span&gt; x&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt; 0&lt;/span&gt;&lt;span&gt;; x&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; &amp;lt;&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt; 10&lt;/span&gt;&lt;span&gt;; x&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt;++&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-6&quot;&gt;    3&lt;/span&gt;&lt;span&gt; │         &lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt;for&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-1 z-i&quot;&gt;let&lt;/span&gt;&lt;span&gt; y&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt; 0&lt;/span&gt;&lt;span&gt;; y&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; &amp;lt;&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt; 10&lt;/span&gt;&lt;span&gt;; y&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt;++&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-4&quot;&gt;// 3. for .. of  vs  Array.forEach&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;code&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt;-&lt;/span&gt;&lt;span&gt;block.js:&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt;1&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt;1&lt;/span&gt;&lt;span&gt; lint&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt;/&lt;/span&gt;&lt;span&gt;complexity&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt;/&lt;/span&gt;&lt;span&gt;noForEach ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ✖ Prefer for…&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt;of&lt;/span&gt;&lt;span&gt; instead&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; of&lt;/span&gt;&lt;span&gt; forEach.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-13&quot;&gt;  &amp;gt;&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt; 1&lt;/span&gt;&lt;span&gt; │ els.&lt;/span&gt;&lt;span class=&quot;z-5&quot;&gt;forEach&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span class=&quot;z-14 z-i&quot;&gt;el&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-1 z-i&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      │ &lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt;^^^^^^^^^^^^^^^^^^^^^&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-13&quot;&gt;  &amp;gt;&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt; 2&lt;/span&gt;&lt;span&gt; │   &lt;/span&gt;&lt;span class=&quot;z-5&quot;&gt;f&lt;/span&gt;&lt;span&gt;(el);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-13&quot;&gt;  &amp;gt;&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt; 3&lt;/span&gt;&lt;span&gt; │ })&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      │ &lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt;^^&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-4&quot;&gt;// 4. no useless catch&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;code&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt;-&lt;/span&gt;&lt;span&gt;block.js:&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt;4&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt;5&lt;/span&gt;&lt;span&gt; lint&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt;/&lt;/span&gt;&lt;span&gt;complexity&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt;/&lt;/span&gt;&lt;span&gt;noUselessCatch ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ✖ The&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; catch&lt;/span&gt;&lt;span&gt; clause that only rethrows the original error is useless.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-6&quot;&gt;    2&lt;/span&gt;&lt;span&gt; │     &lt;/span&gt;&lt;span class=&quot;z-5&quot;&gt;doSomething&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-6&quot;&gt;    3&lt;/span&gt;&lt;span&gt; │ }&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; catch&lt;/span&gt;&lt;span&gt;(e) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-13&quot;&gt;  &amp;gt;&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt; 4&lt;/span&gt;&lt;span&gt; │     &lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt;throw&lt;/span&gt;&lt;span&gt; e;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      │     &lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt;^^^^^^^^&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-4&quot;&gt;// 5. no uselss ternary&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-1 z-i&quot;&gt;const&lt;/span&gt;&lt;span&gt; a&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; =&lt;/span&gt;&lt;span&gt; foo&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; ===&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt; 1&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; ?&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt; false&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; :&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt; true&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span class=&quot;z-4&quot;&gt; // just use foo !== 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;code&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt;-&lt;/span&gt;&lt;span&gt;block.js:&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt;1&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt;9&lt;/span&gt;&lt;span&gt; lint&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt;/&lt;/span&gt;&lt;span&gt;complexity&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt;/&lt;/span&gt;&lt;span&gt;noUselessTernary  FIXABLE  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ✖ Unnecessary use&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; of&lt;/span&gt;&lt;span&gt; boolean literals&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; in&lt;/span&gt;&lt;span&gt; conditional expression.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-13&quot;&gt;  &amp;gt;&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt; 1&lt;/span&gt;&lt;span&gt; │ &lt;/span&gt;&lt;span class=&quot;z-1 z-i&quot;&gt;const&lt;/span&gt;&lt;span&gt; a&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; =&lt;/span&gt;&lt;span&gt; foo&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; ===&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt; 1&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; ?&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt; false&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; :&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt; true&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      │           &lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt;^^^^^^^^^^^^^^^^^^^^^^^^&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;Is trivial, but don&#39;t use useless/harmful types&lt;/li&gt;
&lt;li&gt;Is using the Cyclomatic Complexity to give upper boundaries of complex code. Is code too complex -&amp;gt; make it simpler. Google for &lt;a rel=&quot;external&quot; href=&quot;https://en.wikipedia.org/wiki/Cyclomatic_complexity&quot;&gt;McCabe Metric&lt;/a&gt; to find more details.
Depending on the language we should try to be &amp;lt;10 (10 possible ways of data routes through a function)
and MUST be below 15, as this would be hard to maintain.&lt;/li&gt;
&lt;li&gt;Language features: &lt;code&gt;for .. of ..&lt;/code&gt; is easier to read than &lt;code&gt;Array.forEach(...)&lt;/code&gt;. Also a performance issue!&lt;/li&gt;
&lt;li&gt;obviously almost dead code which doesn&#39;t serve any function/purpose&lt;/li&gt;
&lt;li&gt;Is a great example of &lt;code&gt;FIXABLE&lt;/code&gt; lints: This one can be auto corrected!&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;... Aaaaand a lot more! We should use linters at every stage of our development process. The earlier the better and we WILL be using them soon in our processes.&lt;/p&gt;
&lt;h2 id=&quot;biome-in-your-editor&quot;&gt;Biome in your editor&lt;/h2&gt;
&lt;p&gt;For the huge amount of reasons we should use biome wherever possible.&lt;/p&gt;
&lt;p&gt;For the most common editors here the extension/plugin so that you can benefit directly. Just make sure the red squiggly lines disappear and autoformatting is active&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;General and Vim/Emacs/etc.:&lt;/strong&gt; &lt;a rel=&quot;external&quot; href=&quot;https://biomejs.dev/reference/language-server/#use-the-daemon-with-the-binary&quot;&gt;biomejs.dev&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;webstorm:&lt;/strong&gt; &lt;a rel=&quot;external&quot; href=&quot;https://plugins.jetbrains.com/plugin/22761-biome&quot;&gt;plugins.jetbrains.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;VSCode:&lt;/strong&gt; &lt;a rel=&quot;external&quot; href=&quot;https://marketplace.visualstudio.com/items?itemName=biomejs.biome&quot;&gt;VSCode Marketplace&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CLI:&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;npm install --save-dev --save-exact @biomejs/biome&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Format: &lt;code&gt;biome format FOLDER/FILE&lt;/code&gt; or &lt;code&gt;biome format --write FOLDER/FILE&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Lint: &lt;code&gt;biome lint FOLDER/FILE&lt;/code&gt; or &lt;code&gt;biome lint --write FOLDER/FILE&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;All checks (format and lint): &lt;code&gt;biome check FOLDER/FILE&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Afterwards be sure to:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Set autoformat on&lt;/li&gt;
&lt;li&gt;Actually read and see the errors supplied by the linter&lt;/li&gt;
&lt;/ol&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Magic Numbers</title>
        <published>2024-11-22T10:00:00+00:00</published>
        <updated>2024-11-24T10:52:23+00:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://loki.dev/en/blog/magic-numbers/"/>
        <id>https://loki.dev/en/blog/magic-numbers/</id>
        
        <content type="html" xml:base="https://loki.dev/en/blog/magic-numbers/">&lt;p&gt;Magic numbers are numbers/string literals which are used as const and where the actual use is not directly known.&lt;/p&gt;
&lt;p&gt;This is part of a series I do for work and is intended for junior developers and external people.
When the code is generic enough I use real life examples from work, otherwise I&#39;ll just invent something ;).&lt;/p&gt;
&lt;p&gt;The examples are in typescript as it is readable by almost all developers, but obviously the concepts are similar in other languages. Python for example doesn&#39;t really have &quot;constants&quot;, but you can combine pure UPPERCASE with &lt;code&gt;typing.Final&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-13&quot;&gt;from&lt;/span&gt;&lt;span&gt; typing&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; import&lt;/span&gt;&lt;span&gt; Final&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-6&quot;&gt;MAX_KNOWN_POWER_SIZE&lt;/span&gt;&lt;span&gt;: Final[&lt;/span&gt;&lt;span class=&quot;z-1 z-i&quot;&gt;int&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt; 9000&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This won&#39;t catch resetting the variable at runtime, but will complain when run with &lt;a rel=&quot;external&quot; href=&quot;https://astral.sh/ruff&quot;&gt;ruff&lt;/a&gt; or &lt;a rel=&quot;external&quot; href=&quot;https://www.mypy-lang.org&quot;&gt;mypy&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;examples&quot;&gt;Examples&lt;/h2&gt;
&lt;h3 id=&quot;http-status-codes&quot;&gt;HTTP Status Codes&lt;/h3&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;typescript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-4&quot;&gt;// DONT&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-13&quot;&gt;if&lt;/span&gt;&lt;span&gt; (error.response.status&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; ===&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt; 409&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-13&quot;&gt;    ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-4&quot;&gt;// DO&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-13&quot;&gt;import&lt;/span&gt;&lt;span&gt; { HttpStatus }&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; from&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt; &amp;#39;@nestjs/common&amp;#39;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-13&quot;&gt;if&lt;/span&gt;&lt;span&gt; (error.response.status&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; ===&lt;/span&gt;&lt;span&gt; HttpStatus.CONFLICT) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-13&quot;&gt;    ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now even if we don&#39;t know all the http status codes by heart, we know we have some kind of conflict error without adding comments/complexity&lt;/p&gt;
&lt;h3 id=&quot;timeout&quot;&gt;Timeout&lt;/h3&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;typescript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-4&quot;&gt;// DONT&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-5&quot;&gt;setTimeout&lt;/span&gt;&lt;span&gt;(()&lt;/span&gt;&lt;span class=&quot;z-1 z-i&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-5&quot;&gt;    refreshData&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;},&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt; 300000&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;span class=&quot;z-4&quot;&gt; // What does this number mean?&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-4&quot;&gt;// DO&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-1 z-i&quot;&gt;const&lt;/span&gt;&lt;span&gt; REFRESH_INTERVAL_MS&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt; 300000&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span class=&quot;z-4&quot;&gt; // 5 minutes in milliseconds&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-5&quot;&gt;setTimeout&lt;/span&gt;&lt;span&gt;(()&lt;/span&gt;&lt;span class=&quot;z-1 z-i&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-5&quot;&gt;    refreshData&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}, REFRESH_INTERVAL_MS);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&quot;limits-business-logic&quot;&gt;Limits: business logic&lt;/h3&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;typescript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-4&quot;&gt;// DONT&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-13&quot;&gt;if&lt;/span&gt;&lt;span&gt; (user.age&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; &amp;gt;=&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt; 18&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; &amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; cart.total&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; &amp;gt;=&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt; 50&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-5&quot;&gt;    applyDiscount&lt;/span&gt;&lt;span&gt;(cart);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-4&quot;&gt;// DO&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-1 z-i&quot;&gt;const&lt;/span&gt;&lt;span&gt; MINIMUM_AGE_FOR_DISCOUNT&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt; 18&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-1 z-i&quot;&gt;const&lt;/span&gt;&lt;span&gt; MINIMUM_PURCHASE_FOR_DISCOUNT&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt; 50&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-13&quot;&gt;if&lt;/span&gt;&lt;span&gt; (user.age&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; &amp;gt;=&lt;/span&gt;&lt;span&gt; MINIMUM_AGE_FOR_DISCOUNT&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; &amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    cart.total&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; &amp;gt;=&lt;/span&gt;&lt;span&gt; MINIMUM_PURCHASE_FOR_DISCOUNT) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-5&quot;&gt;    applyDiscount&lt;/span&gt;&lt;span&gt;(cart);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&quot;limits-password&quot;&gt;Limits: password&lt;/h3&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;typescript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-4&quot;&gt;// DONT&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-13&quot;&gt;if&lt;/span&gt;&lt;span&gt; (password.length&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; &amp;lt;&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt; 8&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; ||&lt;/span&gt;&lt;span&gt; password.length&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; &amp;gt;&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt; 128&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-13&quot;&gt;    throw new&lt;/span&gt;&lt;span class=&quot;z-5&quot;&gt; Error&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt;&amp;#39;Invalid password length&amp;#39;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-4&quot;&gt;// DO&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-1 z-i&quot;&gt;const&lt;/span&gt;&lt;span&gt; PASSWORD_LENGTH_MIN&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt; 8&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-1 z-i&quot;&gt;const&lt;/span&gt;&lt;span&gt; PASSWORD_LENGTH_MAX&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt; 128&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-13&quot;&gt;if&lt;/span&gt;&lt;span&gt; (password.length&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; &amp;lt;&lt;/span&gt;&lt;span&gt; PASSWORD_LENGTH_MIN&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; ||&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    password.length&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; &amp;gt;&lt;/span&gt;&lt;span&gt; PASSWORD_LENGTH_MAX) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-13&quot;&gt;    throw new&lt;/span&gt;&lt;span class=&quot;z-5&quot;&gt; Error&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt;&amp;#39;Invalid password length&amp;#39;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Python Unplugged: Mining for hidden batteries</title>
        <published>2024-07-10T00:00:00+00:00</published>
        <updated>2024-07-15T13:42:00+00:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://loki.dev/en/vortraege/europython2024-python-batteries-included/"/>
        <id>https://loki.dev/en/vortraege/europython2024-python-batteries-included/</id>
        
        <content type="html" xml:base="https://loki.dev/en/vortraege/europython2024-python-batteries-included/">&lt;div class=&quot;youtube&quot;&gt;
  &lt;a href=&quot;https://www.youtube.com/watch?v=W5ZOFvGmmbo&quot; data-youtube=&quot;W5ZOFvGmmbo&quot; rel=&quot;noopener&quot;&gt;[ ▶ load video ]&lt;/a&gt;
  &lt;small&gt;One click loads the player from YouTube, which sends your IP address to Google.&lt;/small&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Slides (PDF):&lt;/strong&gt; &lt;a rel=&quot;external&quot; href=&quot;https://loki.dev/slides/vortraege/ep2024-python-unplugged/europython2024-python-unplugged-slides.pdf&quot;&gt;Download PDF&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Repository:&lt;/strong&gt; &lt;a rel=&quot;external&quot; href=&quot;https://github.com/dev-loki/europython2024-python-unplugged-slides&quot;&gt;github.com/dev-loki/europython2024-python-unplugged-slides&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;what-is-this-about&quot;&gt;What is this about?&lt;/h2&gt;
&lt;p&gt;A beginners talk about all the unknown modules of the vanilla/standard python library.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Python is required and at least a basic understanding of python&lt;/li&gt;
&lt;li&gt;No &lt;code&gt;pip install PACKAGE&lt;/code&gt; necessary &amp;lt;3&lt;/li&gt;
&lt;li&gt;Mainly around &lt;code&gt;itertools&lt;/code&gt;, &lt;code&gt;collections&lt;/code&gt; and &lt;code&gt;functools&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Sprinkles of &lt;code&gt;contextlib&lt;/code&gt;, &lt;code&gt;urllib&lt;/code&gt; and other packages&lt;/li&gt;
&lt;li&gt;Some stuff requires &lt;code&gt;python3.12&lt;/code&gt; (e.g. &lt;code&gt;batched&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The repository contains some more code examples, which are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;more detailed&lt;/li&gt;
&lt;li&gt;heavily commented&lt;/li&gt;
&lt;li&gt;&quot;complete&quot; and executable&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;(english only)&lt;/em&gt;&lt;/p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Faster Workflow with Test-driven Development</title>
        <published>2022-04-12T00:00:00+00:00</published>
        <updated>2023-01-29T12:07:00+00:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://loki.dev/en/vortraege/tdd/"/>
        <id>https://loki.dev/en/vortraege/tdd/</id>
        
        <content type="html" xml:base="https://loki.dev/en/vortraege/tdd/">&lt;p&gt;&lt;em&gt;(english only)&lt;/em&gt;&lt;/p&gt;
&lt;h2 id=&quot;original-text&quot;&gt;Original Text&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;In this talk I will show you, that TDD is in fact not a process for testing, but a helpful way of developing, taking away a lot of the annoying &quot;lets open the application and see if this still works&quot; part of development.&lt;/p&gt;
&lt;p&gt;After this talk you will know how to apply TDD to your workflow and use it to your benefit. We will clean up the popular misconception, that TDD is a technique for testing and will have a working prototype at the end.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;addendum&quot;&gt;Addendum&lt;/h2&gt;
&lt;p&gt;In this talk I showed how to do a minimal url shortener with fastapi/pytest and in style of TDD. It was almost successful, as I did not expect so many questions ;).&lt;/p&gt;
&lt;p&gt;In general I was &lt;strong&gt;far&lt;/strong&gt; too nervous to do a good job, and even though people complimented me, I think I could have done a much better job!&lt;/p&gt;
&lt;p&gt;Learning for me: 1h 30min was too little time to dive deep into the TDD methodology. 2h would have been perfect.&lt;/p&gt;
&lt;p&gt;My own expectation to improve this after I changed jobs with a lot of new stuff to learn.&lt;/p&gt;
&lt;h2 id=&quot;will-there-be-a-proper-course&quot;&gt;Will there be a proper course?&lt;/h2&gt;
&lt;p&gt;Most likely, but for non conference usage I would rather do a more intense 2-4h course where I&#39;d also cover issues like database migration and deployment.&lt;/p&gt;
&lt;h2 id=&quot;but-can-i-see-it&quot;&gt;But can I see it?&lt;/h2&gt;
&lt;p&gt;Yes, if you must :D. But beware: My english was very rusty as I hadn&#39;t spoken english for 1-2 years before that. I&#39;m super glad it got better again with my new job :)&lt;/p&gt;
&lt;div class=&quot;youtube&quot;&gt;
  &lt;a href=&quot;https://www.youtube.com/watch?v=_6AaG-cgMjk&quot; data-youtube=&quot;_6AaG-cgMjk&quot; rel=&quot;noopener&quot;&gt;[ ▶ load video ]&lt;/a&gt;
  &lt;small&gt;One click loads the player from YouTube, which sends your IP address to Google.&lt;/small&gt;
&lt;/div&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Advanced GIT: Git Worktree</title>
        <published>2021-11-25T00:00:00+00:00</published>
        <updated>2021-11-25T00:00:00+00:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://loki.dev/en/blog/advanced-git-worktree/"/>
        <id>https://loki.dev/en/blog/advanced-git-worktree/</id>
        
        <content type="html" xml:base="https://loki.dev/en/blog/advanced-git-worktree/">&lt;p&gt;One of the coolest features in Git. And simultaneously one of the least used.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The classic situation:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;You&#39;re in the middle of working on &lt;code&gt;feature/blubb_feature&lt;/code&gt; and then a colleague sends an MR asking you to quickly review &lt;code&gt;hotfix/make_that_users_dont_die_immediately&lt;/code&gt; so it can go live ASAP. Unfortunately the branch is too complex to review entirely in GitLab.&lt;/p&gt;
&lt;p&gt;So you actually need to check it out and dive deep into the code...&lt;/p&gt;
&lt;h3 id=&quot;the-classic-approach&quot;&gt;The classic approach&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;git stash&lt;/code&gt; your changes&lt;/li&gt;
&lt;li&gt;&lt;code&gt;git checkout hotfix/...&lt;/code&gt; to look at the branch and go through everything&lt;/li&gt;
&lt;li&gt;Approve or reject&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;a-much-better-approach&quot;&gt;A much better approach&lt;/h3&gt;
&lt;p&gt;You&#39;ve set up &lt;code&gt;git worktree&lt;/code&gt; (it&#39;s built into git) and have a folder structure like this:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;project_name/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt; - main/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt; - develop/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt; - feature/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;   - blubb_feature/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt; - hotfix/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;   - make_that_users_dont_die_immediately/&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Each folder acts like its own Git repository, but they all share the same Git metadata. Your current state stays untouched. A &lt;code&gt;git worktree add BRANCH_NAME&lt;/code&gt; creates a folder pointing to that branch for you.&lt;/p&gt;
&lt;h2 id=&quot;so-how-does-it-work&quot;&gt;So how does it work?&lt;/h2&gt;
&lt;p&gt;Super simple, really :).&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;mkdir my_project&lt;/code&gt; (create project directory) and navigate into it: &lt;code&gt;cd my_project&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Clone the repository (note the first special step): &lt;code&gt;git clone --bare GIT_URL .bare&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;We only clone the bare repository without checking out a branch&lt;/li&gt;
&lt;li&gt;These metadata are stored in the &lt;code&gt;.bare&lt;/code&gt; folder (the leading &lt;code&gt;.&lt;/code&gt; hides it on macOS/Linux)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Create a file &lt;code&gt;my_project/.git&lt;/code&gt; with the following line:&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;gitdir: ./.bare&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;This tells Git where to find the repository metadata&lt;/li&gt;
&lt;li&gt;From here, worktree works perfectly:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;git worktree add main&lt;/code&gt; adds our main branch&lt;/li&gt;
&lt;li&gt;&lt;code&gt;git worktree add feature/ABC_new_stuff_to_play feature/ABC_new_stuff_to_play&lt;/code&gt; to check out or create branches with &lt;code&gt;/&lt;/code&gt; in the name&lt;/li&gt;
&lt;li&gt;You can also freely choose the folder location in the filesystem. The format is:
&lt;code&gt;git worktree add PATH BRANCHNAME&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;more-useful-commands&quot;&gt;More useful commands&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Delete a branch:
&lt;ul&gt;
&lt;li&gt;Option 1: &lt;code&gt;rm -r BRANCH_NAME&lt;/code&gt; then &lt;code&gt;git worktree prune&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Option 2: &lt;code&gt;git worktree list&lt;/code&gt; lists all worktree branches&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Move a branch: &lt;code&gt;git worktree move SOURCE DESTINATION&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Stress-free Programming with Clean Code I</title>
        <published>2021-09-02T00:00:00+00:00</published>
        <updated>2022-05-30T14:30:00+00:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://loki.dev/en/vortraege/clean-code-i/"/>
        <id>https://loki.dev/en/vortraege/clean-code-i/</id>
        
        <content type="html" xml:base="https://loki.dev/en/vortraege/clean-code-i/">&lt;p&gt;&lt;strong&gt;Talk updated:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Translated into English&lt;/li&gt;
&lt;li&gt;Errors corrected&lt;/li&gt;
&lt;li&gt;Dart-specific parts removed&lt;/li&gt;
&lt;li&gt;Adapted to &lt;a rel=&quot;external&quot; href=&quot;https://mara.solutions&quot;&gt;new employer&lt;/a&gt; :)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;slides&quot;&gt;Slides&lt;/h2&gt;
&lt;iframe src=&quot;https://loki.dev/slides/vortraege/clean-code-i/&quot; style=&quot;width:100%;height:400px&quot; title=&quot;Clean Code I Slides&quot;&gt;
&lt;/iframe&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Voting Aids for the 2021 German Federal Election</title>
        <published>2021-08-23T00:00:00+00:00</published>
        <updated>2021-09-05T14:00:00+00:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://loki.dev/en/blog/wahlhelfer-btw-2021/"/>
        <id>https://loki.dev/en/blog/wahlhelfer-btw-2021/</id>
        
        <content type="html" xml:base="https://loki.dev/en/blog/wahlhelfer-btw-2021/">&lt;p&gt;&lt;em&gt;Note: This article is about the 2021 German federal election (Bundestagswahl). The tools listed are German-language voting aids designed to help German voters understand party positions.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Since I share some of the criticism of the Wahl-O-Mat (Germany&#39;s official party matcher), I want to introduce the alternatives and complements here.&lt;/p&gt;
&lt;p&gt;For completeness, my personal criticisms of the Wahl-O-Mat:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;One-dimensional → there&#39;s no nuance or &quot;not like this, but like this:&quot; option&lt;/li&gt;
&lt;li&gt;It only reflects what politicians say, not what they do. It would be nice if those two things didn&#39;t diverge so often.&lt;/li&gt;
&lt;li&gt;Only a limited set of political theses is covered.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All these points have some merit, since of course you can&#39;t go infinitely deep.&lt;/p&gt;
&lt;p&gt;Still, I find the basic concept of the Wahl-O-Mat super useful, because it enables discussion and can establish a rough &quot;alignment&quot;. If I have 2% overlap with a party, the chance that I should vote for them is very very small.&lt;/p&gt;
&lt;p&gt;Now there isn&#39;t just the Wahl-O-Mat, but also various alternative voting aids with different angles, which I&#39;d like to collect and present here.&lt;/p&gt;
&lt;h2 id=&quot;deinwal-de&quot;&gt;DeinWal.de&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Link:&lt;/strong&gt; &lt;a rel=&quot;external&quot; href=&quot;http://deinwal.de&quot;&gt;deinwal.de&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;del&gt;The current version for the 2021 election wasn&#39;t online yet when I wrote this&lt;/del&gt;, but the concept of deinwal.de is my absolute favourite! Rather than comparing party manifestos or what parties currently claim to stand for, it compares &lt;strong&gt;past votes in parliament&lt;/strong&gt; against your own views.&lt;/p&gt;
&lt;p&gt;Even before the BTW21 version launched, you could work through older elections to see how the major parties actually voted back then. Tip: try the 2019 European election and BTW2017.&lt;/p&gt;
&lt;p&gt;Every question comes with detailed information and direct links to the parliamentary press release, abgeordnetenwatch, and Wikipedia.&lt;/p&gt;
&lt;p&gt;Every vote is rated Yes/No/Abstention and placed in context at the end. Weighting is not possible.&lt;/p&gt;
&lt;p&gt;Despite the lack of smaller parties, a clear recommendation, especially for politically engaged users who want a lot of background information.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Edit:&lt;/strong&gt; The newest version of &lt;strong&gt;deinWal&lt;/strong&gt; is finally online. The core concept is unchanged. Unfortunately, the main disadvantage remains: only established parties are included.&lt;/p&gt;
&lt;h2 id=&quot;wahlswiper&quot;&gt;Wahlswiper&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Link:&lt;/strong&gt; &lt;a rel=&quot;external&quot; href=&quot;https://www.voteswiper.org/de&quot;&gt;voteswiper.org&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Here you can, Tinder-style, swipe political statements good/bad and at the end find out your overlap with the parties.&lt;/p&gt;
&lt;p&gt;The good news: 35 of 39 parties are included, so you&#39;ll find smaller parties like &lt;a rel=&quot;external&quot; href=&quot;https://liebe-ep.eu&quot;&gt;LIEBE&lt;/a&gt; or the &lt;a rel=&quot;external&quot; href=&quot;https://www.lobbyistenfuerkinder.de&quot;&gt;Party for Children, Young People and Families&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;As a bonus, every question comes with a short video and text explaining the topic objectively. Questions can be rated with Agreement/Rejection/Abstention. Topics that matter especially to you can be double-weighted.&lt;/p&gt;
&lt;p&gt;Also a good recommendation for beginners, especially for people who don&#39;t usually follow politics closely.&lt;/p&gt;
&lt;h2 id=&quot;sozial-o-mat&quot;&gt;Sozial-O-Mat&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Link:&lt;/strong&gt; &lt;a rel=&quot;external&quot; href=&quot;https://www.sozial-o-mat.de&quot;&gt;sozial-o-mat.de&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Exclusively focused on social policy, the Sozial-O-Mat compares your views with those of the parties purely on social issues. It covers four topic areas:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Work&lt;/li&gt;
&lt;li&gt;Health&lt;/li&gt;
&lt;li&gt;Family and Children&lt;/li&gt;
&lt;li&gt;Migration&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Operated by the Diakonie (a German social welfare organisation). Each of the four topics opens with a short introduction highlighting its relevance.&lt;/p&gt;
&lt;p&gt;There are no videos or explanations here. Each thesis is backed by a &quot;story&quot;, a personal narrative to help frame the issue. Interesting concept, though it does nudge the reader in a certain direction.&lt;/p&gt;
&lt;p&gt;Each thesis can be rated Positive/Neutral/Negative, or skipped to remove it from the equation.&lt;/p&gt;
&lt;p&gt;Good for a rough overview; I&#39;d treat it mainly as a supplement.&lt;/p&gt;
&lt;h2 id=&quot;steuer-o-mat&quot;&gt;Steuer-O-Mat&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Link:&lt;/strong&gt; &lt;a rel=&quot;external&quot; href=&quot;https://www.steuer-o-mat.de/start&quot;&gt;smartsteuer&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;One of the shortest and most direct voting aids: the Steuer-O-Mat very straightforwardly calculates your approximate net income under each party&#39;s tax plans.&lt;/p&gt;
&lt;p&gt;Enter your gross salary, optionally your married partner&#39;s, and get a pure tax-based voting recommendation.&lt;/p&gt;
&lt;h2 id=&quot;agrar-o-mat&quot;&gt;Agrar-O-Mat&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Link:&lt;/strong&gt; &lt;a rel=&quot;external&quot; href=&quot;https://www.agrarheute.com/agraromat&quot;&gt;agrarheute.com&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Since, as we all know, food obviously only comes from the supermarket, this voting aid is strictly for farmers ;).&lt;/p&gt;
&lt;p&gt;As the name suggests, it focuses exclusively on agricultural policy and adjacent topics. No additional background information.&lt;/p&gt;
&lt;p&gt;Each thesis can be agreed with, rated neutrally, or rejected. Important topics can be double-weighted.&lt;/p&gt;
&lt;p&gt;Interesting as a curiosity and supplement, but I personally lack the expertise to go really deep into agricultural policy.&lt;/p&gt;
&lt;h2 id=&quot;klima-wahl-check&quot;&gt;Klima-Wahl-Check&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Link:&lt;/strong&gt; &lt;a rel=&quot;external&quot; href=&quot;https://klimawahlcheck.org&quot;&gt;klimawahlcheck.org&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I also write about climate &lt;a href=&quot;https://loki.dev/en/blog/klimakatastrophe-i-was-ist/&quot;&gt;here on the blog&lt;/a&gt; and will definitely cover it much more, but specifically climate-focused there&#39;s the Klimawahlcheck.&lt;/p&gt;
&lt;p&gt;It&#39;s divided into 6 categories:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Energy&lt;/li&gt;
&lt;li&gt;Mobility&lt;/li&gt;
&lt;li&gt;Industry&lt;/li&gt;
&lt;li&gt;Buildings&lt;/li&gt;
&lt;li&gt;Climate Justice &amp;amp; Climate Targets&lt;/li&gt;
&lt;li&gt;Agriculture &amp;amp; Biodiversity&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Personally one of the most important tools for me, because it&#39;s not just about &quot;the climate&quot; in a naive sense, but covers all the interconnected social, economic, and scientific topics as well.&lt;/p&gt;
&lt;p&gt;Unlike the others, each topic is rated on a 5-step scale from &lt;span style=&quot;background:red;color:yellow;padding:2px 4px;border-radius:3px&quot;&gt;red&lt;/span&gt; to &lt;span style=&quot;background:green;color:white;padding:2px 4px;border-radius:3px&quot;&gt;green&lt;/span&gt;. Topics can also be skipped. Each thesis comes with a short background summary.&lt;/p&gt;
&lt;p&gt;Afterwards you see an overview diagram placing parties to the left/right/aligned with you. After completing all categories there&#39;s a full evaluation.&lt;/p&gt;
&lt;p&gt;As I said, this is the most important tool for me personally, because climate decisions have significant knock-on effects for the economy, quality of life, and even survival.&lt;/p&gt;
&lt;p&gt;Clear recommendation. Obviously ;).&lt;/p&gt;
&lt;h2 id=&quot;waehlbar2021&quot;&gt;Waehlbar2021&lt;/h2&gt;
&lt;p&gt;Link: &lt;a rel=&quot;external&quot; href=&quot;https://waehlbar2021.de&quot;&gt;waehlbar2021.de&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Going well beyond a simple matcher, &lt;strong&gt;waehlbar2021&lt;/strong&gt; provides comprehensive background information AND detailed positions from almost all parties. CDU/CSU is predictably missing. Apparently they figured that not responding at all means nobody can hold them to anything.&lt;/p&gt;
&lt;p&gt;The best feature is seeing the MPs from your own constituency directly, with their voting records. Enter your postcode and get all local candidates suggested, including their alignment on individual theses.&lt;/p&gt;
&lt;p&gt;MPs who haven&#39;t yet responded can be messaged directly here asking for transparency.&lt;/p&gt;
&lt;p&gt;A clear recommendation here too.&lt;/p&gt;
&lt;h2 id=&quot;wahl-o-mat&quot;&gt;Wahl-o-Mat&lt;/h2&gt;
&lt;p&gt;Link: &lt;a rel=&quot;external&quot; href=&quot;https://web.archive.org/web/20241113152515/https://www.wahl-o-mat.de/bundestagswahl2021/app/main_app.html&quot;&gt;wahl-o-mat.de&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Finally it arrived. As the best-known voting aid, the Wahl-O-Mat interviewed 39 of the 40 competing parties on numerous questions.&lt;/p&gt;
&lt;p&gt;I have to admit this year&#39;s version is slightly better than before. While there&#39;s still no detailed background for questions or enough nuance, we now have the ability to compare &lt;strong&gt;all&lt;/strong&gt; parties directly (&lt;a rel=&quot;external&quot; href=&quot;https://www.volksverpetzer.de/schwer-verpetzt/volt-wahl-o-mat/&quot;&gt;thank you Volt&lt;/a&gt; for that), plus the option to play around with the results and weighting afterwards.&lt;/p&gt;
&lt;p&gt;I personally got a different result than from the other tools, which at least broadens the perspective.&lt;/p&gt;
&lt;p&gt;The questions are also quite varied and cover different areas, though of course familiar topics like a motorway speed limit pop up almost everywhere.&lt;/p&gt;
&lt;p&gt;A clear recommendation here too.&lt;/p&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;In my view, every voter should work through at least one (but ideally all) of these tools before the election, to get a feeling for exactly what each party stands for.&lt;/p&gt;
&lt;p&gt;One of the biggest problems in our democracy (in my opinion) is that parties and politicians have learned to speak without making any concrete commitments. This leads many people to vote for parties that are completely unsuitable for their own interests. The voting aids don&#39;t change that fact, but they help build an understanding of party positions without needing to read full manifestos.&lt;/p&gt;
&lt;p&gt;My recommendation is this &quot;formula&quot;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Work through all the voting aids&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Exclude parties that consistently land in the bottom third&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Then weight all the tools:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;1/3¹ from &lt;strong&gt;deinWal&lt;/strong&gt;, since it&#39;s the only tool that goes beyond (possibly empty) promises.&lt;/p&gt;
&lt;p&gt;If you&#39;re considering smaller parties too, I&#39;d recommend using deinWal at least as an exclusion criterion: I&#39;d rule out the two worst-performing parties.&lt;/p&gt;
&lt;p&gt;¹ assuming you&#39;re only considering established parties anyway&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;1/3 from the &lt;strong&gt;other tools&lt;/strong&gt;, by giving points to each recommended party&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;1/3 from &lt;strong&gt;your own research&lt;/strong&gt;. Here it&#39;s important not to be influenced either by Tagesschau/ZDFheute etc., or (and this is crucial!) by Facebook/WhatsApp groups/etc.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Take the things that matter to you (pension, taxes, climate, whatever) and explicitly engage with the opposing positions and arguments too. This ensures we&#39;re not making a pure gut decision for one of the most important elections in decades.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Climate Crisis I: The Current State</title>
        <published>2021-08-19T00:00:00+00:00</published>
        <updated>2021-08-26T07:15:00+00:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://loki.dev/en/blog/klimakatastrophe-i-was-ist/"/>
        <id>https://loki.dev/en/blog/klimakatastrophe-i-was-ist/</id>
        
        <content type="html" xml:base="https://loki.dev/en/blog/klimakatastrophe-i-was-ist/">&lt;p&gt;&lt;em&gt;First and most importantly: I&#39;m writing this article for family and friends with the goal of not constantly having to be the &quot;annoying climate activist&quot;.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;My aim is to gather as many climate facts as possible in one place and explain why a radical change in politics and how we live together matters so much to me.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;This whole issue is really complex and extensive, which is why I&#39;m trying to split it into 3 articles&lt;/em&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;The Current State (this article)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;What&#39;s coming&lt;/em&gt; (in a timeframe we will definitely still experience, and that forms the core of Ophelia/Aurora&#39;s lives)&lt;/li&gt;
&lt;li&gt;&lt;em&gt;What we must do&lt;/em&gt; (strictly the scientific/economic consensus and the minimum required to minimise the impacts)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;The abstract concept of so-called climate change (more accurately: climate catastrophe) is familiar to everyone by now, and we&#39;ve all heard the rough outline: sea levels are rising, it&#39;s getting hotter, and the probability of extreme weather events is increasing.&lt;/p&gt;
&lt;p&gt;But unfortunately that&#39;s not the complete picture, and in my opinion it doesn&#39;t make the real consequences clear enough.&lt;/p&gt;
&lt;p&gt;In this first piece I want to talk about the impacts we&#39;re already feeling in Germany today, and also look at what we&#39;re seeing around the world right now as a glimpse of Germany&#39;s future. These are all measurable things, and the vast majority were scientifically predicted exactly this way. Unfortunately, usually for a few years further in the future. Unfortunately it turned out that almost all projections were significantly &lt;a rel=&quot;external&quot; href=&quot;https://www.hna.de/welt/klima-wandel-wetter-gletscher-schmilzt-katastrophale-folgen-hna-zr-90852117.html&quot;&gt;too optimistic&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;impacts-in-germany&quot;&gt;Impacts in Germany&lt;/h2&gt;
&lt;p&gt;The closest to home are the consequences in our own country. A brief summary in bullet points:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Heat deaths have doubled&lt;/li&gt;
&lt;li&gt;First lakes have already dried up, others affected by blue-green algae&lt;/li&gt;
&lt;li&gt;Economic damage in the billions&lt;/li&gt;
&lt;li&gt;Crop failures&lt;/li&gt;
&lt;li&gt;Water shortages&lt;/li&gt;
&lt;li&gt;Zoonoses&lt;/li&gt;
&lt;li&gt;Forest die-off&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;heat-deaths&quot;&gt;Heat Deaths&lt;/h3&gt;
&lt;p&gt;Since 1955, the number of heat-related deaths has risen by &lt;a rel=&quot;external&quot; href=&quot;https://www.tagesschau.de/inland/hitzetote-deutschland-103.html&quot;&gt;120%&lt;/a&gt;.
In the four-year period 2014-2018, that was approximately 12,000. A large share of this growth happened in the decade between 2004 and 2014.
For comparison: that&#39;s &lt;a rel=&quot;external&quot; href=&quot;https://de.statista.com/statistik/daten/studie/185/umfrage/todesfaelle-im-strassenverkehr/&quot;&gt;1.5 times more deaths than in all German road traffic&lt;/a&gt;, though it&#39;s not entirely fair to compare directly, since traffic itself contributes to those heat deaths.&lt;/p&gt;
&lt;h3 id=&quot;dried-up-lakes-blue-green-algae&quot;&gt;Dried-up Lakes &amp;amp; Blue-Green Algae&lt;/h3&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.morgenpost.de/vermischtes/article232506469/klimawandel-brandenburg-see-vertrocknet.html&quot;&gt;In Brandenburg&lt;/a&gt;,
the first lakes have already dried up entirely. Where a lake once was, there&#39;s now (still) fertile meadow. Across Germany, many more lakes are affected or will dry up within the next 20 years, only to be periodically flooded by heavy rainfall.&lt;/p&gt;
&lt;p&gt;The lakes that haven&#39;t dried up are &lt;a rel=&quot;external&quot; href=&quot;https://www.ingenieur.de/technik/fachbereiche/umwelt/seen-im-klimawandel-forscher-entdecken-problem/&quot;&gt;already increasingly&lt;/a&gt; affected by cyanobacteria in summer. I still remember my childhood swimming in the gravel pit lake at Oppum. These days, warnings about gravel pit lakes are &lt;a rel=&quot;external&quot; href=&quot;https://www.rnd.de/panorama/baden-in-seen-landesweit-zahlreiche-badeverbote-wegen-blaualgen-7RL2ZSEZVA76CDMS3T74CSESGQ.html&quot;&gt;increasingly common&lt;/a&gt; due to algae.&lt;/p&gt;
&lt;p&gt;To be honest: the climate crisis isn&#39;t the only cause of the algae problem. Intensive agriculture plays a role too. But that topic is complex enough to deserve its own article.&lt;/p&gt;
&lt;h3 id=&quot;economic-damage&quot;&gt;Economic Damage&lt;/h3&gt;
&lt;p&gt;A really important topic. Economics isn&#39;t exactly my favourite subject, but you constantly read and hear arguments about who will pay for it all, and the political mainstream (primarily CDU/SPD) tries to frame climate action as something to be &quot;balanced&quot; with the economy.&lt;/p&gt;
&lt;p&gt;I find that deeply dishonest, since politics has long been advised by both scientists &lt;strong&gt;and&lt;/strong&gt; economists that climate protection IS economic protection. The only thing that would suffer is short-term quarterly results. In the long run &lt;a rel=&quot;external&quot; href=&quot;https://www.goethe.de/prj/eco/de/bil/22136915.html&quot;&gt;climate action is cheaper&lt;/a&gt;, completely ignoring lives and quality of life.&lt;/p&gt;
&lt;h3 id=&quot;in-concrete-numbers&quot;&gt;In Concrete Numbers&lt;/h3&gt;
&lt;p&gt;The fact that we&#39;re not only failing to invest in climate protection but that the CDU and in particular Altmaier &lt;a rel=&quot;external&quot; href=&quot;https://www.tagesschau.de/wirtschaft/offshore-windenergie-ausbau-101.html&quot;&gt;actively obstructed climate action&lt;/a&gt; makes it no easier. An industry with 80,000 jobs in renewable energy, where Germany was once a global pioneer, has &lt;a rel=&quot;external&quot; href=&quot;https://www.windkraft-journal.de/2013/11/17/80-000-arbeitsplatzverluste-in-der-erneuerbare-energien-branche/45906&quot;&gt;practically nothing left&lt;/a&gt;. When even &lt;a rel=&quot;external&quot; href=&quot;https://www.welt.de&quot;&gt;Die Welt&lt;/a&gt;, a newspaper that has recycled climate misinformation from the AFDP-adjacent &quot;EIKE Institut&quot;, &lt;a rel=&quot;external&quot; href=&quot;https://www.welt.de/wirtschaft/article198299117/Windindustrie-In-einem-Jahr-26-000-Arbeitsplaetze-abgebaut.html&quot;&gt;notices&lt;/a&gt;, things are truly dire.&lt;/p&gt;
&lt;h3 id=&quot;crop-failures&quot;&gt;Crop Failures&lt;/h3&gt;
&lt;p&gt;It may have been an anomaly, but last year (2020) crop losses were &lt;a rel=&quot;external&quot; href=&quot;https://www.deutschlandfunk.de/extremwetter-und-ernteausfaelle-klimawandel-stellt.724.de.html?dram:article_id=426057&quot;&gt;between 30 and 50 percent&lt;/a&gt;.
Almost entirely caused by drought. A factor that will definitely intensify. This year (2021) everything is extremely wet, so plants/trees/etc., already weakened, are now fighting rot from the other direction.&lt;/p&gt;
&lt;p&gt;So far these conditions have been softened by the fact that we already throw away enormous amounts of food and could import. But our main vegetable/fruit import countries, Spain and the Netherlands, will struggle even more than us, with heat (Spain) and flooding (Netherlands). The Netherlands is &lt;a rel=&quot;external&quot; href=&quot;https://www.tagesschau.de/ausland/europa/niederlande-klimawandel-103.html&quot;&gt;already&lt;/a&gt; below sea level, currently staying &lt;em&gt;above water&lt;/em&gt; &lt;a rel=&quot;external&quot; href=&quot;https://web.archive.org/web/20241010225307/https://www.youthreporter.eu/de/beitrag/wenn-ein-land-unter-dem-meeresspiegel-liegt.10462/&quot;&gt;only because pumps run continuously&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;water-shortages&quot;&gt;Water Shortages&lt;/h3&gt;
&lt;p&gt;Last year in Nieder-Beerbach we experienced it very directly ourselves: two weeks of mandatory water conservation. In Lower Saxony, the fire brigade had to distribute water in some areas because the tanks were simply empty.&lt;/p&gt;
&lt;p&gt;Due to our geographically fortunate position, it will remain a matter of days to weeks and be locally limited in the medium-term future, but the impacts will still be severe and are &lt;a rel=&quot;external&quot; href=&quot;https://www.dw.com/de/wasserarmut-wasserknappheit-d%C3%BCrre-in-deutschland-nationaler-wasserdialog-wasserstrategie/a-56227882&quot;&gt;already prompting warnings from water utilities and researchers&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;forest-die-off&quot;&gt;Forest Die-off&lt;/h3&gt;
&lt;p&gt;In addition to drinking water shortages, about 1/3 of our forests are currently dying. Drought alone is often enough, and where that doesn&#39;t finish them off, &quot;pests&quot; help deal the final blow.&lt;/p&gt;
&lt;p&gt;The consequences aren&#39;t just &quot;less green&quot; or losing more CO&lt;sub&gt;2&lt;/sub&gt; sinks, but also rapidly declining biodiversity, soil damage (such as landslides), and more severe weather phenomena that are no longer blocked or weakened by forests.&lt;/p&gt;
&lt;p&gt;An area of originally around 12 million hectares has already lost 300,000 hectares in the last 20 years, and that doesn&#39;t even count the coal-power forests. Specifically, only around &lt;a rel=&quot;external&quot; href=&quot;https://www.bmel.de/DE/themen/wald/wald-in-deutschland/waldzustandserhebung.html&quot;&gt;21%&lt;/a&gt; of our trees are still healthy.&lt;/p&gt;
&lt;h3 id=&quot;zoonoses&quot;&gt;Zoonoses&lt;/h3&gt;
&lt;p&gt;COVID-19 was not the first zoonosis and will not be the last. What was once a matter of centuries without human influence will happen more frequently. My generation will live through at least one more zoonosis like COVID, and for my daughters it will become a matter of habit.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;Federal Agency for Civic Education&lt;/strong&gt; itself &lt;a rel=&quot;external&quot; href=&quot;https://www.bpb.de/politik/innenpolitik/coronavirus/308483/pandemien-umwelt-und-klima&quot;&gt;goes into detail on their pages&lt;/a&gt;, but as so often the priorities remain shaped by what RWE or the domestic steel and auto industry wants.&lt;/p&gt;
&lt;p&gt;While we&#39;re still fighting SARS-CoV-2, more pathogens of various kinds are already on their way to us. Not only are our efforts on climate/human protection somewhere between inadequate and failing, as a species we also kick ourselves again by primarily &lt;a rel=&quot;external&quot; href=&quot;https://www.deutschlandfunkkultur.de/multiresistente-keime-in-der-landwirtschaft-die-naechste.976.de.html?dram:article_id=487266&quot;&gt;breeding multi-resistant pathogens through our meat and livestock industry&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;global-impacts&quot;&gt;Global Impacts&lt;/h2&gt;
&lt;p&gt;When we talk about climate change, we often talk about so-called tipping points. We&#39;ve identified around 16 of them so far. A tipping point is a threshold beyond which a process sets in motion that we &lt;strong&gt;cannot&lt;/strong&gt; reverse. After that point, it just runs, and in most cases with self-reinforcing effect.&lt;/p&gt;
&lt;h3 id=&quot;permafrost&quot;&gt;Permafrost&lt;/h3&gt;
&lt;p&gt;One of the tipping points we&#39;ve already reached is that &lt;a rel=&quot;external&quot; href=&quot;https://www.n-tv.de/wissen/Permafrostboeden-weltweit-erwaermen-sich-article20813171.html&quot;&gt;permafrost is thawing worldwide&lt;/a&gt;. This releases large amounts of methane, which naturally accelerates warming further. Methane is probably the most lastingly damaging element, but thawing permafrost brings many more problems.&lt;/p&gt;
&lt;p&gt;For example, &lt;a rel=&quot;external&quot; href=&quot;https://www.geo.de/natur/oekologie/23609-rtkl-gefahr-aus-dem-eis-viren-und-bakterien-werden-durch-den-auftauenden&quot;&gt;the online version of GEO magazine&lt;/a&gt; reports on several studies about further bacteria, viruses, and more hidden there.
Most of it will be harmless, but even harmless 15,000-year-old pathogens can hit our immune systems extremely hard, just as happened during the colonisation of the Americas, when European settlers brought their diseases and wiped out entire tribes, not because the diseases were so dangerous in themselves, but because the immune systems of the Native Americans were entirely unprepared.&lt;/p&gt;
&lt;h3 id=&quot;rainforest&quot;&gt;Rainforest&lt;/h3&gt;
&lt;p&gt;The rainforest has been misleadingly called &lt;a rel=&quot;external&quot; href=&quot;https://www.regenwald.org/themen/der-regenwald&quot;&gt;the green lung of the earth&lt;/a&gt;. Unfortunately, this year we reached the point where the rainforest is releasing &lt;strong&gt;more&lt;/strong&gt; greenhouse gases than it &lt;a rel=&quot;external&quot; href=&quot;https://www.frontiersin.org/articles/10.3389/ffgc.2021.618401/full&quot;&gt;absorbs&lt;/a&gt;.
That is devastating, and in this case almost &lt;a rel=&quot;external&quot; href=&quot;https://www.faszination-regenwald.de/info-center/zerstoerung/&quot;&gt;100% attributable to meat consumption&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;To say it clearly: we&#39;ve reached the point where the rainforest is NO LONGER the green lung. In simple terms, we have to rewrite our textbooks. Practically, it means we have permanently destroyed one of the planet&#39;s most important ecosystems. And for what: highly processed soya?&lt;/p&gt;
&lt;p&gt;Ironically, people like to point to soya fields and blame vegetarians, while conveniently forgetting that only 6% of global soya ends up in veggie products and the rest is animal feed. The soya from the rainforest doesn&#39;t reach German mouths anyway, since German products use semi-regional suppliers. And it takes around 12kg of soya to produce 1kg of beef. Pigs and poultry are thankfully considerably more efficient.&lt;/p&gt;
&lt;h2 id=&quot;but-we-can-t-save-the-world-on-our-own&quot;&gt;&quot;But we can&#39;t save the world on our own&quot;&lt;/h2&gt;
&lt;p&gt;... how do we get others to help?&lt;/p&gt;
&lt;p&gt;We don&#39;t have to. We mainly just need to stop blocking. In Europe, Germany is &lt;a rel=&quot;external&quot; href=&quot;https://www.germanwatch.org/de/15938&quot;&gt;THE&lt;/a&gt; &lt;a rel=&quot;external&quot; href=&quot;https://www.sueddeutsche.de/politik/klimawandel-klimaschutz-deutschland-1.4164096&quot;&gt;brake&lt;/a&gt; &lt;a rel=&quot;external&quot; href=&quot;https://www.tagesspiegel.de/politik/vertrauliche-dokumente-deutschland-wird-zum-klima-bremser/24134082.html&quot;&gt;par excellence&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Although we represent just 1% of the world&#39;s population, we produce around 2% of greenhouse gases. And that doesn&#39;t even include products manufactured in China/Taiwan or services like servers etc. from abroad destined for the German market, which really should be counted against us.&lt;/p&gt;
&lt;p&gt;Per capita, Germany is still significantly ahead of China: China is at around 5t CO&lt;sub&gt;2&lt;/sub&gt;, while we&#39;re at an average of 11t. Twice as much as the &quot;terrible China&quot;. Out of over 200 countries, we&#39;re currently in the top 10 for absolute emissions. Do we really want almost 200 other countries to use the same argument? Would we accept &quot;but the others are doing it!&quot; as an argument from our children?&lt;/p&gt;
&lt;p&gt;Edit: Finally, here&#39;s a wonderful summary by &lt;a rel=&quot;external&quot; href=&quot;https://www.quarks.de/umwelt/klimawandel/so-steht-es-wirklich-um-unser-klima/&quot;&gt;Quarks&lt;/a&gt; of the first IPCC report. In total there will be 3 reports and 1 summary. Importantly, the reports are already finished, but won&#39;t be published until 2022 because governments are allowed to exercise influence first. A summary of the leak from Part 2 (not due until January 2022) was published by &lt;a rel=&quot;external&quot; href=&quot;https://www.spiegel.de/wissenschaft/mensch/geleakter-teil-des-ipcc-berichts-die-reichsten-zehn-prozent-verursachen-mehr-als-ein-drittel-der-treibhausgase-a-6d2e8438-7f3e-49bc-8967-4e3aeb6be585&quot;&gt;Der Spiegel&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It may also be relevant that the REASON for this leak is that the scientists &lt;a rel=&quot;external&quot; href=&quot;https://web.archive.org/web/20240519010058/https://utopia.de/news/leaks-des-ipcc-berichts-die-reichsten-10-prozent-verursachen-ueber-36-prozent-der-treibhausgase/&quot;&gt;are afraid&lt;/a&gt; that the report will be &lt;a rel=&quot;external&quot; href=&quot;https://www.theguardian.com/environment/2021/aug/12/greenhouse-gas-emissions-must-peak-within-4-years-says-leaked-un-report&quot;&gt;watered down by governments&lt;/a&gt;.&lt;/p&gt;
</content>
        
    </entry>
</feed>
