<?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: Azad Mohamed</title>
    <description>The latest articles on DEV Community by Azad Mohamed (@azadhmhd).</description>
    <link>https://dev.to/azadhmhd</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4096202%2F4f5a2f1b-8f70-4741-9d1a-c84ad6ad2f14.jpg</url>
      <title>DEV Community: Azad Mohamed</title>
      <link>https://dev.to/azadhmhd</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/azadhmhd"/>
    <language>en</language>
    <item>
      <title>How to Structure a NestJS Backend for AI-Assisted Development</title>
      <dc:creator>Azad Mohamed</dc:creator>
      <pubDate>Sat, 17 Oct 2026 20:05:56 +0000</pubDate>
      <link>https://dev.to/azadhmhd/how-to-structure-a-nestjs-backend-for-ai-assisted-development-4b7f</link>
      <guid>https://dev.to/azadhmhd/how-to-structure-a-nestjs-backend-for-ai-assisted-development-4b7f</guid>
      <description>&lt;p&gt;AI coding agents are good at writing NestJS code and bad at guessing &lt;em&gt;your&lt;/em&gt; NestJS conventions. Left alone, they put queries in controllers, return entities straight from the database, add a fourth way of throwing errors, and cheerfully offer to run &lt;code&gt;migration:run&lt;/code&gt; against whatever database your &lt;code&gt;.env&lt;/code&gt; points at.&lt;/p&gt;

&lt;p&gt;The fix is not a cleverer prompt. It's a repository that states its rules where agents read them, makes correct changes cheap to verify, and enforces the few rules that must never be broken. Here's the structure I use. None of it needs a particular agent.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. One instruction file, and only one
&lt;/h2&gt;

&lt;p&gt;Every major agent now reads a project instruction file. Codex, Cursor, GitHub Copilot and Gemini CLI (with one setting) read &lt;code&gt;AGENTS.md&lt;/code&gt;. Claude Code reads &lt;code&gt;CLAUDE.md&lt;/code&gt;, which can import another file. So:&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="c"&gt;&amp;lt;!-- CLAUDE.md --&amp;gt;&lt;/span&gt;
@AGENTS.md

&lt;span class="gu"&gt;## Claude Code specifics&lt;/span&gt;
(skills, subagents, permission rules: only what is genuinely Claude-specific)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;.gemini/settings.json&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"context"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"fileName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"AGENTS.md"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rules live in &lt;code&gt;AGENTS.md&lt;/code&gt; only. The day you have the same rule in two files, they start to drift, and some tools (Copilot CLI, for one) merge every instruction file they find without a precedence order. Keep &lt;code&gt;AGENTS.md&lt;/code&gt; short (a project map, commands, conventions, safety rules, report format) and link to &lt;code&gt;docs/&lt;/code&gt; for detail.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Write stop rules, not style advice
&lt;/h2&gt;

&lt;p&gt;"Write clean code" changes nothing. What changes behaviour is a short list of things the agent must not do, written as rules with the exact words you want back:&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="gu"&gt;## Stop rules&lt;/span&gt;
&lt;span class="p"&gt;
1.&lt;/span&gt; Do not generate, run or revert migrations until the user tells you to in this conversation.
   An entity change is not permission. Make the code change and report:
   "Schema change detected. Migration required. Not generated or run: no explicit approval."
&lt;span class="p"&gt;2.&lt;/span&gt; Do not run or edit seeds until the user tells you to.
&lt;span class="p"&gt;3.&lt;/span&gt; Do not format code. The pre-commit hook formats staged files; CI checks formatting.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rule 3 is a cost rule: formatting runs burn agent time and produce noisy diffs, and Husky + lint-staged already do it on every commit.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Make the architecture boring and stated
&lt;/h2&gt;

