<?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: Junkyu Jeon</title>
    <description>The latest articles on DEV Community by Junkyu Jeon (@jakay).</description>
    <link>https://dev.to/jakay</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%2F3881762%2F9a9db767-95ae-450b-bb0c-4b74fd31e375.jpg</url>
      <title>DEV Community: Junkyu Jeon</title>
      <link>https://dev.to/jakay</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jakay"/>
    <language>en</language>
    <item>
      <title>How to Code Review AI-Generated Code: What Needs Human Eyes vs. What Doesn't.</title>
      <dc:creator>Junkyu Jeon</dc:creator>
      <pubDate>Wed, 13 May 2026 06:59:24 +0000</pubDate>
      <link>https://dev.to/jakay/how-to-code-review-ai-generated-code-what-needs-human-eyes-vs-what-doesnt-2po2</link>
      <guid>https://dev.to/jakay/how-to-code-review-ai-generated-code-what-needs-human-eyes-vs-what-doesnt-2po2</guid>
      <description>&lt;p&gt;You shipped a feature with Cursor or Lovable. It works. Then a teammate asks you to walk them through the error handling. You open the file. You trail off. If that moment feels familiar, the problem isn't your code — it's that standard code review instincts weren't built for AI-generated output. AI code is &lt;em&gt;syntactically correct but semantically fragile&lt;/em&gt;: it compiles, TypeScript is happy, the happy path works — but the assumptions underneath were never surfaced. This post gives you a focused mental model: the 5 patterns that require a human judgment call, and the AI self-review prompts that handle the mechanical checks so you don't have to.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why AI Code Review Is Different
&lt;/h2&gt;

&lt;p&gt;Traditional code review assumes a human wrote the code — someone who made decisions with intent, who understood the domain, who chose an approach and can explain it. You're reviewing for logical errors, style consistency, and the occasional "did you think about X?"&lt;/p&gt;

&lt;p&gt;AI-generated code is different in a specific way: it is &lt;strong&gt;syntactically correct but semantically fragile&lt;/strong&gt;. The code compiles. TypeScript is happy. The function returns the right shape. But the logic inside makes assumptions the AI never surfaced — assumptions about data that will always be present, about error cases that will never happen, about external dependencies that will always behave.&lt;/p&gt;

&lt;p&gt;The AI wasn't being careless. It was pattern-matching from training data. When you asked it to "fetch the user's profile and display it," it generated the happy path — a user exists, the fetch succeeds, the data is complete. It didn't hallucinate a bug. It just didn't think past the thing you asked for.&lt;/p&gt;

&lt;p&gt;This means AI code review is less about catching logic errors and more about surfacing hidden assumptions. Your job is not to find what's wrong — it's to find what's &lt;em&gt;assumed&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  5 Patterns That Require Human Eyes
&lt;/h2&gt;

&lt;p&gt;These are the categories where AI-generated code consistently carries risk. For each one: what it looks like, why it's dangerous, and how to fix it.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Hidden State
&lt;/h3&gt;

&lt;p&gt;AI-generated code often carries state in places you don't expect — module-level variables, closure-captured values, or shared mutable objects that survive across calls. The code looks like a stateless function. It isn't.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// AI-generated: looks like a pure utility function&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getUser&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="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&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="nx"&gt;cache&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="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;cache&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="c1"&gt;// ... fetch and populate cache&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;cache&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="c1"&gt;// The problem: cache persists across requests in a serverless/edge context.&lt;/span&gt;
&lt;span class="c1"&gt;// Two users can end up seeing each other's data.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why it's dangerous:&lt;/strong&gt; In serverless and edge runtimes (Cloudflare Workers, Vercel Edge), module-level state is not always reset between requests. The AI generated this pattern because it's sensible in a traditional server with one process per instance — but it becomes a data leak in a shared environment.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Fixed: state lives in the request, not the module&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getUser&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="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;requestCache&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="o"&gt;&amp;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="nx"&gt;requestCache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&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="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;requestCache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetchUserFromDb&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="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;requestCache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&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="nx"&gt;user&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;user&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;&lt;strong&gt;What to look for:&lt;/strong&gt; Any &lt;code&gt;let&lt;/code&gt; or &lt;code&gt;const&lt;/code&gt; declared at the top level of a module that is mutated inside a function. Ask: "What happens if this function is called twice concurrently?"&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Hardcoded Values
&lt;/h3&gt;

&lt;p&gt;AI loves to hardcode. URLs, timeouts, limits, role names, status strings — whatever was in the prompt or in the training data for similar code. These values look fine in the generated code because they match your current reality. They become time bombs as your app evolves.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// AI-generated: values burned directly into the logic&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getUserPosts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;supabase&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;posts&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;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;*&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;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user_id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;status&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;published&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;     &lt;span class="c1"&gt;// hardcoded status&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                      &lt;span class="c1"&gt;// hardcoded limit&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;created_at&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;ascending&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&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="na"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;hasMore&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// hardcoded comparison&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;&lt;strong&gt;Why it's dangerous:&lt;/strong&gt; When you later change the limit to 20, the &lt;code&gt;hasMore&lt;/code&gt; logic silently breaks — it's still comparing against the old constant. Two months from now, a teammate changes the limit on line 8 and doesn't notice the comparison on line 14.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Fixed: named constants, single source of truth&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;POST_STATUS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;PUBLISHED&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;published&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;DRAFT&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;draft&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;POSTS_PER_PAGE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&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;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getUserPosts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;supabase&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;posts&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;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;*&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;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user_id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;status&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;POST_STATUS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PUBLISHED&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;POSTS_PER_PAGE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;created_at&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;ascending&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&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="na"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
    &lt;span class="na"&gt;hasMore&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;POSTS_PER_PAGE&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;p&gt;&lt;strong&gt;What to look for:&lt;/strong&gt; Any number, string literal, or URL that appears inside business logic. If the value would need to change when your requirements change, it should be a named constant or config value.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Silent Error Swallowing
&lt;/h3&gt;

&lt;p&gt;This is the most insidious pattern. AI-generated code frequently wraps operations in try/catch blocks and then does nothing useful in the catch — returns null, returns an empty array, logs to console, or just moves on. The feature appears to work. In reality, it's silently dropping failures.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// AI-generated: errors vanish into the void&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;sendWelcomeEmail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;resend&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;emails&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="na"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;welcome@yourapp.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Welcome!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;html&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;renderWelcomeEmail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="o"&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;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Failed to send welcome email:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// silently returns undefined — caller never knows it failed&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;&lt;strong&gt;Why it's dangerous:&lt;/strong&gt; Your onboarding flow calls &lt;code&gt;sendWelcomeEmail&lt;/code&gt;. The email provider is down for 10 minutes. Zero users get a welcome email. Your code shows no errors. Your monitoring shows no alerts. You find out a week later when a user says they never got the email.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Fixed: errors propagate or are explicitly handled with intent&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;sendWelcomeEmail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="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="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`User &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; not found`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;resend&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;emails&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="na"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;welcome@yourapp.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;to&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;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Welcome!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;html&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;renderWelcomeEmail&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="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// In the caller:&lt;/span&gt;
&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;sendWelcomeEmail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newUser&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;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&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;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Welcome email failed&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;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;newUser&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="nx"&gt;error&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="c1"&gt;// deliberate decision: continue onboarding, retry async&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What to look for:&lt;/strong&gt; Any catch block that does not re-throw, does not return a meaningful error state to the caller, or only logs to console. Ask: "If this catch fires in production, will I know?"&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Assumed External Dependencies
&lt;/h3&gt;

&lt;p&gt;AI builds against the happy path of every dependency. It assumes your database is reachable, your API keys are valid, your third-party service returns the documented shape. It does not model what happens when any of those assumptions are wrong.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// AI-generated: assumes Supabase always returns what we expect&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getDashboardData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;profile&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;supabase&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;profiles&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;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;*&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;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;single&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;subscription&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;supabase&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;subscriptions&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;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;*&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;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user_id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;single&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="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;full_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;        &lt;span class="c1"&gt;// crashes if profile is null&lt;/span&gt;
    &lt;span class="na"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;subscription&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;plan_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;// crashes if no subscription row&lt;/span&gt;
    &lt;span class="na"&gt;renewsAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;subscription&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;renews_at&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;p&gt;&lt;strong&gt;Why it's dangerous:&lt;/strong&gt; New users don't have a subscription row yet. Profiles can be deleted. The Supabase RLS policy might filter the row out. Any of these causes a crash at the property access, with a runtime error that gives no context about &lt;em&gt;why&lt;/em&gt; the data was missing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Fixed: each dependency failure has a deliberate response&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getDashboardData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;profileResult&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;subscriptionResult&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="nx"&gt;supabase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;profiles&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;*&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;single&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="nx"&gt;supabase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;subscriptions&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;*&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user_id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;maybeSingle&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="nx"&gt;profileResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;profileResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Profile not found for user &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&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="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;profileResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;full_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;subscriptionResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;plan_name&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;free&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;renewsAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;subscriptionResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;renews_at&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="kc"&gt;null&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;p&gt;&lt;strong&gt;What to look for:&lt;/strong&gt; Any property access on a value returned from a network call, database query, or external API without a null check. TypeScript with strict mode will catch some of these, but AI often adds non-null assertions (&lt;code&gt;!&lt;/code&gt;) to silence the compiler rather than handling the case.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Untestable Structure
&lt;/h3&gt;

&lt;p&gt;AI-generated code is often structurally untestable — not because the logic is complex, but because the logic, the I/O, and the side effects are all tangled together in a single function with no seams for a test to grab onto.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// AI-generated: logic, database, and email fused together&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;processNewOrder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;order&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;supabase&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;orders&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;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;*, items(*), user(*)&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;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;single&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;sum&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;price&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;quantity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;discountedTotal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;total&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="o"&gt;!&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;is_pro&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="mf"&gt;0.9&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;supabase&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;orders&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;update&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;total&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;discountedTotal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;confirmed&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;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;resend&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;emails&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="cm"&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;&lt;strong&gt;Why it's dangerous:&lt;/strong&gt; You cannot test the discount logic without hitting a real database and triggering a real email. Change the discount rate and you have no way to verify the math is right without a full end-to-end run. This is how bugs hide.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Fixed: pure business logic separated from I/O&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;calculateOrderTotal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;OrderItem&lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;
  &lt;span class="nx"&gt;isPro&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;subtotal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;sum&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;price&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;quantity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&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;subtotal&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isPro&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="mf"&gt;0.9&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Now trivially testable:&lt;/span&gt;
&lt;span class="c1"&gt;// expect(calculateOrderTotal([{ price: 100, quantity: 2 }], true)).toBe(180);&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;processNewOrder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;order&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;supabase&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;orders&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;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;*, items(*), user(*)&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;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;single&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;order&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Order &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; not found`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;calculateOrderTotal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;order&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;is_pro&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;supabase&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;orders&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;update&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;total&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;confirmed&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;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;resend&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;emails&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="cm"&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;&lt;strong&gt;What to look for:&lt;/strong&gt; Functions that mix data fetching, business logic, and side effects without the logic being extractable. Ask: "Can I test the core decision-making in this function without mocking a database?" If the answer is no, extract the logic.&lt;/p&gt;

