<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Roumen Baytchev</title>
    <description>The latest articles on DEV Community by Roumen Baytchev (@baytcho).</description>
    <link>https://dev.to/baytcho</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4081212%2F2501c09f-2914-4150-80b6-e3ab1881e46f.png</url>
      <title>DEV Community: Roumen Baytchev</title>
      <link>https://dev.to/baytcho</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/baytcho"/>
    <language>en</language>
    <item>
      <title>Dead code analysis that is allowed to say "I don't understand this"</title>
      <dc:creator>Roumen Baytchev</dc:creator>
      <pubDate>Mon, 17 Aug 2026 08:40:26 +0000</pubDate>
      <link>https://dev.to/baytcho/dead-code-analysis-that-is-allowed-to-say-i-dont-understand-this-55lh</link>
      <guid>https://dev.to/baytcho/dead-code-analysis-that-is-allowed-to-say-i-dont-understand-this-55lh</guid>
      <description>&lt;p&gt;The two answers problem&lt;br&gt;
Every dead-code tool I have used has exactly two answers: used, or unused.&lt;/p&gt;

&lt;p&gt;That sounds fine until you watch one work. When the tool cannot see a link - because the name is built at runtime, because a class is addressed through a variable, because a framework reads a value instead of calling a function - it still has to pick one of its two answers. And it picks wrong.&lt;/p&gt;

&lt;p&gt;So the tools split into two camps. Some hedge: here is a confidence percentage, good luck. Others commit: they delete your live code and hand you an exception list to maintain by hand, for ever.&lt;/p&gt;

&lt;p&gt;I wanted a third answer: I do not understand this one yet, and I am not going to pretend otherwise.&lt;/p&gt;

&lt;p&gt;What I built&lt;br&gt;
SPIDER is an agent skill. It does not search for names. It traces.&lt;/p&gt;

&lt;p&gt;Split the whole codebase into individual top-level statements. Every statement gets a number and an exact address: file, first line, last line.&lt;br&gt;
Find the entry points - the statements something outside the program starts.&lt;br&gt;
Walk from each entry point along the links, one statement at a time.&lt;br&gt;
Whatever the walk reaches is alive. Whatever it never reaches is not needed.&lt;br&gt;
A spider walks its web thread by thread. Same idea, same name.&lt;/p&gt;

&lt;p&gt;Two things fell out of that design that I have not found elsewhere.&lt;/p&gt;

&lt;p&gt;It works one statement at a time, across four languages at once. Python, TypeScript, JavaScript and CSS end up in one list, measured by one rule. Which means it can tell you that three rules inside a stylesheet are alive and the fourth one is dead - instead of shrugging at the file as a whole. And when a TypeScript file addresses a CSS class, both ends of that link are in the same model. Run three single-language tools instead and nobody sees both ends.&lt;/p&gt;

&lt;p&gt;It has a third state during the work: unresolved. A statement whose links cannot be established is not declared dead. It is set aside, and later opened in the real source code and settled one by one - twenty times over if that is what it takes. Only then does it fall into one of the two final kinds.&lt;/p&gt;

&lt;p&gt;The rule that saves the most code is one line long: a name assembled at runtime is never declared unused. styles[status], getattr(obj, name), a class built by string concatenation. No literal search finds those. That is exactly where careless tools destroy working code.&lt;/p&gt;

&lt;p&gt;What it found&lt;br&gt;
858 statements across 66 files of a production Next.js application.&lt;/p&gt;

&lt;p&gt;It found two entire stylesheets, one abandoned screen with its stylesheet, and nine separate dead rules - 121 statements that nothing in the running program ever reaches. One statement in seven.&lt;/p&gt;

&lt;p&gt;It also found five style rules that a name-based search had wrongly condemned. The code addressed them through an assembled name. Those five were saved by the third state, and they would have been deleted by anything that only searches.&lt;/p&gt;

&lt;p&gt;That list of 121 is the actual product. You do not go back and read your codebase - you read the list. Every entry carries its exact address, so the next step is a job you hand straight back to your agent: audit these hundred addresses in the real source, nothing else. A bounded piece of work with a known size, instead of "audit my repository", which produces an answer nobody can check.&lt;/p&gt;

&lt;p&gt;Now the uncomfortable part&lt;br&gt;
The skill is carried out by an intelligence, not by a compiler. That is where the strength comes from - and the risk.&lt;/p&gt;

&lt;p&gt;The same skill, run by two different models over the same project, gave 121 unneeded statements and 55.&lt;/p&gt;

&lt;p&gt;The whole difference came from one decision at step 2. One of the runs decided, from its own general knowledge of how browsers work, that every CSS rule is an entry point. Nothing in the skill says that. The rule was invented, the run was confident, and the finished report looked exactly as sound as the correct one.&lt;/p&gt;

&lt;p&gt;You cannot tell those two reports apart by reading them. That is the real problem with skills as a format: the instructions are text, execution is probabilistic, and there is no compiler to enforce the rules.&lt;/p&gt;

&lt;p&gt;What actually helps&lt;br&gt;
Not more prose in the instructions. I tried that first, and the run that invented a rule would have sailed past any amount of it.&lt;/p&gt;

&lt;p&gt;What helps is a fixed answer key.&lt;/p&gt;

&lt;p&gt;The repository ships an evals/ folder: a small project carrying six deliberate traps - a style rule named nowhere, a whole stylesheet nobody imports, an exported function nobody calls, a name assembled at runtime, a directive the bundler reads, data the framework reads instead of code somebody calls. Next to it, the correct answer for each one, written down in advance. Twenty-nine machine checks:&lt;/p&gt;

&lt;p&gt;python evals/run_evals.py --self-test          # test the machinery&lt;br&gt;
python evals/run_evals.py --check    # test a real run&lt;br&gt;
If a model declares every style rule an entry point, that check fails immediately - before a single conclusion is drawn. It runs in CI on every push.&lt;/p&gt;

&lt;p&gt;So the README says it straight, near the top: never accept a result you have not checked, run the answer key first, read the recorded decisions, and delete nothing on the strength of one run. The second kind is a list of candidates for your own eyes, never a delete command.&lt;/p&gt;

&lt;p&gt;Try it&lt;br&gt;
github.com/baytcho/spider-dead-code-skill&lt;/p&gt;

&lt;p&gt;MIT. Download spider.skill from the release, or clone the repository if you want the answer key too. Needs Python 3, and Node plus typescript@5 if your project has TypeScript.&lt;/p&gt;

&lt;p&gt;It asks you two questions before it does anything - where to put the analysis and what to analyse - and it will not guess either one. The project itself is only ever read: never modified, never executed, never built.&lt;/p&gt;

&lt;p&gt;If you run it and it gets something wrong on your codebase, I want to hear about it. That is the whole point of shipping the answer key.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>devops</category>
      <category>productivity</category>
    </item>
  </channel>
</rss>
