<?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: Panic Engineer</title>
    <description>The latest articles on DEV Community by Panic Engineer (@panicengineer).</description>
    <link>https://dev.to/panicengineer</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%2F3694249%2Fb19dfb68-f6d7-4a91-9307-2aebb59f3b16.png</url>
      <title>DEV Community: Panic Engineer</title>
      <link>https://dev.to/panicengineer</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/panicengineer"/>
    <language>en</language>
    <item>
      <title>RepoDeck: Automating the One README I Keep Forgetting to Update</title>
      <dc:creator>Panic Engineer</dc:creator>
      <pubDate>Sat, 08 Aug 2026 16:30:43 +0000</pubDate>
      <link>https://dev.to/panicengineer/repodeck-automating-the-one-readme-i-keep-forgetting-to-update-26l2</link>
      <guid>https://dev.to/panicengineer/repodeck-automating-the-one-readme-i-keep-forgetting-to-update-26l2</guid>
      <description>&lt;p&gt;I build a lot of projects.&lt;/p&gt;

&lt;p&gt;Some are web applications, some are developer tools, some are experiments, and lately I've also been exploring things like browser games and YouTube Playables.&lt;/p&gt;

&lt;p&gt;There is one small problem that keeps appearing after every project:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;My GitHub Profile README becomes outdated.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The project exists. The repository exists. The code is there.&lt;/p&gt;

&lt;p&gt;But my profile still has an old list of projects.&lt;/p&gt;

&lt;p&gt;Updating it manually isn't difficult. That's actually what makes the problem interesting.&lt;/p&gt;

&lt;p&gt;It's just repetitive enough that I eventually forget to do it.&lt;/p&gt;

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

&lt;p&gt;&lt;a href="https://github.com/kaushik0010/repodeck" rel="noopener noreferrer"&gt;RepoDeck on GitHub&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F39n2n33kkgkulzhhrlbn.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F39n2n33kkgkulzhhrlbn.png" alt="RepoDeck Workflow Diagram" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;The workflow usually looks something like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Build a project.&lt;/li&gt;
&lt;li&gt;Push it to GitHub.&lt;/li&gt;
&lt;li&gt;Maybe deploy it somewhere.&lt;/li&gt;
&lt;li&gt;Eventually remember that the project should also appear on your profile.&lt;/li&gt;
&lt;li&gt;Open the profile README.&lt;/li&gt;
&lt;li&gt;Manually add/update the project.&lt;/li&gt;
&lt;li&gt;Repeat.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The actual coding isn't the problem.&lt;/p&gt;

&lt;p&gt;The problem is &lt;strong&gt;keeping two representations of the same project synchronized&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Project Repository
       │
       │  manually remember
       ▼
GitHub Profile README
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the more projects you have, the more annoying this becomes.&lt;/p&gt;

&lt;h2&gt;
  
  
  What About Existing README Tools?
&lt;/h2&gt;

&lt;p&gt;There are already tools and generators that can help create GitHub profile READMEs.&lt;/p&gt;

&lt;p&gt;But while looking at the problem from my own perspective, I wasn't looking for another README generator.&lt;/p&gt;

&lt;p&gt;I didn't want to maintain a separate portfolio dataset.&lt;/p&gt;

&lt;p&gt;I didn't want to manually enter every project into another configuration file.&lt;/p&gt;

&lt;p&gt;And I didn't want the profile README to become the source of truth for my projects.&lt;/p&gt;

&lt;p&gt;The repositories already contain the information.&lt;/p&gt;

&lt;p&gt;So I decided to reverse the relationship:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The project repository should be the source of truth. The profile README should be the generated view.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That became the core idea behind RepoDeck.&lt;/p&gt;




&lt;h2&gt;
  
  
  The RepoDeck Workflow
&lt;/h2&gt;

&lt;p&gt;Instead of manually editing the profile README, each participating repository contains a small metadata file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.github/project.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;RepoDeck periodically discovers repositories containing that file, validates their metadata, generates the project showcase, and synchronizes it into the profile README.&lt;/p&gt;

&lt;p&gt;The resulting workflow is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Project Repository
       │
       ▼
.github/project.md
       │
       ▼
RepoDeck
       │
       ├── Discover
       ├── Parse
       ├── Validate
       ├── Render
       └── Inject
       │
       ▼
GitHub Profile README
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important part is that &lt;strong&gt;the profile README is no longer manually maintained for the generated section&lt;/strong&gt;.&lt;/p&gt;




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

&lt;p&gt;I deliberately kept RepoDeck small and separated the responsibilities instead of putting everything into one script.&lt;/p&gt;

