<?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: An Nguyễn Hoàng</title>
    <description>The latest articles on DEV Community by An Nguyễn Hoàng (@an_nguynhong_0c0496258).</description>
    <link>https://dev.to/an_nguynhong_0c0496258</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%2F3570170%2F5005d481-a27d-4a44-b72d-2d2de9e7d9a1.png</url>
      <title>DEV Community: An Nguyễn Hoàng</title>
      <link>https://dev.to/an_nguynhong_0c0496258</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/an_nguynhong_0c0496258"/>
    <language>en</language>
    <item>
      <title>Your coding agent can read a GitHub repo's vibe, not just its README</title>
      <dc:creator>An Nguyễn Hoàng</dc:creator>
      <pubDate>Thu, 05 Nov 2026 19:20:39 +0000</pubDate>
      <link>https://dev.to/an_nguynhong_0c0496258/your-coding-agent-can-read-a-github-repos-vibe-not-just-its-readme-545m</link>
      <guid>https://dev.to/an_nguynhong_0c0496258/your-coding-agent-can-read-a-github-repos-vibe-not-just-its-readme-545m</guid>
      <description>&lt;p&gt;Ask Claude or Cursor "is this GitHub repo still maintained?" and it does the only thing it can from a chat box: fetch the README and guess. The README is marketing. It won't tell you that one file has been rewritten nine times in the last twenty commits, that CI is mostly green-by-skip, or that three of the top five contributors stopped pushing a year ago.&lt;/p&gt;

&lt;p&gt;That gap — &lt;em&gt;the repo's behaviour versus the repo's description&lt;/em&gt; — is exactly the kind of thing an MCP tool can close, because it needs live API calls the model can't make on its own. So I built a small remote MCP server that does six read-only things against any public repo and hands the agent back clean structured JSON.&lt;/p&gt;

&lt;h2&gt;
  
  
  The six tools
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;get_repo_overview&lt;/code&gt; — stars, forks, language, license, topics, last push&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;list_recent_prs&lt;/code&gt; — recent PRs with author, state, merge dates&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;get_contributors&lt;/code&gt; — who actually commits, and how much&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;get_hot_files&lt;/code&gt; — the files churning hardest right now&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;get_ci_status&lt;/code&gt; — sampled check-run health on the default branch&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;get_dependencies&lt;/code&gt; — declared deps&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No local install. It's a hosted &lt;a href="https://apify.com/cg_nguyen/github-repo-intel-mcp" rel="noopener noreferrer"&gt;Standby Actor on Apify&lt;/a&gt; speaking Streamable-HTTP MCP, so you point your client at one URL and it's there.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the output actually looks like
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;get_hot_files&lt;/code&gt; on &lt;code&gt;cli/cli&lt;/code&gt;, last 20 commits:&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;"branch"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"trunk"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"commits_scanned"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"top_files"&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;"path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"go.mod"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"changes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;9&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;"path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"go.sum"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"changes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;9&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;"path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"git/client_test.go"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"changes"&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="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;"path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"pkg/cmd/api/api.go"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"changes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&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;An agent can now say "dependency churn is high, the API command and the git client are the active surfaces" instead of paraphrasing the README.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;get_ci_status&lt;/code&gt; is the one I was most careful with, because a naive version lies. It paginates, so I return what was actually sampled, not a total dressed up as a verdict:&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;"ref"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"trunk"&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;784&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sampled_count"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"totals_sampled"&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;"success"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;46&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"skipped"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;54&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;&lt;code&gt;sampled_count&lt;/code&gt; and &lt;code&gt;totals_sampled&lt;/code&gt; are deliberate field names — if the tool only looked at the first 100 of 784 checks, it says so, and the model doesn't get to tell you "CI is 46% passing" as if it counted all of them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wiring it into a client
&lt;/h2&gt;