&lt;p&gt;Agents copy what they see. If the codebase has one pattern, they copy that one. Pick an explicit structure and write it down:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src/&amp;lt;feature&amp;gt;/
  controllers/   HTTP only: routing, guards, Swagger decorators
  services/      business rules; one class per responsibility
  dto/           request DTOs (class-validator, length limits) and response DTOs
  entities/      the data model; no queries, HTTP, email or JWT logic
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then the dependency rule in one line: &lt;strong&gt;Controller → Service → &lt;code&gt;Repository&amp;lt;Entity&amp;gt;&lt;/code&gt;&lt;/strong&gt;. Other modules inject the owning &lt;em&gt;service&lt;/em&gt;, never another module's repository. And a few "never" rules that agents otherwise break constantly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Responses map entities through &lt;code&gt;ResponseDto.fromEntity()&lt;/code&gt;; never return an entity (that's how password hashes end up in JSON).&lt;/li&gt;
&lt;li&gt;User-owned data is looked up by the authenticated ID from &lt;code&gt;@CurrentUser()&lt;/code&gt;, never by an ID from the request alone.&lt;/li&gt;
&lt;li&gt;Clients never set server-owned fields (status, verification state, roles).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A global &lt;code&gt;ValidationPipe&lt;/code&gt; with &lt;code&gt;whitelist: true, forbidNonWhitelisted: true&lt;/code&gt; backs the last rule up mechanically: a DTO that doesn't declare &lt;code&gt;status&lt;/code&gt; can't receive it.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. One error contract
&lt;/h2&gt;

&lt;p&gt;Give the agent exactly one way to fail:&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="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;abstract&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AppException&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* status + stable code */&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;class&lt;/span&gt; &lt;span class="nc"&gt;NotFoundError&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;AppException&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;class&lt;/span&gt; &lt;span class="nc"&gt;ConflictError&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;AppException&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="c1"&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;ConflictError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;EMAIL_ALREADY_REGISTERED&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="s1"&gt;Email is already registered&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A single global exception filter turns every error, including unexpected ones, into the same shape:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"statusCode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;409&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"EMAIL_ALREADY_REGISTERED"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/api/v1/auth/register"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"requestId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"6f1c..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"timestamp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Stable &lt;code&gt;UPPER_SNAKE&lt;/code&gt; codes give agents (and frontends) something to assert on, and the filter is the one place that guarantees no SQL, stack trace or SMTP detail leaks.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Make verification cheap, or it won't happen
&lt;/h2&gt;

&lt;p&gt;An agent will run the checks you make easy. If tests need a database, Docker and seed data, the agent either skips them or starts containers you didn't ask for. So make the default suites infrastructure-free:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Unit tests&lt;/strong&gt; construct services directly with a small in-memory repository that implements only the TypeORM methods your services use:
&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="cm"&gt;/** Minimal in-memory stand-in for the TypeORM repository methods used by the services. */&lt;/span&gt;
  &lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MockRepository&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&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="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&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;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nf"&gt;create&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="nb"&gt;Partial&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&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="nx"&gt;T&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="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;entity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Partial&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&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;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&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="c1"&gt;// findOne, findAndCount, update... only what's used&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;E2E tests&lt;/strong&gt; boot the real &lt;code&gt;AppModule&lt;/code&gt; with the real pipes, guards, filters and throttler, and override only the repositories and the mail service. That tests authorization and validation where the bugs actually are, in about ten seconds, with no database.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then tell the agent the exact commands, narrowest first (&lt;code&gt;npx jest &amp;lt;path&amp;gt;&lt;/code&gt;), and that a red test is fixed for a real reason, never by weakening the assertion.&lt;/p&gt;

&lt;p&gt;Be honest about the gap: in-memory repositories don't test SQL, constraints or cascades. Say so in &lt;code&gt;docs/testing.md&lt;/code&gt;, and check migrations by hand (up, down, up) against a disposable PostgreSQL before a release.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Enforce the rules that matter; write down the rest
&lt;/h2&gt;

&lt;p&gt;Written rules are followed most of the time. For the few operations that can destroy data, add enforcement your tool supports. In Claude Code, &lt;code&gt;ask&lt;/code&gt; permission rules prompt before matching commands even in permissive modes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"permissions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"ask"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"Bash(npm run migration:*)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Bash(npm run seed *)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Bash(psql *)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"Bash(docker compose down *)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Bash(git push *)"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"deny"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"Read(**/.env)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Read(**/.env.production)"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Know the limits: these rules match command text. A script that runs a migration indirectly isn't caught, and a deny on reading &lt;code&gt;.env&lt;/code&gt; doesn't stop code that opens the file. For other agents, configure their command approval for the same commands.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Demand a fixed report
&lt;/h2&gt;

&lt;p&gt;The most useful line in my &lt;code&gt;AGENTS.md&lt;/code&gt; is the report format:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Implemented       what changed and why
Files changed     paths
Verification      commands actually run and their actual results
Not performed     e.g. migration generation, seed execution, Docker, deploy
Unverified        anything you could not check
Never claim a command passed if you did not run it.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It turns "Done! Everything works" into something you can check in thirty seconds, and the "Not performed" line is where the pending migration shows up instead of silently happening.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Turn recurring jobs into procedures
&lt;/h2&gt;

