<?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: jitendra bhati</title>
    <description>The latest articles on DEV Community by jitendra bhati (@jitendra_bhati_17694e20e5).</description>
    <link>https://dev.to/jitendra_bhati_17694e20e5</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1638365%2F9b0a112d-c11e-4429-abb6-dab3a362b9d0.png</url>
      <title>DEV Community: jitendra bhati</title>
      <link>https://dev.to/jitendra_bhati_17694e20e5</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jitendra_bhati_17694e20e5"/>
    <language>en</language>
    <item>
      <title>I built a release intelligence agent in 4 days with Coral, Groq, and Claude Code. Here's the exact route</title>
      <dc:creator>jitendra bhati</dc:creator>
      <pubDate>Sun, 31 May 2026 19:11:44 +0000</pubDate>
      <link>https://dev.to/jitendra_bhati_17694e20e5/i-built-a-release-intelligence-agent-in-4-days-with-coral-groq-and-claude-code-heres-the-exact-3ii8</link>
      <guid>https://dev.to/jitendra_bhati_17694e20e5/i-built-a-release-intelligence-agent-in-4-days-with-coral-groq-and-claude-code-heres-the-exact-3ii8</guid>
      <description>&lt;p&gt;5 days. Solo. First hackathon. One dashboard that tells you whether to ship.&lt;/p&gt;

&lt;p&gt;This is not a polished retrospective. This is the actual route I took — wrong turns, late debugging sessions, and the one query that made the whole thing feel real.&lt;/p&gt;

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




&lt;h2&gt;
  
  
  The problem I kept hitting
&lt;/h2&gt;

&lt;p&gt;Before every release I open four tabs.&lt;/p&gt;

&lt;p&gt;GitHub to check if the PRs are actually merged. Linear to check which sprint items are done. Sentry to check if anything is actively broken. Slack to scroll through #releases and see if anyone mentioned a concern.&lt;/p&gt;

&lt;p&gt;Then I try to hold all of that in my head at once and make a decision.&lt;/p&gt;

&lt;p&gt;It takes about 40 minutes. And I've still shipped things I shouldn't have — because a PR merged, an error appeared six hours later in Sentry, and I didn't connect the two. The error was sitting right there. I just wasn't looking at both at the same time.&lt;/p&gt;

&lt;p&gt;That's not a process problem. That's a missing JOIN.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Coral actually does
&lt;/h2&gt;

&lt;p&gt;I was going into this hackathon looking for an excuse to use &lt;a href="https://withcoral.com" rel="noopener noreferrer"&gt;Coral&lt;/a&gt;. The short version: Coral is a local CLI that turns developer APIs into SQL tables.&lt;/p&gt;

&lt;p&gt;You run &lt;code&gt;coral source add github&lt;/code&gt;, hand it a token, and now &lt;code&gt;github.pulls&lt;/code&gt; is a real table you can &lt;code&gt;SELECT&lt;/code&gt; from. Same for &lt;code&gt;linear.issues&lt;/code&gt;, &lt;code&gt;sentry.issues&lt;/code&gt;, &lt;code&gt;slack.channels&lt;/code&gt;. You can JOIN across them. You can do date arithmetic. You can pipe the result straight to an LLM without writing a single API client.&lt;/p&gt;

&lt;p&gt;That last part is what I kept coming back to. It's not "you can query GitHub in SQL" — there are GraphQL proxies that do that. It's that &lt;strong&gt;the JOIN works across sources&lt;/strong&gt;. Completely different APIs, completely different auth, one result set.&lt;/p&gt;




&lt;h2&gt;
  
  
  The agent: ShipMind
&lt;/h2&gt;