&lt;h2&gt;
  
  
  What You Can Delegate Back to AI
&lt;/h2&gt;

&lt;p&gt;Not everything needs a human eye. Some checks are mechanical — consistent, rule-based, and well-suited for AI. Use these prompts to let AI do the legwork before you do the judgment calls.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Surface the assumptions:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;List every assumption this code makes — about input values,
external services, data shapes, and environment state.
For each assumption, rate how likely it is to be violated in production.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Find failure inputs:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Give me 5 specific inputs or conditions that would cause this function
to throw an error, return wrong data, or silently do nothing.
Show what the actual output would be for each.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Scan for hardcoded values:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Find every hardcoded string, number, or URL in this code that
represents a business rule or configuration value.
For each one, explain what would break if it changed.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Check error propagation:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Trace every possible error path in this code.
For each catch block or error handler, tell me:
does the caller know the operation failed?
If not, what data or behavior is silently lost?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Assess testability:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Which parts of this code contain business logic that cannot be
tested without hitting a real database or external API?
Suggest how to extract that logic into pure functions.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run these before your human review. The AI will surface most of the mechanical issues; your human review focuses on the patterns that require judgment.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Handoff Checkpoint: Can You Explain This?
&lt;/h2&gt;

&lt;p&gt;Before you hand AI-generated code to a teammate or push it to production, apply one final test. For each significant function or module:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;What does this do when everything goes right?&lt;/strong&gt; If you can't answer fluently, you don't own it yet.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What does this do when the database is slow?&lt;/strong&gt; When the user is not logged in? When the external API returns a 429?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What would I tell a teammate who had to debug this at 2am?&lt;/strong&gt; If your answer is "good luck," it's not ready.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you can't explain it, you have two options. Either spend 30 minutes with the AI asking "explain what this does, step by step, and list everything it assumes" until you can explain it yourself. Or flag it explicitly when you hand it off: "this works but I haven't fully reviewed the error handling — don't merge this until someone does."&lt;/p&gt;

&lt;p&gt;Both are honest. Saying nothing and hoping for the best is not.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing
&lt;/h2&gt;

&lt;p&gt;AI-generated code is not dangerous because it's wrong. It's risky because it's &lt;em&gt;confidently incomplete&lt;/em&gt;. The syntax is clean, the types check out, and the happy path works. The problems live in the assumptions — the things that were never said in the prompt and never surfaced in the output.&lt;/p&gt;

&lt;p&gt;The five patterns — hidden state, hardcoded values, silent errors, assumed dependencies, and untestable structure — show up in almost every meaningful AI-generated codebase. Knowing where to look turns a daunting review into a focused one.&lt;/p&gt;

&lt;p&gt;Build fast. Review with intention. The two are not in conflict.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://bivecode.com/blog/review-ai-generated-code" rel="noopener noreferrer"&gt;bivecode.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>typescript</category>
      <category>webdev</category>
      <category>codereview</category>
    </item>
    <item>
      <title>Stop Prompting One Agent. Build an Agile AI Team That Actually Ships.Your AI Coding Was Magic. Now It's Making Everything Worse.</title>
      <dc:creator>Junkyu Jeon</dc:creator>
      <pubDate>Wed, 13 May 2026 06:53:48 +0000</pubDate>
      <link>https://dev.to/jakay/stop-prompting-one-agent-build-an-agile-ai-team-that-actually-shipsyour-ai-coding-was-magic-now-55d6</link>
      <guid>https://dev.to/jakay/stop-prompting-one-agent-build-an-agile-ai-team-that-actually-shipsyour-ai-coding-was-magic-now-55d6</guid>
      <description>&lt;p&gt;Here's something worth knowing before you read this post: its outline wasn't written by a human. A planner subagent drafted the structure across three revision cycles. A reviewer subagent flagged gaps. A marketer subagent checked the framing. The human — me — wrote the prose. Four passes, four distinct orientations, one document. That's the pattern this post is about.&lt;/p&gt;

&lt;p&gt;A practical guide to harness engineering for solo vibe coders.&lt;/p&gt;

&lt;p&gt;At some point in every vibe coding project, the agent stops being helpful and starts being agreeable. You ask it to add a feature — it says yes, and quietly breaks something upstream. You ask it to review its own work — it says yes, and finds nothing wrong. You ask it to plan and build at the same time — it says yes, and produces code that satisfies neither goal. The tool didn't fail. The architecture failed. You handed one agent too many jobs and expected it to hold them all simultaneously without dropping any.&lt;/p&gt;

&lt;p&gt;The instinct at that point is usually to write a better prompt. More context, stricter constraints, a longer system message. Sometimes that helps. But there's a ceiling — and it's lower than most people realize, because the real constraint isn't the prompt quality. It's the harness around the model.&lt;/p&gt;

&lt;h2&gt;
  
  
  When You Give One Agent Everything, Here's What Actually Happens
&lt;/h2&gt;

&lt;p&gt;The structural problem with a single-agent setup is context pollution. Planning context, implementation context, and review context all land in the same context window, and they actively work against each other. The agent that just spent several turns building a feature has psychological investment in that feature. When you ask it to review what it just wrote, it doesn't approach the code as an outsider — it approaches it as its author. The result is not a review. It's a defense.&lt;/p&gt;

&lt;p&gt;This is the "yes-man" failure mode. The agent has learned the shape of what you want, and it starts optimizing for agreement rather than accuracy. Every new turn narrows the cone of acceptable responses, and the agent gradually loses the ability to push back. What looks like a smart assistant is actually a mirror that reflects your assumptions back at you with better vocabulary.&lt;/p&gt;

&lt;p&gt;There's also a raw capacity problem. When planning artifacts, implementation history, and debugging logs all compete for the same context window, something gets compressed or dropped. Often it's the earliest planning context — the "why" behind the decisions — and the agent starts navigating without a map. The code still compiles. The direction is just wrong.&lt;/p&gt;

&lt;p&gt;If you've ever watched an AI coding session drift from the original goal by turn fifteen, this is the mechanism. It's not hallucination. It's context pollution accumulated over a long conversation.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Agile Team Principle Applies Directly to AI Agents
&lt;/h2&gt;

&lt;p&gt;A good agile team doesn't ask one person to be the product owner, the developer, the QA engineer, and the external stakeholder simultaneously. Role separation exists because different functions require different incentives, different perspectives, and different definitions of "done." The developer who writes a feature is the worst person to approve it — not because they're incompetent, but because they can't fully unsee their own implementation decisions.&lt;/p&gt;

&lt;p&gt;The mapping to AI agents is almost one-to-one:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Planner&lt;/strong&gt; ≈ sprint planner / product owner — defines scope, breaks down requirements, owns the "why"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Developer&lt;/strong&gt; ≈ developer — implements against spec, stays in implementation context&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reviewer&lt;/strong&gt; ≈ QA / code reviewer — evaluates output against spec without implementation bias&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Marketer&lt;/strong&gt; ≈ external stakeholder — reads from the outside, flags language and positioning gaps&lt;/li&gt;
&lt;li&gt;Work cycle ≈ sprint — bounded scope, clear handoff point&lt;/li&gt;
&lt;li&gt;Agent-to-agent handoff ≈ standup / pair programming — explicit transfer of state and intent&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The structural insight: the moment role boundaries are drawn between agents, context pollution stops. The reviewer doesn't carry implementation history. The planner doesn't carry debugging artifacts. Each agent operates in a clean window, with only the context it actually needs to do its job well.&lt;/p&gt;

&lt;h2&gt;
  
  
  The &lt;code&gt;.claude/agents/&lt;/code&gt; Pattern, Dissected
&lt;/h2&gt;

&lt;p&gt;Claude Code's subagent mechanism makes this concrete. When you define agents in a &lt;code&gt;.claude/agents/&lt;/code&gt; directory, you're not just writing configuration files — you're constructing a topology. Each file is a scoped system prompt that defines a role, its permissions, and its focus. The main agent can invoke these subagents explicitly, handing off control for a bounded task and receiving the result back.&lt;/p&gt;

&lt;p&gt;A minimal agent definition file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;reviewer&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Evaluates&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;code&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;and&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;for&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;correctness,&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;security&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;issues,&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;and&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;logical&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;gaps.&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Does&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;not&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;implement&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;fixes&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;—&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;only&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;reports&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;findings."&lt;/span&gt;
&lt;span class="na"&gt;tools&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Read, Bash&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;

&lt;span class="s"&gt;You are a strict code and content reviewer. Your job is to find what is wrong, not to defend what exists. When given a piece of work, identify concrete issues — logical errors, security gaps, factual inaccuracies, missing edge cases. Return findings as a numbered list. Do not suggest how to fix them unless explicitly asked.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The YAML frontmatter declares the agent's identity and which tools it can access. The body is the system prompt — the operating instructions that shape every response this agent produces. Think of it as a role-differentiated version of what CLAUDE.md does for your project at large.&lt;/p&gt;

&lt;p&gt;This isolation is the key property. The reviewer doesn't know how many attempts the developer made. The planner doesn't see the debugging session that happened two hours ago. Each subagent sees what it needs, and nothing else.&lt;/p&gt;

&lt;h2&gt;
  
  
  How BiveCode Actually Runs on This Pattern
&lt;/h2&gt;

&lt;p&gt;BiveCode runs four subagents in &lt;code&gt;.claude/agents/&lt;/code&gt;: planner, marketer, developer, and reviewer. Each has a scoped system prompt defining its role and constraints.&lt;/p&gt;

&lt;p&gt;What each agent handles in practice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Planner&lt;/strong&gt;: topic proposals, outline structure, H2 sequencing, internal link mapping, scope boundaries&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Marketer&lt;/strong&gt;: SEO keyword targeting, excerpt copy, meta description, CTA placement, positioning against competing content&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Developer&lt;/strong&gt;: implementation — content, code, MDX, routing, schema&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reviewer&lt;/strong&gt;: fact verification, security review, logical consistency, code correctness&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Worth being direct about the current state: BiveCode has role separation — four agents, each with a distinct system prompt. It does not yet have domain separation within those roles. The developer agent handles frontend code, backend code, database schema, and prose equally. That works at the current project size. But it's the next upgrade on the list.&lt;/p&gt;

&lt;h2&gt;
  
  
  From Role Separation to Domain Separation
&lt;/h2&gt;