&lt;p&gt;When you notice you're giving the same multi-step instruction for the third time, make it a file. In Claude Code these are skills (&lt;code&gt;.claude/skills/&amp;lt;name&amp;gt;/SKILL.md&lt;/code&gt;), loaded on demand; for other agents the same Markdown works as "follow &lt;code&gt;.claude/skills/debug/SKILL.md&lt;/code&gt;". Good candidates in a NestJS API:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;generate&lt;/strong&gt;: inspect an existing module first, reuse before creating, add tests, report.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;database-change&lt;/strong&gt;: check entity conventions, decide whether a migration is needed, review an existing migration's &lt;code&gt;down()&lt;/code&gt;, and never generate one without a request.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;debug&lt;/strong&gt;: map symptoms to the right file (&lt;code&gt;400 property X should not exist&lt;/code&gt; → the DTO is missing a field), reproduce with a database-free test, fix at the root.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Name them so they don't shadow built-in commands (&lt;code&gt;review-change&lt;/code&gt;, not &lt;code&gt;review&lt;/code&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  The short version
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;One &lt;code&gt;AGENTS.md&lt;/code&gt;; tool files import it.&lt;/li&gt;
&lt;li&gt;Stop rules with exact words, not style advice.&lt;/li&gt;
&lt;li&gt;One structure, one dependency direction, a few explicit "nevers".&lt;/li&gt;
&lt;li&gt;One error class hierarchy and one filter.&lt;/li&gt;
&lt;li&gt;Default test suites that need no infrastructure.&lt;/li&gt;
&lt;li&gt;Tool-enforced prompts for destructive commands.&lt;/li&gt;
&lt;li&gt;A fixed report that separates "verified" from "not performed".&lt;/li&gt;
&lt;li&gt;Recurring tasks as procedure files.&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;&lt;em&gt;I've packaged this structure, with a complete NestJS 11 + PostgreSQL implementation (auth, admin API, migration, 96 tests, Docker, and the agent files above), as a paid starter kit. If you'd rather start from it than build your own, it's here: &lt;a href="https://azadhmhd.gumroad.com/l/nestjs-production-starter-kit?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=launch_v1&amp;amp;utm_content=structure_article" rel="noopener noreferrer"&gt;NestJS Production Starter Kit for AI Coding Agents&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>nestjs</category>
      <category>typescript</category>
      <category>ai</category>
      <category>backend</category>
    </item>
    <item>
      <title>I Kept Forgetting to Drink Water While Coding, So I Built Kairo</title>
      <dc:creator>Azad Mohamed</dc:creator>
      <pubDate>Wed, 26 Aug 2026 18:22:42 +0000</pubDate>
      <link>https://dev.to/azadhmhd/i-kept-forgetting-to-drink-water-while-coding-so-i-built-kairo-4dd2</link>
      <guid>https://dev.to/azadhmhd/i-kept-forgetting-to-drink-water-while-coding-so-i-built-kairo-4dd2</guid>
      <description>&lt;p&gt;I spend a lot of time in front of a computer.&lt;/p&gt;

&lt;p&gt;And like probably a lot of developers, when I'm deep into work, I completely forget about everything around me.&lt;/p&gt;

&lt;p&gt;Drink water? Forgot.&lt;/p&gt;

&lt;p&gt;Take a break? Forgot.&lt;/p&gt;

&lt;p&gt;Look away from the screen? Forgot.&lt;/p&gt;

&lt;p&gt;Move around for a few minutes? Maybe after another three hours. 😅&lt;/p&gt;

&lt;p&gt;Living in the UAE makes this even easier. It's hot outside for a good part of the year, so it's very easy to stay indoors, sit in front of a screen, turn on the AC, and just keep working.&lt;/p&gt;

&lt;p&gt;At some point I thought:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why don't I just build something to remind me?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So I built &lt;strong&gt;Kairo&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Kairo?
&lt;/h2&gt;

&lt;p&gt;Kairo is a small desktop companion designed to help build healthier computer habits.&lt;/p&gt;

&lt;p&gt;The idea is simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;💧 Remind me to drink water&lt;/li&gt;
&lt;li&gt;👀 Remind me to rest my eyes&lt;/li&gt;
&lt;li&gt;🚶 Remind me to take breaks&lt;/li&gt;
&lt;li&gt;🧘 Encourage healthier working habits&lt;/li&gt;
&lt;li&gt;📊 Keep track of what I've actually been doing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I wanted something that felt less like a notification from an operating system and more like a small companion sitting alongside me while I work.&lt;/p&gt;