&lt;p&gt;I built ShipMind — a release intelligence agent that answers five questions before every deploy:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🔴 &lt;strong&gt;Release blockers&lt;/strong&gt; — unresolved Sentry errors that would break production&lt;/li&gt;
&lt;li&gt;🟡 &lt;strong&gt;Readiness check&lt;/strong&gt; — Linear tickets marked done vs PRs actually merged in GitHub&lt;/li&gt;
&lt;li&gt;🟠 &lt;strong&gt;Risk assessment&lt;/strong&gt; — GitHub PRs cross-joined with Sentry errors introduced after each merge&lt;/li&gt;
&lt;li&gt;💬 &lt;strong&gt;Team signals&lt;/strong&gt; — Slack messages from #releases surfaced and analyzed automatically&lt;/li&gt;
&lt;li&gt;📈 &lt;strong&gt;Sprint velocity&lt;/strong&gt; — Linear cycle completion rate against planned scope&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All five run together. An LLM (Llama 3.3 70B via Groq, or Claude if you prefer) reads the combined results and outputs a score from 0 to 100 with a verdict: &lt;strong&gt;SHIP&lt;/strong&gt;, &lt;strong&gt;SHIP WITH CAUTION&lt;/strong&gt;, or &lt;strong&gt;HOLD&lt;/strong&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🔗 &lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/Jeetubhati/shipmind" rel="noopener noreferrer"&gt;github.com/Jeetubhati/shipmind&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The architecture
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Browser  →  Flask (web/app.py)
                  ↓
     Groq · Llama 3.3 70B  (or Anthropic Claude)
                  ↓
          Coral CLI  (coral sql ...)
                  ↓
   github · linear · sentry · slack · slack_messages · devto
   bundled  bundled  bundled  bundled     custom        custom
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The agent detects which LLM you have at startup — &lt;code&gt;GROQ_API_KEY&lt;/code&gt; set means Groq, &lt;code&gt;ANTHROPIC_API_KEY&lt;/code&gt; means Anthropic, neither means a deterministic stub that still renders the dashboard correctly so you can demo without any credits. The header shows a coloured pill — "Groq · Llama 3.3 70B" or "Anthropic · Claude Sonnet" — so you always know which model answered.&lt;/p&gt;

&lt;p&gt;The web layer is Flask plus a single HTML file. No CDN. No build step. No framework. The whole dashboard — HTML, CSS, JavaScript, the SVG score gauge — is one file you can grep. In a 4-day solo project that was the right call and I'd make it again.&lt;/p&gt;




&lt;h2&gt;
  
  
  The key SQL queries
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Release blockers:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;level&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;first_seen&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;sentry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;issues&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'unresolved'&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;level&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'fatal'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'error'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt;
  &lt;span class="k"&gt;CASE&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;level&lt;/span&gt; &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="s1"&gt;'fatal'&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="s1"&gt;'error'&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;count&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;
&lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Release readiness — Linear vs GitHub:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
  &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;identifier&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;number&lt;/span&gt;    &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;pr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;state&lt;/span&gt;     &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;pr_state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;merged_at&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;linear&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;issues&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;
&lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;github&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pulls&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;
  &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;owner&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'your-org'&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;repo&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'your-repo'&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;LIKE&lt;/span&gt; &lt;span class="s1"&gt;'%'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;identifier&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s1"&gt;'%'&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state_name&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'In Progress'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'In Review'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Done'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Todo'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;priority&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state_name&lt;/span&gt;
&lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A &lt;code&gt;LEFT JOIN&lt;/code&gt; from Linear into GitHub is exactly the right shape for "is everything that's planned actually shipped?" If the PR column is null on a Done ticket, that's a risk worth surfacing before you press deploy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Team signals from Slack:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;user&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;slack_messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;channel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'YOUR_CHANNEL_ID'&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;LIKE&lt;/span&gt; &lt;span class="s1"&gt;'%release%'&lt;/span&gt;
    &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;LIKE&lt;/span&gt; &lt;span class="s1"&gt;'%deploy%'&lt;/span&gt;
    &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;LIKE&lt;/span&gt; &lt;span class="s1"&gt;'%blocker%'&lt;/span&gt;
    &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;LIKE&lt;/span&gt; &lt;span class="s1"&gt;'%broken%'&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ts&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;
&lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I'll come back to why this query needed a custom source spec.&lt;/p&gt;




&lt;h2&gt;
  
  
  The cross-source JOIN that made it feel real
&lt;/h2&gt;

&lt;p&gt;This is the risk assessment query. The one that made me think the project was actually worth something.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
  &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;       &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;pr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;merged_at&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user__login&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;       &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;error_after_merge&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;level&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;count&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;github&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pulls&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;sentry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;issues&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;
  &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;CAST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;first_seen&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="k"&gt;CAST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;merged_at&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;CAST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;first_seen&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt;
      &lt;span class="k"&gt;CAST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;merged_at&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="s1"&gt;'7 days'&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;level&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'fatal'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'error'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;owner&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'withcoral'&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;repo&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'coral'&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;state&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'closed'&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;merged_at&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;CAST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;merged_at&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="k"&gt;current_timestamp&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="s1"&gt;'30 days'&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;merged_at&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;
&lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This returned 20 real rows on the first run against the &lt;code&gt;withcoral/coral&lt;/code&gt; repo. Every recent PR, cross-referenced with the Sentry errors that first appeared within seven days of that PR merging.&lt;/p&gt;

&lt;p&gt;When I saw a community source PR correlated with an &lt;code&gt;AttributeError&lt;/code&gt; that appeared one day later, I realised: this isn't ShipMind-specific. This is a pattern every team can run against their own repo tomorrow. The query is completely generic. &lt;strong&gt;The JOIN is the feature.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In normal Python this would be 30+ lines of pagination, timestamp parsing, and rate-limit handling across two separate API clients. Here it's 18 lines of SQL.&lt;/p&gt;




