<?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: Aliza Ali</title>
    <description>The latest articles on DEV Community by Aliza Ali (@stackmasteraliza).</description>
    <link>https://dev.to/stackmasteraliza</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%2F3703525%2Fcf13c18e-51da-45be-ab73-3da4313b3f47.png</url>
      <title>DEV Community: Aliza Ali</title>
      <link>https://dev.to/stackmasteraliza</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/stackmasteraliza"/>
    <language>en</language>
    <item>
      <title>5 Ways Your Photos Still Leak Your Location (Even Though Social Media Strips It)</title>
      <dc:creator>Aliza Ali</dc:creator>
      <pubDate>Sun, 01 Nov 2026 01:28:23 +0000</pubDate>
      <link>https://dev.to/stackmasteraliza/5-ways-your-photos-still-leak-your-location-even-though-social-media-strips-it-1fhg</link>
      <guid>https://dev.to/stackmasteraliza/5-ways-your-photos-still-leak-your-location-even-though-social-media-strips-it-1fhg</guid>
      <description>&lt;p&gt;"Don't worry, social media strips the location from photos."&lt;/p&gt;

&lt;p&gt;That's &lt;em&gt;mostly&lt;/em&gt; true for the big platforms. Instagram, Facebook and X re-encode your upload, and the metadata usually gets thrown away along the way. But that's a side effect of their pipeline, not a privacy feature, and it only covers those apps.&lt;/p&gt;

&lt;p&gt;Every time a photo leaves your phone some other way, the original file goes with it: GPS coordinates, device model, timestamps, sometimes even your camera's serial number. Here are five everyday ways that happens.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. "Send as file" in chat apps
&lt;/h2&gt;

&lt;p&gt;Sending a photo normally in WhatsApp or Telegram compresses it, which generally drops the metadata. But choosing &lt;strong&gt;"send as document"&lt;/strong&gt; or &lt;strong&gt;"send as file"&lt;/strong&gt; to keep the quality sends the original, untouched, location included.&lt;/p&gt;

&lt;p&gt;People do this constantly for exactly the photos that matter: house listings, ID-adjacent documents, full-resolution shots for a buyer.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Email attachments
&lt;/h2&gt;

&lt;p&gt;Email doesn't re-encode anything. Attach a photo from your gallery, and the recipient gets every byte of the original. That includes the exact spot where you were standing when you took it.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Cloud share links
&lt;/h2&gt;

&lt;p&gt;Google Drive, Dropbox and OneDrive links serve the original file. Share a folder of photos from your house, your kid's school event or a trip, and anyone with the link can download them with full EXIF intact.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Device-to-device sharing
&lt;/h2&gt;

&lt;p&gt;Nearby sharing (Quick Share on Android, AirDrop on iPhone) transfers the original file. AirDrop has an &lt;strong&gt;Options&lt;/strong&gt; toggle in the share sheet to leave the location out, but almost nobody knows it's there.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Marketplaces and forums
&lt;/h2&gt;

&lt;p&gt;Some sites re-encode uploads and some don't, and you can't tell from the outside. Selling a sofa? The photo you took in your living room may carry your living room's coordinates.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to check your own photos (Android)
&lt;/h2&gt;

&lt;p&gt;Open any photo in Google Photos and swipe up. If you see a map, that location is stored in the file and goes wherever the original goes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stop new photos from being tagged
&lt;/h2&gt;

&lt;p&gt;Most camera apps let you turn off location tagging:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pixel Camera:&lt;/strong&gt; Settings → &lt;em&gt;Save location&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Samsung Camera:&lt;/strong&gt; Settings → &lt;em&gt;Location tags&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That protects photos you take from now on. It does nothing for the hundreds already in your gallery.&lt;/p&gt;

&lt;h2&gt;
  
  
  Clean the ones you already have
&lt;/h2&gt;

&lt;p&gt;That's the gap I built &lt;strong&gt;&lt;a href="https://play.google.com/store/apps/details?id=com.alizaali.metaclean" rel="noopener noreferrer"&gt;Drop Metadata for Android&lt;/a&gt;&lt;/strong&gt; to fill. Pick a photo or video, and it shows you everything hidden inside, with the GPS location plotted on a map, before anything changes. Then you choose what goes: drop the location but keep the date, clear the camera details, or wipe everything in one tap.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;100% on-device:&lt;/strong&gt; no server, no account, and it works fully offline&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Photos and videos:&lt;/strong&gt; JPEG, PNG, MP4 and MOV&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No quality loss:&lt;/strong&gt; pixels and video frames are copied byte for byte, so nothing is re-encoded&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://play.google.com/store/apps/details?id=com.alizaali.metaclean" rel="noopener noreferrer"&gt;Get Drop Metadata on Google Play&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Not on Android? The same engine runs in your browser at &lt;strong&gt;&lt;a href="https://dropmetadata.com" rel="noopener noreferrer"&gt;dropmetadata.com&lt;/a&gt;&lt;/strong&gt;. I wrote about how it works without uploading anything &lt;a href="https://dev.to/stackmasteraliza/your-photos-are-telling-on-you-i-built-a-metadata-remover-that-never-uploads-your-files-33kf"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  I'd love your help testing it
&lt;/h2&gt;

&lt;p&gt;The app is new, and real devices find bugs that emulators never will. If you try it, drop a comment with:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Your phone model&lt;/li&gt;
&lt;li&gt;What the app found in a photo straight from your camera&lt;/li&gt;
&lt;li&gt;Anything that looked wrong, missing or confusing&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;And if you know another sneaky way photos leak location, share it below. I'll add it to the list.&lt;/p&gt;

</description>
      <category>privacy</category>
      <category>android</category>
      <category>security</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Your Photos Are Telling On You: I Built a Metadata Remover That Never Uploads Your Files</title>
      <dc:creator>Aliza Ali</dc:creator>
      <pubDate>Sat, 31 Oct 2026 12:45:05 +0000</pubDate>
      <link>https://dev.to/stackmasteraliza/your-photos-are-telling-on-you-i-built-a-metadata-remover-that-never-uploads-your-files-33kf</link>
      <guid>https://dev.to/stackmasteraliza/your-photos-are-telling-on-you-i-built-a-metadata-remover-that-never-uploads-your-files-33kf</guid>
      <description>&lt;p&gt;Every photo you take on a phone carries a small dossier inside the file: the exact GPS coordinates where you stood, the device that took it, the software version, the capture time, and sometimes even the camera's serial number. Most people never see any of it, because image viewers don't show it. But anyone you send the original file to can read it in seconds.&lt;/p&gt;

&lt;p&gt;That's why I built &lt;strong&gt;&lt;a href="https://dropmetadata.com" rel="noopener noreferrer"&gt;Drop Metadata&lt;/a&gt;&lt;/strong&gt;, a free tool that strips hidden metadata from photos and videos without ever uploading them.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's actually hiding in your files
&lt;/h2&gt;

&lt;p&gt;Here's the kind of thing a single phone photo can reveal:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;GPS&lt;/td&gt;
&lt;td&gt;37.774887, −122.419388&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Device&lt;/td&gt;
&lt;td&gt;iPhone 15 Pro&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Software&lt;/td&gt;
&lt;td&gt;iOS 17.2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Captured&lt;/td&gt;
&lt;td&gt;2026-01-15 10:30:00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Serial&lt;/td&gt;
&lt;td&gt;Camera / lens serial number&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C2PA&lt;/td&gt;
&lt;td&gt;AI provenance manifest&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Post that photo of your new desk setup or your kid's first day of school, and you may have shared your home address along with it.&lt;/p&gt;

&lt;p&gt;Drop Metadata removes all of it: GPS coordinates, camera make and model, capture software, timestamps, author and copyright fields, camera and lens serial numbers, XMP editing history, and C2PA content credentials.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why "no upload" was non-negotiable
&lt;/h2&gt;

&lt;p&gt;Most free "metadata remover" sites work by uploading your file to their server, cleaning it there, and sending it back. That means the very file you're trying to protect, GPS location and all, gets handed to a stranger first.&lt;/p&gt;

&lt;p&gt;So the core rule for Drop Metadata was simple: &lt;strong&gt;the file never leaves your device.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The file is read straight from disk into browser memory. No network request is made.&lt;/li&gt;
&lt;li&gt;Every embedded field is listed by category before anything is removed, so nothing is stripped blindly.&lt;/li&gt;
&lt;li&gt;The clean file is rebuilt locally and downloaded straight back to you.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No upload, no account, no server, no copy of your file anywhere. A nice side effect: it's instant, because there's no upload or download round trip.&lt;/p&gt;

&lt;h2&gt;
  
  
  Zero quality loss
&lt;/h2&gt;

&lt;p&gt;A lot of tools "remove" metadata by re-encoding the whole image, which quietly degrades quality every time. Drop Metadata doesn't touch the pixels.&lt;/p&gt;

&lt;p&gt;Metadata lives in its own containers, separate from the image or video data: EXIF and XMP segments in JPEGs, metadata chunks in PNGs, and metadata boxes inside MP4/MOV files. Drop Metadata rewrites only those containers and copies pixel data and video samples &lt;strong&gt;byte for byte&lt;/strong&gt;. Nothing is re-encoded, so the output is visually identical.&lt;/p&gt;

&lt;h2&gt;
  
  
  It works on video too
&lt;/h2&gt;

&lt;p&gt;Video is the part people forget. Phones embed GPS and device tags inside MP4 and MOV containers as well. Drop Metadata cleans them at the container level, so location and device tags are stripped without re-encoding a single frame, and the file size stays effectively identical.&lt;/p&gt;

&lt;p&gt;Supported formats: &lt;strong&gt;JPG, PNG, MP4, MOV&lt;/strong&gt;, any size.&lt;/p&gt;