&lt;p&gt;It's a remote Streamable-HTTP server, so in a client that supports remote MCP you give it the URL and an &lt;code&gt;Authorization: Bearer &amp;lt;apify-token&amp;gt;&lt;/code&gt; header:&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;"mcpServers"&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;"github-repo-intel"&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;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://cg-nguyen--github-repo-intel-mcp.apify.actor/mcp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"headers"&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;"Authorization"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Bearer apify_api_..."&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;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;Then &lt;code&gt;tools/list&lt;/code&gt; returns all six and the agent self-discovers them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two things I'd flag if you build one of these
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Pass a GitHub token.&lt;/strong&gt; Unauthenticated GitHub is 60 requests/hour shared across everyone hitting the same IP. Every tool takes an optional &lt;code&gt;github_token&lt;/code&gt; and with one you get your own 5,000/hr. The fan-out tools (&lt;code&gt;get_hot_files&lt;/code&gt;, &lt;code&gt;get_contributors&lt;/code&gt;) read per-commit, so without a token you'll rate-limit yourself in under ten calls. Read-only &lt;code&gt;public_repo&lt;/code&gt; scope is enough.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Charge on success, never on entry.&lt;/strong&gt; The server bills per tool call, and the charge fires &lt;em&gt;after&lt;/em&gt; the upstream GitHub call succeeds, right before the result is returned. If GitHub 404s or rate-limits, no charge. Charging at handler entry would bill people for failures, which is both wrong and a fast way to get delisted.&lt;/p&gt;

&lt;p&gt;It's live here if you want to point an agent at it: &lt;strong&gt;&lt;a href="https://apify.com/cg_nguyen/github-repo-intel-mcp" rel="noopener noreferrer"&gt;https://apify.com/cg_nguyen/github-repo-intel-mcp&lt;/a&gt;&lt;/strong&gt;. First calls are cheap; the whole point is that your agent spends a fraction of a cent to stop guessing about a repo and actually look.&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>ai</category>
      <category>github</category>
      <category>tools</category>
    </item>
    <item>
      <title>I measured link rot on 2012 Hacker News and resurrected 84% of it (Kiroween 2026)</title>
      <dc:creator>An Nguyễn Hoàng</dc:creator>
      <pubDate>Thu, 05 Nov 2026 17:03:57 +0000</pubDate>
      <link>https://dev.to/an_nguynhong_0c0496258/i-measured-link-rot-on-2012-hacker-news-and-resurrected-84-of-it-kiroween-2026-32fg</link>
      <guid>https://dev.to/an_nguynhong_0c0496258/i-measured-link-rot-on-2012-hacker-news-and-resurrected-84-of-it-kiroween-2026-32fg</guid>
      <description>&lt;p&gt;&lt;em&gt;This post was written as part of the **Kiroween 2026 hackathon&lt;/em&gt;* (Resurrection category).*&lt;/p&gt;

&lt;h2&gt;
  
  
  The dead tech I wanted to resurrect: the hyperlink
&lt;/h2&gt;

&lt;p&gt;Open any Hacker News front page from 2012 and start clicking. A lot of it is gone. I wanted a number instead of a feeling, so I built &lt;strong&gt;Necrolink&lt;/strong&gt;: it measures link rot on a cohort of links and brings the dead ones back from the Wayback Machine.&lt;/p&gt;

&lt;p&gt;The current result, generated from committed data (never hand-typed):&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Of 299 links on Hacker News front-page stories from 2012, 55.2% are dead or moved today; Necrolink resurrects 84.2% of those from the Wayback Machine. Control: of 300 links from 2025, 10% are dead.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;Repo (MIT, &lt;code&gt;.kiro/&lt;/code&gt; committed): &lt;a href="https://github.com/luongs3/necrolink" rel="noopener noreferrer"&gt;https://github.com/luongs3/necrolink&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Live graveyard: &lt;a href="https://luongs3.github.io/necrolink/web/" rel="noopener noreferrer"&gt;https://luongs3.github.io/necrolink/web/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Offline judge demo: &lt;code&gt;make judge-demo&lt;/code&gt; (under 1 second, no keys)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why a control cohort
&lt;/h2&gt;

