<?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: Zayd Mulani</title>
    <description>The latest articles on DEV Community by Zayd Mulani (@zaydmulani09).</description>
    <link>https://dev.to/zaydmulani09</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%2F3939133%2Fa61f4cd3-0e64-4bac-9a1e-00ae898bf88f.png</url>
      <title>DEV Community: Zayd Mulani</title>
      <link>https://dev.to/zaydmulani09</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/zaydmulani09"/>
    <language>en</language>
    <item>
      <title>I built a local-first coverage-guided fuzzer in Rust with LLM seed bootstrap</title>
      <dc:creator>Zayd Mulani</dc:creator>
      <pubDate>Tue, 14 Jul 2026 13:27:34 +0000</pubDate>
      <link>https://dev.to/zaydmulani09/i-built-a-local-first-coverage-guided-fuzzer-in-rust-with-llm-seed-bootstrap-4c4a</link>
      <guid>https://dev.to/zaydmulani09/i-built-a-local-first-coverage-guided-fuzzer-in-rust-with-llm-seed-bootstrap-4c4a</guid>
      <description>&lt;p&gt;I built a local-first coverage-guided fuzzer in Rust with LLM seed bootstrap&lt;/p&gt;

&lt;h1&gt;
  
  
  rust #opensource #security #fuzzing
&lt;/h1&gt;




&lt;p&gt;The problem that kept bothering me: I wanted to fuzz a custom binary protocol parser. Not something with a public corpus of real traffic. Just a service my team built that speaks a length-prefixed binary format over TCP.&lt;/p&gt;

&lt;p&gt;AFL++ requires a C harness. cargo-fuzz wraps libFuzzer but you still need to write the harness and you're starting from random bytes. Neither tool helps you when the first thing the parser does is check a 4-byte magic header and exit if it doesn't match. You spend hours getting past that check before you find anything real.&lt;/p&gt;

&lt;p&gt;So I built Phaedra.&lt;/p&gt;

&lt;h2&gt;
  
  
  The LLM seeding idea
&lt;/h2&gt;

&lt;p&gt;The core insight is that you don't need random bytes to start. You need bytes that look plausible. If you describe what your target parses in plain English, a language model can generate a structured initial corpus.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;phaedra fuzz &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--target&lt;/span&gt; ./my_parser &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--description&lt;/span&gt; &lt;span class="s2"&gt;"TLV binary protocol with PHDR magic header, u8 type, u16be length, payload bytes"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Phaedra sends that description to a local Ollama instance and gets back hex-encoded seed inputs. No API key, no data leaving your machine. OpenAI and Anthropic work as drop-in backends if you want them.&lt;/p&gt;

&lt;p&gt;The seed generation prompt asks for variety: valid inputs, boundary values, truncated records, inputs where the length field exceeds the payload. The parser gets past the magic check immediately because the seeds include valid magic bytes. Campaign starts finding new coverage on the first execution instead of the hundredth.&lt;/p&gt;

&lt;h2&gt;
  
  
  Coverage via SanCov edge bitmaps
&lt;/h2&gt;

&lt;p&gt;Once you have seeds, you need coverage feedback. I chose SanCov edge bitmaps over shared memory rather than LLVM &lt;code&gt;.profraw&lt;/code&gt; files for one reason: speed. Reading a profraw file after each execution means a file write, a file read, and a parse. Shared memory is a single &lt;code&gt;shmat&lt;/code&gt; call and a memcpy.&lt;/p&gt;

&lt;p&gt;The setup: Phaedra allocates a 65536-slot edge bitmap in POSIX shared memory, passes the segment ID to the target via &lt;code&gt;__PHAEDRA_SHM_ID&lt;/code&gt;, and the target's SanCov runtime shim writes edge hit counts directly into that segment on every execution. After the process exits, Phaedra reads the bitmap, XORs against the global "seen" bitmap, and if new edges fired, the input goes into the corpus.&lt;/p&gt;

&lt;p&gt;The shim is a small C file that gets compiled into the target:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;__sanitizer_cov_trace_pc_guard&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uint32_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;guard&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!*&lt;/span&gt;&lt;span class="n"&gt;guard&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;phaedra_map&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="n"&gt;phaedra_map&lt;/span&gt;&lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;guard&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="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;PHAEDRA_MAP_SIZE&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&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;h2&gt;
  
  
  Schema-aware mutation