&lt;h2&gt;
  
  
  📱 Get the Android app
&lt;/h2&gt;

&lt;p&gt;Most of the photos worth worrying about never touch a laptop. They go straight from your camera roll to a marketplace listing, an email attachment or a "send as file" chat. So I built &lt;strong&gt;&lt;a href="https://play.google.com/store/apps/details?id=com.alizaali.metaclean" rel="noopener noreferrer"&gt;Drop Metadata for Android&lt;/a&gt;&lt;/strong&gt; to clean them right where they live.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;See before you strip.&lt;/strong&gt; The app lists every field it finds by category, and plots the photo's GPS location on a map, before anything changes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You choose what goes.&lt;/strong&gt; Each category has its own switch. Remove the location but keep the date, clear the camera details but keep your copyright, or wipe everything in one tap.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Photos and videos.&lt;/strong&gt; JPEG, PNG, MP4 and MOV. Video is cleaned at the container level, so no frame is re-encoded.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fully offline.&lt;/strong&gt; No server, no account, no sign-up, and it works with no internet connection at all. The Play Store data-safety label says no data collected and none shared.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No quality loss.&lt;/strong&gt; Pixels and video frames are copied byte for byte, and photo orientation is preserved, so portrait shots stay upright.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A quick reality check on "but social media strips metadata anyway": the big platforms usually do, as a side effect of re-encoding. Email, messaging apps sending files, cloud links, marketplaces and forums usually don't. Cleaning the file before it leaves your phone is the only reliable approach.&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://play.google.com/store/apps/details?id=com.alizaali.metaclean" rel="noopener noreferrer"&gt;Download Drop Metadata on Google Play&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  🙋 I'm looking for Android testers
&lt;/h3&gt;

&lt;p&gt;The app is brand new, and I'd really appreciate real-world feedback. If you have a few minutes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Install it and open a photo straight from your camera.&lt;/li&gt;
&lt;li&gt;Tell me in the comments which fields it found, and whether anything looked wrong or missing.&lt;/li&gt;
&lt;li&gt;Try a video too, and let me know your phone model if anything breaks.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Bug reports are just as welcome as praise.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://dropmetadata.com" rel="noopener noreferrer"&gt;dropmetadata.com&lt;/a&gt;&lt;/strong&gt;: drop in a photo and see what it's been saying about you.&lt;/p&gt;

&lt;p&gt;I'd love feedback from the DEV community, especially on formats you'd like supported next, or metadata fields you've seen leak that I should check for. Drop a comment below!&lt;/p&gt;

</description>
      <category>privacy</category>
      <category>webdev</category>
      <category>showdev</category>
      <category>android</category>
    </item>
    <item>
      <title>SOLSTICE — feed the light, hold back the night (an ode to Alan Turing)</title>
      <dc:creator>Aliza Ali</dc:creator>
      <pubDate>Sun, 21 Jun 2026 10:28:05 +0000</pubDate>
      <link>https://dev.to/stackmasteraliza/solstice-feed-the-light-hold-back-the-night-an-ode-to-alan-turing-41hm</link>
      <guid>https://dev.to/stackmasteraliza/solstice-feed-the-light-hold-back-the-night-an-ode-to-alan-turing-41hm</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/june-game-jam-2026-06-03"&gt;June Solstice Game Jam&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;SOLSTICE&lt;/strong&gt; is a juicy, single-file browser arcade game about the longest day of the year — and a tribute to &lt;strong&gt;Alan Turing&lt;/strong&gt;, born in June, the man who turned the longest nights of a war toward dawn.&lt;/p&gt;