&lt;p&gt;Role separation is the first axis. Domain separation is the second — and it's where the pattern scales.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For a solo vibe coder just starting out, three agents are enough:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Builder&lt;/strong&gt; — system prompt focused on implementation. Knows the stack, the conventions, the patterns in use. Optimizes for "make it work."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Critic&lt;/strong&gt; — system prompt focused on finding gaps. No implementation bias. Optimizes for "find what's wrong."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security checker&lt;/strong&gt; — system prompt focused on trust boundaries. Auth flows, secret handling, input validation. Optimizes for "what could go wrong."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Three agents, three context windows that never contaminate each other. Even this minimal configuration catches a meaningful class of errors that a single agent systematically misses — because no single agent can hold implementation incentive, adversarial review, and security paranoia simultaneously.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When the project grows, the next move is domain-specialized agent separation:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Instead of one developer agent handling everything, you might have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Frontend developer&lt;/strong&gt; — React, Tailwind, component architecture, accessibility&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backend developer&lt;/strong&gt; — API routes, business logic, service boundaries&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Database engineer&lt;/strong&gt; — schema, migrations, query optimization, RLS policies&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DevOps engineer&lt;/strong&gt; — deployment config, environment variables, build pipelines&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The reason this works is context segmentation. When a context window is focused on a single domain, the density of relevant knowledge is higher. That narrowing reduces hallucination surface, reduces context pollution, and increases the precision of outputs within that domain.&lt;/p&gt;

&lt;p&gt;The progression: "write a better prompt" → "separate roles" → "separate domains within roles." Each step is a level of abstraction above the previous one.&lt;/p&gt;

&lt;h2&gt;
  
  
  When This Pattern Is Overkill
&lt;/h2&gt;

&lt;p&gt;Multi-agent orchestration has real overhead. Situations where a single agent is the right call:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A one or two-day prototype where the goal is to learn whether an idea works, not to ship&lt;/li&gt;
&lt;li&gt;Fully exploratory phases where requirements are still undefined&lt;/li&gt;
&lt;li&gt;Simple, bounded tasks — a standalone script, a single-page utility&lt;/li&gt;
&lt;li&gt;Time-sensitive debugging where the overhead costs more than the review would save&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The pattern earns its keep when the project has enough complexity that context pollution is actually happening — when you've had the experience of an agent breaking something it just fixed, or validating something it should have questioned. Add it when you feel the friction, not before.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Name for What You're Doing
&lt;/h2&gt;

&lt;p&gt;Once you've designed role boundaries, defined domain-specialized agents, and built explicit handoff logic between them, you've given your work a new shape. The thing you're doing has a name: &lt;strong&gt;harness engineering&lt;/strong&gt;. Designing not the model itself, but the structure around it — the system prompts, the tool definitions, the subagent topology, the context window management strategy. It's the discipline that operates one abstraction level above prompt engineering, and it scales where prompts stop scaling.&lt;/p&gt;

&lt;p&gt;An AI agent harness is the set of structural decisions that determine what each agent sees, what it can do, and how its outputs flow to the next stage. Context segmentation is the mechanism; harness engineering is the discipline.&lt;/p&gt;

&lt;p&gt;This is not a hobby-level configuration exercise. Designing a harness that correctly separates roles, scopes domains, and manages context boundaries for a real project requires the same kind of thinking as designing a service architecture — you're making decisions that will compound. Bad boundaries compound badly. Good ones compound well.&lt;/p&gt;

&lt;p&gt;What you're doing isn't hobby configuration. It's engineering.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://bivecode.com/blog/multi-agent-vibe-coding" rel="noopener noreferrer"&gt;bivecode.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>productivity</category>
      <category>aiagents</category>
      <category>vibecoding</category>
    </item>
    <item>
      <title>How to Write Better Prompts for Bolt, Lovable, and Cursor</title>
      <dc:creator>Junkyu Jeon</dc:creator>
      <pubDate>Mon, 20 Apr 2026 23:21:36 +0000</pubDate>
      <link>https://dev.to/jakay/how-to-write-better-prompts-for-bolt-lovable-and-cursor-do1</link>
      <guid>https://dev.to/jakay/how-to-write-better-prompts-for-bolt-lovable-and-cursor-do1</guid>
      <description>&lt;p&gt;You and your friend both use Cursor. You both ask for "a simple to-do app." Three hours later, your friend has a working app with auth, persistence, and a clean UI. You have a pile of files, a broken login, and a database that won't connect.&lt;/p&gt;

&lt;p&gt;Same model. Different prompt.&lt;/p&gt;

&lt;p&gt;People talk about prompt engineering like mysticism. It isn't. It's closer to a checklist. Here's the checklist — plus templates you can copy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Most Prompts Fail
&lt;/h2&gt;

&lt;p&gt;Bad prompts share a few patterns. The AI isn't guessing maliciously — it's filling blanks you left empty.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No stack specified.&lt;/strong&gt; "Add a database" — which one? SQLite? Supabase? Postgres? AI picks. Often picks something that doesn't fit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Whole-app requests.&lt;/strong&gt; "Build me an Airbnb clone." 40 files of plausible-looking code, none of which fit together.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vague verbs.&lt;/strong&gt; "Make it better." "Refactor this." No target — so it changes everything, including parts that were fine.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No examples.&lt;/strong&gt; "Format the date nicely." AI guesses what "nicely" means.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Missing constraints.&lt;/strong&gt; No edge cases, no max length, no expected error behavior. AI handles the happy path and ignores the rest.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each blank is a decision the AI makes for you — usually not the one you'd have made.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Anatomy of a Good Prompt
&lt;/h2&gt;

&lt;p&gt;Five parts. Don't need every part every time, but more = less guessing.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Context
&lt;/h3&gt;

&lt;p&gt;What is this project? What stack? What conventions? If you have a CLAUDE.md or .cursorrules, this is already covered. If not, lead with one sentence: &lt;em&gt;"This is a Next.js 15 + Supabase + Tailwind app. We use shadcn/ui and follow App Router conventions."&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Goal
&lt;/h3&gt;

&lt;p&gt;What specifically do you want? One concrete change. &lt;em&gt;"Add a button on the dashboard that exports the visible projects to CSV"&lt;/em&gt; — not &lt;em&gt;"add export functionality."&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Constraints
&lt;/h3&gt;

&lt;p&gt;Must do? Must not do? &lt;em&gt;"Must work for up to 10,000 rows. Must not block the UI. Use the existing supabase client from &lt;code&gt;lib/supabase.ts&lt;/code&gt;."&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Output Format
&lt;/h3&gt;

&lt;p&gt;How do you want the answer? Code only? Diff only? Specify it.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Verification
&lt;/h3&gt;

&lt;p&gt;How will you know it worked? Tell the AI. &lt;em&gt;"After this, I should be able to click Export, get a CSV download named &lt;code&gt;projects-{date}.csv&lt;/code&gt;, and open it in Excel without warnings."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;When the AI knows what success looks like, it writes code aimed at that result — not at "something CSV-ish."&lt;/p&gt;

&lt;h2&gt;
  
  
  5 Patterns That Make Prompts Work
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Specify the Stack and Version
&lt;/h3&gt;

&lt;p&gt;"Use Next.js" is not enough. Next.js 13 Pages Router and Next.js 15 App Router are different worlds.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bad:&lt;/strong&gt; &lt;em&gt;"Add a server-side API route."&lt;/em&gt;&lt;br&gt;
&lt;strong&gt;Good:&lt;/strong&gt; &lt;em&gt;"Add a Next.js 15 App Router route handler at &lt;code&gt;app/api/export/route.ts&lt;/code&gt;. Use the new Web Request/Response API, not the old req/res."&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  2. Show, Don't Tell
&lt;/h3&gt;

&lt;p&gt;Examples beat adjectives every time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bad:&lt;/strong&gt; &lt;em&gt;"Format the price nicely."&lt;/em&gt;&lt;br&gt;
&lt;strong&gt;Good:&lt;/strong&gt; &lt;em&gt;"Format as USD with dollar sign and 2 decimals: '$1,234.56'. Under $1, show 4 decimals: '$0.0042'."&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  3. One Feature at a Time
&lt;/h3&gt;

&lt;p&gt;Resist the mega-prompt. Split it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;em&gt;"Create &lt;code&gt;/profile&lt;/code&gt; route. Fetch current user from Supabase, display name/email/joined date."&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;"Add Edit button. Toggles name/email to inputs."&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;"Add Save button. Updates user via Supabase. Shows success toast."&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;"Add avatar upload. Stores in Supabase Storage. Updates avatar_url."&lt;/em&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Each step verifiable. When something breaks, you know which step. With a mega-prompt, you don't.&lt;/p&gt;
&lt;h3&gt;
  
  
  4. Constrain the Data Shape
&lt;/h3&gt;

&lt;p&gt;Most bugs come from data the AI didn't expect.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bad:&lt;/strong&gt; &lt;em&gt;"Calculate the order total."&lt;/em&gt;&lt;br&gt;
&lt;strong&gt;Good:&lt;/strong&gt; &lt;em&gt;"Inputs: array of &lt;code&gt;{ price: number, quantity: number }&lt;/code&gt; (price in cents, quantity positive integer), and optional &lt;code&gt;discountPercent&lt;/code&gt; (0-100, default 0). Return cents. Empty array → return 0. Negative quantity → throw."&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  5. Ask for Tests Explicitly
&lt;/h3&gt;

&lt;p&gt;AI rarely writes tests unless asked. Almost never thinks about edge cases unless told.&lt;/p&gt;

&lt;p&gt;Add one line: &lt;em&gt;"Also write a test file covering: empty input, single item, max items, zero discount, 100% discount, one negative input that should throw."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The test file does double duty: forces edge-case thinking, &lt;em&gt;and&lt;/em&gt; gives you a regression net.&lt;/p&gt;
&lt;h2&gt;
  
  
  5 Anti-Patterns to Avoid
&lt;/h2&gt;
&lt;h3&gt;
  
  
  1. "Make it better"
&lt;/h3&gt;

&lt;p&gt;The vaguest possible prompt. Name what you want: &lt;em&gt;"Reduce the number of state variables. Right now there are 7 useState calls; consolidate where possible."&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  2. "Add authentication"
&lt;/h3&gt;

&lt;p&gt;Auth isn't one feature. Sign-up, sign-in, sign-out, password reset, session, route protection, email verification — at minimum. Pick a flow:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"Add Supabase email/password sign-in. One page at &lt;code&gt;/login&lt;/code&gt;. On success, redirect to &lt;code&gt;/dashboard&lt;/code&gt;. On failure, show Supabase error inline. Don't add sign-up or password reset yet."&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  3. Pasting whole logs without context
&lt;/h3&gt;

&lt;p&gt;200-line stack trace + "fix this" = AI guessing what part matters and what your code looks like.&lt;/p&gt;

&lt;p&gt;Paste the &lt;em&gt;relevant&lt;/em&gt; error line, the function it points to, one-line description of what you were doing.&lt;/p&gt;
&lt;h3&gt;
  
  
  4. "Refactor this"
&lt;/h3&gt;

&lt;p&gt;Refactor for what? Performance? Readability? Reuse? Each goal points at different changes.&lt;/p&gt;

&lt;p&gt;Better: &lt;em&gt;"Extract the price-formatting logic from this component into a function in &lt;code&gt;lib/format.ts&lt;/code&gt;. Don't change behavior. Update component to import and use the new function."&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  5. Mid-stream stack swaps
&lt;/h3&gt;

