<?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: Bryandero98</title>
    <description>The latest articles on DEV Community by Bryandero98 (@bryandero98).</description>
    <link>https://dev.to/bryandero98</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%2F4099110%2F5f24db31-daef-4bbd-bf38-c5b1cef8e7b3.jpg</url>
      <title>DEV Community: Bryandero98</title>
      <link>https://dev.to/bryandero98</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/bryandero98"/>
    <language>en</language>
    <item>
      <title>Your AI Agent Forgot Why It Did That. PacketForge Is the Fix.</title>
      <dc:creator>Bryandero98</dc:creator>
      <pubDate>Tue, 01 Sep 2026 19:01:12 +0000</pubDate>
      <link>https://dev.to/bryandero98/your-ai-agent-forgot-why-it-did-that-packetforge-is-the-fix-2b66</link>
      <guid>https://dev.to/bryandero98/your-ai-agent-forgot-why-it-did-that-packetforge-is-the-fix-2b66</guid>
      <description>&lt;p&gt;&lt;strong&gt;Try it live:&lt;/strong&gt; &lt;a href="https://packetforge.onrender.com/dashboard" rel="noopener noreferrer"&gt;packetforge.onrender.com/dashboard&lt;/a&gt; · Repo (MIT): &lt;a href="https://github.com/Bryandero98/packetforge" rel="noopener noreferrer"&gt;https://github.com/Bryandero98/packetforge&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;An AI coding agent burns through a task graph fast - ten tasks in an&lt;br&gt;
afternoon isn't unusual anymore. Each one makes real decisions: "chose&lt;br&gt;
Postgres over SQLite for the MVP," "left auth storing tokens in plaintext,&lt;br&gt;
that's tracked debt," "picked this library because the other one had no&lt;br&gt;
TypeScript types." None of that reasoning survives past the session that&lt;br&gt;
produced it. It's not in the diff. It's not in a commit message anyone&lt;br&gt;
reads later. The next session - or the next agent, or the next human -&lt;br&gt;
starts from the code and has to re-derive &lt;em&gt;why&lt;/em&gt;, or just guesses and&lt;br&gt;
sometimes guesses wrong.&lt;/p&gt;

&lt;p&gt;Code comments don't fix this. A comment sits in one file, read only by&lt;br&gt;
whoever opens that exact file next. It doesn't get surfaced to a&lt;br&gt;
&lt;em&gt;different&lt;/em&gt; task that depends on the same decision, and it's invisible to&lt;br&gt;
anything searching by meaning instead of by file path.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/Bryandero98/packetforge" rel="noopener noreferrer"&gt;PacketForge&lt;/a&gt; is shared memory&lt;br&gt;
for a task graph: a small, self-hosted API any agent, CLI, or editor&lt;br&gt;
plugin can read from and write to, so "why did the last task do it this&lt;br&gt;
way" has a real answer instead of a re-read-the-diff-and-guess.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;

&lt;p&gt;Every task gets two kinds of notes attached to it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Decisions&lt;/strong&gt; - why it was built the way it was.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Debt&lt;/strong&gt; - what's still wrong with it, left for whatever depends on it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both get embedded (Gemini's &lt;code&gt;gemini-embedding-001&lt;/code&gt;, free tier, no card&lt;br&gt;
required - pluggable behind a one-method &lt;code&gt;EmbeddingProvider&lt;/code&gt; interface) so&lt;br&gt;
&lt;code&gt;GET /graph/search&lt;/code&gt; finds them&lt;br&gt;
by &lt;em&gt;meaning&lt;/em&gt;, not literal text - search "how are cards modeled" and it&lt;br&gt;
surfaces the decision that said "plain object, no behavior yet," not just&lt;br&gt;
rows containing the word "model." Writing a near-duplicate decision on the&lt;br&gt;
same task flags a conflict automatically (cosine similarity against&lt;br&gt;
existing notes) - a warning, never a rejection, since the agent writing it&lt;br&gt;
is in a better position to judge than a threshold is.&lt;/p&gt;

&lt;h2&gt;
  
  
  What shipped this round
&lt;/h2&gt;