&lt;p&gt;You &lt;strong&gt;are&lt;/strong&gt; a spark of light. Move with your mouse (or finger) and the whole world responds: the sky shifts from blazing noon to deep night, the sun rises and sets with your daylight, particles burst and the music speeds up as the danger climbs.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🌗 &lt;strong&gt;Daylight is your health.&lt;/strong&gt; Gather the sun's &lt;strong&gt;motes&lt;/strong&gt; to keep it burning; touch a &lt;strong&gt;shadow&lt;/strong&gt; and the night creeps in. Let it hit zero and the screen pulses red as night falls.&lt;/li&gt;
&lt;li&gt;🌀 &lt;strong&gt;The Solstice Turn.&lt;/strong&gt; At the midpoint the world flips — the longest &lt;em&gt;day&lt;/em&gt; becomes the longest &lt;em&gt;night&lt;/em&gt; (exactly the prompt's "longest day for half the world, shortest for the other"). The sky goes cold, the sun becomes a moon, and light becomes precious.&lt;/li&gt;
&lt;li&gt;🌑 &lt;strong&gt;Two bosses.&lt;/strong&gt; &lt;em&gt;The Longest Night&lt;/em&gt; at day 10, and the bigger, faster, bullet-spitting &lt;strong&gt;Eclipse King&lt;/strong&gt; at day 20 — each with a phase-2 rage mode and a &lt;strong&gt;codebreaking finisher&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;☄ &lt;strong&gt;Power-ups &amp;amp; a stockpile-able Nova.&lt;/strong&gt; Time Warp, Light Magnet, Freeze, Sun Fragment — plus the Sun Nova you can bank ×3 and unleash to wipe the screen.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It connects to the theme on every axis: &lt;strong&gt;the solstice&lt;/strong&gt; (longest day, the Turn), &lt;strong&gt;light vs. dark&lt;/strong&gt; as the core mechanic, and &lt;strong&gt;Alan Turing&lt;/strong&gt; woven into the story and the boss fights.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;▶ Play it live:&lt;/strong&gt; &lt;a href="https://stackmasteraliza.github.io/solstice-cipher/" rel="noopener noreferrer"&gt;https://stackmasteraliza.github.io/solstice-cipher/&lt;/a&gt;&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
      &lt;div class="c-embed__body flex items-center justify-between"&gt;
        &lt;a href="https://stackmasteraliza.github.io/solstice-cipher/" rel="noopener noreferrer" class="c-link fw-bold flex items-center"&gt;
          &lt;span class="mr-2"&gt;stackmasteraliza.github.io&lt;/span&gt;
          

        &lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  Video Demo
&lt;/h2&gt;

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

&lt;p&gt;▶ Watch the demo: &lt;a href="https://youtu.be/n6lRqo1CBSo" rel="noopener noreferrer"&gt;https://youtu.be/n6lRqo1CBSo&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;p&gt;The whole game is one &lt;code&gt;index.html&lt;/code&gt; — no build step, no dependencies, no external assets.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/stackmasteraliza" rel="noopener noreferrer"&gt;
        stackmasteraliza
      &lt;/a&gt; / &lt;a href="https://github.com/stackmasteraliza/solstice-cipher" rel="noopener noreferrer"&gt;
        solstice-cipher
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      SOLSTICE — feed the light, hold back the night. A 20-level arcade game for the June Solstice Game Jam, an ode to Alan Turing.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;☀ SOLSTICE&lt;/h1&gt;
&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;feed the light · hold back the night&lt;/h3&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;A juicy 20-level arcade game for the &lt;a href="https://dev.to/devteam" rel="nofollow"&gt;June Solstice Game Jam&lt;/a&gt; — and an ode to Alan Turing.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;The longest day of the year, and the dark wants it back. Glide your light, gather the sun's motes,&lt;/em&gt;
&lt;em&gt;grab power-ups, survive the Solstice Turn, and break two beasts of night.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://stackmasteraliza.github.io/solstice-cipher/" rel="nofollow noopener noreferrer"&gt;&lt;strong&gt;▶ Play it live&lt;/strong&gt;&lt;/a&gt; · &lt;a href="https://youtu.be/n6lRqo1CBSo" rel="nofollow noopener noreferrer"&gt;&lt;strong&gt;🎬 Watch the demo&lt;/strong&gt;&lt;/a&gt; · &lt;a href="https://github.com/stackmasteraliza/solstice-cipher#-power-ups" rel="noopener noreferrer"&gt;Power-ups&lt;/a&gt; · &lt;a href="https://github.com/stackmasteraliza/solstice-cipher#-built-with" rel="noopener noreferrer"&gt;How it's built&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;✨ Why it's fun&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;You &lt;strong&gt;are&lt;/strong&gt; a spark of light. Move with the mouse (or your finger) and the whole screen responds —
the sky shifts from blazing noon to deep night, the sun rises and sets with your daylight, particles
burst, combos chime, and the music speeds up as the danger climbs.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;🌗 &lt;strong&gt;The light is the game.&lt;/strong&gt; Daylight is your health bar. Gather motes to keep it burning; touch a
shadow and the night…&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/stackmasteraliza/solstice-cipher" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;p&gt;Plain &lt;strong&gt;HTML5 Canvas + vanilla JavaScript&lt;/strong&gt;. A few decisions I'm happy with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;One variable drives the whole atmosphere.&lt;/strong&gt; The sky, the sun's arc, the glow and the star field are all tied to a single value (&lt;code&gt;--night&lt;/code&gt;, 0 = blazing noon → 1 = full dark). The game loop just sets it from your remaining daylight and everything follows — the sun literally sinks as you run out of light.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;All audio is synthesized live with the Web Audio API&lt;/strong&gt; — no sound files. There's a kick/hi-hat/bass &lt;strong&gt;beat track that speeds up every level&lt;/strong&gt;, a separate &lt;strong&gt;furious minor-key boss track&lt;/strong&gt;, win/lose stingers, and reactive SFX whose pitch rises with your combo.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;20 levels, two bosses, a power-up economy, and four enemy types&lt;/strong&gt; (normal, fast &lt;em&gt;darters&lt;/em&gt;, heavy &lt;em&gt;tanks&lt;/em&gt;, and &lt;em&gt;splitters&lt;/em&gt; that break in two) — all from a compact entity loop.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Honest Mastermind scoring&lt;/strong&gt; for the Turing finisher, with correct duplicate-glyph handling (the classic trap), unit-tested against the tricky cases.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Onboarding built in:&lt;/strong&gt; a guided &lt;strong&gt;practice with tooltips&lt;/strong&gt; the first run, and a &lt;strong&gt;boss briefing&lt;/strong&gt; before each beast, so a new player (or a judge) is taught every mechanic right before they need it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Accessible &amp;amp; portable:&lt;/strong&gt; fully keyboard + touch playable; colour &lt;em&gt;and&lt;/em&gt; shape pairing for enemies; runs from a single file anywhere.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Prize Category
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;🎯 Best Ode to Alan Turing.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;June is Turing's birth month, so the game is built around the thing he's famous for — &lt;strong&gt;code-breaking and the triumph of logic over darkness&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Mechanic:&lt;/strong&gt; each boss can't just be hit to death. At zero HP it's &lt;em&gt;exposed&lt;/em&gt;, and &lt;strong&gt;Turing's Bombe&lt;/strong&gt; reveals a cipher — you &lt;strong&gt;"Decode the Dawn"&lt;/strong&gt; by repeating the sequence to break the night for good. Code-breaking &lt;em&gt;is&lt;/em&gt; the finishing move.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Narrative:&lt;/strong&gt; between-level "transmissions" trace his arc — Bletchley, the Enigma, his persecution for who he loved, "never granted his own daylight." The win screen, &lt;strong&gt;DAWN&lt;/strong&gt;, is a tribute: &lt;em&gt;"For Alan Turing (1912–1954) — who broke the world's hardest cipher and was never allowed his own daylight."&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Design:&lt;/strong&gt; the Enigma rotor motif and the light-vs-shadow (1s and 0s) framing run throughout.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Not submitting to Best Google AI Usage — no Google AI is used in this build.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Thanks for running the jam — happy solstice! ☀&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>gamechallenge</category>
      <category>gamedev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>My 'Incident-to-PR Autopilot' Never Actually Opened the PR. I Finally Finished It.</title>
      <dc:creator>Aliza Ali</dc:creator>
      <pubDate>Sun, 07 Jun 2026 18:48:00 +0000</pubDate>
      <link>https://dev.to/stackmasteraliza/my-incident-to-pr-autopilot-never-actually-opened-the-pr-i-finally-finished-it-a4j</link>
      <guid>https://dev.to/stackmasteraliza/my-incident-to-pr-autopilot-never-actually-opened-the-pr-i-finally-finished-it-a4j</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/github-2026-05-21"&gt;GitHub Finish-Up-A-Thon Challenge&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;FixForward&lt;/strong&gt; — an &lt;em&gt;incident-to-PR autopilot&lt;/em&gt; powered by GitHub Copilot CLI. One command turns a failing test suite into a &lt;strong&gt;verified, ready-to-merge pull request&lt;/strong&gt;: it detects the failures, classifies them, asks GitHub Copilot for a minimal fix, applies it on a safe branch, re-runs the tests to prove the fix works, and opens a labelled PR — locally or automatically in CI.&lt;/p&gt;

&lt;p&gt;It started life as my submission to an earlier &lt;strong&gt;GitHub Copilot CLI challenge&lt;/strong&gt;. It placed well… and then I left it one step short. The tool literally had &lt;strong&gt;"PR" in its name&lt;/strong&gt;, but it stopped at &lt;em&gt;printing&lt;/em&gt; a PR description and made you &lt;code&gt;git push&lt;/code&gt; and &lt;code&gt;gh pr create&lt;/code&gt; by hand. I got busy, and that last mile sat unfinished for months.&lt;/p&gt;

&lt;p&gt;This challenge was the push to finally land the plane. FixForward is the tool I actually want sitting in my repos: when a build breaks, a fix PR should just &lt;em&gt;appear&lt;/em&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;fixforward
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Diagnosis&lt;/strong&gt; — FixForward detects the failing tests and classifies them:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4z4q8io15lh3t0hmcaep.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4z4q8io15lh3t0hmcaep.png" alt="FixForward diagnosing failing tests" width="800" height="292"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Outcome&lt;/strong&gt; — Copilot's fix, verified green, opened as a labelled PR automatically:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz7fzjg774h1y2oy35nej.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz7fzjg774h1y2oy35nej.png" alt="FixForward opening a verified pull request" width="800" height="380"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🎥 Watch it open a real PR, live:&lt;/strong&gt;&lt;/p&gt;

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

&lt;p&gt;And here's the pull request FixForward opened on a clean demo repo — Copilot's fix, the before/after verification table, and the &lt;code&gt;high-confidence&lt;/code&gt; label, all generated automatically:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj2294ewwk4tqqc9ar2ee.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj2294ewwk4tqqc9ar2ee.png" alt="Pull request FixForward opened on a clean demo repo" width="800" height="453"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Try it yourself:&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;# locally: fix a broken build AND open the PR&lt;/span&gt;
fixforward run &lt;span class="nt"&gt;--open-pr&lt;/span&gt; &lt;span class="nt"&gt;--min-confidence&lt;/span&gt; 0.7

&lt;span class="c"&gt;# in CI: end-to-end, hands-off&lt;/span&gt;
fixforward ship &lt;span class="nt"&gt;--min-confidence&lt;/span&gt; 0.6
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The repo ships with intentionally-broken Python/Node/Rust demos so you can watch the whole loop, plus a one-click &lt;strong&gt;"Demo – Auto-fix a broken build"&lt;/strong&gt; GitHub Action.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🔗 &lt;strong&gt;Repo:&lt;/strong&gt; &lt;a href="https://github.com/stackmasteraliza/fixforward" rel="noopener noreferrer"&gt;https://github.com/stackmasteraliza/fixforward&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📦 &lt;strong&gt;PyPI:&lt;/strong&gt; &lt;a href="https://pypi.org/project/fixforward/" rel="noopener noreferrer"&gt;https://pypi.org/project/fixforward/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Comeback Story
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Where it was:&lt;/strong&gt; FixForward did almost everything it promised — detect, classify, fix, verify, and even generate a polished PR title and body. Then it printed them to the terminal and stopped. The old code literally ended by telling &lt;em&gt;you&lt;/em&gt; to finish:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# display.py (v0.1)
&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;[dim]Review the branch, then push and open a PR.[/]&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There were also &lt;strong&gt;no automated tests&lt;/strong&gt; — ironic for a tool that auto-fixes other people's tests.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I changed — before → after:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;v0.1 (before)&lt;/th&gt;
&lt;th&gt;v0.2 (after)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Detect + classify failures&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Copilot generates the fix&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Verify tests pass&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Write the PR description&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Push the branch&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;❌ you did it&lt;/td&gt;
&lt;td&gt;✅ automatic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Open the PR&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;❌ you did it&lt;/td&gt;
&lt;td&gt;✅ &lt;code&gt;gh pr create&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Label by confidence&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅ high / needs-review / low&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Run in CI, hands-off&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;❌ laptop only&lt;/td&gt;
&lt;td&gt;✅ GitHub Action&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;I gave myself one rule: &lt;strong&gt;finish the original promise, don't bolt on unrelated features.&lt;/strong&gt; Everything in v0.2.0 serves one sentence — &lt;em&gt;go from a red build to a ready-to-merge PR with no human in the loop.&lt;/em&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The last mile — a new &lt;code&gt;shipper.py&lt;/code&gt;.&lt;/strong&gt; Pushes the &lt;code&gt;fixforward/auto-*&lt;/code&gt; branch and opens the PR via &lt;code&gt;gh pr create&lt;/code&gt;, reusing the title/body the tool already generated. The thing the name always promised.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trust, made explicit.&lt;/strong&gt; The confidence score was computed but never &lt;em&gt;used&lt;/em&gt;. Now every PR is labelled &lt;code&gt;high-confidence&lt;/code&gt; / &lt;code&gt;needs-review&lt;/code&gt; / &lt;code&gt;low-confidence&lt;/code&gt;, &lt;code&gt;--min-confidence&lt;/code&gt; refuses to open a PR it doesn't trust, and &lt;code&gt;--auto-merge&lt;/code&gt; lets high-confidence fixes squash-merge once CI is green.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Off the laptop — a GitHub Action.&lt;/strong&gt; A composite Action so a failing build opens its own fix PR:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;   &lt;span class="pi"&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;FixForward&lt;/span&gt;
     &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;steps.tests.outcome == 'failure'&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;stackmasteraliza/fixforward@v1&lt;/span&gt;
     &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
       &lt;span class="na"&gt;min-confidence&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0.6"&lt;/span&gt;
       &lt;span class="na"&gt;github-token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.GITHUB_TOKEN }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The proof — tests + CI.&lt;/strong&gt; Added a unit suite for the confidence/labelling policy and the PR-report generator, plus CI on Python 3.9 / 3.11 / 3.13.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The hard part of "almost done" isn't the missing code — it's that the last 5% is the unglamorous, failure-prone part (auth, remotes, CI permissions) that's easy to defer forever. Finishing FixForward meant treating that last mile as the &lt;em&gt;whole point&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Experience with GitHub Copilot
&lt;/h2&gt;