&lt;/h2&gt;

&lt;p&gt;Random byte mutation gets past the magic check once you have good seeds. But it still struggles with structured fields. If your protocol has a u16be length prefix followed by exactly that many payload bytes, random mutation almost never produces a valid record because it corrupts the length without adjusting the payload.&lt;/p&gt;

&lt;p&gt;The schema DSL solves this. You describe your protocol in TOML:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="py"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"my_protocol"&lt;/span&gt;

&lt;span class="nn"&gt;[[fields]]&lt;/span&gt;
&lt;span class="py"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"magic"&lt;/span&gt;
&lt;span class="py"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"magic"&lt;/span&gt;
&lt;span class="py"&gt;length&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;
&lt;span class="py"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"50484452"&lt;/span&gt;
&lt;span class="py"&gt;mutable&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;

&lt;span class="nn"&gt;[[fields]]&lt;/span&gt;
&lt;span class="py"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"length"&lt;/span&gt;
&lt;span class="py"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"u16_be"&lt;/span&gt;

&lt;span class="nn"&gt;[[fields]]&lt;/span&gt;
&lt;span class="py"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"payload"&lt;/span&gt;
&lt;span class="py"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"lp_bytes16_be"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;30% of mutations operate at the field level: corrupting the length prefix specifically, setting it to 0, to max value, or to a value larger than the actual payload. The other 70% are still raw byte mutations. The split keeps the campaign from getting stuck in schema-valid inputs only.&lt;/p&gt;

&lt;p&gt;If you have a corpus but no schema, &lt;code&gt;phaedra infer&lt;/code&gt; analyzes the samples with six heuristics and writes a starting schema:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;phaedra infer &lt;span class="nt"&gt;--corpus-db&lt;/span&gt; ./phaedra-corpus/corpus.db &lt;span class="nt"&gt;--name&lt;/span&gt; my_proto
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Crash triage
&lt;/h2&gt;

&lt;p&gt;When Phaedra finds a crash it deduplicates by signal number and input fingerprint. SIGSEGV and SIGABRT are CRITICAL. SIGILL and SIGFPE are HIGH. No-signal crashes are LOW. Each unique signature gets one entry in SQLite with a hit counter so if the same bug triggers 500 times you see one entry with hit_count=500, not 500 files in your crash directory.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;phaedra crashes

ID  SEVERITY   HITS  SIGNATURE     STATUS
1   LOW        47    crash_3f2a1b  Crash { signal: None }
2   LOW        12    crash_7e4d2c  Crash { signal: None }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;phaedra minimize&lt;/code&gt; runs delta minimization to reduce a crashing input to the smallest bytes that still trigger it. Found a 200-byte crash? It will usually get it down to under 10.&lt;/p&gt;

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



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cargo &lt;span class="nb"&gt;install &lt;/span&gt;phaedra
phaedra fuzz &lt;span class="nt"&gt;--demo&lt;/span&gt; tlv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The TLV demo target has a real bounds-check bug. Phaedra finds it within the first few seconds.&lt;/p&gt;

&lt;p&gt;GitHub: &lt;a href="https://github.com/zaydmulani09/phaedra" rel="noopener noreferrer"&gt;https://github.com/zaydmulani09/phaedra&lt;/a&gt;&lt;/p&gt;

</description>
      <category>rust</category>
      <category>opensource</category>
      <category>security</category>
      <category>testing</category>
    </item>
    <item>
      <title>I built a local token cost tracker for Claude Code in Rust</title>
      <dc:creator>Zayd Mulani</dc:creator>
      <pubDate>Mon, 13 Jul 2026 21:58:53 +0000</pubDate>
      <link>https://dev.to/zaydmulani09/i-built-a-local-token-cost-tracker-for-claude-code-in-rust-500h</link>
      <guid>https://dev.to/zaydmulani09/i-built-a-local-token-cost-tracker-for-claude-code-in-rust-500h</guid>
      <description>&lt;p&gt;After one too many surprise Claude Code bills, I built agentwatch.&lt;br&gt;