&lt;p&gt;A claim like "55% of old links are dead" is useless if your checker calls everything dead. So the spec requires a second cohort: 300 links from 2025 HN. If the tool says those are dead too, the tool is broken. They come out 10% dead and 76.7% alive, so the checker isn't just a pessimism machine.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Kiro shaped the build
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Spec first, not vibes.&lt;/strong&gt; I wrote &lt;code&gt;.kiro/specs/necrolink/requirements.md&lt;/code&gt; in EARS form ("WHEN the user runs &lt;code&gt;scan&lt;/code&gt; THEN the system SHALL…"), then &lt;code&gt;design.md&lt;/code&gt; and &lt;code&gt;tasks.md&lt;/code&gt;, and had Kiro CLI implement every task from the spec. The difference from vibe coding showed up the first time a real scan went wrong. I didn't patch the code by hand. I amended the spec and let Kiro re-implement against it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Wayback 429s.&lt;/strong&gt; archive.org's availability API rate-limits under concurrency. The spec now says lookups run &lt;em&gt;serially&lt;/em&gt; with a 1.2s gap, exponential backoff that honors &lt;code&gt;Retry-After&lt;/code&gt;, and a separate &lt;code&gt;ERROR&lt;/code&gt; state that is distinct from &lt;code&gt;UNRECOVERABLE&lt;/code&gt;. "We couldn't ask" is not the same as "no snapshot exists".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bot walls are not death.&lt;/strong&gt; 401/403/405/429/436 now map to a &lt;code&gt;BLOCKED&lt;/code&gt; class that is reported in its own column and kept out of the dead%. Without this rule the 2012 number would be inflated by Cloudflare.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Both fixes live in the requirements doc, so the reason for each behavior is written down next to it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Steering.&lt;/strong&gt; &lt;code&gt;product.md&lt;/code&gt; holds the one rule that matters: every headline number must be falsifiable and must have a control. &lt;code&gt;tech.md&lt;/code&gt; keeps it zero-dependency (Node built-ins only). That stopped Kiro from pulling in an HTTP library or a test framework.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hooks.&lt;/strong&gt; &lt;code&gt;test-on-save&lt;/code&gt; runs the suite whenever &lt;code&gt;lib/&lt;/code&gt; changes. &lt;code&gt;claim-sync&lt;/code&gt; regenerates the README headline whenever &lt;code&gt;data/&lt;/code&gt; changes, so the number in the README can't drift away from the data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Property-based tests.&lt;/strong&gt; The URL normalizer gets randomized never-throw and idempotence tests. They caught a float-formatting bug (39.7 + 15.7 = 55.400000000000006) that would have ended up in the headline.&lt;/p&gt;

&lt;p&gt;82 tests, all passing.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd tell someone starting with Kiro
&lt;/h2&gt;

&lt;p&gt;The spec is not paperwork. It's where bug fixes go. When reality disagrees with your code, change the requirement first, and the fix plus the reason for it stay in one place.&lt;/p&gt;

&lt;h1&gt;
  
  
  Kiro #KiroweenHackathon26
&lt;/h1&gt;

</description>
      <category>kiro</category>
      <category>hackathon</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>A stale skill is worse than no skill</title>
      <dc:creator>An Nguyễn Hoàng</dc:creator>
      <pubDate>Thu, 04 Jun 2026 07:26:21 +0000</pubDate>
      <link>https://dev.to/an_nguynhong_0c0496258/a-stale-skill-is-worse-than-no-skill-334b</link>
      <guid>https://dev.to/an_nguynhong_0c0496258/a-stale-skill-is-worse-than-no-skill-334b</guid>
      <description>&lt;p&gt;While building a skill index I almost recommended a repo with sixteen thousand stars. Looked great. Then I checked when it was last touched: January 2023. Three years dead. If an agent had loaded its instructions it would have followed them confidently, and they're wrong now. That's the whole problem nobody talks about with these skill libraries.&lt;/p&gt;