&lt;p&gt;The current structure looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src/
├── index.ts
├── config.ts
├── github/
│   ├── githubClient.ts
│   ├── githubContent.ts
│   └── githubDiscovery.ts
├── markdown/
│   ├── injector.ts
│   ├── parser.ts
│   ├── renderer.ts
│   └── schema.ts
├── services/
│   ├── profileReadme.ts
│   ├── projectDiscovery.ts
│   ├── projectLoader.ts
│   └── readmeUpdater.ts
└── utils/
    ├── fileReader.ts
    └── remoteLoader.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This isn't because having more folders automatically makes code better.&lt;/p&gt;

&lt;p&gt;It is because the responsibilities are genuinely different.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Discovery
&lt;/h2&gt;

&lt;p&gt;RepoDeck first asks GitHub for the authenticated user's repositories.&lt;/p&gt;

&lt;p&gt;It doesn't have a hardcoded list of projects.&lt;/p&gt;

&lt;p&gt;Instead, repositories opt into RepoDeck by containing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.github/project.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That means adding a new project doesn't require modifying RepoDeck itself.&lt;/p&gt;

&lt;p&gt;Just add the metadata file to the repository.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Metadata as the Contract
&lt;/h2&gt;

&lt;p&gt;The metadata file uses YAML frontmatter.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;RepoDeck&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;An automated CI/CD pipeline for keeping a GitHub Profile README synchronized with project repositories.&lt;/span&gt;
&lt;span class="na"&gt;category&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Automation&lt;/span&gt;
&lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Active&lt;/span&gt;
&lt;span class="na"&gt;featured&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="na"&gt;tech&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;TypeScript&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;Node.js&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;Octokit&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;Zod&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;GitHub Actions&lt;/span&gt;
&lt;span class="na"&gt;repo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://github.com/kaushik0010/repodeck&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important design decision here is that the schema only contains information useful for the profile presentation.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;title&lt;/li&gt;
&lt;li&gt;description&lt;/li&gt;
&lt;li&gt;category&lt;/li&gt;
&lt;li&gt;status&lt;/li&gt;
&lt;li&gt;featured&lt;/li&gt;
&lt;li&gt;tech&lt;/li&gt;
&lt;li&gt;repository URL&lt;/li&gt;
&lt;li&gt;live URL&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I intentionally didn't try to put everything about a project into this file.&lt;/p&gt;

&lt;p&gt;It's &lt;strong&gt;portfolio metadata&lt;/strong&gt;, not another README.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Runtime Validation
&lt;/h2&gt;

&lt;p&gt;The metadata comes from repositories outside the core application.&lt;/p&gt;

&lt;p&gt;That means I don't trust it blindly.&lt;/p&gt;

&lt;p&gt;RepoDeck uses Zod to validate the parsed metadata against a strict schema.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GitHub
  │
  ▼
project.md
  │
  ▼
YAML Frontmatter
  │
  ▼
Zod Validation
  │
  ├── Valid → Continue
  │
  └── Invalid → Fail
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This was one of the things I wanted to learn properly while building the project:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TypeScript types alone don't protect you from malformed external data.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The TypeScript type describes what my application expects.&lt;/p&gt;

&lt;p&gt;Zod validates what actually arrived.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Rendering
&lt;/h2&gt;

&lt;p&gt;Once the metadata has been validated, RepoDeck turns it into Markdown.&lt;/p&gt;

&lt;p&gt;I initially used a Markdown table.&lt;/p&gt;

&lt;p&gt;Then I changed my mind.&lt;/p&gt;

&lt;p&gt;Tables looked structured, but they became increasingly awkward once I considered:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;mobile GitHub clients&lt;/li&gt;
&lt;li&gt;descriptions of different lengths&lt;/li&gt;
&lt;li&gt;projects with or without links&lt;/li&gt;
&lt;li&gt;games without repositories&lt;/li&gt;
&lt;li&gt;projects with only a live URL&lt;/li&gt;
&lt;li&gt;future categories&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So I switched to a showcase-style layout:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;### ⭐ RepoDeck&lt;/span&gt;

An automated GitHub Profile README synchronization tool.
&lt;span class="p"&gt;
-&lt;/span&gt; 🏷️ Category: Automation
&lt;span class="p"&gt;-&lt;/span&gt; 🛠 Tech: TypeScript · Node.js · Octokit
&lt;span class="p"&gt;-&lt;/span&gt; 🔗 GitHub Repository
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is also why I separated the renderer from the rest of the pipeline.&lt;/p&gt;

&lt;p&gt;The data acquisition layer doesn't care how the project is displayed.&lt;/p&gt;

&lt;p&gt;If I ever wanted a different presentation format, I could replace the renderer without rewriting GitHub discovery.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Injection With Explicit Boundaries
&lt;/h2&gt;

