<?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: tupe12334</title>
    <description>The latest articles on DEV Community by tupe12334 (@tupe12334).</description>
    <link>https://dev.to/tupe12334</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F719701%2F847212bc-9a5b-4f56-8e98-a592a02c7165.png</url>
      <title>DEV Community: tupe12334</title>
      <link>https://dev.to/tupe12334</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tupe12334"/>
    <language>en</language>
    <item>
      <title>Why You Should Start Using Negative If Statements in Your Code</title>
      <dc:creator>tupe12334</dc:creator>
      <pubDate>Tue, 24 Mar 2026 13:04:06 +0000</pubDate>
      <link>https://dev.to/tupe12334/why-you-should-start-using-negative-if-statements-in-your-code-4l39</link>
      <guid>https://dev.to/tupe12334/why-you-should-start-using-negative-if-statements-in-your-code-4l39</guid>
      <description>&lt;p&gt;We've all been there: the code looks fine, the tests pass, but somehow bugs still make it to production. So what can you do to write more correct code and significantly reduce the number of bugs?&lt;/p&gt;

&lt;p&gt;One technique I use regularly to prevent exactly these situations is writing &lt;strong&gt;negative if statements&lt;/strong&gt; — also known as the &lt;strong&gt;Early Return&lt;/strong&gt; pattern.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Does That Actually Mean?
&lt;/h2&gt;

&lt;p&gt;Instead of first checking the case where the action &lt;em&gt;should&lt;/em&gt; happen, you check the invalid cases first and eliminate them as early as possible. This approach makes your code significantly more readable and focused.&lt;/p&gt;

&lt;p&gt;For example, instead of writing this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isLoggedIn&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hasPermission&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;performSensitiveAction&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's better to use a negative check:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isLoggedIn&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hasPermission&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Handle the invalid situation&lt;/span&gt;
  &lt;span class="c1"&gt;// Make sure to log it&lt;/span&gt;
  &lt;span class="c1"&gt;// throw | return | continue&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;performSensitiveAction&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The happy path — the thing you actually want to do — sits at the bottom, unindented and obvious. Each guard at the top handles one specific failure case.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnmyq3car5dmd5fwnt0bn.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnmyq3car5dmd5fwnt0bn.gif" alt="Nested If vs. Early Returns animation"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Is This Better?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Readability:&lt;/strong&gt; The code becomes clearer and more focused because edge cases are checked and dismissed upfront. You don't have to mentally track nested conditions to understand what the function actually does.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Safety:&lt;/strong&gt; It's easier to spot bugs and prevent them from escaping, because critical conditions are checked explicitly and visibly at the top.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Maintainability:&lt;/strong&gt; It's much easier to add new conditions or handle additional cases when your checks are clearly laid out at the start of the function.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Log the Failure While You're There
&lt;/h2&gt;

&lt;p&gt;When you use negative if statements, you get a natural place to document &lt;em&gt;why&lt;/em&gt; an action didn't succeed. This is the perfect spot to add detailed logs that help you debug the system both in real time and after the fact.&lt;/p&gt;

&lt;p&gt;Here's a more complete example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isLoggedIn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Access attempt without login: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ip&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;401&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Please login first&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hasPermission&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Permission denied for user: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;performSensitiveAction&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;requiredPermission&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;admin&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;403&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Insufficient permissions&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;performSensitiveAction&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Good logging at these guard points enables:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Security tracking&lt;/strong&gt; — detecting unauthorized access attempts&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bug understanding&lt;/strong&gt; — logs show exactly what happened when something went wrong&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Better UX&lt;/strong&gt; — you can identify where users get stuck&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Faster response times&lt;/strong&gt; — support teams can resolve issues faster with full context&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Pair It with Proper Error Handling
&lt;/h2&gt;

&lt;p&gt;Logging is part of the picture, but error handling matters too:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Use &lt;code&gt;try/catch&lt;/code&gt;&lt;/strong&gt; — especially for async operations or calls to external resources&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Return clear error messages&lt;/strong&gt; — so users know what happened and what to do&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monitor automatically&lt;/strong&gt; — tools like Sentry or LogRocket track errors in real time so nothing slips by silently&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Combining negative if statements with solid error handling makes your code not just more readable, but more resilient and reliable.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Counter-Argument
&lt;/h2&gt;

&lt;p&gt;Sometimes, writing too many early returns makes a long or complex function harder to follow. When a function spans many lines and has a dozen early exits, it can become difficult to track the overall flow.&lt;/p&gt;

&lt;h2&gt;
  
  
  So Which Is Better?
&lt;/h2&gt;

&lt;p&gt;Like most things in code: balance matters.&lt;/p&gt;

&lt;p&gt;For &lt;strong&gt;short, focused functions&lt;/strong&gt;, negative if statements are almost always a win. For &lt;strong&gt;long, complex functions&lt;/strong&gt;, it's sometimes better to keep a positive if structure — and invest instead in breaking the function into smaller pieces.&lt;/p&gt;