&lt;p&gt;Three hours into a Supabase project. You read a tweet about Drizzle ORM. &lt;em&gt;"Switch the database to use Drizzle."&lt;/em&gt; AI tries. Half-rewrites a few files, leaves Supabase imports scattered, nothing works.&lt;/p&gt;

&lt;p&gt;Stack swaps mid-project are surgery, not a prompt. Plan it as its own deliberate session.&lt;/p&gt;
&lt;h2&gt;
  
  
  Real Before / After
&lt;/h2&gt;

&lt;p&gt;Same goal, two prompts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Before:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Add export to CSV.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What the AI does: invents a button somewhere, picks a CSV library you don't have, exports fields it guesses are interesting, ignores filtering, blocks UI for large datasets.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;After:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Next&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;js&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt; &lt;span class="nx"&gt;Router&lt;/span&gt; &lt;span class="nx"&gt;project&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Supabase&lt;/span&gt; &lt;span class="nx"&gt;backend&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="nx"&gt;Dashboard&lt;/span&gt; &lt;span class="nx"&gt;at&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;dashboard&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tsx&lt;/span&gt; &lt;span class="nx"&gt;shows&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="nx"&gt;table&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;projects&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="nx"&gt;filtered&lt;/span&gt; &lt;span class="nx"&gt;by&lt;/span&gt; &lt;span class="nx"&gt;search&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt; &lt;span class="nx"&gt;and&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="nx"&gt;dropdown&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;

&lt;span class="nx"&gt;Goal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Add&lt;/span&gt; &lt;span class="nx"&gt;an&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Export CSV&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;button&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;search&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt; &lt;span class="nx"&gt;that&lt;/span&gt;
&lt;span class="nx"&gt;downloads&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;currently&lt;/span&gt; &lt;span class="nx"&gt;visible&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filtered&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;rows&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;CSV&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;

&lt;span class="nx"&gt;Constraints&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;No&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;dependencies&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="nx"&gt;Build&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;CSV&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="nx"&gt;manually&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;Columns&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="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;created_at &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ISO&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;owner_email&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;Handle&lt;/span&gt; &lt;span class="nx"&gt;commas&lt;/span&gt; &lt;span class="nx"&gt;and&lt;/span&gt; &lt;span class="nx"&gt;quotes&lt;/span&gt; &lt;span class="nf"&gt;correctly &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;RFC&lt;/span&gt; &lt;span class="mi"&gt;4180&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;Filename&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;projects&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;YYYY&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;MM&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;DD&lt;/span&gt;&lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="nx"&gt;csv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;Must&lt;/span&gt; &lt;span class="nx"&gt;work&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nx"&gt;up&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;000&lt;/span&gt; &lt;span class="nx"&gt;rows&lt;/span&gt; &lt;span class="nx"&gt;without&lt;/span&gt; &lt;span class="nx"&gt;freezing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;

&lt;span class="nx"&gt;Output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Diff&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;dashboard&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tsx&lt;/span&gt; &lt;span class="nx"&gt;and&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;helper&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="nx"&gt;No&lt;/span&gt; &lt;span class="nx"&gt;explanation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;

&lt;span class="nx"&gt;Verification&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;With&lt;/span&gt; &lt;span class="nx"&gt;dashboard&lt;/span&gt; &lt;span class="nx"&gt;filtered&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;active&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;I&lt;/span&gt; &lt;span class="nx"&gt;click&lt;/span&gt;
&lt;span class="nx"&gt;Export&lt;/span&gt; &lt;span class="nx"&gt;CSV&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nx"&gt;download&lt;/span&gt; &lt;span class="kd"&gt;with&lt;/span&gt; &lt;span class="nx"&gt;only&lt;/span&gt; &lt;span class="nx"&gt;active&lt;/span&gt; &lt;span class="nx"&gt;projects&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;named&lt;/span&gt;
&lt;span class="nx"&gt;projects&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;2026&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;04&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;21&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;csv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;opens&lt;/span&gt; &lt;span class="nx"&gt;cleanly&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;Excel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Second prompt: 60 seconds longer to write. Saves 30 minutes of back-and-forth.&lt;/p&gt;

&lt;h2&gt;
  
  
  Steal These Templates
&lt;/h2&gt;

&lt;h3&gt;
  
  
  New Feature
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Context: [stack + relevant existing code]
Goal: [one specific user-visible change]
Constraints:
- [must do X]
- [must not do Y]
- [data shape, edge cases]
Output: [code only / diff only / explain first]
Verification: After this, I should be able to [observable thing].
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Bug Fix
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Repro:
- Steps: [1, 2, 3]
- Input: [exact values]
- Expected: [what should happen]
- Actual: [what happens, including error]

Suspect file: [path]

Don't change behavior elsewhere. Show your hypothesis before
the fix, then minimal change to address the root cause.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Data Model
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;Add&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;Supabase&lt;/span&gt; &lt;span class="k"&gt;table&lt;/span&gt; &lt;span class="nv"&gt;"[name]"&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt; &lt;span class="k"&gt;primary&lt;/span&gt; &lt;span class="k"&gt;key&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;column&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;nullable&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;created_at&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;timestamptz&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;RLS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;who&lt;/span&gt; &lt;span class="k"&gt;reads&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;who&lt;/span&gt; &lt;span class="n"&gt;writes&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;Generate&lt;/span&gt; &lt;span class="k"&gt;SQL&lt;/span&gt; &lt;span class="n"&gt;migration&lt;/span&gt; &lt;span class="k"&gt;under&lt;/span&gt; &lt;span class="n"&gt;supabase&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;migrations&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt;
&lt;span class="nb"&gt;timestamp&lt;/span&gt; &lt;span class="k"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;and&lt;/span&gt; &lt;span class="n"&gt;TypeScript&lt;/span&gt; &lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;types&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  When Prompting Alone Isn't Enough
&lt;/h2&gt;

&lt;p&gt;For a single feature, a good prompt is enough. For a whole app, you need a sequence — and getting the sequence right is its own skill. Data model before UI. Auth before features that need it. Deploy setup before you have anything to deploy.&lt;/p&gt;




&lt;p&gt;Better prompts aren't about being clever. They're about leaving fewer blanks. Every blank is a decision the AI makes for you — and it doesn't know your project, your users, or what you actually want.&lt;/p&gt;

&lt;p&gt;The skill compounds. Every well-crafted prompt teaches you what to specify next time. Within a few weeks, your hit rate moves from "sometimes works, mostly weird" to "ships first try, most of the time."&lt;/p&gt;

&lt;p&gt;Steal the templates. Add to them as you find what your stack needs. Your AI is the same as everyone else's — your prompts don't have to be.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://bivecode.com/blog/write-better-prompts-vibe-coding" rel="noopener noreferrer"&gt;bivecode.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>productivity</category>
      <category>beginners</category>
    </item>
    <item>
      <title>How to Debug AI-Generated Code: A Systematic Approach</title>
      <dc:creator>Junkyu Jeon</dc:creator>
      <pubDate>Sun, 19 Apr 2026 14:23:57 +0000</pubDate>
      <link>https://dev.to/jakay/how-to-debug-ai-generated-code-a-systematic-approach-407d</link>
      <guid>https://dev.to/jakay/how-to-debug-ai-generated-code-a-systematic-approach-407d</guid>
      <description>&lt;h2&gt;
  
  
  The Debugging Trap With AI Code
&lt;/h2&gt;

&lt;p&gt;You built something cool with Cursor or Bolt. It worked on the demo. Then a user tried it with a slightly different input — and it blew up. You paste the error into the AI, it confidently rewrites the function, and now a &lt;em&gt;different&lt;/em&gt; thing is broken. Welcome to the debugging trap.&lt;/p&gt;

&lt;p&gt;Research from GitClear and others suggests &lt;strong&gt;around 43% of AI-generated projects need real debugging work before they're production-ready&lt;/strong&gt;. AI coding tools are good at the happy path — the flow you described in the prompt. They are bad at the unspoken edges: empty arrays, zero values, null users, timezone boundaries, race conditions.&lt;/p&gt;

&lt;p&gt;And here's the trap: when something breaks, the natural move is to paste the error into the AI and say "fix this." The AI does what you asked. It patches the symptom. The bug moves somewhere else. You paste &lt;em&gt;that&lt;/em&gt; error. Repeat. Pretty soon you have a codebase held together with duct tape and nobody — not you, not the AI — understands what's actually happening.&lt;/p&gt;

&lt;p&gt;There's a better way. It isn't magic. It's the same debugging discipline engineers have used for decades, just applied intentionally to AI-authored code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why "Just Ask AI to Fix It" Doesn't Work
&lt;/h2&gt;

&lt;p&gt;AI is a great debugging &lt;em&gt;assistant&lt;/em&gt;, but a terrible debugging &lt;em&gt;driver&lt;/em&gt;. Here's why:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;AI doesn't know which parts are working.&lt;/strong&gt; From its perspective, everything in the file is suspect. It will often "fix" code that was fine and leave the actual bug untouched.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Without a reproduction case, AI guesses.&lt;/strong&gt; If you say "sometimes the total is wrong," the AI has no way to know &lt;em&gt;when&lt;/em&gt; or &lt;em&gt;why&lt;/em&gt;. It will generate plausible-looking code that addresses a plausible-sounding problem. Plausible is not correct.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Each "fix" can introduce new bugs.&lt;/strong&gt; AI can't see your app's runtime behavior. It can't observe the actual values. It's writing code based on pattern-matching, not evidence.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The fix-depth problem.&lt;/strong&gt; AI tends to fix symptoms, not causes. A wrong total? Add a check in the caller. A null crash? Wrap it in a try/catch. The real bug — upstream, where the bad data was born — stays alive and keeps spawning new symptoms.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The fix is to stop outsourcing the &lt;em&gt;thinking&lt;/em&gt; part of debugging. Use AI as a tool inside a process you control.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Systematic 5-Step Debugging Approach
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 1: Reproduce Reliably
&lt;/h3&gt;

&lt;p&gt;You cannot fix what you cannot reproduce. Before anything else, get the bug to happen on demand.&lt;/p&gt;

&lt;p&gt;Write down:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The exact steps that trigger it&lt;/li&gt;
&lt;li&gt;The exact input values&lt;/li&gt;
&lt;li&gt;The expected output&lt;/li&gt;
&lt;li&gt;The actual output (including error messages and stack traces)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then try to shrink it. This is called a &lt;strong&gt;minimal reproduction case (MRC)&lt;/strong&gt;. Remove everything that isn't part of the bug. If the bug shows up when you submit a form with 20 fields, can you reproduce it with 3? With 1? The smaller the MRC, the faster every subsequent step goes.&lt;/p&gt;

&lt;p&gt;If you can't reliably reproduce it, don't skip to "fix." Keep poking until you can. An intermittent bug you can't reproduce is a bug you cannot verify you've actually fixed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Isolate the Scope
&lt;/h3&gt;

&lt;p&gt;Once you can reproduce, figure out &lt;em&gt;where&lt;/em&gt; the bug lives. The answer is almost never "the whole codebase."&lt;/p&gt;