&lt;p&gt;RepoDeck doesn't rewrite the entire profile README.&lt;/p&gt;

&lt;p&gt;That would be dangerous.&lt;/p&gt;

&lt;p&gt;Instead, I added explicit boundaries:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- REPODECK:START --&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- REPODECK:END --&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;RepoDeck only owns the content between these markers.&lt;/p&gt;

&lt;p&gt;Everything outside them belongs to the user.&lt;/p&gt;

&lt;p&gt;The injector also validates that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;both markers exist&lt;/li&gt;
&lt;li&gt;neither marker is duplicated&lt;/li&gt;
&lt;li&gt;the markers are in the correct order&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This gives RepoDeck a clearly defined write boundary.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Profile README

┌───────────────────────────────┐
│ Personal introduction         │
│ Skills                        │
│ Other sections                │
│                               │
│ &amp;lt;!-- REPODECK:START --&amp;gt;       │
│                               │
│   RepoDeck generated content  │
│                               │
│ &amp;lt;!-- REPODECK:END --&amp;gt;         │
│                               │
│ Contact information           │
└───────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That separation was important to me.&lt;/p&gt;

&lt;p&gt;Automation should know exactly what it owns.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Idempotency
&lt;/h2&gt;

&lt;p&gt;This is probably one of my favorite parts of the project.&lt;/p&gt;

&lt;p&gt;RepoDeck doesn't create a GitHub commit every time it runs.&lt;/p&gt;

&lt;p&gt;Before updating the README, it compares:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;current README
        vs
generated README
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If they're identical:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;No changes detected
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and the process stops.&lt;/p&gt;

&lt;p&gt;If they're different, only then does RepoDeck update the file.&lt;/p&gt;

&lt;p&gt;This gives us:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Same input → same output → no unnecessary commit.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That matters because the workflow runs automatically every day.&lt;/p&gt;

&lt;p&gt;Without change detection, an automation that runs daily could create a lot of useless Git history.&lt;/p&gt;




&lt;h2&gt;
  
  
  7. GitHub SHA and Optimistic Concurrency
&lt;/h2&gt;

&lt;p&gt;While implementing the update step, I also learned about something I hadn't previously thought much about: &lt;strong&gt;GitHub's file SHA&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;When RepoDeck fetches the profile README, GitHub gives us the current file SHA.&lt;/p&gt;

&lt;p&gt;When updating the file, RepoDeck sends that SHA back.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RepoDeck reads README
       │
       ▼
README + SHA
       │
       │
       ├── Nobody changed it
       │        ↓
       │     Update
       │
       └── Someone changed it
                ↓
             Conflict
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This prevents RepoDeck from blindly overwriting a newer version of the README.&lt;/p&gt;

&lt;p&gt;It's a small detail, but it introduced me to the idea of &lt;strong&gt;optimistic concurrency control&lt;/strong&gt; in a practical situation.&lt;/p&gt;




&lt;h2&gt;
  
  
  8. Configuration
&lt;/h2&gt;

&lt;p&gt;I also didn't want application-wide policies scattered throughout the codebase.&lt;/p&gt;

&lt;p&gt;So RepoDeck has a deliberately tiny configuration module:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PROJECT_METADATA_PATH
INCLUDE_PRIVATE
INCLUDE_ARCHIVED
COMMIT_MESSAGE
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;I intentionally avoided turning &lt;code&gt;config.ts&lt;/code&gt; into a dumping ground for every constant in the application.&lt;/p&gt;

&lt;p&gt;For example, the README injection markers remain inside the injector because they're part of the injector's responsibility.&lt;/p&gt;

&lt;p&gt;This was another useful architectural lesson:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Not every constant belongs in a global configuration file.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  9. GitHub Actions
&lt;/h2&gt;

&lt;p&gt;Once the local pipeline worked, I wanted RepoDeck to actually solve the original problem.&lt;/p&gt;

&lt;p&gt;So I moved the execution into GitHub Actions.&lt;/p&gt;

&lt;p&gt;The workflow runs daily and can also be triggered manually.&lt;/p&gt;

&lt;p&gt;The production pipeline is roughly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GitHub Actions
      │
      ├── Checkout
      ├── Setup Node 22
      ├── npm ci
      ├── Typecheck
      ├── Build
      └── Run RepoDeck
                │
                ▼
          GitHub API
                │
                ▼
       Profile README
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I also added:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;concurrency protection&lt;/li&gt;
&lt;li&gt;a five-minute job timeout&lt;/li&gt;
&lt;li&gt;deterministic &lt;code&gt;npm ci&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;TypeScript type checking&lt;/li&gt;
&lt;li&gt;a production build&lt;/li&gt;
&lt;li&gt;least-privilege permissions for the workflow runner&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Authentication Was Another Learning Experience
&lt;/h2&gt;