&lt;p&gt;If you've used Claude Code or any agent setup recently, you've seen the pattern. There's a folder of &lt;code&gt;SKILL.md&lt;/code&gt; files, or an MCP registry, or some &lt;code&gt;awesome-agent-skills&lt;/code&gt; repo, and the agent reaches into it when it needs to do a thing. Everyone is building the library. The library is easy. It's a folder of markdown.&lt;/p&gt;

&lt;p&gt;The part nobody builds is the part that actually matters: how does the agent know which skill in the pile won't lie to it?&lt;/p&gt;

&lt;p&gt;A skill is instructions an AI follows. Confidently. That's different from code. When code is wrong it crashes, loud, you notice. When a skill is wrong the agent just does the wrong thing and tells you it went great. So a stale or subtly-broken skill isn't neutral. It's negative. It produces a confident wrong answer instead of making the agent stop and think. No skill at all would have been safer.&lt;/p&gt;

&lt;p&gt;I went looking for the existing solution and what I found was a hundred link lists. They tell you a skill exists. They do not tell you the one thing you need before you load it: can I trust this, and is it still true.&lt;/p&gt;

&lt;p&gt;So I built the missing layer. It's called Skill Atlas. It's a public index of skills, but organized by &lt;em&gt;job&lt;/em&gt; (the thing you're about to do, like "work on Upwork" or "build an MCP server" or "write Go" or "set up CI"), and every single entry carries three things a link list skips:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;where it came from, and is that source reputable&lt;/li&gt;
&lt;li&gt;a trust tier, A through D&lt;/li&gt;
&lt;li&gt;a &lt;code&gt;last_validated&lt;/code&gt; date, that an actual check happened, not the publish date&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The tiers are simple. A is canonical, the official source, you trust it because the vendor wrote it. B is community-proven, high reputation and still maintained. C is useful but verify before you lean on it. D is caution, it's stale or unmaintained, listed on purpose so you don't waste an afternoon rediscovering it's dead.&lt;/p&gt;

&lt;p&gt;The freshness part isn't optional, and the English repo is why.&lt;/p&gt;

&lt;p&gt;That English repo from the top? Sixteen thousand stars makes it an easy B by the usual logic, maybe higher. The January 2023 last-push makes it a D. Stars said trust it. The date said run.&lt;/p&gt;

&lt;p&gt;Same story with a famous interview-prep repo, around three hundred fifty thousand stars, hasn't been touched since last August, so it drops to C. Stars are a lagging signal. People star a thing once and never unstar it when it rots. Stars plus last-push tell the truth that stars alone hide.&lt;/p&gt;

&lt;p&gt;That's the actual product. Not the list, the judgment about the list.&lt;/p&gt;

&lt;p&gt;To keep it from rotting itself, there's a script that re-checks every source, liveness plus live star counts plus last-push, and a GitHub Action runs it monthly and opens an issue the moment a link dies. An entry that hasn't been re-validated in six months gets treated as "verify before trusting" no matter what tier it claims. The atlas has to hold itself to the same bar it holds everything else, or it becomes the exact thing it warns about.&lt;/p&gt;

&lt;p&gt;One more thing that surprised me while building it. For some jobs there is no good public skill, and the honest move is to say so. "How to win on Upwork" doesn't have a trustworthy public skill, because the real one is bespoke, it lives in your own head and your own win/loss history. So the atlas says that out loud instead of padding the slot with generic junk. The whole model is: find the good public starting point, then fork it private and make it yours. The public layer is the floor, not the ceiling.&lt;/p&gt;

&lt;p&gt;It's 34 jobs right now. Backend stuff mostly, because that's what I do (Go, Postgres, docker, observability, API design, auth, the usual), plus the agent-specific ones (MCP, prompt engineering) and a few soft ones (interviews, careers). MIT, and it installs as a skill itself so your agent routes to the right vetted entry at the start of a task.&lt;/p&gt;

&lt;p&gt;Repo's here if you want to poke at it: &lt;a href="https://github.com/luongs3/skill-atlas" rel="noopener noreferrer"&gt;https://github.com/luongs3/skill-atlas&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The question I actually can't answer yet, and the reason I'm posting this: what other signal would make &lt;em&gt;you&lt;/em&gt; trust a skill before loading it? Stars and last-push are what I have. They're not enough. Install counts maybe, but those lie too. If you've got a better trust signal I want to hear it.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>claude</category>
      <category>tools</category>
    </item>
    <item>
      <title>I gave my coding agent root on my VPS so it would stop making me deploy by hand</title>
      <dc:creator>An Nguyễn Hoàng</dc:creator>
      <pubDate>Tue, 02 Jun 2026 03:38:43 +0000</pubDate>
      <link>https://dev.to/an_nguynhong_0c0496258/i-gave-my-ai-agent-root-on-my-server-so-it-could-deploy-for-me-1881</link>
      <guid>https://dev.to/an_nguynhong_0c0496258/i-gave-my-ai-agent-root-on-my-server-so-it-could-deploy-for-me-1881</guid>
      <description>&lt;p&gt;Last week I built a little dashboard with Claude. Took maybe ten minutes. Then I spent the next hour trying to get it online.&lt;/p&gt;

&lt;p&gt;ssh in, install docker, write a Dockerfile, set up nginx, run certbot, certbot fails, read the log, oh the DNS hasn't propagated, wait, run it again, open port 443, realize ufw was blocking it the whole time. By the time it was live I'd forgotten what the app even did.&lt;/p&gt;

&lt;p&gt;I've done that maybe a few hundred times by now. I'm a backend guy, I'm fast at it. But fast at something boring still means doing the boring thing.&lt;/p&gt;

&lt;p&gt;So at some point I just thought: the AI already wrote the app. Why does it stop right when the annoying part starts? Why doesn't it just deploy the thing itself?&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%2Fluongs3%2Fvibe-deploy%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%2Fluongs3%2Fvibe-deploy%2Fmain%2Fassets%2Fdemo.gif" alt="one message, app goes live with https" width="720" height="393"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The reason is it has no hands. The model can write you a perfect docker-compose file. It can't ssh into your box and run it. No connection to your server, nowhere to hold your key.&lt;/p&gt;

&lt;p&gt;So I gave it hands. It's an MCP server, vibe-deploy. You hook it up once to a VPS you own, and then you just say "deploy this to notes.mydomain.com" and the agent containerizes it, ships it over ssh, sets up nginx, gets a real Let's Encrypt cert. Node, Python, Go, plain static. It figures out the stack and writes the Dockerfile.&lt;/p&gt;

&lt;p&gt;No PaaS, no per-seat pricing, no free tier you'll outgrow. A $5 box runs a dozen of my projects and I own the whole thing.&lt;/p&gt;

&lt;p&gt;The "you gave an AI root on your server??" reaction is fair, so: it runs locally, your key never leaves your laptop. I used a separate ssh key scoped to deploys, not my real one, and you should too. It checks the server host key before connecting and validates everything you pass it, because a deploy tool that pastes your input straight into a shell is a horror story waiting to happen. I had someone audit the security before I put it out. They found two real bugs. I fixed them.&lt;/p&gt;

&lt;p&gt;It's free and MIT, on &lt;a href="https://github.com/luongs3/vibe-deploy" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; and npm as &lt;code&gt;@cgnguyen/vibe-deploy&lt;/code&gt;. I built it because I wanted it. If you live in the same gap between "it works on localhost" and "it's online", try it and tell me where it breaks. The stacks it supports right now are the ones I needed. Next ones are whatever you ask for.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>mcp</category>
      <category>devops</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