&lt;p&gt;Techniques that work:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Binary search.&lt;/strong&gt; Comment out half the code path. Does the bug still happen? If yes, it's in the remaining half. If no, it's in the commented half. Keep halving until you land on it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Logs at boundaries.&lt;/strong&gt; Put a &lt;code&gt;console.log&lt;/code&gt; at the entry and exit of each function in the suspect path. Print the inputs and outputs. The bug is between the last log that looks right and the first log that looks wrong.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Git bisect.&lt;/strong&gt; If it used to work and now doesn't, &lt;code&gt;git bisect&lt;/code&gt; will pinpoint the commit that introduced the bug.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Suspect path: submit -&amp;gt; validate -&amp;gt; calculateTotal -&amp;gt; persist&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handleSubmit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[submit] input:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;valid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[submit] validated:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;valid&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;calculateTotal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;valid&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[submit] total:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;total&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;persist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;valid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;total&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;Boring? Yes. Effective? Extremely. Four logs will usually cut the search space in half.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Form a Hypothesis (Not a Guess)
&lt;/h3&gt;

&lt;p&gt;Once you've narrowed the scope, stop and think. Before you change any code, write down:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;What do I think is happening?&lt;/strong&gt; In one sentence.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What would prove me right?&lt;/strong&gt; A specific log value, a specific state, a specific code path.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What would prove me wrong?&lt;/strong&gt; Because you might be wrong, and you want to know fast.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This sounds fussy. It isn't. The difference between a hypothesis and a guess is that a hypothesis is &lt;em&gt;falsifiable&lt;/em&gt;. "Maybe the discount is being applied twice" is a hypothesis — you can check it. "Something's wrong with the pricing" is a vibe, and vibes lead to vibe-fixes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Verify With Evidence, Not Vibes
&lt;/h3&gt;

&lt;p&gt;Test the hypothesis against reality. Read the actual values. Don't assume, observe.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;console.log&lt;/code&gt; the specific variable at the specific line&lt;/li&gt;
&lt;li&gt;Drop a &lt;code&gt;debugger;&lt;/code&gt; statement and step through in DevTools&lt;/li&gt;
&lt;li&gt;Set a breakpoint in your editor's debugger&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This also applies to AI output. If the AI says "the bug is that &lt;code&gt;discount&lt;/code&gt; is undefined at this line," don't just trust it — &lt;code&gt;console.log(discount)&lt;/code&gt; and see. AI is often close but not exactly right, and acting on a confident wrong diagnosis wastes more time than verifying it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Trust but verify&lt;/strong&gt; applies to AI the same way it applies to your own assumptions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 5: Fix the Root, Not the Symptom
&lt;/h3&gt;

&lt;p&gt;Once you've confirmed the hypothesis, resist the urge to patch the nearest visible symptom. Trace upstream.&lt;/p&gt;

&lt;p&gt;If a function returned a wrong number, don't add &lt;code&gt;if (total &amp;lt; 0) total = 0&lt;/code&gt; in the caller. Ask: &lt;em&gt;why&lt;/em&gt; did it return a wrong number? Where did the bad input come from? Fix it there.&lt;/p&gt;

&lt;p&gt;A common anti-pattern: wrapping symptoms in if-statements and try/catches. It feels like progress because the error goes away. But the bad data is still flowing through your system; you've just muffled its scream. A month later, the same root cause shows up in a different shape.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Use AI Effectively During Debugging
&lt;/h2&gt;

&lt;p&gt;AI belongs inside this process, not on top of it. Use it like a sharp tool with a specific job:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Give AI the MRC, not the whole file.&lt;/strong&gt; A 10-line reproduction is easier for both of you to reason about than 400 lines of context.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Share the actual evidence.&lt;/strong&gt; The error message, the stack trace, the input values, the expected vs actual output. Not "it doesn't work."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ask AI to explain its hypothesis before writing code.&lt;/strong&gt; "What do you think is happening, and why?" If the explanation doesn't match what you've observed, don't let it write the fix.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;If the fix doesn't work, go back to Step 1.&lt;/strong&gt; Don't ask AI to "try again." That's how you end up with ten rounds of whack-a-mole. If the fix failed, your hypothesis was wrong. Re-reproduce, re-isolate, re-hypothesize.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Also: good tests make debugging dramatically faster, because they tell you which parts of the code are already proven to work. Tests catch regressions while you debug, so you're not accidentally breaking other things while chasing the current bug.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Real Debugging Example
&lt;/h2&gt;

&lt;p&gt;Let's make this concrete. Here's a function an AI generated for calculating an order total with a discount:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// pricing.ts&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;calculateTotal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;quantity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;discountPercent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;subtotal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;price&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;quantity&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;discount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;subtotal&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;discountPercent&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;100&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;subtotal&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;discount&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;Your users are reporting that when they apply no discount, the total comes out as &lt;code&gt;NaN&lt;/code&gt;. Let's walk through the 5 steps.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1 — Reproduce.&lt;/strong&gt; In a test or a REPL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;calculateTotal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;    &lt;span class="c1"&gt;// expected: 200, actual: NaN&lt;/span&gt;
&lt;span class="nf"&gt;calculateTotal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// NaN&lt;/span&gt;
&lt;span class="nf"&gt;calculateTotal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;       &lt;span class="c1"&gt;// NaN&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Confirmed: it breaks when &lt;code&gt;discountPercent&lt;/code&gt; is &lt;code&gt;0&lt;/code&gt;, &lt;code&gt;null&lt;/code&gt;, or missing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2 — Isolate.&lt;/strong&gt; Add logs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;price:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;quantity:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;quantity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;discount%:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;discountPercent&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;subtotal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;price&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;quantity&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;subtotal:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;subtotal&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;discount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;subtotal&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;discountPercent&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;discount:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;discount&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output for the zero case shows &lt;code&gt;subtotal: 200&lt;/code&gt;, &lt;code&gt;discount: 0&lt;/code&gt;, total: &lt;code&gt;200&lt;/code&gt;. So the zero case is actually fine. It's the &lt;em&gt;missing&lt;/em&gt; case that prints &lt;code&gt;discount: NaN&lt;/code&gt;, because &lt;code&gt;undefined / 100&lt;/code&gt; is &lt;code&gt;NaN&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3 — Hypothesis.&lt;/strong&gt; When &lt;code&gt;discountPercent&lt;/code&gt; is &lt;code&gt;undefined&lt;/code&gt; (not passed in), &lt;code&gt;undefined / 100&lt;/code&gt; produces &lt;code&gt;NaN&lt;/code&gt;, which then contaminates &lt;code&gt;subtotal - discount&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 4 — Verify.&lt;/strong&gt; &lt;code&gt;console.log(undefined / 100)&lt;/code&gt; in DevTools → &lt;code&gt;NaN&lt;/code&gt;. Confirmed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 5 — Fix the root.&lt;/strong&gt; The root cause is that the function doesn't handle the "no discount" case. Don't patch the caller; fix the signature:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// pricing.ts&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;calculateTotal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;quantity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;discountPercent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;subtotal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;price&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;quantity&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;discount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;subtotal&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;discountPercent&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;100&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;subtotal&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;discount&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;One character fixed the bug. But you wouldn't have known &lt;em&gt;which&lt;/em&gt; character without the 5 steps. Notice how much more useful this is than pasting "my total is NaN, fix it" into the AI — which might have rewritten the whole function, added a try/catch, or coerced every input to a number just in case.&lt;/p&gt;

&lt;h2&gt;
  
  
  Debugging Tools Vibe Coders Should Know
&lt;/h2&gt;

&lt;p&gt;A small toolkit goes a very long way:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Browser DevTools.&lt;/strong&gt; Console, Network, Sources. Learn the Sources tab specifically — breakpoints, watch expressions, and the call stack will save you more time than any AI prompt.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Strategic &lt;code&gt;console.log&lt;/code&gt;.&lt;/strong&gt; Log at boundaries, print the variable name with the value (&lt;code&gt;console.log('user:', user)&lt;/code&gt;), and clean up when you're done.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The &lt;code&gt;debugger&lt;/code&gt; statement.&lt;/strong&gt; Drop &lt;code&gt;debugger;&lt;/code&gt; anywhere in your code and execution will pause there when DevTools is open. Step through line by line.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Error tracking (Sentry free tier).&lt;/strong&gt; Captures errors from real users in production with full stack traces and the state at the moment of failure. You stop debugging in the dark.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Honest truth: for a lot of everyday bugs, a developer who knows DevTools is faster than any AI. The AI has to reason about possibilities. You can look at the actual value.&lt;/p&gt;

&lt;h2&gt;
  
  
  When Debugging Reveals Architectural Problems
&lt;/h2&gt;

&lt;p&gt;Sometimes the problem isn't the bug in front of you. It's the shape of the code around it.&lt;/p&gt;

&lt;p&gt;Watch for these signals:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You keep fixing bugs in the same file or module&lt;/li&gt;
&lt;li&gt;Every fix breaks two other things&lt;/li&gt;
&lt;li&gt;You can't explain how the data flows from input to output&lt;/li&gt;
&lt;li&gt;The same logic is duplicated in three places, and you have to fix each one&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When this happens, debugging is telling you something bigger: the code is tightly coupled, the abstractions are missing, the data flow is unclear. These are signs that refactoring — not more patches — is the real fix.&lt;/p&gt;

&lt;p&gt;And if every bug fix seems to break something else, you might be past the point where you can debug your way out of it. That's when it's time to bring in help to stabilize the codebase before you keep shipping on top of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing
&lt;/h2&gt;

&lt;p&gt;Debugging is a skill, not a magic AI trick. Each bug you debug properly makes you measurably better at the next one — you build intuition about where bugs hide, which tools to reach for, and which AI fixes to trust.&lt;/p&gt;

&lt;p&gt;AI accelerates debugging when you use it inside a real process. It replaces debugging when you use it instead of one. The difference is whether you're in control, or whether you're stuck in a loop asking "fix it" until the code is unrecognizable and the bug is still there.&lt;/p&gt;

&lt;p&gt;Next time something breaks: reproduce, isolate, hypothesize, verify, fix the root. In that order. Every time.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://bivecode.com/blog/debug-ai-generated-code" rel="noopener noreferrer"&gt;bivecode.com&lt;/a&gt;. If you're struggling with an AI-built codebase that's become unmanageable, &lt;a href="https://bivecode.com/rescue" rel="noopener noreferrer"&gt;BiveCode Rescue&lt;/a&gt; can help.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>debugging</category>
      <category>webdev</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Why AI Coding Goes Off the Rails — And How to Take Back Control</title>
      <dc:creator>Junkyu Jeon</dc:creator>
      <pubDate>Thu, 16 Apr 2026 06:44:54 +0000</pubDate>
      <link>https://dev.to/jakay/why-ai-coding-goes-off-the-rails-and-how-to-take-back-control-3nmb</link>
      <guid>https://dev.to/jakay/why-ai-coding-goes-off-the-rails-and-how-to-take-back-control-3nmb</guid>
      <description>&lt;h2&gt;
  
  
  The Honeymoon Phase