&lt;p&gt;One of the questions I initially had was:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why not just use GitHub's built-in &lt;code&gt;GITHUB_TOKEN&lt;/code&gt;?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Because RepoDeck isn't only modifying the repository where the workflow runs.&lt;/p&gt;

&lt;p&gt;It needs to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;discover my repositories&lt;/li&gt;
&lt;li&gt;read project metadata from them&lt;/li&gt;
&lt;li&gt;read my profile README&lt;/li&gt;
&lt;li&gt;update my profile README&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The built-in Actions token wasn't the right identity for this architecture.&lt;/p&gt;

&lt;p&gt;So for my current personal setup, RepoDeck uses a &lt;strong&gt;fine-grained Personal Access Token&lt;/strong&gt; stored as a GitHub Actions repository secret.&lt;/p&gt;

&lt;p&gt;The important distinction is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GitHub Secret
     │
     ▼
REPODECK_PAT
     │
     ▼
Workflow environment
     │
     ▼
GITHUB_TOKEN
     │
     ▼
Octokit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The actual token never lives in the repository source code.&lt;/p&gt;

&lt;p&gt;And because this is an open-source repository, that separation is essential.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I Learned Building It
&lt;/h2&gt;

&lt;p&gt;The interesting part of RepoDeck wasn't really the final feature.&lt;/p&gt;

&lt;p&gt;It was everything I had to understand to make a relatively small automation reliable.&lt;/p&gt;

&lt;p&gt;Some of the things I learned while building it:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Separate data acquisition from presentation
&lt;/h3&gt;

&lt;p&gt;GitHub discovery shouldn't know how the README looks.&lt;/p&gt;

&lt;p&gt;The renderer shouldn't know where projects came from.&lt;/p&gt;

&lt;p&gt;That separation made changing the table into a showcase layout almost trivial.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. External data needs runtime validation
&lt;/h3&gt;

&lt;p&gt;TypeScript can't validate data coming from GitHub.&lt;/p&gt;

&lt;p&gt;Zod can.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Automation needs idempotency
&lt;/h3&gt;

&lt;p&gt;If a scheduled process produces the same result, it shouldn't create another commit.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Write boundaries matter
&lt;/h3&gt;

&lt;p&gt;Automation should never assume it owns an entire document when it only needs one section.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Concurrency isn't only a distributed-systems problem
&lt;/h3&gt;

&lt;p&gt;The GitHub SHA mechanism made optimistic concurrency something I could see and implement in a very concrete way.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. CI catches things local development can hide
&lt;/h3&gt;

&lt;p&gt;One of the first workflow runs failed because TypeScript's strict checks caught an unused API response.&lt;/p&gt;

&lt;p&gt;Another failure came from my own project metadata missing a required &lt;code&gt;title&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Those failures were useful.&lt;/p&gt;

&lt;p&gt;They proved the CI pipeline was actually doing its job.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why I Kept RepoDeck Small
&lt;/h2&gt;

&lt;p&gt;There was a temptation to keep adding features.&lt;/p&gt;

&lt;p&gt;Multiple renderers.&lt;/p&gt;

&lt;p&gt;More metadata fields.&lt;/p&gt;

&lt;p&gt;Custom templates.&lt;/p&gt;

&lt;p&gt;A plugin system.&lt;/p&gt;

&lt;p&gt;A GitHub App.&lt;/p&gt;

&lt;p&gt;A marketplace action.&lt;/p&gt;

&lt;p&gt;But I stopped.&lt;/p&gt;

&lt;p&gt;The current version already solves the problem I originally wanted to solve:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Keep my GitHub Profile README's project section synchronized with my actual repositories without manually maintaining two sources of truth.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I don't want to invent a roadmap just to make the project look bigger.&lt;/p&gt;

&lt;p&gt;If I eventually discover a real problem worth solving, I'll build it then.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Result
&lt;/h2&gt;

&lt;p&gt;Today the workflow is simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Build a project
      ↓
Add/update .github/project.md
      ↓
Push
      ↓
RepoDeck discovers it
      ↓
RepoDeck validates the metadata
      ↓
RepoDeck generates the showcase
      ↓
RepoDeck updates the profile README
      ↓
Done
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And because the process is scheduled, I don't have to remember to update my profile after every project.&lt;/p&gt;

&lt;p&gt;That's exactly what I wanted.&lt;/p&gt;

&lt;p&gt;Not a huge platform.&lt;/p&gt;

&lt;p&gt;Not a SaaS product.&lt;/p&gt;

&lt;p&gt;Just a small piece of automation that removes a repetitive task from my own development workflow.&lt;/p&gt;