&lt;p&gt;That's where the animated character comes in.&lt;/p&gt;

&lt;h2&gt;
  
  
  I wanted it to be private
&lt;/h2&gt;

&lt;p&gt;One thing I didn't want was another productivity application that required an account and sent all my activity to some server.&lt;/p&gt;

&lt;p&gt;So I designed Kairo around an &lt;strong&gt;offline-first&lt;/strong&gt; approach.&lt;/p&gt;

&lt;p&gt;The core application works locally and stores its data on the machine.&lt;/p&gt;

&lt;p&gt;AI is also completely optional.&lt;/p&gt;

&lt;p&gt;If you want AI-powered coaching, Kairo can connect to providers such as &lt;strong&gt;Ollama&lt;/strong&gt; or &lt;strong&gt;LM Studio&lt;/strong&gt;, so you can run the AI locally instead of sending your data to a cloud service.&lt;/p&gt;

&lt;p&gt;The application should still work perfectly fine without AI.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I built it
&lt;/h2&gt;

&lt;p&gt;Honestly, this wasn't originally meant to become a big open-source project.&lt;/p&gt;

&lt;p&gt;I built it because &lt;strong&gt;I needed it&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I kept noticing the same pattern in my own work:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Start coding → get focused → lose track of time → forget water → forget breaks → realize hours have passed.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If Kairo can interrupt that cycle even a little, then it has already done what I wanted.&lt;/p&gt;

&lt;h2&gt;
  
  
  A little bit about the engineering
&lt;/h2&gt;

&lt;p&gt;Although the idea is simple, I wanted to use the project to experiment with building a proper desktop application.&lt;/p&gt;

&lt;p&gt;Kairo is built with &lt;strong&gt;Flutter&lt;/strong&gt; and is currently focused on macOS.&lt;/p&gt;

&lt;p&gt;Some of the things I'm working with include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Flutter desktop&lt;/li&gt;
&lt;li&gt;SQLite for local persistence&lt;/li&gt;
&lt;li&gt;Event-driven communication&lt;/li&gt;
&lt;li&gt;Modular packages&lt;/li&gt;
&lt;li&gt;Native desktop integration&lt;/li&gt;
&lt;li&gt;Offline-first architecture&lt;/li&gt;
&lt;li&gt;Optional AI providers&lt;/li&gt;
&lt;li&gt;Local AI through OpenAI-compatible endpoints&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One design decision I'm particularly happy with is keeping AI &lt;strong&gt;out of the critical reminder path&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The reminder system shouldn't stop working just because an AI provider is unavailable.&lt;/p&gt;

&lt;p&gt;AI should enhance the experience, not become a dependency for the basic functionality.&lt;/p&gt;

&lt;h2&gt;
  
  
  It's still a work in progress
&lt;/h2&gt;

&lt;p&gt;There are plenty of things I want to improve.&lt;/p&gt;

&lt;p&gt;I'm still figuring out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Better reminder behaviour&lt;/li&gt;
&lt;li&gt;More useful habit insights&lt;/li&gt;
&lt;li&gt;Better AI coaching&lt;/li&gt;
&lt;li&gt;More customization&lt;/li&gt;
&lt;li&gt;Improving the desktop experience&lt;/li&gt;
&lt;li&gt;Supporting more platforms&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And this is where I'd really like some feedback.&lt;/p&gt;

&lt;p&gt;If you spend most of your day working at a computer:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What would actually help you build healthier working habits?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Not another productivity dashboard.&lt;/p&gt;

&lt;p&gt;Not another app asking you to track 20 things.&lt;/p&gt;

&lt;p&gt;Just something that quietly helps you remember the basic things we tend to forget when we're focused.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try Kairo
&lt;/h2&gt;

&lt;p&gt;The project is open source:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/azadhmhd/kairo" rel="noopener noreferrer"&gt;https://github.com/azadhmhd/kairo&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you find it useful, give it a try.&lt;/p&gt;

&lt;p&gt;And if you have ideas, suggestions, or even things you think I've approached badly, I'd love to hear them.&lt;/p&gt;

&lt;p&gt;I built Kairo for myself, but maybe it can become something useful for other developers too.&lt;/p&gt;

&lt;p&gt;Maybe we can build a healthier workspace together.&lt;/p&gt;

</description>
      <category>buildinpublic</category>
      <category>productivity</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