&lt;p&gt;Two honest layers, no hand-waving.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Copilot &lt;em&gt;is&lt;/em&gt; the engine.&lt;/strong&gt; FixForward's fix generation is a call to &lt;code&gt;gh copilot&lt;/code&gt; — not a wrapper claim, it's the core of &lt;code&gt;copilot.py&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;cmd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gh&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;copilot&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-p&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                 &lt;span class="c1"&gt;# failure context + source files
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--allow-all-tools&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--add-dir&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;project_path&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--silent&lt;/span&gt;&lt;span class="sh"&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;It hands Copilot the classified failures and the relevant source and asks for the &lt;strong&gt;smallest&lt;/strong&gt; change that makes the suite pass. FixForward then applies it and &lt;em&gt;verifies&lt;/em&gt; it against real tests — so a Copilot fix only ships if it actually turns the build green.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Copilot helped me finish it, too.&lt;/strong&gt; The fiddliest part of the last mile was the &lt;code&gt;gh pr create&lt;/code&gt; plumbing — long markdown bodies, label creation, base/head branches, and the dozen ways it fails in CI (no remote, no auth, missing scopes) — plus making the parser robust to the agentic CLI's ANSI output and its habit of editing files directly. I worked through those edge cases with Copilot, then verified each path against real &lt;code&gt;gh&lt;/code&gt; behaviour.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built in Python. Fix engine powered by GitHub Copilot CLI. MIT licensed.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>githubchallenge</category>
      <category>githubcopilot</category>
      <category>python</category>
    </item>
    <item>
      <title>NotionOps AI — 4 AI Agents That Run Your Dev Workflow End-to-End</title>
      <dc:creator>Aliza Ali</dc:creator>
      <pubDate>Fri, 13 Mar 2026 14:35:46 +0000</pubDate>
      <link>https://dev.to/stackmasteraliza/notionops-ai-4-ai-agents-that-run-your-dev-workflow-end-to-end-11ki</link>
      <guid>https://dev.to/stackmasteraliza/notionops-ai-4-ai-agents-that-run-your-dev-workflow-end-to-end-11ki</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/notion-2026-03-04"&gt;Notion MCP Challenge&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;NotionOps AI&lt;/strong&gt; is an autonomous workflow engine that connects GitHub, OpenAI, and Notion into a single pipeline for software development teams.&lt;/p&gt;

&lt;p&gt;It runs &lt;strong&gt;4 specialized AI agents&lt;/strong&gt; that automate the daily dev lifecycle:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Agent&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Issue Triage&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Fetches open GitHub issues, classifies priority &amp;amp; type with AI, creates Notion tasks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;PR Review&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Reviews PR diffs, scores code quality 1-10, posts GitHub comments, creates Notion approval gates&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Sprint Planner&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Selects top tasks from backlog, estimates story points with AI, populates Notion sprint board&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Daily Report&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Aggregates sprint metrics, generates standup summary, saves report to Notion&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The agents chain together into a full pipeline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GitHub Issues/PRs → OpenAI Agents → Human Review in Notion → Notion Databases
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The dashboard provides real-time SSE streaming of agent activity, live stat cards, a kanban sprint board, and a full pipeline visualization.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key design decision&lt;/strong&gt;: The PR Review agent creates &lt;strong&gt;human-in-the-loop approval gates&lt;/strong&gt; in Notion when it finds low-quality PRs (score &amp;lt; 6). AI never takes destructive action without human sign-off.&lt;/p&gt;
&lt;h3&gt;
  
  
  Screenshots
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Dashboard&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy598k0bj55joeb7y84ho.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy598k0bj55joeb7y84ho.png" alt="Dashboard" width="800" height="404"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pipeline Running&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fllpw7wi0rqazw3fwq76u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fllpw7wi0rqazw3fwq76u.png" alt="Pipeline Running" width="800" height="398"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI Agent Output&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdzbuejofzeb8u01a3hwl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdzbuejofzeb8u01a3hwl.png" alt="AI Output" width="800" height="411"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tasks View&lt;/th&gt;
&lt;th&gt;Sprint Board&lt;/th&gt;
&lt;th&gt;Reports&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy718486i2ng6mfkox36o.png" alt="Tasks" width="800" height="405"&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvbboeyy5yuue6ado38v0.png" alt="Sprint" width="799" height="402"&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftlio4cugl0r9btgr0f1j.png" alt="Reports" width="800" height="408"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Notion Integration&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tasks Database&lt;/th&gt;
&lt;th&gt;Sprint Board&lt;/th&gt;
&lt;th&gt;Agent Reports&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7ng35ye51mm9fny9hyx5.png" alt="Notion Tasks" width="800" height="414"&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foq3iq127a414as4g7nx4.png" alt="Notion Sprint" width="800" height="422"&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqo0v9gx7gob6tl329p5v.png" alt="Notion Reports" width="800" height="596"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h2&gt;
  
  
  Video Demo
&lt;/h2&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/_Ksiqn_yz4s"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;
&lt;h2&gt;
  
  
  Show us the code
&lt;/h2&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/stackmasteraliza" rel="noopener noreferrer"&gt;
        stackmasteraliza
      &lt;/a&gt; / &lt;a href="https://github.com/stackmasteraliza/notionops-ai" rel="noopener noreferrer"&gt;
        notionops-ai
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Autonomous AI workflow engine — 4 Gemini-powered agents that triage GitHub issues, review PRs, plan sprints, and generate standups, all coordinated through Notion.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;NotionOps AI — Software Dev Pipeline That Runs Itself 🤖&lt;/h1&gt;
&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Notion MCP Challenge Submission&lt;/strong&gt; — An autonomous AI workflow engine that closes the loop between GitHub, OpenAI, human judgment, and Notion — in real time.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;What It Does&lt;/h2&gt;
&lt;/div&gt;

&lt;p&gt;Most dev teams have a painful daily ritual: check GitHub for new issues, triage them manually, review PRs, update Notion, plan the sprint, write the standup. &lt;strong&gt;NotionOps AI eliminates every step of that with four specialized AI agents&lt;/strong&gt; and a real-time neon dashboard.&lt;/p&gt;

&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;&lt;pre class="notranslate"&gt;&lt;code&gt;GitHub Issues/PRs  →  OpenAI Agents  →  Human Review Gate  →  Notion (Tasks · Sprint · Reports)
        ↑                                           |
        └───────────── feedback loop ───────────────┘
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;One click. The pipeline runs itself.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Demo Video&lt;/h3&gt;
&lt;/div&gt;

&lt;p&gt;&lt;a href="https://youtu.be/_Ksiqn_yz4s" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fstackmasteraliza%2Fnotionops-ai%2FHEAD%2Fscreenshots%2Fdashboard.png" alt="Watch Demo"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Four AI Agents&lt;/h2&gt;

&lt;/div&gt;

&lt;p&gt;&lt;/p&gt;&lt;div class="table-wrapper-paragraph"&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;table&gt;

&lt;thead&gt;

&lt;tr&gt;

&lt;th&gt;Agent&lt;/th&gt;

&lt;th&gt;What it does&lt;/th&gt;

&lt;th&gt;Output&lt;/th&gt;

&lt;/tr&gt;

&lt;/thead&gt;

&lt;tbody&gt;

&lt;tr&gt;

&lt;td&gt;🔍 &lt;strong&gt;Issue Triage&lt;/strong&gt;&lt;br&gt;
&lt;/td&gt;

&lt;td&gt;Fetches all open GitHub issues, classifies each as Bug/Feature/Issue with High/Medium/Low priority&lt;/td&gt;

&lt;td&gt;Notion task per issue + triage report&lt;/td&gt;

&lt;/tr&gt;

&lt;tr&gt;

&lt;td&gt;👀 &lt;strong&gt;PR Review&lt;/strong&gt;&lt;br&gt;
&lt;/td&gt;

&lt;td&gt;Reviews every open PR, scores code&lt;/td&gt;

&lt;/tr&gt;

&lt;/tbody&gt;

&lt;/table&gt;&lt;/div&gt;…&lt;p&gt;&lt;/p&gt;&lt;/div&gt;
&lt;br&gt;
  &lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/stackmasteraliza/notionops-ai" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;h2&gt;
  
  
  How I Used Notion MCP
&lt;/h2&gt;

&lt;p&gt;Notion is the &lt;strong&gt;central coordination layer&lt;/strong&gt; between AI agents and humans. Four databases drive the entire workflow:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Tasks Database&lt;/strong&gt;&lt;br&gt;
AI-triaged GitHub issues land here with structured metadata (priority, type, status, agent notes, GitHub URL). This is the single source of truth — the Sprint Planner reads from it, and humans can override AI classifications directly in Notion.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Approvals Database&lt;/strong&gt;&lt;br&gt;
When the PR Review agent finds a problematic PR, it creates an approval page with its recommendation. Team members review and approve/reject directly in Notion. This is the human-in-the-loop gate — downstream agents respect these decisions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Sprint Database&lt;/strong&gt;&lt;br&gt;
The Sprint Planner reads pending tasks from Notion, uses AI to estimate complexity and assign Fibonacci story points, then writes selected tasks back with Backlog/In Progress/Done status tracking. The dashboard renders this as a live kanban board.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Reports Database&lt;/strong&gt;&lt;br&gt;
Daily standup summaries are generated from sprint data and stored as rich-text report pages. This creates an automatic paper trail of team velocity and AI-generated insights over time.&lt;/p&gt;

&lt;p&gt;The key unlock: &lt;strong&gt;agents write structured data into Notion, humans make decisions in a familiar interface, and downstream agents read those decisions to continue the workflow.&lt;/strong&gt; No context switching between GitHub, AI tools, and project management — it all flows through Notion.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tech Stack
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Runtime&lt;/strong&gt;: Node.js + TypeScript&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI&lt;/strong&gt;: OpenAI via OpenRouter (structured JSON outputs)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;APIs&lt;/strong&gt;: Notion API, GitHub REST API (Octokit)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Server&lt;/strong&gt;: Express.js with Server-Sent Events&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Frontend&lt;/strong&gt;: Vanilla HTML/CSS/JS (zero framework dependencies)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  About Me
&lt;/h2&gt;