&lt;h2&gt;
  
  
  Open Source
&lt;/h2&gt;

&lt;p&gt;RepoDeck is open source, and I'd genuinely love to see how other developers would approach the same problem.&lt;/p&gt;

&lt;p&gt;If you maintain a GitHub profile with multiple projects, I'd especially be interested in hearing how you currently keep your project showcase updated.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Repository:&lt;/strong&gt; &lt;a href="https://github.com/kaushik0010/repodeck" rel="noopener noreferrer"&gt;github.com/kaushik0010/repodeck&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you find the idea useful, a ⭐ or fork is always appreciated.&lt;/p&gt;

&lt;p&gt;And if you see an architectural decision you'd approach differently, I'd be even more interested in that.&lt;/p&gt;




&lt;h3&gt;
  
  
  Final Thought
&lt;/h3&gt;

&lt;p&gt;The project started with a very small annoyance:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"I keep forgetting to update my GitHub README."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It ended up teaching me about runtime validation, separation of concerns, idempotency, optimistic concurrency, GitHub APIs, authentication, secrets, CI/CD, scheduled automation, and designing systems around explicit ownership boundaries.&lt;/p&gt;

&lt;p&gt;That's probably my favorite kind of project:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;one that solves a small real problem while forcing me to learn something substantially bigger.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>github</category>
      <category>developertools</category>
      <category>automation</category>
    </item>
    <item>
      <title>What Building and Deploying a Real Production App Taught Me Beyond Tutorials</title>
      <dc:creator>Panic Engineer</dc:creator>
      <pubDate>Mon, 18 May 2026 09:39:53 +0000</pubDate>
      <link>https://dev.to/panicengineer/what-building-and-deploying-a-real-production-app-taught-me-beyond-tutorials-57bk</link>
      <guid>https://dev.to/panicengineer/what-building-and-deploying-a-real-production-app-taught-me-beyond-tutorials-57bk</guid>
      <description>&lt;p&gt;Recently I finished building and deploying a production real-estate platform for a non-technical client using:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Next.js&lt;/li&gt;
&lt;li&gt;MongoDB&lt;/li&gt;
&lt;li&gt;Cloudinary&lt;/li&gt;
&lt;li&gt;Vercel&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The project itself wasn’t “massive” from an enterprise perspective, but it taught me something important:&lt;/p&gt;

&lt;p&gt;real-world software engineering starts where tutorials usually stop.&lt;/p&gt;

&lt;p&gt;A lot of my learning during this project had very little to do with writing React components or building CRUD APIs.&lt;/p&gt;

&lt;p&gt;Instead, most of the valuable lessons came from:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;debugging production issues&lt;/li&gt;
&lt;li&gt;handling infrastructure constraints&lt;/li&gt;
&lt;li&gt;deployment behavior&lt;/li&gt;
&lt;li&gt;SEO/discoverability&lt;/li&gt;
&lt;li&gt;UX/business tradeoffs&lt;/li&gt;
&lt;li&gt;understanding how technical decisions affect real users&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Difference Between “Building Features” and “Shipping Products”
&lt;/h2&gt;

&lt;p&gt;Before this project, most of my learning came from:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;tutorials&lt;/li&gt;
&lt;li&gt;isolated side projects&lt;/li&gt;
&lt;li&gt;local development environments&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In those environments:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;APIs usually work&lt;/li&gt;
&lt;li&gt;deployment is ignored&lt;/li&gt;
&lt;li&gt;infra is abstracted away&lt;/li&gt;
&lt;li&gt;assets are small&lt;/li&gt;
&lt;li&gt;performance doesn’t matter much&lt;/li&gt;
&lt;li&gt;search engines don’t exist&lt;/li&gt;
&lt;li&gt;users don’t exist&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Production projects behave very differently.&lt;/p&gt;

&lt;p&gt;Once real users, real hosting platforms and real business requirements enter the picture, software engineering becomes much more holistic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Vercel Payload Limits and Large Image Uploads
&lt;/h2&gt;

&lt;p&gt;One of the first interesting issues I encountered happened during property creation.&lt;/p&gt;

&lt;p&gt;The admin could upload multiple high-quality property images. Initially everything worked fine with smaller images, but once I started testing realistic production scenarios with larger files, uploads suddenly started failing.&lt;/p&gt;

&lt;p&gt;At first I suspected:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cloudinary upload issues&lt;/li&gt;
&lt;li&gt;MongoDB problems&lt;/li&gt;
&lt;li&gt;request handling bugs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of those were actually the problem.&lt;/p&gt;

&lt;p&gt;The real issue was that the total request payload exceeded Vercel’s limits before the upload logic even completed.&lt;/p&gt;