&lt;/h2&gt;

&lt;p&gt;You remember the moment. You opened Cursor, or Bolt, or Claude for the first time, typed something like "Build me a landing page with a hero section and a pricing table," and watched as fully functional code materialized in front of you. In minutes, not hours.&lt;/p&gt;

&lt;p&gt;You kept going. "Add a contact form." Done. "Make it responsive." Done. "Add dark mode." Done. You felt like a 10x developer. The possibilities felt limitless. You started telling friends about it. You stayed up late building things you'd been putting off for months.&lt;/p&gt;

&lt;p&gt;This phase is real. It's not a trick. AI coding tools genuinely are that powerful for getting something off the ground. The problem is what happens next.&lt;/p&gt;

&lt;h2&gt;
  
  
  When Things Start Breaking
&lt;/h2&gt;

&lt;p&gt;It's subtle at first. Around the time your project hits 20-30 files, something shifts. The AI that was nailing everything starts... stumbling.&lt;/p&gt;

&lt;p&gt;You notice it in small ways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It forgets decisions it made earlier.&lt;/strong&gt; You established a pattern for how components fetch data, but the AI starts using a completely different approach in new files.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It contradicts its own patterns.&lt;/strong&gt; Half your files use one error-handling style, the other half use another. Neither is wrong, but the inconsistency makes everything harder to follow.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It breaks existing features while adding new ones.&lt;/strong&gt; You ask for a new settings page and suddenly your authentication stops working.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Its fixes create new problems.&lt;/strong&gt; You point out a bug, it patches it, and now something else is broken. You fix that, and the original bug comes back.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It goes in circles.&lt;/strong&gt; Fix this, breaks that. Fix that, breaks this. You spend an hour and end up back where you started, except now the code is messier.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If this sounds familiar, you're not alone. This is the experience of almost every developer who uses AI tools on a project beyond a certain size. And it's not because the AI got dumber or because you're doing something wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Happens: The Context Problem
&lt;/h2&gt;

&lt;p&gt;Here's the thing most people don't understand about AI coding tools: &lt;strong&gt;they have a limited working memory.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;AI models operate within something called a &lt;strong&gt;context window&lt;/strong&gt; — the amount of text they can "see" and think about at any given time. Think of it like a desk. When your project is small, all your files fit on the desk. The AI can see everything at once — every component, every function, every decision you've made together.&lt;/p&gt;

&lt;p&gt;But as your project grows, files start falling off the desk. The AI can only look at a slice of your codebase at a time. It literally &lt;strong&gt;cannot see&lt;/strong&gt; what's in the other files. It doesn't know what patterns were established. It doesn't remember what constraints exist. It doesn't recall the naming conventions you agreed on three sessions ago.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;It's like asking someone to renovate your house, but they can only see one room at a time. They might pick a great paint color for the kitchen — that clashes horribly with the living room they can't see.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And here's the part that really stings: &lt;strong&gt;each conversation starts fresh.&lt;/strong&gt; When you open a new chat or start a new session, the AI has zero memory of your previous interactions. It doesn't know about the bug you spent two hours fixing yesterday. It doesn't know about the architectural decision you made last week. Every time, it's meeting your project for the first time.&lt;/p&gt;

&lt;p&gt;This isn't a flaw that'll be fixed in the next update. It's a fundamental characteristic of how these models work. And once you understand it, the frustrating behavior makes perfect sense.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Snowball Effect
&lt;/h2&gt;

&lt;p&gt;Here's where it gets really painful. When things break, the natural instinct is to tell the AI: "Fix it." And the AI obliges — it patches the immediate symptom. But because it can't see the full picture, it's not fixing the root cause. It's applying a band-aid.&lt;/p&gt;

&lt;p&gt;Each band-aid adds complexity. And that complexity makes it even harder for the AI to understand the codebase on the next request. So the next fix is even more likely to be a band-aid. And the cycle continues:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Something breaks&lt;/li&gt;
&lt;li&gt;AI patches the symptom&lt;/li&gt;
&lt;li&gt;The patch adds complexity&lt;/li&gt;
&lt;li&gt;The added complexity causes something else to break&lt;/li&gt;
&lt;li&gt;Go to step 1&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Your codebase becomes a patchwork of contradictory patterns, redundant logic, and fragile workarounds. Eventually the AI is spending more tokens trying to understand the mess than actually solving the problem you asked about.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This is why vibe coding hits a wall.&lt;/strong&gt; Not because AI is bad at coding. Not because you're not technical enough. But because the approach of "just keep prompting" doesn't scale. The more you build, the less effective each prompt becomes, until you're fighting the tool instead of building with it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Shift: From Prompting to Harness Engineering
&lt;/h2&gt;

&lt;p&gt;When people hit this wall, their first instinct is to write better prompts. More detailed instructions. Longer explanations. "Be very careful not to break the auth system when you add this feature."&lt;/p&gt;

&lt;p&gt;This helps a little. But it's treating the symptom, not the disease. You can't prompt your way out of a structural problem.&lt;/p&gt;

&lt;p&gt;The real unlock is something we call &lt;strong&gt;harness engineering&lt;/strong&gt; — designing the environment and structure that the AI works within.&lt;/p&gt;

&lt;p&gt;Think about it this way:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Prompt engineering&lt;/strong&gt; is telling the AI &lt;em&gt;what&lt;/em&gt; to do.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Harness engineering&lt;/strong&gt; is setting up &lt;em&gt;where&lt;/em&gt; and &lt;em&gt;how&lt;/em&gt; the AI works.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A "harness" is everything that surrounds the AI: the project structure, the rules it follows, the context it receives, the guardrails that catch its mistakes. It's the difference between dropping a builder in an empty field and saying "build a house," versus handing them blueprints, a materials list, and a building code.&lt;/p&gt;

&lt;p&gt;When you invest in the harness, the AI performs dramatically better — even with the exact same prompts you were using before. Because the environment compensates for the AI's limitations.&lt;/p&gt;

&lt;p&gt;Here's what harness engineering looks like in practice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;CLAUDE.md&lt;/code&gt; / &lt;code&gt;.cursorrules&lt;/code&gt; files&lt;/strong&gt; that give the AI persistent context about your project — your tech stack, your conventions, your constraints. Things the AI would otherwise forget between sessions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clean folder structure&lt;/strong&gt; so the AI only needs to see the relevant files for any given task, keeping more of the important context on that "desk."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Module boundaries&lt;/strong&gt; that limit the blast radius of AI changes. If the AI makes a mistake in the settings module, it shouldn't be able to break authentication.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test suites&lt;/strong&gt; that catch when the AI breaks something, before you even notice.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Memory systems&lt;/strong&gt; that preserve decisions across sessions — so the AI doesn't have to rediscover your architecture every time.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these require you to be a senior engineer. They require you to think like an &lt;strong&gt;architect&lt;/strong&gt;, not a prompter.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical Steps to Take Back Control
&lt;/h2&gt;

&lt;p&gt;You don't need to rebuild everything from scratch. Here are concrete things you can do today to improve how AI works with your project:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Set Up a CLAUDE.md or .cursorrules
&lt;/h3&gt;

&lt;p&gt;This is the single highest-leverage thing you can do. Create a file that tells the AI everything it keeps forgetting: your tech stack, your folder structure, your naming conventions, your patterns, your constraints. The AI reads this at the start of every session, giving it the context it would otherwise lack.&lt;/p&gt;

&lt;p&gt;We wrote a full guide on how to do this well: &lt;a href="https://bivecode.com/blog/claude-md-guide" rel="noopener noreferrer"&gt;How to Make AI Tools Understand Your Codebase&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Break Your Project Into Modules
&lt;/h3&gt;

&lt;p&gt;Smaller, self-contained pieces mean less context needed per task. When your auth logic is in its own module with clear boundaries, the AI can work on it without needing to understand your entire app. Feature-based folder structure is a great starting point — see our guide on &lt;a href="https://bivecode.com/blog/structure-ai-project" rel="noopener noreferrer"&gt;how to structure your AI project&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Give AI One Job at a Time
&lt;/h3&gt;

&lt;p&gt;Instead of "Build the auth system with social login, password reset, 2FA, and admin panel," try:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"Add a basic email/password login page"&lt;/li&gt;
&lt;li&gt;"Add password reset functionality"&lt;/li&gt;
&lt;li&gt;"Add Google OAuth login"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each focused request is more likely to succeed because it requires less context and produces smaller, more reviewable changes.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Add Tests Before Adding Features
&lt;/h3&gt;

&lt;p&gt;Tests are your safety net. When the AI changes something, tests tell you immediately if it broke something else. You don't need 100% coverage. Even basic tests for your critical paths — login works, data saves correctly, pages load — will catch the most damaging regressions.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Review Before Accepting
&lt;/h3&gt;

&lt;p&gt;This is the hardest habit to build. AI-generated code comes out fast and looks professional, so it's tempting to accept it without reading. Don't. Spend 30 seconds scanning each change:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Does it follow your existing patterns?&lt;/li&gt;
&lt;li&gt;Did it change files it shouldn't have touched?&lt;/li&gt;
&lt;li&gt;Does the logic actually make sense, or does it just look right?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You don't need to understand every line. But you should understand the &lt;em&gt;shape&lt;/em&gt; of the change.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Version Control Religiously
&lt;/h3&gt;

&lt;p&gt;Commit every working state. Every one. When the AI inevitably breaks something, you can roll back to the last good version instead of trying to untangle the damage. &lt;code&gt;git commit&lt;/code&gt; is your undo button. Use it early and often.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Mindset Shift
&lt;/h2&gt;

&lt;p&gt;Here's the most important thing to internalize: &lt;strong&gt;you're not a prompter. You're an architect.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The AI is the builder. It's fast, it's capable, and it works tirelessly. But builders need blueprints. They need someone who understands the whole structure, who can see across all the rooms at once, who makes sure the kitchen paint works with the living room.&lt;/p&gt;

&lt;p&gt;That's you.&lt;/p&gt;

&lt;p&gt;The best AI-assisted developers don't write the most code. They don't write the fanciest prompts. They design the best &lt;strong&gt;structures&lt;/strong&gt; for AI to work within. They set up the harness so well that the AI almost can't fail.&lt;/p&gt;

&lt;p&gt;And that's a skill worth developing — because as AI tools get better, the people who know how to direct them effectively will build things the rest of the world didn't think were possible.&lt;/p&gt;




&lt;p&gt;If your project has already gone off the rails and you're not sure how to get it back on track, &lt;a href="https://bivecode.com/rescue" rel="noopener noreferrer"&gt;we can help&lt;/a&gt;. We specialize in taking AI-built codebases and giving them the structure they need to keep growing.&lt;/p&gt;