&lt;p&gt;I'm &lt;strong&gt;Aliza Ali&lt;/strong&gt; — a developer who loves building AI-powered tools that solve real workflow problems. If you liked this project, check out my other challenge submission:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/stackmasteraliza/fixforward" rel="noopener noreferrer"&gt;FixForward: One Command to Go from Broken Build to Ready-to-Merge PR&lt;/a&gt;&lt;/strong&gt; — A CLI autopilot that detects failing tests, classifies bugs, generates fixes via GitHub Copilot CLI, and creates verified PRs. One command. Three ecosystems. Zero manual patching.&lt;/p&gt;

&lt;p&gt;Want to know more? Visit &lt;a href="https://alizaali.com" rel="noopener noreferrer"&gt;alizaali.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>notionchallenge</category>
      <category>mcp</category>
      <category>ai</category>
    </item>
    <item>
      <title>Auto-Generate Swagger Docs in Laravel — Zero Annotations, Zero Config</title>
      <dc:creator>Aliza Ali</dc:creator>
      <pubDate>Wed, 18 Feb 2026 17:55:16 +0000</pubDate>
      <link>https://dev.to/stackmasteraliza/auto-generate-swagger-docs-in-laravel-zero-annotations-zero-config-hj</link>
      <guid>https://dev.to/stackmasteraliza/auto-generate-swagger-docs-in-laravel-zero-annotations-zero-config-hj</guid>
      <description>&lt;h2&gt;
  
  
  Writing Swagger Annotations Is Pain. What If You Didn't Have To?
&lt;/h2&gt;