&lt;p&gt;Since I started using this pattern consistently, the number of bugs reaching production dropped significantly, and code reviews became much smoother.&lt;/p&gt;




&lt;p&gt;For a deeper dive into this idea, I highly recommend this video by CodeAesthetic:&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/CFRhGnuXG-4"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Do you use early returns consistently? Are there cases where you intentionally avoid them? I'd love to hear your take in the comments.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>cleancode</category>
      <category>programming</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Why I Built an ESLint Rule That Forces className on Every HTML Element</title>
      <dc:creator>tupe12334</dc:creator>
      <pubDate>Wed, 18 Feb 2026 12:06:54 +0000</pubDate>
      <link>https://dev.to/tupe12334/why-i-built-an-eslint-rule-that-forces-classname-on-every-html-element-1fd</link>
      <guid>https://dev.to/tupe12334/why-i-built-an-eslint-rule-that-forces-classname-on-every-html-element-1fd</guid>
      <description>&lt;p&gt;You paste a React component into an LLM and ask it to "change the padding on the header div." The LLM responds — but it modified the wrong &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt;. You try again: "no, the second div, the one wrapping the title." Two more rounds of back and forth until it finally understands which element you meant.&lt;/p&gt;

&lt;p&gt;I got tired of this. So I built &lt;a href="https://github.com/tupe12334/eslint-plugin-jsx-classname" rel="noopener noreferrer"&gt;&lt;code&gt;eslint-plugin-jsx-classname&lt;/code&gt;&lt;/a&gt; — an ESLint plugin that enforces &lt;code&gt;className&lt;/code&gt; on every HTML element in your JSX. Not for styling. For giving every element a name that both humans and LLMs can reference unambiguously.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem: Anonymous Elements
&lt;/h2&gt;

&lt;p&gt;In most React codebases, it's easy for bare HTML elements to slip through code review:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Card&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;children&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now tell an LLM to "add margin to the content wrapper." Which &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; is the content wrapper? The LLM has to guess based on structure, and it often guesses wrong. You end up spending more time correcting the AI than writing the code yourself.&lt;/p&gt;

&lt;p&gt;Compare that to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Card&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;children&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"card"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"card-header"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"card-title"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"card-body"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now "add margin to &lt;code&gt;card-body&lt;/code&gt;" is unambiguous. The LLM knows exactly which element you mean. No back and forth. No guessing. One prompt, one correct edit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Matters Now
&lt;/h2&gt;

&lt;p&gt;AI-assisted coding is becoming the norm. Whether you're using Claude, Copilot, or Cursor — you're constantly describing parts of your UI to an LLM. Semantic class names act as a shared vocabulary between you and the AI.&lt;/p&gt;

&lt;p&gt;Without them, every conversation about your JSX becomes a game of "which div do I mean?" With them, you can point at any element by name. It's the difference between giving directions with street names versus "turn left at the third building after the tall one."&lt;/p&gt;

&lt;h2&gt;
  
  
  What the Plugin Does
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;eslint-plugin-jsx-classname&lt;/code&gt; provides a single rule: &lt;code&gt;require-classname&lt;/code&gt;. It checks every HTML element in your JSX and reports when &lt;code&gt;className&lt;/code&gt; is missing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;⚠ HTML element &amp;lt;div&amp;gt; is missing a className attribute.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It only checks standard HTML elements — custom components like &lt;code&gt;&amp;lt;MyButton&amp;gt;&lt;/code&gt; or &lt;code&gt;&amp;lt;Card&amp;gt;&lt;/code&gt; are ignored, since they already have a name.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--save-dev&lt;/span&gt; eslint-plugin-jsx-classname
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Configuration
&lt;/h2&gt;

&lt;p&gt;The plugin uses ESLint flat config. The quickest way to get started:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// eslint.config.js&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;jsxClassname&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;eslint-plugin-jsx-classname&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;jsxClassname&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;configs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;recommended&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This enables the rule as a warning with default options. For stricter enforcement:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;jsxClassname&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;configs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;strict&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The strict config sets the rule to &lt;code&gt;error&lt;/code&gt; level and enables &lt;code&gt;ignoreTailwind&lt;/code&gt; (more on that below).&lt;/p&gt;

&lt;h2&gt;
  
  
  Customizing the Rule
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Check Only Specific Elements
&lt;/h3&gt;

&lt;p&gt;If enforcing &lt;code&gt;className&lt;/code&gt; on every element is too aggressive for your codebase, narrow it down:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;jsx-classname/require-classname&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;warn&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;elements&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;div&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;span&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;section&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Exclude Certain Elements
&lt;/h3&gt;