&lt;h2&gt;
  
  
  Three things I learned about Coral the hard way
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Table functions are not tables
&lt;/h3&gt;

&lt;p&gt;The bundled Slack source has &lt;code&gt;slack.channels&lt;/code&gt; and &lt;code&gt;slack.users&lt;/code&gt; as plain tables — but messages live in a &lt;em&gt;table function&lt;/em&gt;: &lt;code&gt;slack.messages(channel =&amp;gt; 'C0...')&lt;/code&gt;. I checked &lt;code&gt;coral.tables&lt;/code&gt; on day one, saw no messages table, and nearly dropped Slack from the project entirely.&lt;/p&gt;

&lt;p&gt;The fix was running &lt;code&gt;SELECT * FROM coral.table_functions&lt;/code&gt; too. The deeper fix was writing a &lt;code&gt;slack_messages&lt;/code&gt; custom source spec so I could write &lt;code&gt;WHERE channel = '...'&lt;/code&gt; like any other table. Much cleaner SQL and the LLM generates it correctly on the first attempt.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Column names don't match the API docs
&lt;/h3&gt;

&lt;p&gt;Linear's GraphQL returns &lt;code&gt;state.name&lt;/code&gt;. Coral exposes it as &lt;code&gt;state_name&lt;/code&gt; (single underscore). GitHub's REST returns &lt;code&gt;user.login&lt;/code&gt;. Coral exposes it as &lt;code&gt;user__login&lt;/code&gt; (double underscore — nested field separator). Every query I wrote on day two failed on the first run because I used the API field names.&lt;/p&gt;

&lt;p&gt;The fix every time was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;column_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data_type&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;coral&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;columns&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="k"&gt;schema_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'linear'&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;table_name&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'issues'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After the third time I created a &lt;code&gt;CORAL_CONTEXT&lt;/code&gt; constant in &lt;code&gt;agent/config.py&lt;/code&gt; listing the canonical column names for every source and injected it into every LLM system prompt. Claude and Llama now generate correct Coral SQL on the first try.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. LIMIT is not pushed down to the API
&lt;/h3&gt;

&lt;p&gt;When I queried &lt;code&gt;devto.articles WHERE username = 'ben' LIMIT 5&lt;/code&gt;, Coral fetched every article Ben has published — over 3,000 — and then took the first five locally. My source spec had &lt;code&gt;max_pages: 50&lt;/code&gt; set, which was not enough and the query failed hard.&lt;/p&gt;

&lt;p&gt;The fix was bumping &lt;code&gt;per_page&lt;/code&gt; from 30 to 1000 so a typical user's full feed fits in a single fetch. Every source author needs to think about this tradeoff when choosing between page-mode and cursor-mode pagination. Cursor-mode with a &lt;code&gt;first&lt;/code&gt; parameter handles large datasets much more cleanly and should be the default for anything that could have hundreds of pages.&lt;/p&gt;




&lt;h2&gt;
  
  
  Writing the custom sources
&lt;/h2&gt;

&lt;p&gt;I wrote two source specs as part of this project.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;slack_messages&lt;/code&gt;&lt;/strong&gt; — wraps Slack's &lt;code&gt;conversations.history&lt;/code&gt; endpoint so messages become a regular filterable table instead of a table function call. Purely internal to ShipMind but it makes the SQL uniform across all five queries.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;devto&lt;/code&gt;&lt;/strong&gt; — exposes the dev.to (Forem) public API as two tables: &lt;code&gt;devto.articles&lt;/code&gt; and &lt;code&gt;devto.tags&lt;/code&gt;. Auth is a single &lt;code&gt;api-key&lt;/code&gt; header, no OAuth. Submitted as a community contribution:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🔗 &lt;strong&gt;PR:&lt;/strong&gt; &lt;a href="https://github.com/withcoral/coral/pull/1067" rel="noopener noreferrer"&gt;withcoral/coral #1067 — feat(sources/community/devto): add dev.to source&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Writing a source spec taught me something useful: it's just a YAML file that names a table, points at an HTTP path, declares auth, and maps response fields to typed columns. The hardest part isn't the YAML syntax — Coral's linter is friendly and tells you exactly what's wrong. The hardest part is discovering the actual field shapes. I ended up reading the source manifest JSON schema directly and everything clicked after that.&lt;/p&gt;