&lt;p&gt;This was one of those moments where I realized:&lt;br&gt;
production platforms have infrastructure constraints you rarely encounter in tutorials.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Fix&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Instead of blindly increasing limits or changing infrastructure immediately, I implemented client-side image optimization/compression before submission.&lt;/p&gt;

&lt;p&gt;The result:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;drastically reduced payload size&lt;/li&gt;
&lt;li&gt;significantly improved reliability&lt;/li&gt;
&lt;li&gt;better upload performance&lt;/li&gt;
&lt;li&gt;reduced bandwidth usage&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This was also a reminder that optimization is often about understanding constraints rather than prematurely overengineering systems.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cloudinary PDF Preview Behavior
&lt;/h2&gt;

&lt;p&gt;Another interesting issue involved property PDFs.&lt;/p&gt;

&lt;p&gt;The client wanted uploaded PDFs to preview directly in browser rather than forcing users/admins to download them.&lt;/p&gt;

&lt;p&gt;Initially, Cloudinary-hosted PDFs were downloading instead of previewing.&lt;/p&gt;

&lt;p&gt;I tried:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;URL manipulation approaches&lt;/li&gt;
&lt;li&gt;response behavior tweaks&lt;/li&gt;
&lt;li&gt;different delivery methods&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Eventually I discovered that using Google Docs Viewer as a lightweight preview layer solved the issue cleanly.&lt;/p&gt;

&lt;p&gt;The implementation itself wasn’t complicated, but the interesting part was the debugging process:&lt;br&gt;
understanding browser behavior, file delivery behavior and how external services interact together.&lt;/p&gt;

&lt;p&gt;Deployment Is a Different Skillset Entirely&lt;/p&gt;

&lt;p&gt;Before this project I underestimated how much engineering exists beyond writing application code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deployment introduced an entirely different category of problems:
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;DNS propagation&lt;/li&gt;
&lt;li&gt;custom domain setup&lt;/li&gt;
&lt;li&gt;SSL behavior&lt;/li&gt;
&lt;li&gt;Vercel deployment restrictions&lt;/li&gt;
&lt;li&gt;GitHub contributor behavior&lt;/li&gt;
&lt;li&gt;infrastructure caching&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One interesting moment happened during custom domain setup.&lt;/p&gt;

&lt;p&gt;Some devices/networks showed the updated Vercel deployment immediately, while others still displayed the default GoDaddy placeholder page.&lt;/p&gt;

&lt;p&gt;Initially it looked like a broken deployment.&lt;/p&gt;

&lt;p&gt;The actual issue:&lt;br&gt;
DNS propagation and ISP-level caching differences.&lt;/p&gt;

&lt;p&gt;This taught me that deployment debugging often involves understanding distributed systems behavior more than application logic itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  SEO Is More Technical Than I Expected
&lt;/h2&gt;

&lt;p&gt;This was also the first time I handled SEO/discoverability setup seriously for a production project.&lt;/p&gt;

&lt;p&gt;That included:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;sitemap.xml&lt;/li&gt;
&lt;li&gt;robots.txt&lt;/li&gt;
&lt;li&gt;OpenGraph metadata&lt;/li&gt;
&lt;li&gt;canonical URLs&lt;/li&gt;
&lt;li&gt;Google Search Console&lt;/li&gt;
&lt;li&gt;indexing requests&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One thing I realized quickly:&lt;br&gt;
deploying a website does not mean search engines automatically understand or discover it properly.&lt;/p&gt;

&lt;p&gt;There’s an entire layer of infrastructure and communication happening between:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;your application&lt;/li&gt;
&lt;li&gt;crawlers&lt;/li&gt;
&lt;li&gt;social platforms&lt;/li&gt;
&lt;li&gt;indexing systems&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Even after submitting the sitemap and requesting indexing, Google still needed time to crawl and process the website.&lt;/p&gt;

&lt;p&gt;At first many pages simply showed:&lt;br&gt;
“URL is unknown to Google.”&lt;/p&gt;

&lt;p&gt;That experience helped me understand that SEO is less about “gaming rankings” and more about:&lt;br&gt;
clarity, discoverability and technical communication.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lighthouse Scores vs Real UX
&lt;/h2&gt;

&lt;p&gt;Another useful lesson came from performance optimization.&lt;/p&gt;

&lt;p&gt;I tested the project using Lighthouse/PageSpeed and the scores were actually quite good overall.&lt;/p&gt;

&lt;p&gt;But I also realized something important:&lt;br&gt;
blindly chasing perfect benchmark scores can become counterproductive.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
real-estate platforms naturally depend heavily on high-quality imagery because trust and presentation matter.&lt;/p&gt;