&lt;p&gt;Some elements don't need names — &lt;code&gt;&amp;lt;hr&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;br&amp;gt;&lt;/code&gt;, or &lt;code&gt;&amp;lt;input type="hidden"&amp;gt;&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;jsx-classname/require-classname&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;warn&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;excludeElements&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hr&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;br&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;input&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The Tailwind Trap
&lt;/h3&gt;

&lt;p&gt;This is the option I'm most excited about. If your team uses Tailwind CSS, you've probably seen this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"flex items-center justify-between p-4 mt-2"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"text-lg font-bold text-gray-800"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Technically, every element has a &lt;code&gt;className&lt;/code&gt;. But these are all Tailwind utilities — the element still has no semantic identity. Tell an LLM "modify the &lt;code&gt;flex items-center justify-between p-4 mt-2&lt;/code&gt; div" and you're back to the same problem.&lt;/p&gt;

&lt;p&gt;With &lt;code&gt;ignoreTailwind: true&lt;/code&gt;, the rule requires at least one non-Tailwind class:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;jsx-classname/require-classname&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;ignoreTailwind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="p"&gt;}]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now this fails:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"flex items-center p-4"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But this passes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"card-header flex items-center p-4"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You get Tailwind utilities for styling, plus a semantic class name that gives the element a referenceable identity.&lt;/p&gt;

&lt;p&gt;Under the hood, Tailwind detection uses &lt;a href="https://github.com/dcastil/tailwind-merge" rel="noopener noreferrer"&gt;&lt;code&gt;tailwind-merge&lt;/code&gt;&lt;/a&gt; to programmatically identify utility classes. This means it stays current with Tailwind updates automatically, without maintaining a massive regex list.&lt;/p&gt;

&lt;h2&gt;
  
  
  "But That's a Lot of Work"
&lt;/h2&gt;

&lt;p&gt;That's the first thing people say. Adding a meaningful &lt;code&gt;className&lt;/code&gt; to every single HTML element? In the past, that would have been tedious busywork that no team would actually maintain.&lt;/p&gt;

&lt;p&gt;But here's the thing — we're not writing all the code ourselves anymore. Your AI coding agent is already generating most of the JSX. With this lint rule in place, the agent simply adds semantic class names as it writes components. It's zero extra effort for you.&lt;/p&gt;

&lt;p&gt;And to make sure the agent never skips the pre-commit hooks that enforce this rule, pair it with &lt;a href="https://www.npmjs.com/package/block-no-verify" rel="noopener noreferrer"&gt;&lt;code&gt;block-no-verify&lt;/code&gt;&lt;/a&gt;. This package prevents &lt;code&gt;--no-verify&lt;/code&gt; from bypassing git hooks, so the lint rule is always enforced — no matter who or what is committing the code.&lt;/p&gt;

&lt;p&gt;The combination is simple: the lint rule defines the standard, the agent does the work, and &lt;code&gt;block-no-verify&lt;/code&gt; makes sure nothing slips through.&lt;/p&gt;

&lt;h2&gt;
  
  
  Give It a Try
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--save-dev&lt;/span&gt; eslint-plugin-jsx-classname
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check out the full documentation and source on &lt;a href="https://github.com/tupe12334/eslint-plugin-jsx-classname" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;. If it's useful to you, a star would mean a lot.&lt;/p&gt;

&lt;p&gt;How do you handle communicating with LLMs about your UI components? I'd love to hear your approach in the comments.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>react</category>
      <category>eslint</category>
      <category>ai</category>
    </item>
    <item>
      <title>The Project Cookbook</title>
      <dc:creator>tupe12334</dc:creator>
      <pubDate>Sun, 08 Feb 2026 14:31:12 +0000</pubDate>
      <link>https://dev.to/tupe12334/the-project-cookbook-3o0m</link>
      <guid>https://dev.to/tupe12334/the-project-cookbook-3o0m</guid>
      <description>&lt;p&gt;After a year in a new workplace I have noticed several development lifecycle tools that are missing from projects all over, some have it partially, some not at all.&lt;/p&gt;

&lt;p&gt;This article covers the essential tools that will improve your team's development experience and code maintainability.&lt;/p&gt;

&lt;h2&gt;
  
  
  pnpm
&lt;/h2&gt;