&lt;p&gt;PacketForge started as an API-only MVP. This batch turned it into&lt;br&gt;
something closer to real infrastructure:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Multi-project.&lt;/strong&gt; One deployment now serves several repos instead of&lt;br&gt;
needing one per project. &lt;code&gt;POST /projects&lt;/code&gt;, then scope tasks with&lt;br&gt;
&lt;code&gt;projectId&lt;/code&gt; - omit it anywhere and everything still defaults to &lt;code&gt;"default"&lt;/code&gt;,&lt;br&gt;
so nothing existing breaks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A dashboard that didn't exist before.&lt;/strong&gt; &lt;code&gt;GET /dashboard&lt;/code&gt; - a Kanban&lt;br&gt;
board grouped by whatever status values are actually in use, a&lt;br&gt;
click-through detail panel, a project filter, a live semantic search box.&lt;br&gt;
One file, no build step, no framework - it just calls PacketForge's own&lt;br&gt;
REST API like anything else would.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;MCP, and why there's no dedicated n8n node.&lt;/strong&gt; &lt;code&gt;POST /mcp&lt;/code&gt; exposes every&lt;br&gt;
read/write as a &lt;a href="https://modelcontextprotocol.io" rel="noopener noreferrer"&gt;Model Context Protocol&lt;/a&gt;&lt;br&gt;
tool. n8n ships a built-in MCP Client node that connects to any MCP server&lt;br&gt;
with just a URL - so an n8n workflow can already read and write the graph&lt;br&gt;
today, no PacketForge-specific integration required on either side.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A Cursor adapter&lt;/strong&gt;, on top of the existing &lt;code&gt;generic-json&lt;/code&gt; one -&lt;br&gt;
&lt;code&gt;GET /graph/tasks/:id/packet?adapter=cursor&lt;/code&gt; renders a task's decisions and&lt;br&gt;
debt as the Markdown Cursor's own context mechanism expects, instead of a&lt;br&gt;
JSON blob.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Actual ops surface&lt;/strong&gt;: &lt;code&gt;GET /health&lt;/code&gt; (real database check, not "the&lt;br&gt;
process is running"), &lt;code&gt;GET /export&lt;/code&gt; (the whole graph as one JSON document&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;this data lives nowhere except the database, unlike code), and
structured JSON request logging with a correlatable request id.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;A chronological timeline.&lt;/strong&gt; &lt;code&gt;GET /graph/timeline&lt;/code&gt; merges every decision&lt;br&gt;
and every piece of debt across the whole graph (or one project) into a&lt;br&gt;
single feed ordered by when it actually happened - "what did this agent&lt;br&gt;
&lt;em&gt;do&lt;/em&gt;, in order" instead of hunting per-task.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;An audit log.&lt;/strong&gt; Every create/update/delete on a task, decision, debt&lt;br&gt;
entry, or project now writes a row to &lt;code&gt;GET /audit-log&lt;/code&gt; - who touched what,&lt;br&gt;
and when, independent of the graph data itself. It's the boring&lt;br&gt;
infrastructure a shared, multi-agent memory store needs before you'd trust&lt;br&gt;
it in anything real.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rate limiting.&lt;/strong&gt; Write routes are capped (20/min by default, configurable&lt;br&gt;
per route) via &lt;code&gt;@nestjs/throttler&lt;/code&gt; - the same protection any public write&lt;br&gt;
API needs once more than one agent can reach it at once.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The dashboard learned two more tabs and a second language.&lt;/strong&gt; Board is&lt;br&gt;
still there, but now it sits next to a Timeline tab and an Audit Log tab -&lt;br&gt;
same page, same live data, no separate tool. Everything - labels, empty&lt;br&gt;
states, the project picker - now also renders in Spanish, toggled from the&lt;br&gt;
header and remembered per browser.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it without installing anything
&lt;/h2&gt;

&lt;p&gt;The link above is a live instance - Kanban board, timeline, audit log, all&lt;br&gt;
of it. It starts empty; &lt;code&gt;POST&lt;/code&gt; a task to it (see the tutorial below) and&lt;br&gt;
watch it show up on the board in real time. It's the fastest way to see&lt;br&gt;
what an agent-shared task graph actually looks like before deciding&lt;br&gt;
whether to self-host one. It's on Render's free tier, so the first request&lt;br&gt;
after a quiet period takes ~50s to wake up - after that it's normal&lt;br&gt;
speed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick tutorial
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/Bryandero98/packetforge
&lt;span class="nb"&gt;cd &lt;/span&gt;packetforge
npm &lt;span class="nb"&gt;install
&lt;/span&gt;docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt;      &lt;span class="c"&gt;# Postgres 17 + pgvector, one container&lt;/span&gt;
&lt;span class="nb"&gt;cp&lt;/span&gt; .env.example .env
npm run db:migrate
npm run start:dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Create a task&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST localhost:3000/graph/tasks &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"id": "CARD-MODEL", "title": "Card domain model"}'&lt;/span&gt;

&lt;span class="c"&gt;# Record why it was built a certain way&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST localhost:3000/decisions &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"taskId": "CARD-MODEL", "note": "Plain object, not a class - no behavior yet"}'&lt;/span&gt;