&lt;p&gt;And if you want to prevent this from happening on your next project, start with our guide on &lt;a href="https://bivecode.com/blog/structure-ai-project" rel="noopener noreferrer"&gt;how to structure your AI project from day one&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://bivecode.com/blog/why-ai-coding-goes-off-rails" rel="noopener noreferrer"&gt;bivecode.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>webdev</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Cursor Rules vs CLAUDE.md: When to Use Which (And How They Work Together)</title>
      <dc:creator>Junkyu Jeon</dc:creator>
      <pubDate>Thu, 16 Apr 2026 06:39:12 +0000</pubDate>
      <link>https://dev.to/jakay/cursor-rules-vs-claudemd-when-to-use-which-and-how-they-work-together-5hg</link>
      <guid>https://dev.to/jakay/cursor-rules-vs-claudemd-when-to-use-which-and-how-they-work-together-5hg</guid>
      <description>&lt;p&gt;If you're using AI coding tools seriously, you've probably heard of both &lt;code&gt;.cursorrules&lt;/code&gt; and &lt;code&gt;CLAUDE.md&lt;/code&gt;. They solve the same fundamental problem: &lt;strong&gt;giving AI persistent context about your project.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Without them, every AI session starts from scratch. The AI doesn't know your tech stack, your folder conventions, your naming patterns, or the quirks that make your project unique. You end up repeating the same instructions over and over.&lt;/p&gt;

&lt;p&gt;Both files fix this. But they work differently, they're read by different tools, and they have different strengths. Most developers pick one and ignore the other. That's a mistake — they're most powerful when used together.&lt;/p&gt;

&lt;h2&gt;
  
  
  What .cursorrules Does
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;.cursorrules&lt;/code&gt; is Cursor's project-level configuration file. When you open a project in Cursor, it automatically reads this file and applies the rules to every AI interaction within that project.&lt;/p&gt;

&lt;p&gt;Key characteristics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tool-specific&lt;/strong&gt; — only Cursor reads it. Other AI tools ignore it completely.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Instruction-focused&lt;/strong&gt; — best for telling the AI what to do and what not to do.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Applies to all interactions&lt;/strong&gt; — every chat, every inline edit, every Cmd+K generation follows these rules.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Supports glob patterns&lt;/strong&gt; — you can scope rules to specific file types or directories.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A typical &lt;code&gt;.cursorrules&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You are an expert in TypeScript, React, Next.js App Router, and Tailwind CSS.

Key Principles:
- Write concise, technical TypeScript code
- Use functional and declarative programming patterns
- Prefer iteration and modularization over duplication
- Use descriptive variable names with auxiliary verbs (isLoading, hasError)

Naming Conventions:
- Components: PascalCase (UserProfile.tsx)
- Hooks: camelCase with "use" prefix (useAuth.ts)
- Utilities: camelCase (formatDate.ts)
- Constants: SCREAMING_SNAKE_CASE

DO NOT:
- Use `any` type
- Use default exports (except for pages)
- Put business logic in components
- Use inline styles
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What CLAUDE.md Does
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;CLAUDE.md&lt;/code&gt; is Claude's project context file. Claude Code, Claude in the terminal, and other Claude-powered tools read it automatically when they enter your project directory.&lt;/p&gt;

&lt;p&gt;Key characteristics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tool-specific&lt;/strong&gt; — Claude tools read it. Cursor doesn't (unless you explicitly include it in context).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Context-focused&lt;/strong&gt; — best for explaining your project's architecture, decisions, and constraints. Not just rules, but &lt;em&gt;why&lt;/em&gt; those rules exist.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hierarchical&lt;/strong&gt; — you can have a root &lt;code&gt;CLAUDE.md&lt;/code&gt; plus subdirectory-specific ones (e.g., &lt;code&gt;src/components/CLAUDE.md&lt;/code&gt;). Claude merges them based on what files it's working with.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Supports project commands&lt;/strong&gt; — you can define shortcuts for common tasks like building, testing, and deploying.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A typical &lt;code&gt;CLAUDE.md&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# Project Overview&lt;/span&gt;
Next.js 15 SaaS app for invoice management.
&lt;span class="p"&gt;-&lt;/span&gt; Frontend: React 19, TypeScript, Tailwind CSS
&lt;span class="p"&gt;-&lt;/span&gt; Backend: Next.js API routes, Drizzle ORM
&lt;span class="p"&gt;-&lt;/span&gt; Database: PostgreSQL (Supabase)
&lt;span class="p"&gt;-&lt;/span&gt; Auth: Better Auth with Google OAuth
&lt;span class="p"&gt;-&lt;/span&gt; Payments: Stripe
&lt;span class="p"&gt;-&lt;/span&gt; Deployment: Vercel

&lt;span class="gh"&gt;# Architecture&lt;/span&gt;
Feature-based folder structure:
src/
  app/         # Pages and API routes
  features/    # Feature modules (auth/, billing/, invoices/)
  components/  # Shared UI components
  lib/         # Utilities, DB client, API helpers

&lt;span class="gh"&gt;# Important Constraints&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Auth middleware runs on Edge Runtime — no Node.js APIs
&lt;span class="p"&gt;-&lt;/span&gt; All monetary values stored as integers (cents), displayed as decimals
&lt;span class="p"&gt;-&lt;/span&gt; Invoices use optimistic locking — always check version before update

&lt;span class="gh"&gt;# Commands&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Build: npm run build
&lt;span class="p"&gt;-&lt;/span&gt; Test: npm run test
&lt;span class="p"&gt;-&lt;/span&gt; Dev: npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Key Differences
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Aspect&lt;/th&gt;
&lt;th&gt;.cursorrules&lt;/th&gt;
&lt;th&gt;CLAUDE.md&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Read by&lt;/td&gt;
&lt;td&gt;Cursor only&lt;/td&gt;
&lt;td&gt;Claude tools only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Primary strength&lt;/td&gt;
&lt;td&gt;Coding rules and patterns&lt;/td&gt;
&lt;td&gt;Project context and architecture&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tone&lt;/td&gt;
&lt;td&gt;Imperative ("Do this, don't do that")&lt;/td&gt;
&lt;td&gt;Descriptive ("Here's how the project works")&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hierarchy&lt;/td&gt;
&lt;td&gt;Single file at project root&lt;/td&gt;
&lt;td&gt;Root + subdirectory files, merged contextually&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Best for&lt;/td&gt;
&lt;td&gt;Enforcing code style and patterns&lt;/td&gt;
&lt;td&gt;Explaining architecture and constraints&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Project commands&lt;/td&gt;
&lt;td&gt;Not supported&lt;/td&gt;
&lt;td&gt;Supported (build, test, lint)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Glob scoping&lt;/td&gt;
&lt;td&gt;Supported&lt;/td&gt;
&lt;td&gt;Via subdirectory files&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  When to Use Which
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Use .cursorrules when:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Your team primarily uses Cursor as their AI coding tool&lt;/li&gt;
&lt;li&gt;You want to enforce strict coding patterns across all AI interactions&lt;/li&gt;
&lt;li&gt;Your rules are mostly about code style: naming, imports, error handling patterns&lt;/li&gt;
&lt;li&gt;You want rules that apply to inline edits and quick generations, not just chat&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Use CLAUDE.md when:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Your team uses Claude Code or Claude-powered tools&lt;/li&gt;
&lt;li&gt;You need to document complex architecture that requires explanation, not just rules&lt;/li&gt;
&lt;li&gt;Your project has non-obvious constraints (edge runtime limitations, data format decisions, etc.)&lt;/li&gt;
&lt;li&gt;You want project commands integrated into the AI workflow&lt;/li&gt;
&lt;li&gt;Different parts of the codebase need different context (use subdirectory CLAUDE.md files)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Use both when:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Team members use different AI tools (some on Cursor, some on Claude)&lt;/li&gt;
&lt;li&gt;You want consistent AI behavior regardless of which tool is used&lt;/li&gt;
&lt;li&gt;You want the best of both: Cursor's pattern enforcement + Claude's architectural context&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How to Use Them Together
&lt;/h2&gt;

&lt;p&gt;Here's the approach that works best: &lt;strong&gt;put architectural context in CLAUDE.md, put coding rules in .cursorrules, and keep them in sync.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  What goes in CLAUDE.md only:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Project overview and tech stack&lt;/li&gt;
&lt;li&gt;Architecture decisions and their reasoning&lt;/li&gt;
&lt;li&gt;Non-obvious constraints ("monetary values are stored as cents because...")&lt;/li&gt;
&lt;li&gt;Common tasks with step-by-step instructions&lt;/li&gt;
&lt;li&gt;Build/test/deploy commands&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What goes in .cursorrules only:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;The persona prompt ("You are an expert in...")&lt;/li&gt;
&lt;li&gt;Cursor-specific behaviors (how to handle inline edits, tab completions)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What goes in both (keep synced):
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Naming conventions&lt;/li&gt;
&lt;li&gt;Folder structure&lt;/li&gt;
&lt;li&gt;Import patterns&lt;/li&gt;
&lt;li&gt;Error handling approach&lt;/li&gt;
&lt;li&gt;"Do not" rules&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Yes, there's some duplication. That's the cost of supporting multiple tools. The alternative — having rules in one file and not the other — means your AI behavior is inconsistent depending on which tool you're using. That's worse.&lt;/p&gt;

&lt;h3&gt;
  
  
  Keeping them in sync
&lt;/h3&gt;

&lt;p&gt;The practical approach: treat &lt;code&gt;CLAUDE.md&lt;/code&gt; as the source of truth for architecture and constraints, and &lt;code&gt;.cursorrules&lt;/code&gt; as the source of truth for coding patterns. When you update a shared rule (like naming conventions), update both files. It takes 30 seconds and prevents drift.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Mistakes
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Making them too long
&lt;/h3&gt;

&lt;p&gt;Both files compete for the AI's attention window. A 2,000-line rules file means the AI is spending context on your instructions instead of on understanding the code it's working with. Aim for 200-500 lines max for each file.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Being vague
&lt;/h3&gt;

&lt;p&gt;"Write clean code" is useless in both files. "Use named exports for all non-page files" is actionable. Be specific enough that there's only one way to interpret the rule.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Never updating them
&lt;/h3&gt;

&lt;p&gt;Your project evolves. Your rules should too. If you switched from REST to tRPC three months ago but your CLAUDE.md still describes REST patterns, the AI will generate REST code. Review your files monthly.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Not including examples
&lt;/h3&gt;

&lt;p&gt;AI tools are pattern matchers. A rule like "use the repository pattern for database access" is vague. A rule with a 5-line code example of what that looks like in your project is crystal clear.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started Today
&lt;/h2&gt;

&lt;p&gt;If you have neither file, here's the 30-minute setup:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Create CLAUDE.md&lt;/strong&gt; — write your project overview, tech stack, folder structure, and top 5 constraints.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Create .cursorrules&lt;/strong&gt; — write your coding conventions, naming rules, and "do not" list.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Copy shared rules&lt;/strong&gt; — naming conventions, folder structure, and import patterns should appear in both.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test it&lt;/strong&gt; — ask both tools to create a new component. Does the output match your conventions? If not, your rules aren't specific enough.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you already have one file, creating the other takes 15 minutes — most of the thinking is already done.&lt;/p&gt;