It's a local HTTP proxy written in Rust. Point your agent at localhost:7878, and it intercepts every request, parses the token usage, and shows a live Ratatui TUI dashboard of what you're spending — in real time.&lt;br&gt;
Everything stays local. SQLite database, zero signup, single binary via cargo install agentwatch.&lt;br&gt;
Supports Claude Code, Codex, and Gemini CLI.&lt;br&gt;
github.com/zaydmulani09/agentwatch&lt;/p&gt;

</description>
      <category>rust</category>
      <category>ai</category>
      <category>opensource</category>
      <category>productivity</category>
    </item>
    <item>
      <title>I built a self-hosted reverse proxy for MCP servers in Rust</title>
      <dc:creator>Zayd Mulani</dc:creator>
      <pubDate>Thu, 02 Jul 2026 18:52:40 +0000</pubDate>
      <link>https://dev.to/zaydmulani09/i-built-a-self-hosted-reverse-proxy-for-mcp-servers-in-rust-45a1</link>
      <guid>https://dev.to/zaydmulani09/i-built-a-self-hosted-reverse-proxy-for-mcp-servers-in-rust-45a1</guid>
      <description>&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;Every AI tool that talks to MCP servers (Claude, Cursor, etc.) &lt;br&gt;
connects directly — no auth, no rate limiting, no observability. &lt;br&gt;
You have no idea what's hitting your servers or how often.&lt;/p&gt;

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