&lt;p&gt;Be honest — how many times have you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Skipped API documentation because &lt;code&gt;@OA\Get&lt;/code&gt; annotations are a nightmare?&lt;/li&gt;
&lt;li&gt;Spent more time writing Swagger comments than actual business logic?&lt;/li&gt;
&lt;li&gt;Shipped an API with &lt;strong&gt;zero docs&lt;/strong&gt; because "we'll add them later" (you didn't)?&lt;/li&gt;
&lt;li&gt;Manually maintained a Postman collection that's always 3 endpoints behind?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What if your Laravel app could &lt;strong&gt;scan your controllers, read your FormRequest rules, and generate a complete Swagger UI automatically?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;No &lt;code&gt;@OA\&lt;/code&gt; annotations. No YAML files. No config. &lt;strong&gt;Just your existing code.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Introducing: Laravel API Response Builder
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;composer require stackmasteraliza/laravel-api-response
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;v4.7.0&lt;/strong&gt; | &lt;strong&gt;Laravel 10, 11 &amp;amp; 12&lt;/strong&gt; | &lt;strong&gt;PHP 8.1+&lt;/strong&gt; | &lt;strong&gt;MIT License&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://github.com/stackmasteraliza/laravel-api-response-builder" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; | &lt;a href="https://packagist.org/packages/stackmasteraliza/laravel-api-response" rel="noopener noreferrer"&gt;Packagist&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;The all-in-one Laravel API toolkit:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Auto-generated Swagger/OpenAPI docs — &lt;strong&gt;no annotations&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;One-click export to Postman &amp;amp; Insomnia&lt;/li&gt;
&lt;li&gt;Standardized JSON responses&lt;/li&gt;
&lt;li&gt;WebSocket testing built-in&lt;/li&gt;
&lt;li&gt;API versioning out of the box&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Zero configuration required.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Installation (30 Seconds)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;composer require stackmasteraliza/laravel-api-response
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. No config publishing. No migrations. No setup wizards.&lt;/p&gt;

&lt;p&gt;Your Swagger docs are already live at &lt;strong&gt;&lt;code&gt;/api-docs&lt;/code&gt;&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Auto-Generated Swagger Docs (The Main Event)
&lt;/h2&gt;

&lt;p&gt;This is why you're here. &lt;strong&gt;You don't write a single &lt;code&gt;@OA\&lt;/code&gt; annotation.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The package scans your controllers, detects response methods like &lt;code&gt;ApiResponse::success()&lt;/code&gt; and &lt;code&gt;ApiResponse::created()&lt;/code&gt;, reads your &lt;code&gt;FormRequest&lt;/code&gt; validation rules, and generates a &lt;strong&gt;complete OpenAPI 3.0 specification&lt;/strong&gt; — automatically.&lt;/p&gt;

&lt;p&gt;Visit &lt;code&gt;/api-docs&lt;/code&gt; and you get:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Beautiful, modern Swagger UI with &lt;strong&gt;dark/light theme toggle&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Every endpoint documented with correct HTTP methods&lt;/li&gt;
&lt;li&gt;Request body schemas &lt;strong&gt;auto-generated from your FormRequest rules&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Response schemas matching your actual response structure&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Try-it-out&lt;/strong&gt; functionality — test endpoints right from the browser&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  How It Detects Everything
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// The package sees this in your controller...&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;StoreUserRequest&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;validated&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;ApiResponse&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;created&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'User created'&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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ...reads your FormRequest rules...&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;StoreUserRequest&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;FormRequest&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;array&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="s1"&gt;'name'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'required|string|max:255'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'email'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'required|email|unique:users'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'password'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'required|min:8'&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Result:&lt;/strong&gt; A fully documented &lt;code&gt;POST&lt;/code&gt; endpoint in Swagger with request body schema, required fields, validation types, and a &lt;code&gt;201 Created&lt;/code&gt; response — all without you writing a single doc comment.&lt;/p&gt;

&lt;h3&gt;
  
  
  Want Even More Control? Use PHP Attributes
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Stackmasteraliza\ApiResponse\Attributes\ApiEndpoint&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Stackmasteraliza\ApiResponse\Attributes\ApiRequestBody&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;ApiEndpoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;summary&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'Create a new user'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'Register a new user account'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'Users'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;ApiRequestBody&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;properties&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'name'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'string'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'email'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'string'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'password'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'string'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;required&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'email'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'password'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;StoreUserRequest&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;JsonResponse&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;validated&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;ApiResponse&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;created&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'User created'&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;Attributes are &lt;strong&gt;optional&lt;/strong&gt; — use them only when you want to add summaries, descriptions, or override auto-detected schemas.&lt;/p&gt;




&lt;h2&gt;
  
  
  Export to Postman &amp;amp; Insomnia (One Click)
&lt;/h2&gt;

&lt;p&gt;From the Swagger UI, export your entire API documentation:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Format&lt;/th&gt;
&lt;th&gt;Use Case&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Postman&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Import directly into Postman&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Insomnia&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Import directly into Insomnia&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;JSON&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;CI/CD pipelines, SDK generation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;YAML&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Human-readable specs&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Your Postman collection is &lt;strong&gt;always in sync&lt;/strong&gt; with your code. No manual maintenance.&lt;/p&gt;




&lt;h2&gt;
  
  
  API Versioning in Swagger UI
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;API_DOCS_VERSIONING=true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The package auto-detects &lt;code&gt;api/v1/*&lt;/code&gt;, &lt;code&gt;api/v2/*&lt;/code&gt; patterns and adds a &lt;strong&gt;version switcher&lt;/strong&gt; in the Swagger UI. Each version gets its own OpenAPI spec. Your frontend team can browse docs for any version.&lt;/p&gt;




&lt;h2&gt;
  
  
  WebSocket Testing (Yes, In Swagger)
&lt;/h2&gt;

&lt;p&gt;Test WebSocket connections directly from the Swagger UI. Send messages, subscribe to channels, see real-time responses — with pre-built templates for Subscribe, Unsubscribe, Ping, and Client Events.&lt;/p&gt;

&lt;p&gt;No Postman. No wscat. Just click and test.&lt;/p&gt;




&lt;h2&gt;
  
  
  But Wait — It's Also a Complete Response Builder
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Facade Way
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Stackmasteraliza\ApiResponse\Facades\ApiResponse&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserController&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Controller&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$users&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;paginate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;ApiResponse&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;success&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Users retrieved'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;StoreUserRequest&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;validated&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;ApiResponse&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;created&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'User created'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;destroy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;User&lt;/span&gt; &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;ApiResponse&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;noContent&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Or Use the Trait
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Stackmasteraliza\ApiResponse\Traits\HasApiResponse&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserController&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Controller&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;HasApiResponse&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;index&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="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;success&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;paginate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s1"&gt;'Users retrieved'&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;Both give you perfectly consistent responses:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"status_code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"success"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&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;"Users retrieved"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"John Doe"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Jane Doe"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"meta"&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;"current_page"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"per_page"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"total"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"last_page"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"from"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"to"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"links"&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;"first"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://example.com/api/users?page=1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"next"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://example.com/api/users?page=2"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pagination metadata is &lt;strong&gt;automatically detected and included&lt;/strong&gt;. No extra code.&lt;/p&gt;




&lt;h2&gt;
  
  
  Every Error Response You'll Ever Need
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nc"&gt;ApiResponse&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;badRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Invalid input'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;           &lt;span class="c1"&gt;// 400&lt;/span&gt;
&lt;span class="nc"&gt;ApiResponse&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;unauthorized&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Token expired'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;          &lt;span class="c1"&gt;// 401&lt;/span&gt;
&lt;span class="nc"&gt;ApiResponse&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;forbidden&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Admin only'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;                &lt;span class="c1"&gt;// 403&lt;/span&gt;
&lt;span class="nc"&gt;ApiResponse&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;notFound&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'User not found'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;             &lt;span class="c1"&gt;// 404&lt;/span&gt;
&lt;span class="nc"&gt;ApiResponse&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;conflict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Email already exists'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;       &lt;span class="c1"&gt;// 409&lt;/span&gt;
&lt;span class="nc"&gt;ApiResponse&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;validationError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$errors&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;               &lt;span class="c1"&gt;// 422&lt;/span&gt;
&lt;span class="nc"&gt;ApiResponse&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;tooManyRequests&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Slow down'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;       &lt;span class="c1"&gt;// 429&lt;/span&gt;
&lt;span class="nc"&gt;ApiResponse&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;serverError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Something broke'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;          &lt;span class="c1"&gt;// 500&lt;/span&gt;
&lt;span class="nc"&gt;ApiResponse&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;serviceUnavailable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Back soon'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;         &lt;span class="c1"&gt;// 503&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Consistent structure every time:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"status_code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;422&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"success"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&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;"Validation failed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"errors"&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;"email"&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;"The email field is required."&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"password"&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;"The password must be at least 8 characters."&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;h2&gt;
  
  
  Exception Handling That Just Works
&lt;/h2&gt;

&lt;p&gt;Drop this into your &lt;code&gt;bootstrap/app.php&lt;/code&gt; (Laravel 11+):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Stackmasteraliza\ApiResponse\Exceptions\ApiExceptionHandler&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;withExceptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Exceptions&lt;/span&gt; &lt;span class="nv"&gt;$exceptions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$exceptions&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Throwable&lt;/span&gt; &lt;span class="nv"&gt;$e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Request&lt;/span&gt; &lt;span class="nv"&gt;$request&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="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ApiExceptionHandler&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$request&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;Now every exception — &lt;code&gt;ValidationException&lt;/code&gt;, &lt;code&gt;ModelNotFoundException&lt;/code&gt;, &lt;code&gt;AuthenticationException&lt;/code&gt; — returns a clean, consistent JSON response. No more HTML error pages in your API.&lt;/p&gt;




&lt;h2&gt;
  
  
  Testing Helpers
&lt;/h2&gt;

&lt;p&gt;Write expressive API tests:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getJson&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/api/users'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;assertApiSuccess&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
         &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;assertApiStatusCode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
         &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;assertApiMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Users retrieved'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
         &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;assertApiHasData&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
         &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;assertApiPaginated&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Customize Everything
&lt;/h2&gt;

&lt;p&gt;Publish the config and make it yours:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php artisan vendor:publish &lt;span class="nt"&gt;--tag&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;api-response-config
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Change response keys, pagination keys, default messages, theme colors, branding — everything is configurable.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// config/api-response.php&lt;/span&gt;
&lt;span class="s1"&gt;'keys'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s1"&gt;'success'&lt;/span&gt;     &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'success'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'message'&lt;/span&gt;     &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'message'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'data'&lt;/span&gt;        &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'data'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'errors'&lt;/span&gt;      &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'errors'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'meta'&lt;/span&gt;        &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'meta'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'status_code'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'status_code'&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;
  
  
  Extend With Custom Macros
&lt;/h2&gt;

&lt;p&gt;Need custom response types? The package is macroable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nc"&gt;ApiResponse&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;macro&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'banned'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$reason&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Account suspended'&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="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$reason&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;403&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Then use it anywhere&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;ApiResponse&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;banned&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Your account has been suspended'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Why Developers Are Switching
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Before&lt;/th&gt;
&lt;th&gt;After&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Writing &lt;code&gt;@OA\&lt;/code&gt; annotations for hours&lt;/td&gt;
&lt;td&gt;Auto-generated Swagger docs from code&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Postman collections always outdated&lt;/td&gt;
&lt;td&gt;One-click Postman/Insomnia export&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;No WebSocket documentation&lt;/td&gt;
&lt;td&gt;Built-in WebSocket tester in Swagger&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Inconsistent response formats&lt;/td&gt;
&lt;td&gt;One standardized JSON structure&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Custom exception handlers&lt;/td&gt;
&lt;td&gt;Built-in error handling&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Copy-paste response code&lt;/td&gt;
&lt;td&gt;One-liner fluent methods&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Quick Start Recap
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1. Install&lt;/span&gt;
composer require stackmasteraliza/laravel-api-response

&lt;span class="c"&gt;# 2. Use in controllers&lt;/span&gt;
&lt;span class="c"&gt;# (Facade or Trait — your choice)&lt;/span&gt;

&lt;span class="c"&gt;# 3. Visit /api-docs&lt;/span&gt;
&lt;span class="c"&gt;# Your documentation is already there&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Supports Laravel 10, 11, and 12 | PHP 8.1+&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GitHub&lt;/strong&gt;: &lt;a href="https://github.com/stackmasteraliza/laravel-api-response" rel="noopener noreferrer"&gt;stackmasteraliza/laravel-api-response&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Packagist&lt;/strong&gt;: &lt;a href="https://packagist.org/packages/stackmasteraliza/laravel-api-response" rel="noopener noreferrer"&gt;stackmasteraliza/laravel-api-response&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Let's Connect
&lt;/h2&gt;

&lt;p&gt;If this package saves you time, give it a star on GitHub and share it with your team.&lt;/p&gt;

&lt;p&gt;Got questions, ideas, or feature requests? Drop a comment below or open an issue on GitHub.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Follow me for more Laravel packages and PHP tips:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Dev.to&lt;/strong&gt;: &lt;a href="https://dev.to/stackmasteraliza"&gt;@stackmasteraliza&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub&lt;/strong&gt;: &lt;a href="https://github.com/stackmasteraliza" rel="noopener noreferrer"&gt;@stackmasteraliza&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Happy coding!&lt;br&gt;
— &lt;strong&gt;Aliza Ali&lt;/strong&gt; (&lt;strong&gt;&lt;a class="mentioned-user" href="https://dev.to/stackmasteraliza"&gt;@stackmasteraliza&lt;/a&gt;&lt;/strong&gt;)&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>php</category>
      <category>swagger</category>
      <category>api</category>
    </item>
    <item>
      <title>FixForward: One Command to Go from Broken Build to Ready-to-Merge PR</title>
      <dc:creator>Aliza Ali</dc:creator>
      <pubDate>Sun, 15 Feb 2026 14:54:11 +0000</pubDate>
      <link>https://dev.to/stackmasteraliza/fixforward-one-command-to-go-from-broken-build-to-ready-to-merge-pr-167g</link>
      <guid>https://dev.to/stackmasteraliza/fixforward-one-command-to-go-from-broken-build-to-ready-to-merge-pr-167g</guid>
      <description>&lt;p&gt;&lt;strong&gt;GitHub Copilot CLI Challenge Submission&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This is my submission for the &lt;a href="https://dev.to/challenges/github"&gt;GitHub Copilot CLI Challenge&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;Every developer knows the pain: your build breaks, your tests fail, and suddenly you're down a rabbit hole of stack traces, file-hopping, and manual patching. I've been there one too many times, so I built &lt;strong&gt;FixForward&lt;/strong&gt; — a CLI autopilot that takes you from broken tests to a verified, ready-to-merge PR in a single command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;fixforward
fixforward run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. One command.&lt;/p&gt;

&lt;p&gt;FixForward detects your test framework, runs the suite, parses the failures, classifies each one, asks &lt;strong&gt;GitHub Copilot CLI&lt;/strong&gt; to generate a minimal fix, applies it on a safe branch, re-runs your tests to verify the fix actually works, and generates a PR description with a confidence score. The entire pipeline is automated, and the entire tool is powered by Copilot CLI under the hood.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsryrio20awbl94f8qbbg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsryrio20awbl94f8qbbg.png" alt="FixForward Cover" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;Here is FixForward running against a real broken Python project. It detects the failing test, classifies the bug as an assertion failure, generates a one-line fix via Copilot CLI, patches the file, re-runs the tests, and reports 95% confidence — all without leaving the terminal:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fstackmasteraliza%2Ffixforward%2Fmain%2Fassets%2Fdemo.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fstackmasteraliza%2Ffixforward%2Fmain%2Fassets%2Fdemo.gif" alt="FixForward Demo" width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Pipeline: How It Works
&lt;/h2&gt;

&lt;p&gt;FixForward is not a wrapper around a single Copilot prompt. It's a &lt;strong&gt;full incident response pipeline&lt;/strong&gt; with 8 distinct stages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;tests fail → parse output → classify failure → Copilot generates fix
     → apply on safe branch → re-run tests → confidence score → PR report
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;1. Detect&lt;/strong&gt; — Scans for &lt;code&gt;pytest.ini&lt;/code&gt;, &lt;code&gt;package.json&lt;/code&gt;, or &lt;code&gt;Cargo.toml&lt;/code&gt; to identify your ecosystem automatically. No config files, no setup.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Run Tests&lt;/strong&gt; — Executes your test suite (&lt;code&gt;pytest&lt;/code&gt;, &lt;code&gt;npm test&lt;/code&gt;, or &lt;code&gt;cargo test&lt;/code&gt;) and captures full raw output.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Parse&lt;/strong&gt; — Custom parsers extract individual failures with file paths, line numbers, error messages, and tracebacks. This isn't regex on the whole blob — there are dedicated parsers for pytest, Jest/Mocha, and cargo test that understand each format's quirks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Classify&lt;/strong&gt; — Categorizes every failure using heuristic pattern matching:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;th&gt;Examples&lt;/th&gt;
&lt;th&gt;Confidence&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;syntax_error&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;SyntaxError&lt;/code&gt;, &lt;code&gt;IndentationError&lt;/code&gt;, &lt;code&gt;Unexpected token&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;95%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;dependency&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;ModuleNotFoundError&lt;/code&gt;, &lt;code&gt;Cannot find module&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;90%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;api_change&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;AttributeError&lt;/code&gt;, &lt;code&gt;TypeError&lt;/code&gt; (wrong args)&lt;/td&gt;
&lt;td&gt;85%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;assertion&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;AssertionError&lt;/code&gt;, &lt;code&gt;assert_eq!&lt;/code&gt;, &lt;code&gt;expect().toEqual()&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;85%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;env_mismatch&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Version conflicts, missing commands&lt;/td&gt;
&lt;td&gt;80%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;lint&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Flake8, ESLint, Clippy warnings&lt;/td&gt;
&lt;td&gt;75%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;flaky_test&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Timeouts, connection refused, intermittent&lt;/td&gt;
&lt;td&gt;60%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;5. Generate Patch&lt;/strong&gt; — Sends structured failure context + relevant source files to GitHub Copilot CLI (&lt;code&gt;gh copilot -p&lt;/code&gt;) for a minimal fix. Copilot reads the actual code, understands the bug, and generates the smallest possible change.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. Apply&lt;/strong&gt; — Creates a &lt;code&gt;fixforward/auto-*&lt;/code&gt; branch, writes the patched files, and commits. Your working branch is &lt;strong&gt;never touched&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7. Verify&lt;/strong&gt; — Re-runs the entire test suite on the fix branch and computes a before/after confidence score.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;8. Report&lt;/strong&gt; — Generates a Markdown PR title and body with what changed, why, and the verification results.&lt;/p&gt;

&lt;h2&gt;
  
  
  Screenshots
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Full Autopilot Run
&lt;/h3&gt;

&lt;p&gt;The complete pipeline from broken tests to verified fix:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1arg61oo7gdwrqd5iawp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1arg61oo7gdwrqd5iawp.png" alt="Full run" width="780" height="546"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Diagnose Mode
&lt;/h3&gt;

&lt;p&gt;Use &lt;code&gt;fixforward diagnose&lt;/code&gt; to inspect failures without applying any changes:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz975p1dfw226kkx5maf3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz975p1dfw226kkx5maf3.png" alt="Diagnose" width="780" height="582"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Dependency Detection
&lt;/h3&gt;

&lt;p&gt;FixForward recognizes when the fix isn't a code change but a missing package:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0kw6tfiuzivyvn2ni6xu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0kw6tfiuzivyvn2ni6xu.png" alt="Dependency" width="780" height="690"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Multi-Ecosystem: Node.js Support
&lt;/h3&gt;

&lt;p&gt;Same pipeline, different ecosystem. Jest failures parsed and classified automatically:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg9iuwjhsm3y6wwcod5xw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg9iuwjhsm3y6wwcod5xw.png" alt="Node.js" width="780" height="618"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Three Ecosystems, One Interface
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Ecosystem&lt;/th&gt;
&lt;th&gt;Test Command&lt;/th&gt;
&lt;th&gt;What Gets Parsed&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Python&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;pytest --tb=long -v&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Failures, tracebacks, assertion details, collection errors&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Node.js&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;npm test&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Jest and Mocha output, suite-level failures, missing modules&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Rust&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;cargo test&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Panics, &lt;code&gt;assert_eq!&lt;/code&gt; failures, test summaries&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;I didn't just support one language and call it a day. Each ecosystem has its own parser that understands the specific output format — pytest tracebacks look nothing like Jest failures, and cargo test panics are their own thing entirely. FixForward handles all of them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Safety First
&lt;/h2&gt;

&lt;p&gt;I spent a lot of time thinking about what could go wrong. When you're auto-applying code patches, you better not destroy someone's working tree. FixForward has several layers of protection:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Never touches your working branch&lt;/strong&gt; — all fixes go on &lt;code&gt;fixforward/auto-*&lt;/code&gt; branches&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stashes dirty state&lt;/strong&gt; — uncommitted changes are saved and restored on rollback&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Patch preview&lt;/strong&gt; — see the exact diff before confirming&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dry-run mode&lt;/strong&gt; — &lt;code&gt;fixforward run --dry-run&lt;/code&gt; diagnoses without touching anything&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One-command rollback&lt;/strong&gt; — &lt;code&gt;fixforward rollback&lt;/code&gt; undoes everything cleanly&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;State persistence&lt;/strong&gt; — rollback info stored at &lt;code&gt;~/.fixforward/state.json&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I wanted this to be a tool you could trust running in your repo without hesitation.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Experience with GitHub Copilot CLI
&lt;/h2&gt;

&lt;p&gt;Copilot CLI is not just a helper in this project — it &lt;strong&gt;is&lt;/strong&gt; the engine. FixForward uses &lt;code&gt;gh copilot -p&lt;/code&gt; at its core to generate the actual code fixes. Here's what that looks like internally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gh copilot &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;"I have a python project with failing tests.
  The test test_divide in test_app.py fails with AssertionError:
  assert 3.333 == 3. Generate a minimal fix..."&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--allow-all-tools&lt;/span&gt; &lt;span class="nt"&gt;--add-dir&lt;/span&gt; ./project &lt;span class="nt"&gt;--silent&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Copilot reads the source files through &lt;code&gt;--add-dir&lt;/code&gt;, understands the test context, and generates the smallest code change. FixForward then parses Copilot's response (which can come in several formats — code blocks, file headers, inline diffs), extracts the patched files, and applies them.&lt;/p&gt;

&lt;p&gt;Building the Copilot response parser was one of the trickier parts. Copilot doesn't always respond in the same format, so I built multiple parsing strategies:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Fenced code blocks&lt;/strong&gt; with &lt;code&gt;FILE:&lt;/code&gt; markers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Filename headers&lt;/strong&gt; (### file.js, &lt;strong&gt;file.js&lt;/strong&gt;, backtick file.js) followed by code blocks&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Language-tagged blocks&lt;/strong&gt; matched to project files&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fuzzy file matching&lt;/strong&gt; against the project tree (skipping node_modules)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If one strategy fails, the next one kicks in. This makes FixForward resilient to Copilot's varying output formats.&lt;/p&gt;

&lt;p&gt;I also used Copilot CLI extensively during development — for scaffolding parsers, debugging edge cases in Jest output handling, and figuring out the right subprocess patterns for capturing test output across platforms.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try It Yourself
&lt;/h2&gt;

&lt;p&gt;The repo includes ready-made broken demo projects:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/stackmasteraliza/fixforward.git
&lt;span class="nb"&gt;cd &lt;/span&gt;fixforward

&lt;span class="c"&gt;# Python: division bug (a / b should be a // b)&lt;/span&gt;
fixforward run &lt;span class="nt"&gt;--path&lt;/span&gt; demo/broken_python

&lt;span class="c"&gt;# Node.js: truncation bug ("..." becomes "..")&lt;/span&gt;
fixforward diagnose &lt;span class="nt"&gt;--path&lt;/span&gt; demo/broken_node

&lt;span class="c"&gt;# Rust: off-by-one in clamp()&lt;/span&gt;
fixforward diagnose &lt;span class="nt"&gt;--path&lt;/span&gt; demo/broken_rust

&lt;span class="c"&gt;# Safe mode: see the diagnosis without changing anything&lt;/span&gt;
fixforward run &lt;span class="nt"&gt;--path&lt;/span&gt; demo/broken_python &lt;span class="nt"&gt;--dry-run&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Install from PyPI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;fixforward
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Architecture
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fixforward/
├── cli.py          # argparse CLI: run, diagnose, rollback
├── detector.py     # Ecosystem detection + test runner
├── classifier.py   # Regex heuristic failure classification
├── copilot.py      # GitHub Copilot CLI integration
├── patcher.py      # Safe branch creation + file patching
├── verifier.py     # Test re-run + confidence scoring
├── reporter.py     # PR title/body generation
├── display.py      # Rich-based terminal UI
├── state.py        # Rollback state persistence
└── parsers/
    ├── pytest_parser.py   # pytest output parser
    ├── npm_parser.py      # Jest/Mocha output parser
    └── cargo_parser.py    # cargo test output parser
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Only one dependency:&lt;/strong&gt; &lt;a href="https://github.com/Textualize/rich" rel="noopener noreferrer"&gt;&lt;code&gt;rich&lt;/code&gt;&lt;/a&gt;. Everything else is Python standard library.&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;PyPI:&lt;/strong&gt; &lt;a href="https://pypi.org/project/fixforward/" rel="noopener noreferrer"&gt;fixforward on PyPI&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Repo:&lt;/strong&gt; &lt;a href="https://github.com/stackmasteraliza/fixforward" rel="noopener noreferrer"&gt;stackmasteraliza/fixforward on GitHub&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Thanks for reading! I'd love to hear your feedback — especially if you try it on your own broken tests.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>githubchallenge</category>
      <category>cli</category>
      <category>githubcopilot</category>
    </item>
    <item>
      <title>I built Spotify Wrapped for Git using GitHub Copilot CLI</title>
      <dc:creator>Aliza Ali</dc:creator>
      <pubDate>Sun, 15 Feb 2026 06:37:15 +0000</pubDate>
      <link>https://dev.to/stackmasteraliza/i-built-spotify-wrapped-for-git-using-github-copilot-cli-10lj</link>
      <guid>https://dev.to/stackmasteraliza/i-built-spotify-wrapped-for-git-using-github-copilot-cli-10lj</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/github-2026-01-21"&gt;GitHub Copilot CLI Challenge&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;git-wrapped&lt;/strong&gt; — Spotify Wrapped, but for your Git history.&lt;/p&gt;

&lt;p&gt;Every December, Spotify tells you what kind of listener you are. I wanted the same thing for coding. What if your terminal could tell you that you're a Night Owl who codes at 2 AM, or a Code Surgeon who deletes more than they write?&lt;/p&gt;

&lt;p&gt;git-wrapped is a Python CLI tool that analyzes any Git repository and generates a beautiful, colorful terminal report. No browser, no dashboard, no account — just run a command and see your year in code.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Full Feature Set
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The Numbers&lt;/strong&gt; — Total commits, files changed, lines added/deleted, net line impact, active days&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Activity Heatmap&lt;/strong&gt; — A GitHub-style contribution calendar rendered entirely in your terminal using Unicode block characters&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;When You Code&lt;/strong&gt; — Hour-of-day and day-of-week bar charts showing your coding rhythm&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Top Files&lt;/strong&gt; — Leaderboard of your most frequently modified files&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Languages&lt;/strong&gt; — Programming language breakdown with percentage bars&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Streaks &amp;amp; Records&lt;/strong&gt; — Longest streak, current streak, busiest single day, best month&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Coder DNA&lt;/strong&gt; — A personality assessment based on your commit patterns: Night Owl, Early Bird, Weekend Warrior, Streak Master, Feature Machine, Code Surgeon, or Balanced Builder&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fun Facts&lt;/strong&gt; — Holiday commits, longest/shortest commit messages, contributor count, productivity stats&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compare Mode&lt;/strong&gt; — Year-over-year comparison (&lt;code&gt;--compare 2024 2025&lt;/code&gt;) with delta percentages and personality shift tracking&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Share Card&lt;/strong&gt; — A copy-pasteable plain text card for sharing on social media (&lt;code&gt;--share&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JSON Export&lt;/strong&gt; — Dump raw stats as JSON for further analysis or integrations&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Why I Built This
&lt;/h3&gt;

&lt;p&gt;I spend a lot of time in the terminal. I wanted something that celebrates the work developers do — not just the PRs that get merged, but the late-night debugging sessions, the weekend side projects, the 14-day commit streaks that nobody notices.&lt;/p&gt;

&lt;p&gt;git-wrapped makes that visible. And it's fun.&lt;/p&gt;




&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;GitHub repo:&lt;/strong&gt; &lt;a href="https://github.com/stackmasteraliza/git-wrapped" rel="noopener noreferrer"&gt;stackmasteraliza/git-wrapped&lt;/a&gt;&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Try it yourself
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Install from PyPI&lt;/span&gt;
pip &lt;span class="nb"&gt;install &lt;/span&gt;git-wrapped

&lt;span class="c"&gt;# Or install from source&lt;/span&gt;
git clone https://github.com/stackmasteraliza/git-wrapped.git
&lt;span class="nb"&gt;cd &lt;/span&gt;git-wrapped
pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;

&lt;span class="c"&gt;# Run on any repo&lt;/span&gt;
git-wrapped

&lt;span class="c"&gt;# Analyze a specific year&lt;/span&gt;
git-wrapped &lt;span class="nt"&gt;--year&lt;/span&gt; 2025

&lt;span class="c"&gt;# Compare two years&lt;/span&gt;
git-wrapped &lt;span class="nt"&gt;--compare&lt;/span&gt; 2024 2025

&lt;span class="c"&gt;# Generate a share card&lt;/span&gt;
git-wrapped &lt;span class="nt"&gt;--share&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Tech Stack
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Python 3.8+&lt;/strong&gt; — Core language&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rich&lt;/strong&gt; — Terminal formatting (panels, tables, progress bars, styled text)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Git log parsing&lt;/strong&gt; — Custom parser using &lt;code&gt;--numstat&lt;/code&gt; and &lt;code&gt;--format&lt;/code&gt; for file-level stats&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero external API calls&lt;/strong&gt; — Everything runs locally, your data never leaves your machine&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One external dependency. That's it.&lt;/p&gt;




&lt;h2&gt;
  
  
  My Experience with GitHub Copilot CLI
&lt;/h2&gt;

&lt;p&gt;GitHub Copilot CLI was my pair programmer for this entire project. Instead of tab-switching between Stack Overflow, Git docs, and Rich documentation, I stayed in my terminal and asked Copilot directly.&lt;/p&gt;

&lt;p&gt;Here's exactly how I used it at each stage of development:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Understanding git log format
&lt;/h3&gt;

&lt;p&gt;The first challenge was figuring out how to extract structured data from &lt;code&gt;git log&lt;/code&gt;. I asked Copilot CLI to explain the &lt;code&gt;--format&lt;/code&gt; flags, and it gave me a clear breakdown of every placeholder — &lt;code&gt;%H&lt;/code&gt; for hash, &lt;code&gt;%an&lt;/code&gt; for author name, &lt;code&gt;%aI&lt;/code&gt; for ISO date, &lt;code&gt;%s&lt;/code&gt; for subject line — plus how &lt;code&gt;--numstat&lt;/code&gt; adds per-file insertion/deletion stats and how &lt;code&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/code&gt; delimiters can separate commits for reliable parsing.&lt;/p&gt;

&lt;p&gt;This saved me from trial-and-error with Git's dense documentation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foizmomnfqcp7gxupzvqq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foizmomnfqcp7gxupzvqq.png" alt="Copilot CLI — git log format" width="798" height="251"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Calculating commit streaks
&lt;/h3&gt;

&lt;p&gt;Computing the longest daily commit streak was trickier than expected. I needed to handle gaps, deduplicate dates, and track both the longest historical streak and the current active one.&lt;/p&gt;

&lt;p&gt;I asked Copilot CLI for approaches and it gave me multiple solutions — a Python version using &lt;code&gt;datetime.timedelta&lt;/code&gt; to detect consecutive days, and even a Bash one-liner. I adapted the Python approach directly into my analyzer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Copilot helped me arrive at this pattern
&lt;/span&gt;&lt;span class="n"&gt;sorted_dates&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sorted&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="n"&gt;commit_dates&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;streak&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_streak&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&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;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sorted_dates&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
    &lt;span class="nf"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sorted_dates&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;sorted_dates&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="n"&gt;days&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;streak&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="n"&gt;max_streak&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_streak&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;streak&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;streak&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fljyu5xivaulqoz9t50ez.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fljyu5xivaulqoz9t50ez.png" alt="Copilot CLI — streak calculation" width="800" height="520"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Grouping commits by hour of day
&lt;/h3&gt;

&lt;p&gt;For the "When You Code" charts, I needed to count commits grouped by hour. Copilot CLI gave me multiple shell approaches — from simple &lt;code&gt;cut&lt;/code&gt; pipelines to &lt;code&gt;awk&lt;/code&gt; loops — which helped me understand the data shape before writing the Python version.&lt;/p&gt;

&lt;p&gt;Seeing the shell approach first made the Python implementation obvious.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frp83vg71d5xhv428gfeo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frp83vg71d5xhv428gfeo.png" alt="Copilot CLI — hourly commits" width="799" height="331"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Rendering the terminal heatmap
&lt;/h3&gt;

&lt;p&gt;This was the hardest visual to get right. A GitHub-style contribution heatmap, but in the terminal, using colored Unicode blocks.&lt;/p&gt;

&lt;p&gt;I asked Copilot CLI how to render a colored heatmap using Python's Rich library, and it showed me two approaches:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;Table-based approach&lt;/strong&gt; using Rich's &lt;code&gt;Table&lt;/code&gt; with colored cells&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;block character approach&lt;/strong&gt; mapping values to &lt;code&gt;█&lt;/code&gt; characters with ANSI colors&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I ended up combining both — using Rich's &lt;code&gt;Console&lt;/code&gt; for color management and block characters for the grid cells:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;colors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bright_black&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dark_green&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;green&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bright_green&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;char&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;█&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;week&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;weeks&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;day&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;intensity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_intensity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;week&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;day&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;[&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;colors&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;intensity&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;]&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;char&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;[/]&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn9g9japdx7p9j0xahkac.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn9g9japdx7p9j0xahkac.png" alt="Copilot CLI — heatmap rendering" width="800" height="520"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The Impact
&lt;/h3&gt;

&lt;p&gt;Without Copilot CLI, each of these problems would have meant opening a browser, scanning docs, finding a relevant example, adapting it, and testing. That cycle breaks flow.&lt;/p&gt;

&lt;p&gt;With Copilot CLI, the cycle was: ask a question → get a working answer → adapt and move on. I stayed in my terminal the entire time. The cognitive load dropped and I could focus on making the output feel delightful instead of fighting implementation details.&lt;/p&gt;

&lt;p&gt;It felt less like using a tool and more like pair programming with someone who has every man page memorized.&lt;/p&gt;




&lt;h2&gt;
  
  
  Challenges &amp;amp; Discoveries
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The heatmap was the hardest part.&lt;/strong&gt; Terminals don't have pixels — you're working with a character grid where each "cell" is a text character. Getting the heatmap to look like GitHub's contribution graph required careful math: mapping 52 weeks × 7 days into a grid, choosing the right Unicode block characters, and picking green shades that look good across different terminal themes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Personality detection was surprisingly fun.&lt;/strong&gt; The "Coder DNA" feature assigns you a personality based on your commit patterns. Calibrating the thresholds (e.g., "30% of commits after 10 PM = Night Owl") required running it against a bunch of real repos to make sure the labels felt accurate and fun, not arbitrary.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rich is incredible.&lt;/strong&gt; Python's Rich library does the heavy lifting for all the terminal visuals — panels, tables, progress bars, colored text. It's the reason git-wrapped looks polished without needing a web UI.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Makes This Special
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;For developers:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;See your coding habits visualized in seconds&lt;/li&gt;
&lt;li&gt;Discover patterns you didn't know you had (turns out I code most on Thursdays)&lt;/li&gt;
&lt;li&gt;A fun conversation starter in team standups&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;For teams:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Run it on shared repos to see team-wide patterns&lt;/li&gt;
&lt;li&gt;Compare year-over-year to track how the team's rhythm evolves&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;For the community:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fully open source — fork it, extend it, make it yours&lt;/li&gt;
&lt;li&gt;Single dependency — easy to install anywhere&lt;/li&gt;
&lt;li&gt;Privacy-first — everything runs locally, no data leaves your machine&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Built with Python, Rich, and a lot of late-night commits. Powered by GitHub Copilot CLI.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub Repository:&lt;/strong&gt; &lt;a href="https://github.com/stackmasteraliza/git-wrapped" rel="noopener noreferrer"&gt;stackmasteraliza/git-wrapped&lt;/a&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>githubchallenge</category>
      <category>cli</category>
      <category>githubcopilot</category>
    </item>
  </channel>
</rss>