&lt;p&gt;For a deeper dive into CLAUDE.md specifically, check out our &lt;a href="https://bivecode.com/blog/claude-md-guide" rel="noopener noreferrer"&gt;CLAUDE.md setup guide&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://bivecode.com/blog/cursor-rules-vs-claude-md" rel="noopener noreferrer"&gt;bivecode.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>programming</category>
      <category>productivity</category>
    </item>
    <item>
      <title>The Vibe Coding Security Checklist: 7 Things to Check Before You Ship</title>
      <dc:creator>Junkyu Jeon</dc:creator>
      <pubDate>Thu, 16 Apr 2026 06:36:05 +0000</pubDate>
      <link>https://dev.to/jakay/the-vibe-coding-security-checklist-7-things-to-check-before-you-ship-4e66</link>
      <guid>https://dev.to/jakay/the-vibe-coding-security-checklist-7-things-to-check-before-you-ship-4e66</guid>
      <description>&lt;p&gt;Here's a stat that should keep you up at night: &lt;strong&gt;24.7% of AI-generated code contains security vulnerabilities.&lt;/strong&gt; Nearly 45% of those hit the OWASP Top 10 — the most common, most exploitable categories of web security flaws.&lt;/p&gt;

&lt;p&gt;This isn't because AI is bad at coding. It's because AI optimizes for making things &lt;em&gt;work&lt;/em&gt;, not making them &lt;em&gt;safe&lt;/em&gt;. When you ask it to "add a user login," it builds a functional login. It doesn't think about session fixation, brute force protection, or what happens when someone puts a SQL statement in the email field.&lt;/p&gt;

&lt;p&gt;If you've built an app with Cursor, Bolt.new, Lovable, or any AI coding tool and you're about to ship it to real users — run through this checklist first. Each item takes 5-15 minutes. All of them together could save you from a breach that kills your product.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Secrets Are Not in Your Code
&lt;/h2&gt;

&lt;p&gt;This is the most common and most dangerous mistake in AI-built apps. AI tools love to hardcode API keys, database URLs, and secrets directly in the source code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Check for:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;API keys in JavaScript/TypeScript files (search for &lt;code&gt;sk-&lt;/code&gt;, &lt;code&gt;pk_&lt;/code&gt;, &lt;code&gt;key_&lt;/code&gt;, &lt;code&gt;secret&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Database connection strings in source code&lt;/li&gt;
&lt;li&gt;JWT secrets hardcoded in auth files&lt;/li&gt;
&lt;li&gt;Third-party service credentials (Stripe, SendGrid, AWS) in client-side code&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;How to fix:&lt;/strong&gt;&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;# Search your codebase for exposed secrets&lt;/span&gt;
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-rn&lt;/span&gt; &lt;span class="s2"&gt;"sk_live&lt;/span&gt;&lt;span class="se"&gt;\|&lt;/span&gt;&lt;span class="s2"&gt;sk_test&lt;/span&gt;&lt;span class="se"&gt;\|&lt;/span&gt;&lt;span class="s2"&gt;password&lt;/span&gt;&lt;span class="se"&gt;\|&lt;/span&gt;&lt;span class="s2"&gt;secret&lt;/span&gt;&lt;span class="se"&gt;\|&lt;/span&gt;&lt;span class="s2"&gt;api_key&lt;/span&gt;&lt;span class="se"&gt;\|&lt;/span&gt;&lt;span class="s2"&gt;apiKey"&lt;/span&gt; src/

&lt;span class="c"&gt;# Move all secrets to environment variables&lt;/span&gt;
&lt;span class="c"&gt;# .env.local (never committed to git)&lt;/span&gt;
&lt;span class="nv"&gt;DATABASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;postgres://...
&lt;span class="nv"&gt;STRIPE_SECRET_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;sk_live_...
&lt;span class="nv"&gt;JWT_SECRET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your-secret-here

&lt;span class="c"&gt;# .gitignore (make sure this exists)&lt;/span&gt;
.env
.env.local
.env.production
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Critical:&lt;/strong&gt; If secrets were ever committed to git, they're in the history even after removal. Rotate them immediately — generate new keys and revoke the old ones.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. All User Input Is Validated
&lt;/h2&gt;

&lt;p&gt;AI-generated code almost never validates input properly. It trusts whatever the user sends, which opens the door to injection attacks, crashes, and data corruption.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Check for:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Form inputs used directly without validation&lt;/li&gt;
&lt;li&gt;API route parameters used without type checking&lt;/li&gt;
&lt;li&gt;Database queries built from user input (SQL injection risk)&lt;/li&gt;
&lt;li&gt;File uploads without type/size restrictions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;How to fix:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Use Zod for input validation in API routes&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;zod&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;CreateUserSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;email&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;number&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;13&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;150&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;optional&lt;/span&gt;&lt;span class="p"&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;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;POST&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;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;CreateUserSchema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;safeParse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&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;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;success&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;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;flatten&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;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;400&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;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;age&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="c1"&gt;// ... safe to use&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Validate on the server, always. Client-side validation is for UX — server-side validation is for security. Never trust the client.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Authentication Is Actually Protecting Routes
&lt;/h2&gt;

&lt;p&gt;AI tools often generate auth that &lt;em&gt;looks&lt;/em&gt; right but has gaps. The login page works, but the API routes behind it? Wide open.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Check for:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;API routes that should require authentication but don't check for a session&lt;/li&gt;
&lt;li&gt;Middleware that checks auth on some routes but misses others&lt;/li&gt;
&lt;li&gt;Admin-only endpoints accessible to regular users (broken access control)&lt;/li&gt;
&lt;li&gt;Direct object references — can user A access user B's data by changing an ID in the URL?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Quick test:&lt;/strong&gt; Open your browser's dev tools, find an API call your app makes, copy it as a cURL command, remove the auth cookie/token, and run it. If it still returns data, your route isn't protected.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Every protected API route should start with this&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;GET&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;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getSession&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="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;session&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;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Unauthorized&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;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="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// For user-specific data, always filter by the authenticated user&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findMany&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;session&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="c1"&gt;// NOT from URL params&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;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&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;
  
  
  4. No Sensitive Data in Client-Side Code
&lt;/h2&gt;

&lt;p&gt;Everything in your frontend JavaScript is visible to anyone who opens dev tools. AI tools frequently put things on the client side that should stay on the server.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Check for:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;API keys in &lt;code&gt;.env&lt;/code&gt; files prefixed with &lt;code&gt;NEXT_PUBLIC_&lt;/code&gt; that shouldn't be public&lt;/li&gt;
&lt;li&gt;Business logic that reveals pricing algorithms, discount rules, or internal calculations&lt;/li&gt;
&lt;li&gt;Error messages that expose database schema, file paths, or internal system details&lt;/li&gt;
&lt;li&gt;User data from other users leaking into API responses (overfetching)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;How to fix:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Only prefix env variables with &lt;code&gt;NEXT_PUBLIC_&lt;/code&gt; if they're truly meant to be public&lt;/li&gt;
&lt;li&gt;Move business logic to server-side API routes or server components&lt;/li&gt;
&lt;li&gt;Return generic error messages to the client, log detailed errors on the server&lt;/li&gt;
&lt;li&gt;Shape API responses to include only what the requesting user needs to see&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  5. Rate Limiting Exists
&lt;/h2&gt;

&lt;p&gt;Without rate limiting, anyone can hammer your API thousands of times per second. This leads to DDoS vulnerability, brute force attacks on login, and runaway costs on pay-per-use services (like AI APIs).&lt;/p&gt;

&lt;p&gt;AI-generated code almost never includes rate limiting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to protect:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Login/signup endpoints&lt;/strong&gt; — prevent brute force (5-10 attempts per minute)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI generation endpoints&lt;/strong&gt; — prevent cost abuse&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Password reset&lt;/strong&gt; — prevent email bombing (2-3 per hour)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;General API&lt;/strong&gt; — prevent abuse (100-1000 requests per minute)
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Using Upstash Redis for serverless rate limiting&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Ratelimit&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@upstash/ratelimit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Redis&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@upstash/redis&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ratelimit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Ratelimit&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromEnv&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;limiter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Ratelimit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slidingWindow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1 m&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;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;POST&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;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ip&lt;/span&gt; &lt;span class="o"&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;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;x-forwarded-for&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;127.0.0.1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;success&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ratelimit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;limit&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="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;success&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;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Too many requests&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;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;429&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;span class="c1"&gt;// ... handle request&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  6. Dependencies Are Not a Liability
&lt;/h2&gt;

&lt;p&gt;AI tools add npm packages liberally. Each dependency is code you didn't write running in your app. Some may have known vulnerabilities, be abandoned, or even be malicious.&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;# Check for known vulnerabilities&lt;/span&gt;
npm audit

&lt;span class="c"&gt;# See what you've got installed&lt;/span&gt;
npm &lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;--depth&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0

&lt;span class="c"&gt;# Check for unused dependencies&lt;/span&gt;
npx depcheck
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fix critical and high vulnerabilities. Remove packages you don't use.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. HTTPS, Headers, and CORS Are Configured
&lt;/h2&gt;

&lt;p&gt;The boring infrastructure stuff that AI never sets up but attackers always check.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Essential security headers&lt;/strong&gt; (add to &lt;code&gt;next.config.ts&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;securityHeaders&lt;/span&gt; &lt;span class="o"&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;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;X-Content-Type-Options&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;nosniff&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;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;X-Frame-Options&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;DENY&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;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;X-XSS-Protection&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1; mode=block&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;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Referrer-Policy&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;strict-origin-when-cross-origin&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nextConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;headers&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="na"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/(.*)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;securityHeaders&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;p&gt;&lt;strong&gt;CORS:&lt;/strong&gt; If your API is only used by your own frontend, don't enable CORS at all. If you need CORS, whitelist specific origins — never use &lt;code&gt;*&lt;/code&gt; in production.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 15-Minute Security Sprint
&lt;/h2&gt;

&lt;p&gt;If you can only do one thing from this list, here's the priority order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Secrets audit&lt;/strong&gt; (5 min) — grep for exposed keys. Highest-impact, easiest-to-exploit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auth check&lt;/strong&gt; (5 min) — test your API routes without authentication. Find the gaps.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Input validation&lt;/strong&gt; (5 min) — add Zod to your most critical endpoint.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These three catch the majority of real-world attacks on AI-built apps.&lt;/p&gt;




&lt;p&gt;A security breach doesn't just break your app. It breaks trust. Users whose data gets leaked don't come back.&lt;/p&gt;

&lt;p&gt;AI made it possible to build an app in a weekend. But shipping it without a security review is like driving a car without brakes — it works fine until it doesn't, and when it fails, it fails catastrophically.&lt;/p&gt;

&lt;p&gt;Spend the hour. Run the checklist. Ship with confidence.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://bivecode.com/blog/vibe-coding-security-checklist" rel="noopener noreferrer"&gt;bivecode.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>ai</category>
      <category>webdev</category>
      <category>beginners</category>
    </item>
  </channel>
</rss>