&lt;span class="c"&gt;# Search by meaning, not literal text&lt;/span&gt;
curl &lt;span class="s1"&gt;'localhost:3000/graph/search?q=data+model+for+cards&amp;amp;limit=5'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then open &lt;code&gt;localhost:3000/dashboard&lt;/code&gt; and watch the same data show up as a&lt;br&gt;
board.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it's headed
&lt;/h2&gt;

&lt;p&gt;The extension points are deliberately small on purpose: a new output&lt;br&gt;
format is one &lt;code&gt;PacketAdapter&lt;/code&gt; class (see&lt;br&gt;
&lt;a href="https://github.com/Bryandero98/packetforge/blob/main/src/adapter/adapters/cursor.adapter.ts" rel="noopener noreferrer"&gt;&lt;code&gt;src/adapter/adapters/cursor.adapter.ts&lt;/code&gt;&lt;/a&gt;&lt;br&gt;
for the pattern), a new embedding model is one &lt;code&gt;EmbeddingProvider&lt;/code&gt; class.&lt;br&gt;
Open issues if you want to try one:&lt;br&gt;
&lt;a href="https://github.com/Bryandero98/packetforge/issues" rel="noopener noreferrer"&gt;good first issue&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Repo (MIT): &lt;a href="https://github.com/Bryandero98/packetforge" rel="noopener noreferrer"&gt;https://github.com/Bryandero98/packetforge&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;If this is useful to you, a &lt;a href="https://ko-fi.com/bryandero98" rel="noopener noreferrer"&gt;Ko-fi&lt;/a&gt; or a bit&lt;br&gt;
of USDT goes a long way for a solo-maintained project - both are linked in&lt;br&gt;
the repo's README.&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>opensource</category>
      <category>ai</category>
      <category>postgres</category>
    </item>
    <item>
      <title>CLI Snapshot Testing: Stop Your Internal Tools From Breaking Everyone Else's Pipeline</title>
      <dc:creator>Bryandero98</dc:creator>
      <pubDate>Fri, 28 Aug 2026 14:23:57 +0000</pubDate>
      <link>https://dev.to/bryandero98/cli-snapshot-testing-stop-your-internal-tools-from-breaking-everyone-elses-pipeline-3ln8</link>
      <guid>https://dev.to/bryandero98/cli-snapshot-testing-stop-your-internal-tools-from-breaking-everyone-elses-pipeline-3ln8</guid>
      <description>&lt;p&gt;Every team with more than a couple of engineers ends up with at least one&lt;br&gt;
internal CLI - a deploy script, a codegen tool, a scaffold generator,&lt;br&gt;
something wrapping three other tools into one command. It starts small.&lt;br&gt;
Then other repos start depending on it in their own CI pipelines, other&lt;br&gt;
scripts start shelling out to it, and at some point it quietly becomes&lt;br&gt;
load-bearing infrastructure that nobody explicitly signed up to maintain a&lt;br&gt;
compatibility promise for.&lt;/p&gt;

&lt;p&gt;Then someone - reasonably, with good intentions - notices that a flag&lt;br&gt;
really &lt;em&gt;should&lt;/em&gt; be required, or that a subcommand name doesn't match the&lt;br&gt;
new naming convention, and changes it. It merges clean. Tests pass, because&lt;br&gt;
the CLI's own tests only ever tested its own repo. Twenty minutes later,&lt;br&gt;
three other teams' CI pipelines are red, and nobody connects the dots for&lt;br&gt;
another hour because the error is "missing required argument," not "your&lt;br&gt;
CLI's contract changed."&lt;/p&gt;

&lt;p&gt;REST APIs stopped having this problem years ago - most serious API teams&lt;br&gt;
run some flavor of contract testing (Pact, Specmatic, oasdiff) as a matter&lt;br&gt;
of course, specifically to catch exactly this class of accidental breaking&lt;br&gt;
change before it ships. CLIs never got the equivalent tooling, even though&lt;br&gt;
the failure mode is identical: a public interface changed shape, and&lt;br&gt;
whoever depends on that interface found out the hard way.&lt;/p&gt;
&lt;h2&gt;
  
  
  How CLI snapshotting works
&lt;/h2&gt;

&lt;p&gt;The idea behind &lt;a href="https://github.com/Bryandero98/cliguard" rel="noopener noreferrer"&gt;&lt;code&gt;cliguard&lt;/code&gt;&lt;/a&gt; is&lt;br&gt;
the same one behind Jest's snapshot tests, applied to a CLI's public&lt;br&gt;
surface instead of a rendered component:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Capture.&lt;/strong&gt; &lt;code&gt;cliguard init&lt;/code&gt; extracts your CLI's full contract - every
command, subcommand, flag (with its type, default, and whether it's
required), and positional argument - and writes it to
&lt;code&gt;.cliguard/contract.json&lt;/code&gt;. You commit that file like any other snapshot.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check.&lt;/strong&gt; On every PR, &lt;code&gt;cliguard check&lt;/code&gt; re-extracts the &lt;em&gt;current&lt;/em&gt;
surface and diffs it against the committed contract. Every difference
gets classified:

&lt;ul&gt;
&lt;li&gt;🔴 &lt;strong&gt;BREAKING&lt;/strong&gt; - something was removed, an optional thing became
required, a default or value type changed.&lt;/li&gt;
&lt;li&gt;🟢 &lt;strong&gt;ADDITIVE&lt;/strong&gt; - a new optional command/flag/argument was added.
Nothing that already calls your CLI can break because of it.&lt;/li&gt;
&lt;li&gt;🟡 &lt;strong&gt;PATCH&lt;/strong&gt; - cosmetic only (a description changed, an alias was
added, something required became optional).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fail loud, in the right place.&lt;/strong&gt; &lt;code&gt;cliguard check&lt;/code&gt; exits &lt;code&gt;1&lt;/code&gt; the moment
it finds even one &lt;code&gt;BREAKING&lt;/code&gt; change. That failure shows up on the PR
that introduced it - not as a mystery failure in someone else's
pipeline three hours later.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The part worth calling out specifically: extraction never parses rendered&lt;br&gt;
&lt;code&gt;--help&lt;/code&gt; text with regular expressions. It loads your CLI's entry file into&lt;br&gt;
the Node process and reads the framework's own object graph directly - for&lt;br&gt;
Commander.js, that's &lt;code&gt;command.options&lt;/code&gt;, &lt;code&gt;command.commands&lt;/code&gt;, and&lt;br&gt;
&lt;code&gt;command.registeredArguments&lt;/code&gt;. Every field in the contract is guaranteed to&lt;br&gt;
match what the framework will actually do at runtime, because it &lt;em&gt;is&lt;/em&gt; what&lt;br&gt;
the framework will do at runtime, not a text rendering of it that has to be&lt;br&gt;
parsed back apart.&lt;/p&gt;
&lt;h2&gt;
  
  
  Quick tutorial
&lt;/h2&gt;

&lt;p&gt;Your CLI's entry file needs to export its Commander &lt;code&gt;Command&lt;/code&gt; instance&lt;br&gt;
instead of calling &lt;code&gt;.parse()&lt;/code&gt; on it directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// bin/cli.js&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;Command&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;commander&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;program&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;Command&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;program&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;command&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;build&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;requiredOption&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;-t, --target &amp;lt;target&amp;gt;&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;build target&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// ...&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;program&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt; &lt;span class="c1"&gt;// cliguard reads this - it never runs your CLI&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Install it as a dev dependency:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Capture the baseline and commit it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx cliguard init ./bin/cli.js
git add .cliguard/contract.json
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"chore: add cliguard contract"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wire the check into CI:&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="c1"&gt;# .github/workflows/cliguard.yml&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;CLI contract&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;check&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-node@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;22.x&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm ci&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npx cliguard check ./bin/cli.js&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, the moment a PR removes a flag, flips one from optional to required,&lt;br&gt;
or changes a default value, the build fails right there with exactly what&lt;br&gt;
changed and where:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;🔴 [root -&amp;gt; build -&amp;gt; option[--target]] Option "--target" was removed.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And when you add something new and safe, the check passes straight&lt;br&gt;
through:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;🟢 [root -&amp;gt; build -&amp;gt; option[--dry-run]] New optional option "--dry-run" was added.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you make an intentional breaking change - a real major version bump -&lt;br&gt;
just accept the new contract:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx cliguard update ./bin/cli.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Where it's headed
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;cliguard&lt;/code&gt; supports Commander.js and CAC today - the core (the contract&lt;br&gt;
types and the diff engine) is 100% framework-agnostic, with every&lt;br&gt;
framework-specific detail behind a small &lt;code&gt;CliAdapter&lt;/code&gt; interface. Yargs is&lt;br&gt;
the next open &lt;a href="https://github.com/Bryandero98/cliguard/issues" rel="noopener noreferrer"&gt;good first issue&lt;/a&gt;&lt;br&gt;
if you want to try implementing one.&lt;/p&gt;

&lt;p&gt;Repo (MIT): &lt;a href="https://github.com/Bryandero98/cliguard" rel="noopener noreferrer"&gt;https://github.com/Bryandero98/cliguard&lt;/a&gt;&lt;/p&gt;

</description>
      <category>automation</category>
      <category>cli</category>
      <category>devops</category>
      <category>testing</category>
    </item>
  </channel>
</rss>