&lt;p&gt;MCP Gateway sits in front of all your MCP servers and handles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Routing&lt;/strong&gt; — /mcp/server-name/* proxies to the right backend&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auth&lt;/strong&gt; — per-server API keys encrypted AES-256-GCM in memory&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rate limiting&lt;/strong&gt; — token bucket per client, 429 + Retry-After&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Logging&lt;/strong&gt; — SQLite log of every request with latency + status&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Usage tracking&lt;/strong&gt; — token counts from Anthropic/OpenAI responses&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CLI&lt;/strong&gt; — mcpgw server add/list/remove, mcpgw logs show, mcpgw stats&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Quickstart
&lt;/h2&gt;

&lt;p&gt;MCPGW_MASTER_SECRET=your-secret docker compose up --build&lt;/p&gt;

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

&lt;p&gt;Rust, Axum, SQLite (rusqlite), DashMap, AES-GCM&lt;/p&gt;

&lt;p&gt;GitHub: &lt;a href="https://github.com/zaydmulani09/mcp-gateway" rel="noopener noreferrer"&gt;https://github.com/zaydmulani09/mcp-gateway&lt;/a&gt;&lt;/p&gt;

</description>
      <category>rust</category>
      <category>mcp</category>
      <category>ai</category>
      <category>opensource</category>
    </item>
    <item>
      <title>I built a local-first AI memory layer for LLMs in Rust (no cloud, no API keys)</title>
      <dc:creator>Zayd Mulani</dc:creator>
      <pubDate>Wed, 03 Jun 2026 20:33:22 +0000</pubDate>
      <link>https://dev.to/zaydmulani09/i-built-a-local-first-ai-memory-layer-for-llms-in-rust-no-cloud-no-api-keys-1eh8</link>
      <guid>https://dev.to/zaydmulani09/i-built-a-local-first-ai-memory-layer-for-llms-in-rust-no-cloud-no-api-keys-1eh8</guid>
      <description>&lt;p&gt;Every LLM app has the same problem — the model forgets everything between &lt;br&gt;
conversations. Cloud solutions like Mem0 exist but they send your data &lt;br&gt;
to their servers. I built mnemo to solve this locally.&lt;/p&gt;

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

&lt;p&gt;mnemo runs as a sidecar process next to your app. You POST text to it, &lt;br&gt;
it extracts named entities and relationships using a local LLM (Ollama), &lt;br&gt;
builds a persistent knowledge graph, and injects relevant context back &lt;br&gt;
into your prompts automatically.&lt;/p&gt;

&lt;h2&gt;
  
  
  The stack
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Rust&lt;/strong&gt; — core engine, 4 crates (mnemo-core, mnemo-api, mnemo-cli, mnemo-bench)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SQLite + WAL mode&lt;/strong&gt; — persistent storage, survives restarts&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;petgraph&lt;/strong&gt; — in-memory knowledge graph with BFS traversal&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Axum&lt;/strong&gt; — REST API sidecar any app can call&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ollama&lt;/strong&gt; — fully local LLM, zero API costs&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Fully free by default
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt;
docker &lt;span class="nb"&gt;exec &lt;/span&gt;mnemo-ollama ollama pull llama3
curl http://localhost:8080/health
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Works with OpenAI or Anthropic too if you bring your own key.&lt;/p&gt;

&lt;h2&gt;
  
  
  Python SDK
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;mnemo&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;MnemoClient&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MnemoClient&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ingest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;I&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;m building a Rust vector database called vecdb&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_context&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;what am I working on?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Numbers
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;122 Rust tests, 21 Python SDK tests&lt;/li&gt;
&lt;li&gt;Sub-millisecond entity lookup&lt;/li&gt;
&lt;li&gt;~4ms full retrieval pipeline (debug build)&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;GitHub: &lt;a href="https://github.com/zaydmulani09/mnemo" rel="noopener noreferrer"&gt;https://github.com/zaydmulani09/mnemo&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Would love feedback, especially on the retrieval scoring and graph &lt;br&gt;
traversal approach.&lt;/p&gt;

</description>
      <category>rust</category>
      <category>ai</category>
      <category>opensource</category>
      <category>llm</category>
    </item>
    <item>
      <title>I built an open-source dependency intelligence platform in TypeScript — here's how it works</title>
      <dc:creator>Zayd Mulani</dc:creator>
      <pubDate>Fri, 29 May 2026 12:13:27 +0000</pubDate>
      <link>https://dev.to/zaydmulani09/i-built-an-open-source-dependency-intelligence-platform-in-typescript-heres-how-it-works-1ofi</link>
      <guid>https://dev.to/zaydmulani09/i-built-an-open-source-dependency-intelligence-platform-in-typescript-heres-how-it-works-1ofi</guid>
      <description>&lt;p&gt;Most teams find out their dependencies are risky after something breaks. A maintainer disappears, a vulnerability sits unpatched for months, or a single package with one contributor becomes a hidden outage waiting to happen. I wanted a tool that told you this stuff before it became a problem.&lt;br&gt;
So I built depgraph — an open-source dependency intelligence platform that crawls npm, PyPI, and Cargo registries, builds a live risk-scored dependency graph, and tells you exactly which packages in your supply chain are about to cause you pain.&lt;br&gt;
What it does&lt;br&gt;
Risk scoring across 6 dimensions&lt;br&gt;
Every package gets scored on security (open advisories, CVSS scores), maintenance (commit recency, release cadence), compatibility (semver violation rate), concentration (bus factor, single-maintainer risk), blast radius (how many downstream packages break if this one changes), and operational health (issue response latency, PR merge time).&lt;br&gt;
These combine into a single composite score with full explanations — not just a number, but "35% because maintainer activity fell 80% in 120 days."&lt;br&gt;
Interactive dependency graph&lt;br&gt;
Built with Cytoscape.js. Click any package to see its full transitive dependency tree, blast radius stats, and which packages it would take down if it disappeared. Chokepoint detection highlights the packages that are structurally too central to ignore.&lt;br&gt;
Policy engine with CI gate&lt;br&gt;
Define rules like "block packages with one maintainer and no release in 180 days" or "require approval for anything with 500+ downstream dependents." A GitHub Action runs the policy check on every PR and fails the build on violations — with a formatted comment showing exactly what triggered and how to fix it.&lt;br&gt;
Abandonment detection&lt;br&gt;
Time-series signals track commit frequency trends, maintainer count decay, and bus factor over time. A weighted model produces an abandonment probability score per package so you can see which ones are quietly dying before they become your problem.&lt;br&gt;
Historical snapshot diffs&lt;br&gt;
Every scan creates a full risk snapshot. Compare any two snapshots to see which packages degraded, which improved, what new vulnerability chains appeared, and how your overall supply chain health changed over time.&lt;br&gt;
Tech stack&lt;br&gt;
TypeScript monorepo with pnpm workspaces. Hono API, React + Vite frontend, PostgreSQL + Drizzle ORM, BullMQ workers, MinIO for raw storage. Fully self-hostable — one docker compose up and you're running. Zero paid services required.&lt;br&gt;
Supports npm, PyPI, and Cargo with a shared normalization layer that maps all three ecosystems to a canonical data model.&lt;br&gt;
Running it locally&lt;br&gt;
bashgit clone &lt;a href="https://github.com/zaydmulani09/depgraph" rel="noopener noreferrer"&gt;https://github.com/zaydmulani09/depgraph&lt;/a&gt;&lt;br&gt;
cd depgraph&lt;br&gt;
pnpm install --ignore-scripts&lt;br&gt;
cp .env.example .env&lt;br&gt;
docker compose up -d&lt;br&gt;
pnpm db:migrate&lt;br&gt;
pnpm --filter @depgraph/api dev &amp;amp;&lt;br&gt;
pnpm --filter @depgraph/crawler dev &amp;amp;&lt;br&gt;
pnpm --filter @depgraph/ui dev&lt;br&gt;
Open localhost:5173. The crawler seeds 10 packages immediately and starts processing — within a few minutes you'll see risk scores, graphs, and explanations populating in the UI.&lt;br&gt;
What I learned building this&lt;br&gt;
The hardest part wasn't the risk scoring or the UI — it was maintaining a living graph. Getting data once is easy. Keeping it fresh, detecting drift, diffing snapshots, and making all of that fast enough to be useful is where the real complexity lives.&lt;br&gt;
The second hardest part was normalization. npm, PyPI, and Cargo all have wildly different versioning semantics, dependency specifier formats, and registry API shapes. Building a canonical model that works cleanly across all three took longer than any other single component.&lt;/p&gt;

&lt;p&gt;Check it out, open issues, and PRs are welcome.&lt;br&gt;
GitHub: &lt;a href="https://github.com/zaydmulani09/depgraph" rel="noopener noreferrer"&gt;https://github.com/zaydmulani09/depgraph&lt;/a&gt;&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>opensource</category>
      <category>security</category>
      <category>webdev</category>
    </item>
    <item>
      <title>I built a local-first hybrid vector database in Rust from scratch</title>
      <dc:creator>Zayd Mulani</dc:creator>
      <pubDate>Tue, 19 May 2026 01:29:22 +0000</pubDate>
      <link>https://dev.to/zaydmulani09/i-built-a-local-first-hybrid-vector-database-in-rust-from-scratch-3fd3</link>
      <guid>https://dev.to/zaydmulani09/i-built-a-local-first-hybrid-vector-database-in-rust-from-scratch-3fd3</guid>
      <description>&lt;p&gt;A few months ago I started building vecdb — a vector database that &lt;br&gt;
runs entirely on your own machine. No cloud, no API keys, no subscription.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;Most vector databases make you choose — semantic search OR keyword search.&lt;br&gt;
Semantic search finds meaning but misses exact keywords. Keyword search &lt;br&gt;
finds exact matches but misses meaning.&lt;/p&gt;

&lt;p&gt;vecdb combines both in a two-stage pipeline:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;HNSW dense index retrieves candidates by meaning&lt;/li&gt;
&lt;li&gt;BM25 sparse index re-scores by keyword relevance
&lt;/li&gt;
&lt;li&gt;A fusion function combines both scores&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What it can do
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Hybrid HNSW + BM25 retrieval&lt;/li&gt;
&lt;li&gt;SQL-like query language with VECTOR_SIM predicate&lt;/li&gt;
&lt;li&gt;Python and TypeScript SDKs&lt;/li&gt;
&lt;li&gt;Single binary, Docker support&lt;/li&gt;
&lt;li&gt;187 tests&lt;/li&gt;
&lt;li&gt;MIT license&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Example query
&lt;/h2&gt;

&lt;p&gt;SELECT * FROM documents&lt;br&gt;
WHERE VECTOR_SIM(vec, [0.1, 0.2, 0.3]) &amp;gt; 0.75&lt;br&gt;
AND payload-&amp;gt;&amp;gt;'region' = 'US'&lt;br&gt;
LIMIT 10;&lt;/p&gt;

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

&lt;p&gt;GitHub: &lt;a href="https://github.com/zaydmulani09/vecdb" rel="noopener noreferrer"&gt;https://github.com/zaydmulani09/vecdb&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Would love feedback from the community — especially on the &lt;br&gt;
architecture and what to tackle in v0.2.0.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>rust</category>
      <category>opensource</category>
      <category>database</category>
    </item>
  </channel>
</rss>