&lt;p&gt;Aggressively compressing everything or removing visual quality purely to maximize Lighthouse scores can negatively affect actual user perception.&lt;/p&gt;

&lt;p&gt;That changed how I think about optimization.&lt;/p&gt;

&lt;p&gt;Good engineering is often about balancing tradeoffs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;performance&lt;/li&gt;
&lt;li&gt;aesthetics&lt;/li&gt;
&lt;li&gt;usability&lt;/li&gt;
&lt;li&gt;responsiveness&lt;/li&gt;
&lt;li&gt;business requirements&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;rather than maximizing a single metric at all costs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mobile-First Thinking Changed A Lot
&lt;/h2&gt;

&lt;p&gt;This project also changed how I think about mobile UX.&lt;/p&gt;

&lt;p&gt;Real-estate traffic is heavily mobile-first.&lt;/p&gt;

&lt;p&gt;While testing the production deployment on actual devices, I realized that many interfaces that felt “clean” on desktop became frustrating on smaller screens.&lt;/p&gt;

&lt;p&gt;That affected decisions around:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;spacing&lt;/li&gt;
&lt;li&gt;modal behavior&lt;/li&gt;
&lt;li&gt;tap targets&lt;/li&gt;
&lt;li&gt;navigation&lt;/li&gt;
&lt;li&gt;lead capture flow&lt;/li&gt;
&lt;li&gt;scrolling experience&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Responsive design is not only about making layouts fit smaller screens.&lt;br&gt;
It’s about reducing friction during real interactions.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI Tools Accelerated Development, But Didn’t Replace Engineering
&lt;/h2&gt;

&lt;p&gt;AI tools definitely accelerated parts of development during this project.&lt;/p&gt;

&lt;p&gt;They were extremely useful for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;speeding up implementation&lt;/li&gt;
&lt;li&gt;generating boilerplate&lt;/li&gt;
&lt;li&gt;exploring approaches&lt;/li&gt;
&lt;li&gt;debugging directionally&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But the hardest parts still required engineering judgment:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;identifying root causes&lt;/li&gt;
&lt;li&gt;understanding constraints&lt;/li&gt;
&lt;li&gt;evaluating tradeoffs&lt;/li&gt;
&lt;li&gt;adapting architecture&lt;/li&gt;
&lt;li&gt;debugging infrastructure issues&lt;/li&gt;
&lt;li&gt;balancing UX and business goals&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The “generate code” layer was only one part of shipping the product.&lt;/p&gt;

&lt;h2&gt;
  
  
  Technical Decisions Directly Affect Business Outcomes
&lt;/h2&gt;

&lt;p&gt;One of the biggest mindset shifts during this project was realizing how directly technical decisions influence business outcomes.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;improving upload reliability improved admin experience&lt;/li&gt;
&lt;li&gt;better metadata improved social previews&lt;/li&gt;
&lt;li&gt;SEO setup improved discoverability&lt;/li&gt;
&lt;li&gt;mobile UX decisions reduced friction&lt;/li&gt;
&lt;li&gt;lead capture flow decisions affected conversions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The project gradually stopped feeling like:&lt;br&gt;
“building features”&lt;/p&gt;

&lt;p&gt;and started feeling more like:&lt;br&gt;
“designing systems that solve business problems reliably.”&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Project Was Valuable
&lt;/h2&gt;

&lt;p&gt;The most valuable part of this project wasn’t the final deployment itself.&lt;/p&gt;

&lt;p&gt;It was learning that real-world engineering involves much more than writing code:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;debugging unfamiliar problems&lt;/li&gt;
&lt;li&gt;understanding infrastructure behavior&lt;/li&gt;
&lt;li&gt;communicating with non-technical stakeholders&lt;/li&gt;
&lt;li&gt;balancing tradeoffs&lt;/li&gt;
&lt;li&gt;improving reliability&lt;/li&gt;
&lt;li&gt;thinking about users/business impact&lt;/li&gt;
&lt;li&gt;shipping systems end-to-end&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I think that’s why production projects teach differently from tutorials.&lt;/p&gt;

&lt;p&gt;They force you to move beyond:&lt;br&gt;
“Does this work locally?”&lt;/p&gt;

&lt;p&gt;and start thinking:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What happens in production?&lt;/li&gt;
&lt;li&gt;What happens under constraints?&lt;/li&gt;
&lt;li&gt;What happens for real users?&lt;/li&gt;
&lt;li&gt;What happens after deployment?&lt;/li&gt;
&lt;li&gt;What breaks unexpectedly?&lt;/li&gt;
&lt;li&gt;How do we recover/debug systematically?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Still learning, but this project definitely changed how I think about software engineering.&lt;/p&gt;