&lt;h2&gt;
  
  
  How to run it yourself
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Clone and install
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/Jeetubhati/shipmind
&lt;span class="nb"&gt;cd &lt;/span&gt;shipmind
python &lt;span class="nt"&gt;-m&lt;/span&gt; venv venv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Activate the virtualenv
&lt;/h3&gt;

&lt;p&gt;On Windows PowerShell:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;\venv\Scripts\Activate.ps1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On macOS or Linux:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;source &lt;/span&gt;venv/bin/activate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then install dependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Connect the four Coral sources
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;GITHUB_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ghp_...
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;LINEAR_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;lin_api_...
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;SENTRY_AUTH_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;sntryu_...
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;SENTRY_ORG_SLUG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your-org-slug
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;SLACK_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;xoxb-...

coral &lt;span class="nb"&gt;source &lt;/span&gt;add github
coral &lt;span class="nb"&gt;source &lt;/span&gt;add linear
coral &lt;span class="nb"&gt;source &lt;/span&gt;add &lt;span class="nt"&gt;--interactive&lt;/span&gt; sentry
coral &lt;span class="nb"&gt;source &lt;/span&gt;add &lt;span class="nt"&gt;--interactive&lt;/span&gt; slack
coral &lt;span class="nb"&gt;source &lt;/span&gt;add &lt;span class="nt"&gt;--file&lt;/span&gt; sources/slack_messages/slack_messages.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Configure the agent
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cp&lt;/span&gt; .env.example .env
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open &lt;code&gt;.env&lt;/code&gt; and add one LLM key:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;GROQ_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;gsk_...        &lt;span class="c"&gt;# free at console.groq.com — recommended for speed&lt;/span&gt;
&lt;span class="nv"&gt;ANTHROPIC_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;sk-...    &lt;span class="c"&gt;# alternative if you have Claude credits&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add your project identifiers too:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;GITHUB_ORG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your-org
&lt;span class="nv"&gt;GITHUB_REPO&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your-repo
&lt;span class="nv"&gt;SENTRY_ORG_SLUG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your-org-slug
&lt;span class="nv"&gt;SLACK_CHANNEL_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;C0XXXXXXXXX
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Start the dashboard
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python web/app.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open &lt;a href="http://localhost:5000" rel="noopener noreferrer"&gt;http://localhost:5000&lt;/a&gt; and click &lt;strong&gt;Run Full Release Check&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The README has a complete walkthrough including how to seed demo data if you're setting up fresh accounts.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I'd build next
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;PagerDuty integration.&lt;/strong&gt; Sentry tells you what's broken. PagerDuty tells you what's &lt;em&gt;actively paging right now&lt;/em&gt;. That's a different urgency signal and the same JOIN pattern applies directly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Push-down filter contribution upstream.&lt;/strong&gt; Coral's GitHub source doesn't currently push &lt;code&gt;WHERE state = 'closed'&lt;/code&gt; down to the GitHub API, so on a repo with 500+ PRs it fetches everything before filtering locally. Fixing that on the bundled source would speed up the readiness query for every Coral user, not just ShipMind.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Slack digest on cron.&lt;/strong&gt; Run the full check on a schedule, post the verdict as a Slack thread 30 minutes before every planned deploy window. That's roughly 20 extra lines of Python.&lt;/p&gt;




&lt;h2&gt;
  
  
  The thing I'll actually remember
&lt;/h2&gt;

&lt;p&gt;I spent day one worrying about the LLM prompts. Day two worrying about the dashboard layout. The thing that made the demo land was a single SQL query where GitHub and Sentry answered one question together — "show me the PRs and the errors they caused" — and returned real rows in 30 seconds without me writing a single API client.&lt;/p&gt;

&lt;p&gt;Everything else in ShipMind is in service of that query.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The JOIN is the feature.&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Links&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🚢 GitHub: &lt;a href="https://github.com/Jeetubhati/shipmind" rel="noopener noreferrer"&gt;github.com/Jeetubhati/shipmind&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🪸 Coral: &lt;a href="https://withcoral.com" rel="noopener noreferrer"&gt;withcoral.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🏴‍☠️ Hackathon: &lt;a href="https://www.wemakedevs.org/hackathons/coral" rel="noopener noreferrer"&gt;Pirates of the Coral-bean — WeMakeDevs&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🐠 dev.to source spec PR: &lt;a href="https://github.com/withcoral/coral/pull/1067" rel="noopener noreferrer"&gt;withcoral/coral #1067&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Jitendra Bhati · Built solo for Pirates of the Coral-bean · Track 1: Enterprise Agent · May 2026&lt;/em&gt;&lt;/p&gt;

</description>
      <category>coral</category>
      <category>hackathon</category>
      <category>wemakedevs</category>
      <category>sql</category>
    </item>
  </channel>
</rss>