&lt;p&gt;We all know npm, this is the go-to, youtube tutorial, default package manager in the NodeJS ecosystem.&lt;br&gt;
In every code boot-camp or school you will learn npm, as a derivative of that fact and the fact that the &lt;code&gt;npmjs.com&lt;/code&gt; registry is the default registry for NodeJS packages we come to the result that the &lt;code&gt;npm&lt;/code&gt; package manager is the most used package manager in the NodeJS ecosystem.&lt;br&gt;
But is it the best?&lt;br&gt;
It's a matter of opinion, but no, it's not.&lt;br&gt;
There are several package managers in the NodeJS ecosystem, the most popular ones are &lt;code&gt;yarn&lt;/code&gt; and &lt;code&gt;pnpm&lt;/code&gt;.&lt;br&gt;
Both of them are great, but I prefer &lt;code&gt;pnpm&lt;/code&gt; for several reasons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Performance&lt;/strong&gt;: &lt;code&gt;pnpm&lt;/code&gt; is faster than &lt;code&gt;npm&lt;/code&gt; and &lt;code&gt;yarn&lt;/code&gt; in most cases, this is because &lt;code&gt;pnpm&lt;/code&gt; uses a unique approach to store packages, it creates a single store for all packages and uses hard links to link the packages to the project, this way it avoids duplication of packages and saves disk space.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Determinism&lt;/strong&gt;: &lt;code&gt;pnpm&lt;/code&gt; is more deterministic than &lt;code&gt;npm&lt;/code&gt; and &lt;code&gt;yarn&lt;/code&gt;, this means that the same set of dependencies will always result in the same set of installed packages, this is important for reproducible builds and for avoiding issues with dependency resolution.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monorepo support&lt;/strong&gt;: &lt;code&gt;pnpm&lt;/code&gt; has built-in support for monorepos, this means that you can manage multiple packages in a single repository with ease, this is important for large projects and for teams that work on multiple packages.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Strictness&lt;/strong&gt;: &lt;code&gt;pnpm&lt;/code&gt; is more strict than &lt;code&gt;npm&lt;/code&gt; and &lt;code&gt;yarn&lt;/code&gt;, this means that it enforces stricter rules for dependency resolution and package installation, this helps to avoid issues with conflicting dependencies and ensures that the project is always in a good state.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For these reasons, I recommend using &lt;code&gt;pnpm&lt;/code&gt; as the package manager for your NodeJS projects.&lt;br&gt;
But again, it's a matter of opinion, you should try both and see which one works best for you and your team.&lt;/p&gt;
&lt;h2&gt;
  
  
  ESLint
&lt;/h2&gt;

&lt;p&gt;ESLint is likely the most adopted tool on this list, so I'll keep this brief—if you're reading an article about project tooling, you probably already know what it does.&lt;/p&gt;

&lt;p&gt;What's worth mentioning: ESLint 9 introduced flat config (&lt;code&gt;eslint.config.js&lt;/code&gt;), which simplifies configuration significantly. If you're still using &lt;code&gt;.eslintrc&lt;/code&gt;, consider migrating. The &lt;a href="https://eslint.org/docs/latest/" rel="noopener noreferrer"&gt;official documentation&lt;/a&gt; covers the migration path.&lt;/p&gt;
&lt;h2&gt;
  
  
  Prettier
&lt;/h2&gt;

&lt;p&gt;Prettier eliminates formatting debates—tabs vs spaces, semicolons, quote style—by enforcing a consistent style automatically.&lt;/p&gt;

&lt;p&gt;Configure it with a &lt;code&gt;.prettierrc&lt;/code&gt; file and integrate it with ESLint using &lt;code&gt;eslint-config-prettier&lt;/code&gt; to avoid rule conflicts.&lt;/p&gt;
&lt;h2&gt;
  
  
  cspell
&lt;/h2&gt;

&lt;p&gt;Typos are the enemy of code quality—they make code harder to read and search for.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;cspell&lt;/code&gt; is a spell checker designed for code. Unlike regular spell checkers, it understands camelCase, PascalCase, and snake_case, so it won't flag &lt;code&gt;getUserById&lt;/code&gt; as a typo.&lt;/p&gt;

&lt;p&gt;Configure it with a &lt;code&gt;cspell.json&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0.2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"language"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"en"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"words"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"myproject"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"signup"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"authtoken"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"ignorePaths"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"node_modules"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"dist"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"coverage"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"pnpm-lock.yaml"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add project-specific terms to the &lt;code&gt;words&lt;/code&gt; array. VS Code users can install the &lt;a href="https://marketplace.visualstudio.com/items?itemName=streetsidesoftware.code-spell-checker" rel="noopener noreferrer"&gt;Code Spell Checker&lt;/a&gt; extension for real-time feedback.&lt;/p&gt;

&lt;h2&gt;
  
  
  knip
&lt;/h2&gt;