</description>
      <category>learning</category>
      <category>nextjs</category>
      <category>softwareengineering</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Building SOC-AI: Sub-Second Security Triage with Groq &amp; Kestra</title>
      <dc:creator>Panic Engineer</dc:creator>
      <pubDate>Tue, 06 Jan 2026 12:38:00 +0000</pubDate>
      <link>https://dev.to/panicengineer/building-soc-ai-sub-second-security-triage-with-groq-kestra-51</link>
      <guid>https://dev.to/panicengineer/building-soc-ai-sub-second-security-triage-with-groq-kestra-51</guid>
      <description>&lt;h2&gt;
  
  
  The Motivation: Solving Alert Fatigue 🛡️
&lt;/h2&gt;

&lt;p&gt;Security teams are drowning in logs. I built &lt;strong&gt;SOC-AI&lt;/strong&gt; solo during the &lt;strong&gt;AI Agents Assemble Hackathon&lt;/strong&gt; (hosted on @wemakedevs) to prove that AI can handle the "boring" triage while humans keep control.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Triage:&lt;/strong&gt; Groq (Llama-3.3) for 500ms log analysis.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Orchestration:&lt;/strong&gt; Kestra for semi-autonomous remediation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Frontend:&lt;/strong&gt; Next.js 15 (Live on Vercel!).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Technical Deep Dive – Reliable AI &amp;amp; Orchestration
&lt;/h2&gt;

&lt;p&gt;One of the biggest challenges in AI agents is &lt;strong&gt;hallucination&lt;/strong&gt;. In a security context, a "hallucinated" IP address or action could be a disaster. I solved this using two core patterns:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Forcing Structured Output with Zod &amp;amp; Groq
&lt;/h3&gt;

&lt;p&gt;I didn't just ask the AI to "analyze the log." I defined a strict contract using Zod. This ensures that the high-speed Llama-3.3 model on Groq returns a precise JSON object that my backend can trust.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Define the Triage Schema&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;SecurityTriageSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;severity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enum&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;low&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;medium&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;high&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;critical&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
  &lt;span class="na"&gt;threat_type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Categorization like Brute Force, SQLi, etc.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;action_suggested&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enum&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;block_ip&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;disable_user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;monitor&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
  &lt;span class="na"&gt;reasoning&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Forcing Groq to adhere to the schema&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;chatCompletion&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;groq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;completions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;llama-3.3-70b-versatile&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;response_format&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;json_object&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="c1"&gt;// Crucial for reliable JSON&lt;/span&gt;
  &lt;span class="na"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;system&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;You are a SOC Triage Agent. Output ONLY JSON.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Analyze this log: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;rawLogData&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Semi-Autonomous Remediation via Kestra
&lt;/h3&gt;

&lt;p&gt;Trust is everything in security. Instead of letting the AI run wild, I built a &lt;strong&gt;Human-in-the-Loop&lt;/strong&gt; system.&lt;/p&gt;

&lt;p&gt;When the AI suggests an action (like &lt;code&gt;block_ip&lt;/code&gt;), it appears on my &lt;strong&gt;Next.js Dashboard&lt;/strong&gt;. Only after I click &lt;strong&gt;"Approve"&lt;/strong&gt; does the backend trigger a &lt;strong&gt;Kestra Workflow&lt;/strong&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Why Kestra?
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Retries:&lt;/strong&gt; If the Firewall API is down, Kestra handles the retry logic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Audit Trail:&lt;/strong&gt; Every action taken is logged visually in the Kestra UI.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Separation of Concerns:&lt;/strong&gt; My Next.js app handles the UI, while Kestra handles the heavy infrastructure automation.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Result
&lt;/h2&gt;

&lt;p&gt;By combining &lt;strong&gt;Groq's speed&lt;/strong&gt; with &lt;strong&gt;Kestra's reliability&lt;/strong&gt;, SOC-AI can triage a log and present a remediation plan to an analyst in less than a second.&lt;/p&gt;

&lt;p&gt;This project was a solo build, and it taught me that the future of AI isn't just about the "chat" - it's about &lt;strong&gt;orchestration&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Links &amp;amp; Demo
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/kaushik0010/soc-ai" rel="noopener noreferrer"&gt;https://github.com/kaushik0010/soc-ai&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Video Walkthrough:&lt;/strong&gt; &lt;a href="https://youtu.be/LbuHXiPznJE" rel="noopener noreferrer"&gt;https://youtu.be/LbuHXiPznJE&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What are your thoughts on semi-autonomous security? Would you trust an AI to suggest your firewall rules? Let's talk in the comments!&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>cybersecurity</category>
      <category>javascript</category>
      <category>hackathon</category>
    </item>
  </channel>
</rss>