&lt;p&gt;Dead code is a silent burden—it increases bundle size, confuses developers, and makes refactoring harder.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;knip&lt;/code&gt; finds unused files, dependencies, and exports across your entire project—not just unused variables within a file. It detects:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Unused files not imported anywhere&lt;/li&gt;
&lt;li&gt;Unused dependencies in &lt;code&gt;package.json&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Unused exports never imported elsewhere&lt;/li&gt;
&lt;li&gt;Unlisted dependencies used but not declared&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Configure it with a &lt;code&gt;knip.json&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"entry"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"src/index.ts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"src/main.ts"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"project"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"src/**/*.ts"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"ignore"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"**/*.test.ts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"**/*.spec.ts"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"ignoreDependencies"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"@types/*"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Running &lt;code&gt;knip&lt;/code&gt; regularly keeps your codebase lean and makes refactoring easier.&lt;/p&gt;

&lt;h2&gt;
  
  
  Husky
&lt;/h2&gt;

&lt;p&gt;Now that we have all these quality tools, we need a way to enforce them on every commit and push. Git hooks can do this, but they're local to your machine—not shareable with the team.&lt;/p&gt;

&lt;p&gt;Husky solves this by placing hook scripts in a &lt;code&gt;.husky&lt;/code&gt; folder that gets committed to the repository and automatically installed when running &lt;code&gt;pnpm install&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A typical &lt;code&gt;pre-commit&lt;/code&gt; hook runs lint-staged (covered next), while a &lt;code&gt;pre-push&lt;/code&gt; hook ensures you don't push broken code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/sh&lt;/span&gt;
&lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;dirname&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$0&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;/_/husky.sh"&lt;/span&gt;
pnpm tsc &lt;span class="nt"&gt;--noEmit&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; pnpm &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;--run&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This runs type-checking and tests before every push. If either fails, the push is blocked.&lt;/p&gt;

&lt;h2&gt;
  
  
  lint-staged
&lt;/h2&gt;

&lt;p&gt;But wait—what if you have work-in-progress code in your working directory that isn't ready for linting? You don't want unfinished code blocking your commit.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;lint-staged&lt;/code&gt; runs validation tools only on staged files, so you can commit clean code while keeping your messy experiments untouched.&lt;/p&gt;

&lt;p&gt;Configure it in &lt;code&gt;.lintstagedrc.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"*.{ts,tsx,js,jsx}"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"eslint --fix"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"prettier --write"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"*.{json,md}"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"prettier --write"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"*.{ts,tsx,js,md,json}"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"cspell lint --no-must-find-files"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Commitlint With Conventional Commits
&lt;/h2&gt;

&lt;p&gt;We all saw that commit message that was "fix" or "update" or even "fox" or the worse: ".".&lt;/p&gt;

&lt;p&gt;We all wondered what was fixed or updated, and we all wished that the commit message was more descriptive.&lt;/p&gt;

&lt;p&gt;This is the job of commitlint, to make sure your co-workers (and you) write good commit messages.&lt;/p&gt;

&lt;p&gt;Commitlint is a tool that helps us to enforce a consistent commit message format, this is important for several reasons, it makes it easier to understand the history of the project, it makes it easier to generate changelogs, and it makes it easier to automate the release process.&lt;br&gt;
We can use &lt;code&gt;commitlint&lt;/code&gt; with the &lt;code&gt;conventional commits&lt;/code&gt; format to enforce a consistent commit message format.&lt;br&gt;
The &lt;code&gt;conventional commits&lt;/code&gt; format is a specification for adding human and machine readable meaning to commit messages, it consists of a type, a scope, and a subject, for example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;feat(auth): add login functionality
fix(api): fix the bug in the user endpoint
docs: update the README file
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can configure &lt;code&gt;commitlint&lt;/code&gt; to use the &lt;code&gt;conventional commits&lt;/code&gt; format by installing the &lt;code&gt;@commitlint/config-conventional&lt;/code&gt; package and creating a &lt;code&gt;commitlint.config.js&lt;/code&gt; file like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;extends&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@commitlint/config-conventional&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Dependabot/Renovate
&lt;/h2&gt;

&lt;p&gt;Keeping dependencies up to date is crucial for security and stability, but manually checking for updates is tedious and often forgotten.&lt;br&gt;
This is where automated dependency update tools come in.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dependabot&lt;/strong&gt; is GitHub's built-in solution for automated dependency updates.&lt;br&gt;
It scans your &lt;code&gt;package.json&lt;/code&gt; (and other dependency files) and opens pull requests when new versions are available.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Renovate&lt;/strong&gt; is a more powerful and flexible alternative that works across multiple platforms (GitHub, GitLab, Bitbucket, etc.).&lt;br&gt;
It offers features like grouping updates, automerging minor updates, and custom scheduling.&lt;/p&gt;

&lt;p&gt;Both tools integrate with your CI pipeline, so you can ensure that updates don't break your tests before merging.&lt;/p&gt;

&lt;p&gt;Which one should you use? Dependabot is simpler and requires no setup if you're on GitHub.&lt;br&gt;
Renovate offers more control and works across platforms, making it ideal for complex projects or organizations with specific update policies.&lt;/p&gt;

&lt;h2&gt;
  
  
  Centy
&lt;/h2&gt;

&lt;p&gt;Issue tracking is essential for any project, but traditional tools like Jira or Linear live outside your codebase, creating a disconnect between your code and your tasks.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://centy.io/" rel="noopener noreferrer"&gt;Centy&lt;/a&gt; takes a different approach by storing issues as Markdown files directly in your repository.&lt;br&gt;
This means your issues live alongside your code, are version controlled with git, and require no external services or databases.&lt;/p&gt;

&lt;p&gt;Why is this approach beneficial?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Git-friendly&lt;/strong&gt;: Issues are plain Markdown files, so you can track changes, review history, and collaborate using the same git workflow you use for code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Offline-first&lt;/strong&gt;: Everything runs locally, no internet connection or external service required.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI-ready&lt;/strong&gt;: Designed to work seamlessly with AI coding assistants, making it easy to reference and resolve issues programmatically.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Simple&lt;/strong&gt;: No complex setup, no accounts, no subscriptions. Just initialize and start tracking.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you prefer keeping everything in one place and want your issue tracking to be as version-controlled as your code, Centy is worth exploring.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;These tools form a quality pipeline that catches issues at every stage:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;pnpm&lt;/strong&gt; manages dependencies efficiently&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ESLint + Prettier&lt;/strong&gt; keep your code consistent and clean&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;cspell&lt;/strong&gt; catches typos before they become permanent&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;knip&lt;/strong&gt; removes dead weight from your codebase&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Husky + lint-staged&lt;/strong&gt; enforce quality on every commit&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Commitlint&lt;/strong&gt; keeps your git history readable&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dependabot/Renovate&lt;/strong&gt; keep dependencies fresh&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Centy&lt;/strong&gt; tracks issues alongside your code&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You don't need to adopt everything at once. Start with ESLint, Prettier, and Husky—they give you the most impact for the least effort. Add the others as your project matures.&lt;/p&gt;

&lt;p&gt;The goal isn't to have the most tools, it's to have the right tools that help your team ship better code with less friction.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Full disclosure: I am the creator of Centy, AI assisted fixing typos in this article.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>javascript</category>
      <category>productivity</category>
      <category>devops</category>
    </item>
    <item>
      <title>How I Stopped My AI Coding Assistant from Cheating on Git Hooks</title>
      <dc:creator>tupe12334</dc:creator>
      <pubDate>Tue, 13 Jan 2026 16:12:00 +0000</pubDate>
      <link>https://dev.to/tupe12334/how-i-stopped-my-ai-coding-assistant-from-cheating-on-git-hooks-10af</link>
      <guid>https://dev.to/tupe12334/how-i-stopped-my-ai-coding-assistant-from-cheating-on-git-hooks-10af</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; I built &lt;a href="https://github.com/tupe12334/block-no-verify" rel="noopener noreferrer"&gt;&lt;code&gt;block-no-verify&lt;/code&gt;&lt;/a&gt; - a tool that prevents AI agents like Claude Code from using &lt;code&gt;--no-verify&lt;/code&gt; to bypass git hooks. The AI must fix issues properly; humans keep full autonomy.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem I Didn't Know I Had
&lt;/h2&gt;

&lt;p&gt;I've been using Claude Code as my AI pair programmer. It's genuinely impressive - writes code, runs tests, commits changes. But one day I noticed something odd in my git history.&lt;/p&gt;

&lt;p&gt;My pre-commit hooks weren't catching issues they should have caught.&lt;/p&gt;

&lt;p&gt;I dug into what was happening and found the culprit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"fix: update validation logic"&lt;/span&gt; &lt;span class="nt"&gt;--no-verify&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Claude Code was using &lt;code&gt;--no-verify&lt;/code&gt; on almost every commit. Silently bypassing all my carefully configured hooks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Matters
&lt;/h2&gt;

&lt;p&gt;Git hooks exist for a reason. Mine run:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;ESLint&lt;/strong&gt; - catch code quality issues&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prettier&lt;/strong&gt; - enforce consistent formatting&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Type checking&lt;/strong&gt; - prevent TypeScript errors from slipping through&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Commit message validation&lt;/strong&gt; - keep a clean, conventional commit history&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When the AI bypasses these, I'm essentially getting unreviewed code merged into my codebase. The very guardrails I set up to maintain quality were being circumvented.&lt;/p&gt;

&lt;p&gt;I get &lt;em&gt;why&lt;/em&gt; Claude Code does this - hooks can fail, hooks can be slow, and the AI is trying to be efficient. But efficiency at the cost of quality isn't a trade-off I want to make.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution: Enforce the Rules
&lt;/h2&gt;

&lt;p&gt;Instead of hoping for a configuration option that might never come, I built a tool that intercepts git commands before they execute.&lt;/p&gt;

&lt;p&gt;Meet &lt;a href="https://github.com/tupe12334/block-no-verify" rel="noopener noreferrer"&gt;&lt;code&gt;block-no-verify&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  How It Works
&lt;/h3&gt;

&lt;p&gt;Claude Code has a hooks system that lets you run scripts before certain actions. I use the &lt;code&gt;PreToolUse&lt;/code&gt; hook to inspect every Bash command before it runs:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;.claude/settings.json:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"hooks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"PreToolUse"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"matcher"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Bash"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"hooks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"pnpm dlx block-no-verify"&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When Claude Code tries to run something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"feat: add new feature"&lt;/span&gt; &lt;span class="nt"&gt;--no-verify&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The tool detects the forbidden flag and blocks the command with a clear message:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;❌ Blocked: --no-verify flag is not allowed
The --no-verify flag bypasses git hooks which enforce code quality.
Please fix any issues that would cause hooks to fail.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Smart Detection
&lt;/h3&gt;

&lt;p&gt;One tricky part: the &lt;code&gt;-n&lt;/code&gt; shorthand means different things for different git commands:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;th&gt;
&lt;code&gt;-n&lt;/code&gt; means&lt;/th&gt;
&lt;th&gt;Blocked?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;git commit -n&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;--no-verify&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;git push -n&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;--dry-run&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;git merge -n&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;--no-commit&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The tool understands this context and only blocks when &lt;code&gt;-n&lt;/code&gt; actually means "skip verification."&lt;/p&gt;

&lt;p&gt;It also handles edge cases like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Flags embedded in strings: &lt;code&gt;git commit -m "--no-verify is blocked"&lt;/code&gt;  (allowed - it's just a message)&lt;/li&gt;
&lt;li&gt;Shell syntax: &lt;code&gt;echo "--no-verify" | git commit&lt;/code&gt; (detected)&lt;/li&gt;
&lt;li&gt;Command chaining: &lt;code&gt;git add . &amp;amp;&amp;amp; git commit --no-verify&lt;/code&gt; (blocked)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Philosophy: Asymmetric Governance
&lt;/h2&gt;

&lt;p&gt;Here's what I didn't want: a tool that blocks &lt;code&gt;--no-verify&lt;/code&gt; for everyone.&lt;/p&gt;

&lt;p&gt;Sometimes &lt;em&gt;I&lt;/em&gt; need to bypass hooks. Maybe I'm doing a large refactor and want to commit work-in-progress. Maybe there's a false positive in my linter. As the developer, I can make that judgment call.&lt;/p&gt;

&lt;p&gt;But the AI shouldn't make that call for me.&lt;/p&gt;

&lt;p&gt;So &lt;code&gt;block-no-verify&lt;/code&gt; only affects commands run through Claude Code's hook system. When I run git commands directly in my terminal, nothing changes. Full autonomy preserved.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI agent&lt;/strong&gt; → Must follow the rules, fix issues properly&lt;br&gt;
&lt;strong&gt;Human developer&lt;/strong&gt; → Can override when they judge it appropriate&lt;/p&gt;

&lt;p&gt;This is what I call asymmetric governance. The AI operates under stricter constraints than the human, because the human understands context that the AI doesn't.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Results
&lt;/h2&gt;

&lt;p&gt;Since implementing this, Claude Code actually fixes the issues instead of bypassing them.&lt;/p&gt;

&lt;p&gt;Hook fails because of a lint error? The agent reads the error, fixes the code, and commits cleanly.&lt;/p&gt;

&lt;p&gt;This creates a positive feedback loop:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Hook fails&lt;/li&gt;
&lt;li&gt;Agent investigates the failure&lt;/li&gt;
&lt;li&gt;Agent fixes the root cause&lt;/li&gt;
&lt;li&gt;Code quality actually improves&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Compare this to the bypass behavior:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Hook would fail&lt;/li&gt;
&lt;li&gt;Agent uses &lt;code&gt;--no-verify&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Problem hidden&lt;/li&gt;
&lt;li&gt;Technical debt accumulates silently&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Installation
&lt;/h3&gt;

&lt;p&gt;Add to your Claude Code settings:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;.claude/settings.json:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"hooks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"PreToolUse"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"matcher"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Bash"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"hooks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"pnpm dlx block-no-verify"&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. No global installation, no configuration. The tool reads the command from stdin (passed by Claude Code) and exits with an error if &lt;code&gt;--no-verify&lt;/code&gt; is detected.&lt;/p&gt;

&lt;h2&gt;
  
  
  Broader Implications
&lt;/h2&gt;

&lt;p&gt;As AI coding assistants become more capable and autonomous, we need to think about governance. These tools are powerful, but they optimize for completing tasks - not necessarily for maintaining the standards we've set up.&lt;/p&gt;

&lt;p&gt;Git hooks, linters, tests - these are encoded team knowledge. They represent decisions we've made about quality. When an AI bypasses them, it's not just skipping a step; it's ignoring institutional wisdom.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;block-no-verify&lt;/code&gt; is a small tool solving a specific problem, but it represents a larger pattern: &lt;strong&gt;programmatic enforcement of AI behavior&lt;/strong&gt;. Rather than relying on prompts or hoping the AI "does the right thing," we can build guardrails that make certain behaviors impossible.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next?
&lt;/h2&gt;

&lt;p&gt;I'm considering extending this pattern to other "escape hatch" behaviors:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;git push --force&lt;/code&gt; to protected branches&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;git reset --hard&lt;/code&gt; without confirmation&lt;/li&gt;
&lt;li&gt;Other flags that could cause irreversible damage&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I've also been working on &lt;a href="https://github.com/tupe12334/eslint-config-agent" rel="noopener noreferrer"&gt;&lt;code&gt;eslint-config-agent&lt;/code&gt;&lt;/a&gt; - an ESLint configuration specifically designed for codebases where AI agents are writing code. The idea is to have stricter, more opinionated linting rules that catch the kinds of mistakes AI tends to make. Combined with &lt;code&gt;block-no-verify&lt;/code&gt;, it creates a solid quality gate that AI-generated code must pass through.&lt;/p&gt;

&lt;p&gt;If you're using AI coding assistants and care about maintaining your code quality standards, give &lt;a href="https://github.com/tupe12334/block-no-verify" rel="noopener noreferrer"&gt;&lt;code&gt;block-no-verify&lt;/code&gt;&lt;/a&gt; a try. I'd love to hear your feedback on both projects - what rules would you add to &lt;code&gt;eslint-config-agent&lt;/code&gt;? What other guardrails have you found useful when working with AI assistants?&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Links:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/tupe12334/block-no-verify" rel="noopener noreferrer"&gt;block-no-verify - GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.npmjs.com/package/block-no-verify" rel="noopener noreferrer"&gt;block-no-verify - npm&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/tupe12334/eslint-config-agent" rel="noopener noreferrer"&gt;eslint-config-agent - GitHub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Have you run into similar issues with AI coding assistants bypassing your safeguards? What other governance patterns have you found useful? Let me know in the comments.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>claude</category>
      <category>git</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Compilation vs. Transpilation</title>
      <dc:creator>tupe12334</dc:creator>
      <pubDate>Wed, 21 Dec 2022 16:23:42 +0000</pubDate>
      <link>https://dev.to/tupe12334/compilation-vs-transpilation-4917</link>
      <guid>https://dev.to/tupe12334/compilation-vs-transpilation-4917</guid>
      <description>&lt;h2&gt;
  
  
  TLDR
&lt;/h2&gt;

&lt;p&gt;Compilation is the process of turning source code into machine code, while transpilation is the process of turning source code from one high-level language into another.&lt;/p&gt;

&lt;h2&gt;
  
  
  Background story
&lt;/h2&gt;

&lt;p&gt;Today I had a conversation with two of my colleagues about the difference between compilation and transpilation. They asked me to explain it to them in simple terms, and I thought it would be a great idea to write an article about it for anyone who might be wondering about the same thing.&lt;/p&gt;

&lt;p&gt;Compilation and transpilation are both processes that involve turning source code written in one programming language into another. However, there are some key differences between the two that I will explain below.&lt;/p&gt;

&lt;h2&gt;
  
  
  Compilation
&lt;/h2&gt;

&lt;p&gt;Compilation is the process of converting source code written in a high-level programming language (such as C++ or Java) into machine code that can be run on a computer. This machine code is specific to the type of processor that the computer is using and is generally not readable by humans. Compiling source code allows it to be run more efficiently on a computer, as the machine code is optimized for the specific processor it is being run on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Transpilation
&lt;/h2&gt;

&lt;p&gt;Transpilation, on the other hand, involves converting source code from one high-level programming language into another. For example, transpiling TypeScript to JavaScript involves taking source code written in TypeScript and converting it into JavaScript. This process is often used to allow code written in newer or less widely-used programming languages to be used in environments where those languages are not supported.&lt;/p&gt;

&lt;h3&gt;
  
  
  Typescript and JavaScript
&lt;/h3&gt;

&lt;p&gt;One example of transpilation in action is the conversion of TypeScript code to JavaScript. TypeScript is a programming language that was developed by Microsoft and is a superset of JavaScript. It adds features to JavaScript such as static typing and class-based object-oriented programming, which can make it easier to write large-scale applications. However, not all environments support TypeScript, so transpiling it to JavaScript allows it to be used in those environments.&lt;/p&gt;

&lt;h2&gt;
  
  
  Feel free to contact me
&lt;/h2&gt;

&lt;p&gt;I hope this explanation of the difference between compilation and transpilation has been helpful! If you have any questions or would like further information, please don't hesitate to ask.&lt;/p&gt;

&lt;h2&gt;
  
  
  Full Disclosure
&lt;/h2&gt;

&lt;p&gt;I used tools such as ChatGPT and Grammarly to help me write this article. These tools assisted in the writing process by suggesting alternative phrases and correcting grammar errors, but the content of the article is entirely my own work.&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>typescript</category>
      <category>javascript</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
