<?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: Pramod Basavaraj Menasi</title>
    <description>The latest articles on DEV Community by Pramod Basavaraj Menasi (@pm_22).</description>
    <link>https://dev.to/pm_22</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%2F4197547%2F32a6b482-6732-4b17-896e-89e139e2d974.png</url>
      <title>DEV Community: Pramod Basavaraj Menasi</title>
      <link>https://dev.to/pm_22</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/pm_22"/>
    <language>en</language>
    <item>
      <title>I built siftwise: run only the tests your change needs (open source, self-hosted)</title>
      <dc:creator>Pramod Basavaraj Menasi</dc:creator>
      <pubDate>Wed, 21 Oct 2026 07:25:08 +0000</pubDate>
      <link>https://dev.to/pm_22/i-built-siftwise-run-only-the-tests-your-change-needs-open-source-self-hosted-51jf</link>
      <guid>https://dev.to/pm_22/i-built-siftwise-run-only-the-tests-your-change-needs-open-source-self-hosted-51jf</guid>
      <description>&lt;p&gt;Your coding agent just changed one file. CI is now running &lt;strong&gt;all 18,000 tests&lt;/strong&gt;. Again.&lt;/p&gt;

&lt;p&gt;Coding agents now write code faster than CI can test it. Every pull request, whether a person or an agent wrote it, still runs the entire test suite. Anthropic reported a 25x increase in CI jobs over six months as agents sped up writing code (&lt;a href="https://claude.com/blog/agentic-coding-is-straining-ci-heres-how-we-scaled-test-impact-analysis-at-anthropic" rel="noopener noreferrer"&gt;Agentic coding is straining CI&lt;/a&gt;), and Linear wrote that &lt;a href="https://linear.app/now/ci-bottleneck-reworked" rel="noopener noreferrer"&gt;AI coding has made CI a bottleneck&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;That got me thinking: &lt;strong&gt;what if CI only ran the tests a change could actually break?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So over the last six weeks I built &lt;strong&gt;&lt;a href="https://github.com/PramodBasavarajMenasi/siftwise" rel="noopener noreferrer"&gt;siftwise&lt;/a&gt;&lt;/strong&gt;, an open-source, self-hosted tool that does exactly that. v0.1.0 is out on PyPI and GHCR.&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;siftwise
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This post is the whole guide: what it does, how to run it on your laptop, how to connect your own repo, your coding agent, and your CI. By the end you'll have everything you need.&lt;/p&gt;

&lt;h2&gt;
  
  
  The idea in one sentence
&lt;/h2&gt;

&lt;p&gt;siftwise looks at your diff, works out which tests could be affected, runs only those, and &lt;strong&gt;falls back to the full suite whenever it isn't sure&lt;/strong&gt;, because missing a real failure is worse than running a few extra tests.&lt;/p&gt;

&lt;p&gt;Test impact analysis isn't a new idea; there are good commercial tools for it. What I wanted was something &lt;strong&gt;open source, self-hosted, and built for agent-driven CI&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why you might care
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;⚡ &lt;strong&gt;Faster feedback&lt;/strong&gt;: a one-file change can run just the tests that touch that file, not the whole suite.&lt;/li&gt;
&lt;li&gt;🛡️ &lt;strong&gt;Safe by default&lt;/strong&gt;: unsure → full suite. Server down → full suite. It never blocks CI (unless you opt into &lt;code&gt;fail-on&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;🔍 &lt;strong&gt;Every test has a reason&lt;/strong&gt;: no black box. You see &lt;em&gt;why&lt;/em&gt; each test was picked.&lt;/li&gt;
&lt;li&gt;🧪 &lt;strong&gt;"Is this my fault?"&lt;/strong&gt;: every failure gets a verdict: new regression, already broken on main, flaky, or unknown.&lt;/li&gt;
&lt;li&gt;🤖 &lt;strong&gt;Agent-ready&lt;/strong&gt;: an MCP server, so a coding agent can pick tests and triage failures itself.&lt;/li&gt;
&lt;li&gt;🏠 &lt;strong&gt;Your data stays yours&lt;/strong&gt;: runs on your own machine or server, on your own Postgres. No SaaS, no account, no test instrumentation.&lt;/li&gt;
&lt;li&gt;📏 &lt;strong&gt;Proves itself&lt;/strong&gt;: shadow mode runs the full suite on a sample of PRs and tells you what selection &lt;em&gt;would&lt;/em&gt; have missed.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;select&lt;/strong&gt;: diffs your branch, builds a Python or Go import graph locally, and asks the server which tests the change can affect.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;run&lt;/strong&gt;: you run only those tests (or the full suite if siftwise isn't confident).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;record&lt;/strong&gt;: JUnit results are uploaded to the server.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;rollup&lt;/strong&gt;: per-test history is updated (failures, durations, flakiness, broken-on-main).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;triage&lt;/strong&gt;: every failure gets a verdict.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;shadow compare&lt;/strong&gt; (optional): on sampled runs, the full suite runs anyway and siftwise checks what it would have missed.&lt;/li&gt;
&lt;li&gt;In CI, a &lt;strong&gt;sticky PR comment&lt;/strong&gt; shows it all.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Selection signals
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Path mapping&lt;/strong&gt; (&lt;code&gt;test_cart.py&lt;/code&gt; ↔ &lt;code&gt;cart.py&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Import graphs&lt;/strong&gt; for Python and Go (&lt;code&gt;go list -deps -test&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Failure history&lt;/strong&gt;: tests that failed when these files changed before&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Declared rules&lt;/strong&gt; in &lt;code&gt;.siftwise.toml&lt;/code&gt;, for dependencies no import graph can see&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every selected test comes with a reason, like &lt;code&gt;imports changed module rdflib/term.py&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Supported languages
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Language&lt;/th&gt;
&lt;th&gt;How tests are selected&lt;/th&gt;
&lt;th&gt;Command siftwise gives you&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Python&lt;/strong&gt; (pytest)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;foo.py&lt;/code&gt; → &lt;code&gt;test_foo.py&lt;/code&gt;; test files importing a changed module (incl. parent packages, conftests); changed test files&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;pytest&lt;/code&gt; node IDs or test files&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Go&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;the changed package and every package whose tests import it&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;go test ./pkg&lt;/code&gt;, or &lt;code&gt;go test -run&lt;/code&gt; with the selected test names&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;JS/TS&lt;/strong&gt; (Jest)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;foo.ts&lt;/code&gt; → &lt;code&gt;foo.test.*&lt;/code&gt;, &lt;code&gt;foo.spec.*&lt;/code&gt;, &lt;code&gt;__tests__/foo.*&lt;/code&gt; (needs file paths in the JUnit)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;jest &amp;lt;files&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Java&lt;/strong&gt; (Maven/Gradle)&lt;/td&gt;
&lt;td&gt;recorded with full history (for triage); selection always runs the full suite (for now)&lt;/td&gt;
&lt;td&gt;none: you run your usual &lt;code&gt;mvn test&lt;/code&gt; or &lt;code&gt;./gradlew test&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Safe fallbacks
&lt;/h3&gt;

&lt;p&gt;Unknown diff, a changed build/CI/dependency file, no history yet, or a changed file that no signal covers: &lt;strong&gt;full suite&lt;/strong&gt;. If the server is down, CI just runs everything.&lt;/p&gt;

&lt;h3&gt;
  
  
  Failure triage
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;new_regression  high   tests.test_cart::test_total
    tests/test_cart.py tests changed src/cart.py
pre_existing    high   tests.test_db::test_query
    failing on main on ubuntu-py3.12 since 4f2a9c1 (3 main runs)
flaky           medium tests.test_net::test_fetch
    flaky 1 time in 90 days (1 unrelated fail-then-pass)
siftwise: 3 failing test(s): 1 new regression, 1 pre existing, 1 flaky, 0 unknown
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nobody wastes an hour debugging a test that was already broken on main.&lt;/p&gt;




&lt;h2&gt;
  
  
  Before you start
&lt;/h2&gt;

&lt;p&gt;You need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Docker&lt;/strong&gt; (with Docker Compose) for the server&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;git&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://docs.astral.sh/uv/" rel="noopener noreferrer"&gt;uv&lt;/a&gt;&lt;/strong&gt; (or pip / pipx) for the CLI; Python 3.11+&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Go users:&lt;/strong&gt; &lt;code&gt;go&lt;/code&gt; on your PATH, plus &lt;a href="https://github.com/gotestyourself/gotestsum" rel="noopener noreferrer"&gt;&lt;code&gt;gotestsum&lt;/code&gt;&lt;/a&gt; for JUnit output (&lt;code&gt;go install gotest.tools/gotestsum@latest&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All commands are bash. &lt;strong&gt;On Windows, use Git Bash or WSL.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Try it in 5 minutes (quickstart)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Start the server&lt;/strong&gt; (Postgres included):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/PramodBasavarajMenasi/siftwise.git
&lt;span class="nb"&gt;cd &lt;/span&gt;siftwise
&lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; .env &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"SIFTWISE_API_TOKEN=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;openssl rand &lt;span class="nt"&gt;-hex&lt;/span&gt; 32&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; .env
docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--wait&lt;/span&gt;             &lt;span class="c"&gt;# pulls the image, waits until healthy&lt;/span&gt;
curl &lt;span class="nt"&gt;-fsS&lt;/span&gt; http://localhost:8000/healthz &lt;span class="c"&gt;# {"status":"ok"}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The token in &lt;code&gt;.env&lt;/code&gt; is a shared secret you choose; the server and every client use it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Install the CLI and give it the token:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uv tool &lt;span class="nb"&gt;install &lt;/span&gt;siftwise                &lt;span class="c"&gt;# or: pip install siftwise&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;SIFTWISE_API_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s1"&gt;'s/^SIFTWISE_API_TOKEN=//p'&lt;/span&gt; .env&lt;span class="si"&gt;)&lt;/span&gt;
siftwise &lt;span class="nt"&gt;--version&lt;/span&gt;                      &lt;span class="c"&gt;# 0.1.0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;siftwise: command not found&lt;/code&gt;? Run &lt;code&gt;uv tool update-shell&lt;/code&gt; once and open a new terminal.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Record a run, change a file, and select:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;demo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;mktemp&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;/shop"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cp&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; examples/quickstart &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$demo&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$demo&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
git init &lt;span class="nt"&gt;-q&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; git add &lt;span class="nt"&gt;-A&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; git commit &lt;span class="nt"&gt;-qm&lt;/span&gt; &lt;span class="s2"&gt;"A shop"&lt;/span&gt;
uv run pytest &lt;span class="nt"&gt;-q&lt;/span&gt; &lt;span class="nt"&gt;--junitxml&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;junit.xml
siftwise record junit.xml &lt;span class="nt"&gt;--repo&lt;/span&gt; demo/shop &lt;span class="nt"&gt;--branch&lt;/span&gt; main &lt;span class="nt"&gt;--main&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"# reviewed the bulk discount"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; shop/cart.py &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; git commit &lt;span class="nt"&gt;-qam&lt;/span&gt; &lt;span class="s2"&gt;"Touch the cart"&lt;/span&gt;
siftwise &lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="nt"&gt;--repo&lt;/span&gt; demo/shop &lt;span class="nt"&gt;--base&lt;/span&gt; HEAD~1 &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; selected.sh
&lt;span class="nb"&gt;cat &lt;/span&gt;selected.sh                         &lt;span class="c"&gt;# pytest tests/test_cart.py&lt;/span&gt;
uv run sh selected.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;siftwise: python imports: 1 test file(s) import changed modules
siftwise: selective (2 of 4 known tests): 2 of 4 known tests selected
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You changed &lt;code&gt;cart.py&lt;/code&gt;, and siftwise ran only the tests that import it. 🎉&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Use it on your own project (locally)
&lt;/h2&gt;

&lt;p&gt;Keep the server running. Now point siftwise at &lt;strong&gt;your&lt;/strong&gt; repo.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2.1 Connect the CLI to the server.&lt;/strong&gt; Two environment variables are the whole connection. First print your token (in the &lt;code&gt;siftwise&lt;/code&gt; folder):&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;sed&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s1"&gt;'s/^SIFTWISE_API_TOKEN=//p'&lt;/span&gt; .env
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, in your project:&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;cd&lt;/span&gt; ~/code/my-app
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;SIFTWISE_SERVER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;http://localhost:8000     &lt;span class="c"&gt;# the default; change it for a remote server&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;SIFTWISE_API_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;&lt;span class="nb"&gt;paste &lt;/span&gt;the token&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Put both lines in your &lt;code&gt;~/.bashrc&lt;/code&gt; (or &lt;code&gt;~/.zshrc&lt;/code&gt;) so you don't have to repeat them. Never commit the token.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;2.2 Record a first full run on &lt;code&gt;main&lt;/code&gt;.&lt;/strong&gt; This gives siftwise history to work with. Pick a repo name (&lt;code&gt;owner/name&lt;/code&gt;) and &lt;strong&gt;use the same one in every command&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git checkout main

&lt;span class="c"&gt;# Python&lt;/span&gt;
pytest &lt;span class="nt"&gt;--junitxml&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;junit.xml

&lt;span class="c"&gt;# Go&lt;/span&gt;
gotestsum &lt;span class="nt"&gt;--junitfile&lt;/span&gt; junit.xml ./...

&lt;span class="c"&gt;# Jest: use a JUnit reporter that writes file paths (e.g. jest-junit with addFileAttribute="true")&lt;/span&gt;

siftwise record junit.xml &lt;span class="nt"&gt;--repo&lt;/span&gt; me/my-app &lt;span class="nt"&gt;--branch&lt;/span&gt; main &lt;span class="nt"&gt;--main&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Want history from the start? If your GitHub Actions runs already upload JUnit XML as artifacts, &lt;code&gt;scripts/backfill.py&lt;/code&gt; in the siftwise repo can import past runs (see the README's "Seed history from past CI runs").&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;2.3 Make a change and select.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git checkout &lt;span class="nt"&gt;-b&lt;/span&gt; fix-discount
&lt;span class="c"&gt;# ...edit src/my_app/pricing.py...&lt;/span&gt;
git commit &lt;span class="nt"&gt;-am&lt;/span&gt; &lt;span class="s2"&gt;"Fix discount rounding"&lt;/span&gt;

siftwise &lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="nt"&gt;--repo&lt;/span&gt; me/my-app &lt;span class="nt"&gt;--base&lt;/span&gt; main &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; selected.sh
&lt;span class="nb"&gt;cat &lt;/span&gt;selected.sh            &lt;span class="c"&gt;# only the tests your change can affect&lt;/span&gt;
sh selected.sh             &lt;span class="c"&gt;# in a uv project: uv run sh selected.sh&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;--json&lt;/code&gt; prints every selected test with the reason it was picked.&lt;/li&gt;
&lt;li&gt;An &lt;strong&gt;empty&lt;/strong&gt; &lt;code&gt;selected.sh&lt;/code&gt; means no tests are affected.&lt;/li&gt;
&lt;li&gt;If you touched something siftwise can't reason about (say &lt;code&gt;pyproject.toml&lt;/code&gt; or a CI file), &lt;code&gt;selected.sh&lt;/code&gt; runs the full suite. That's by design.&lt;/li&gt;
&lt;li&gt;Exit code &lt;strong&gt;3&lt;/strong&gt; means "full suite needed, but I don't know your command": just run your normal test command.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;2.4 A test failed? Ask whether it's your fault.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Run the failing tests with a JUnit report, then triage it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pytest tests/test_pricing.py &lt;span class="nt"&gt;--junitxml&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;junit.xml    &lt;span class="c"&gt;# the tests from selected.sh&lt;/span&gt;
siftwise triage junit.xml &lt;span class="nt"&gt;--repo&lt;/span&gt; me/my-app &lt;span class="nt"&gt;--base&lt;/span&gt; main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add &lt;code&gt;--json&lt;/code&gt; for the full evidence.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2.5 Keep feeding it.&lt;/strong&gt; Record your branch runs too (with &lt;code&gt;--base&lt;/code&gt;, so triage can link later failures to this change):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;siftwise record junit.xml &lt;span class="nt"&gt;--repo&lt;/span&gt; me/my-app &lt;span class="nt"&gt;--branch&lt;/span&gt; fix-discount &lt;span class="nt"&gt;--base&lt;/span&gt; main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The more runs it has, the better its history: flakiness, "already broken on main", and co-failures all come from recorded runs. In practice, CI does this for you (Step 4).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2.6 Add rules for what imports can't see.&lt;/strong&gt; Drop a &lt;code&gt;.siftwise.toml&lt;/code&gt; in your repo root:&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;always_run&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"tests/smoke/**"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"tests.test_health::*"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;  &lt;span class="c"&gt;# test file paths or test IDs&lt;/span&gt;
&lt;span class="py"&gt;generated&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"build/reports/**"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;          &lt;span class="c"&gt;# files your test runs write (never "changed code")&lt;/span&gt;
&lt;span class="py"&gt;known_flaky&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"tests/test_net.py"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;       &lt;span class="c"&gt;# triage: unlinked failures are flaky&lt;/span&gt;

&lt;span class="nn"&gt;[[depends]]&lt;/span&gt;                               &lt;span class="c"&gt;# dependencies no import graph can see&lt;/span&gt;
&lt;span class="py"&gt;tests&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"test/cli/**"&lt;/span&gt;                     &lt;span class="c"&gt;# test file paths, or &amp;lt;Go package dir&amp;gt;/&amp;lt;TestName&amp;gt;&lt;/span&gt;
&lt;span class="py"&gt;on&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"**/*.go"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"!**/*_test.go"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;         &lt;span class="c"&gt;# ! excludes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Globs: &lt;code&gt;**&lt;/code&gt; spans directories; &lt;code&gt;*&lt;/code&gt; and &lt;code&gt;?&lt;/code&gt; stay within one folder. Go test IDs contain the import path, so match them with &lt;code&gt;**::TestName&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2.7 Stop the server&lt;/strong&gt; when you're done: &lt;code&gt;docker compose down&lt;/code&gt; in the siftwise folder (&lt;code&gt;down -v&lt;/code&gt; also deletes its data).&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Connect your coding agent (MCP)
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;siftwise mcp&lt;/code&gt; is a standard stdio MCP server with three tools:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;The agent calls it&lt;/th&gt;
&lt;th&gt;Returns&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;select_tests&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;before running tests after a change&lt;/td&gt;
&lt;td&gt;the mode, the command to run, a reason per test&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;triage_failure&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;when tests fail, before fixing them&lt;/td&gt;
&lt;td&gt;a verdict per failure, with evidence&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;explain&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;when a selection or verdict surprises it&lt;/td&gt;
&lt;td&gt;one test's recent history and why it would (not) be selected&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;It includes &lt;strong&gt;uncommitted work&lt;/strong&gt; (the diff runs to your working tree, untracked files too), and it fails open: if the server is down, it tells the agent to run the full suite.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3.1 Install:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uv tool &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="s1"&gt;'siftwise[mcp]'&lt;/span&gt;
uv tool update-shell        &lt;span class="c"&gt;# once, if ~/.local/bin isn't on your PATH; then restart the agent&lt;/span&gt;
uv tool &lt;span class="nb"&gt;dir&lt;/span&gt; &lt;span class="nt"&gt;--bin&lt;/span&gt;           &lt;span class="c"&gt;# prints the folder, if you need the full path below&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3.2 Add it to your agent.&lt;/strong&gt; Open the agent &lt;strong&gt;inside your repository&lt;/strong&gt;: siftwise reads the repo name from your &lt;code&gt;origin&lt;/code&gt; remote and the base branch from &lt;code&gt;origin/HEAD&lt;/code&gt; (else &lt;code&gt;main&lt;/code&gt;). Override with &lt;code&gt;SIFTWISE_REPO&lt;/code&gt; / &lt;code&gt;SIFTWISE_BASE_REF&lt;/code&gt; if they don't match the name you recorded with.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Claude Code&lt;/strong&gt; (tested ✅):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude mcp add siftwise &lt;span class="nt"&gt;--env&lt;/span&gt; &lt;span class="nv"&gt;SIFTWISE_SERVER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;http://localhost:8000 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--env&lt;/span&gt; &lt;span class="nv"&gt;SIFTWISE_API_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your-token &lt;span class="nt"&gt;--&lt;/span&gt; siftwise mcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or a project &lt;code&gt;.mcp.json&lt;/code&gt;, which reads the token from your environment so it never lands in git:&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;"siftwise"&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;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"siftwise"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"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;"env"&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;"SIFTWISE_SERVER"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://localhost:8000"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"SIFTWISE_API_TOKEN"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"${SIFTWISE_API_TOKEN}"&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;&lt;strong&gt;Codex&lt;/strong&gt; (&lt;code&gt;~/.codex/config.toml&lt;/code&gt;):&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="nn"&gt;[mcp_servers.siftwise]&lt;/span&gt;
&lt;span class="py"&gt;command&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"siftwise"&lt;/span&gt;
&lt;span class="py"&gt;args&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"mcp"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="py"&gt;env&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;SIFTWISE_SERVER&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"http://localhost:8000"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;SIFTWISE_API_TOKEN&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"your-token"&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;Cursor&lt;/strong&gt; (&lt;code&gt;~/.cursor/mcp.json&lt;/code&gt; for every project, or &lt;code&gt;.cursor/mcp.json&lt;/code&gt; in one): the same JSON as above, with the token value written in.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Honest note:&lt;/strong&gt; siftwise is a standard stdio MCP server, so any MCP client should be able to run it. So far I've tested it with &lt;strong&gt;Claude Code&lt;/strong&gt;. The Codex and Cursor configs follow each tool's MCP format but haven't been tested yet. If you try them, please tell me (or open an issue)!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Agent can't find &lt;code&gt;siftwise&lt;/code&gt;?&lt;/strong&gt; (common on Windows: the agent doesn't see your shell's PATH) Use the full path from &lt;code&gt;uv tool dir --bin&lt;/code&gt; as the command. In JSON, double the backslashes: &lt;code&gt;"C:\\Users\\you\\.local\\bin\\siftwise.exe"&lt;/code&gt;. In TOML, use a single-quoted string.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3.3 Tell the agent when to use it&lt;/strong&gt;, in &lt;code&gt;CLAUDE.md&lt;/code&gt; or &lt;code&gt;AGENTS.md&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;Before running tests, call the siftwise &lt;span class="sb"&gt;`select_tests`&lt;/span&gt; tool and run the command it returns.
When tests fail, call &lt;span class="sb"&gt;`triage_failure`&lt;/span&gt; before fixing anything: fix &lt;span class="sb"&gt;`new_regression`&lt;/span&gt; failures,
re-run &lt;span class="sb"&gt;`flaky`&lt;/span&gt; ones, and leave &lt;span class="sb"&gt;`pre_existing`&lt;/span&gt; ones (already failing on main).
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What that looks like in practice&lt;/strong&gt; (an example flow):&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;You: &lt;em&gt;"Fix the discount rounding bug."&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;The agent edits &lt;code&gt;pricing.py&lt;/code&gt;, calls &lt;code&gt;select_tests&lt;/code&gt; → gets &lt;code&gt;pytest tests/test_pricing.py tests/test_cart.py&lt;/code&gt; instead of the whole suite.&lt;/li&gt;
&lt;li&gt;A test fails → it calls &lt;code&gt;triage_failure&lt;/code&gt; → &lt;code&gt;new_regression&lt;/code&gt;, linked to the file it just changed → it fixes its own bug.&lt;/li&gt;
&lt;li&gt;A different failure is &lt;code&gt;pre_existing&lt;/code&gt; (already broken on main) → the agent leaves it alone instead of "fixing" something unrelated.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Less waiting, fewer wrong fixes, and a reason for every decision.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The MCP server only reads. History comes from runs you (or CI) record, so it gets smarter once Step 4 is in place.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Step 4: Connect it to your CI (GitHub Actions)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;4.1 Host the server where CI can reach it.&lt;/strong&gt; GitHub's runners can't see your &lt;code&gt;localhost&lt;/code&gt;. The quickstart compose file deliberately binds the API to &lt;code&gt;127.0.0.1&lt;/code&gt;, so on a server you put a &lt;strong&gt;TLS reverse proxy&lt;/strong&gt; in front (the token travels in a request header).&lt;/p&gt;

&lt;p&gt;On any Linux VM with Docker:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/PramodBasavarajMenasi/siftwise.git &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;siftwise
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"SIFTWISE_API_TOKEN=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;openssl rand &lt;span class="nt"&gt;-hex&lt;/span&gt; 32&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; .env
docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--wait&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Point a DNS record (e.g. &lt;code&gt;siftwise.your-domain.com&lt;/code&gt;) at the VM, open ports 80 and 443, install &lt;a href="https://caddyserver.com/docs/install" rel="noopener noreferrer"&gt;Caddy&lt;/a&gt;, and use this &lt;code&gt;Caddyfile&lt;/code&gt; (Caddy gets the HTTPS certificate for you):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;siftwise.your-domain.com {
    reverse_proxy localhost:8000
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl reload caddy
curl &lt;span class="nt"&gt;-fsS&lt;/span&gt; https://siftwise.your-domain.com/healthz   &lt;span class="c"&gt;# {"status":"ok"}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Already have Postgres? Run just the image: &lt;code&gt;docker run -d -p 8000:8000 -e SIFTWISE_API_TOKEN=... -e SIFTWISE_DATABASE_URL=postgresql+psycopg://user:password@db-host:5432/siftwise ghcr.io/pramodbasavarajmenasi/siftwise:0.1.0&lt;/code&gt;. It applies database migrations on start.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;4.2 Add two repo secrets&lt;/strong&gt; (Settings → Secrets and variables → Actions):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Secret&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;SIFTWISE_URL&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;https://siftwise.your-domain.com&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;SIFTWISE_TOKEN&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;the server's &lt;code&gt;SIFTWISE_API_TOKEN&lt;/code&gt; (from its &lt;code&gt;.env&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;4.3 Add the workflow.&lt;/strong&gt; The &lt;code&gt;push: main&lt;/code&gt; trigger matters: main-branch runs are the history siftwise learns from.&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="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;CI&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;main&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;      &lt;span class="c1"&gt;# records main history&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;           &lt;span class="c1"&gt;# NEVER pull_request_target: it exposes secrets to fork code&lt;/span&gt;
&lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;read&lt;/span&gt;
  &lt;span class="na"&gt;actions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;read&lt;/span&gt;           &lt;span class="c1"&gt;# the run's start time&lt;/span&gt;
  &lt;span class="na"&gt;pull-requests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt;    &lt;span class="c1"&gt;# the PR comment&lt;/span&gt;
&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v7&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;fetch-depth&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;  &lt;span class="c1"&gt;# full history, so the diff works&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-python@v7&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;python-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3.12"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pip install -r requirements.txt&lt;/span&gt;   &lt;span class="c1"&gt;# your install step&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;siftwise&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PramodBasavarajMenasi/siftwise/actions/select@v0.1.0&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;server-url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.SIFTWISE_URL }}&lt;/span&gt;
          &lt;span class="na"&gt;token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.SIFTWISE_TOKEN }}&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Tests&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;MODE&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ steps.siftwise.outputs.mode }}&lt;/span&gt;
          &lt;span class="na"&gt;COMMAND&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ steps.siftwise.outputs.command }}&lt;/span&gt;
          &lt;span class="na"&gt;JUNIT_DIR&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ steps.siftwise.outputs.junit-dir }}&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;if [ "$MODE" = selective ]; then&lt;/span&gt;
            &lt;span class="s"&gt;# each pytest command writes its own report to JUNIT_DIR&lt;/span&gt;
            &lt;span class="s"&gt;if [ -n "$COMMAND" ]; then eval "$COMMAND"; else echo "No tests affected."; fi&lt;/span&gt;
          &lt;span class="s"&gt;else&lt;/span&gt;
            &lt;span class="s"&gt;pytest --junitxml="$JUNIT_DIR/full.xml"   # your normal full suite&lt;/span&gt;
          &lt;span class="s"&gt;fi&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always()&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PramodBasavarajMenasi/siftwise/actions/record@v0.1.0&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;server-url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.SIFTWISE_URL }}&lt;/span&gt;
          &lt;span class="na"&gt;token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.SIFTWISE_TOKEN }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few rules that save you debugging time:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pass the command through &lt;code&gt;env:&lt;/code&gt;&lt;/strong&gt; as above, never as &lt;code&gt;${{ }}&lt;/code&gt; inside &lt;code&gt;run:&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;record&lt;/code&gt; uploads `&lt;/strong&gt;/junit*.xml&lt;code&gt;** plus everything in &lt;/code&gt;junit-dir&lt;code&gt;. Name your report files &lt;/code&gt;junit*.xml&lt;code&gt; or set its &lt;/code&gt;junit` input.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Go:&lt;/strong&gt; write JUnit with &lt;code&gt;gotestsum --junitfile&lt;/code&gt;. &lt;strong&gt;Jest:&lt;/strong&gt; use a JUnit reporter with file paths.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Java:&lt;/strong&gt; siftwise gives no command, so run &lt;code&gt;mvn test&lt;/code&gt; or &lt;code&gt;./gradlew test&lt;/code&gt; in the full-suite branch. Their reports are named &lt;code&gt;TEST-*.xml&lt;/code&gt;, which the default glob misses, so set &lt;code&gt;junit: "**/target/surefire-reports/TEST-*.xml"&lt;/code&gt; (Maven) or &lt;code&gt;junit: "**/build/test-results/test/TEST-*.xml"&lt;/code&gt; (Gradle) on &lt;code&gt;record&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pin &lt;code&gt;@v0.1.0&lt;/code&gt;&lt;/strong&gt; (or a commit SHA) for reproducible CI.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Using a test matrix?&lt;/strong&gt; Give each leg's &lt;code&gt;record&lt;/code&gt; a &lt;code&gt;variant&lt;/code&gt;, let only one leg comment (&lt;code&gt;comment: ${{ strategy.job-index == 0 }}&lt;/code&gt;), and add one &lt;code&gt;actions/rollup&lt;/code&gt; job after all legs. Full example in the &lt;a href="https://github.com/PramodBasavarajMenasi/siftwise#matrix" rel="noopener noreferrer"&gt;README&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;4.4 Open a PR.&lt;/strong&gt; You'll get a sticky comment: what ran, what was skipped and why, and a verdict for every failure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4.5 Optional extras:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Gate on real regressions:&lt;/strong&gt; set &lt;code&gt;fail-on: new_regression,unknown&lt;/code&gt; on &lt;code&gt;record&lt;/code&gt; (single job) or &lt;code&gt;rollup&lt;/code&gt; (matrix), give the test step &lt;code&gt;continue-on-error: true&lt;/code&gt;, and make that job a required check. With &lt;code&gt;fail-on&lt;/code&gt; set, siftwise fails closed: if verdicts are unavailable, the step fails too.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Re-run unknown failures&lt;/strong&gt; to catch flakes (pytest): &lt;code&gt;rerun-unknown: 2&lt;/code&gt; and &lt;code&gt;rerun-prefix: uv run&lt;/code&gt; on &lt;code&gt;record&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shadow mode&lt;/strong&gt; (recommended at first): add &lt;code&gt;shadow: 10%&lt;/code&gt; to &lt;code&gt;select&lt;/code&gt;. On 10% of runs the full suite runs anyway, and the PR comment says what selection would have caught or missed. Check your recall any time:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;siftwise report &lt;span class="nt"&gt;--repo&lt;/span&gt; me/my-app &lt;span class="nt"&gt;--days&lt;/span&gt; 30 &lt;span class="nt"&gt;--by&lt;/span&gt; week
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  My suggested rollout
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Run the server and record &lt;code&gt;main&lt;/code&gt; for a few days (just the &lt;code&gt;push: main&lt;/code&gt; trigger).&lt;/li&gt;
&lt;li&gt;Turn on PRs with &lt;code&gt;shadow: always&lt;/code&gt; or &lt;code&gt;shadow: 10%&lt;/code&gt;, and watch the recall.&lt;/li&gt;
&lt;li&gt;Happy with the numbers? Let selection run, and add &lt;code&gt;fail-on&lt;/code&gt; if you want gating.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Security and limits (read this before you host it)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;One token, full access.&lt;/strong&gt; &lt;code&gt;SIFTWISE_API_TOKEN&lt;/code&gt; can read and write every repo on that server. No per-repo or read-only tokens yet. Use a separate server for repos that shouldn't see each other's history.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fork PRs get no secrets&lt;/strong&gt;, so they run the full suite with no comment. That's expected. Keep another required check for them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Protect &lt;code&gt;.github/workflows/&lt;/code&gt;&lt;/strong&gt; with CODEOWNERS or a ruleset: a PR that edits workflows can change any gate.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What's stored:&lt;/strong&gt; test IDs, outcomes, durations, failure messages (truncated to 4 KB), changed paths, branch names, SHAs. Treat the database like your CI logs. PR comments never show raw failure messages.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Always full suite:&lt;/strong&gt; merge-queue, scheduled and manual runs (no diff base). The Action is tested on Linux runners only.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Troubleshooting
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;You see&lt;/th&gt;
&lt;th&gt;Fix&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;siftwise: command not found&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;uv tool update-shell&lt;/code&gt;, then open a new terminal&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Every endpoint returns &lt;strong&gt;503&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;the server has no &lt;code&gt;SIFTWISE_API_TOKEN&lt;/code&gt;; check its &lt;code&gt;.env&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;select&lt;/code&gt; fails in CI about a shallow clone&lt;/td&gt;
&lt;td&gt;add &lt;code&gt;fetch-depth: 0&lt;/code&gt; to &lt;code&gt;actions/checkout&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;No PR comment&lt;/td&gt;
&lt;td&gt;check &lt;code&gt;pull-requests: write&lt;/code&gt; permission and the two secrets; fork PRs never get one&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Always the full suite&lt;/td&gt;
&lt;td&gt;no history yet (record &lt;code&gt;main&lt;/code&gt; first), or the diff touched a build/CI file; run &lt;code&gt;select --json&lt;/code&gt; to see the reason&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Server restarts with &lt;code&gt;Can't locate revision ...&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;you went back to an older image; use the newer one (with &lt;code&gt;--build&lt;/code&gt; from a checkout: rebuild after pulling)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Agent can't find &lt;code&gt;siftwise&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;use the full path from &lt;code&gt;uv tool dir --bin&lt;/code&gt; in its MCP config&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Does it actually work?
&lt;/h2&gt;

&lt;p&gt;I didn't want to just claim it works, so I replayed real CI history from open-source projects, with no peeking at the future:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Repo&lt;/th&gt;
&lt;th&gt;Failures caught&lt;/th&gt;
&lt;th&gt;Test time skipped&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;rdflib&lt;/strong&gt; (Python, ~18,000 tests)&lt;/td&gt;
&lt;td&gt;2,049 of 2,050; 140 of 141 in selective runs (the one miss: a flaky test)&lt;/td&gt;
&lt;td&gt;10.4% on the 10 most recent PRs (from lint-only PRs); 0.6% over 19 main commits&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;substrate&lt;/strong&gt; (Go, ~9,000 tests)&lt;/td&gt;
&lt;td&gt;2,043 of 2,044; 38 of 39 in selective runs, with two declared rules (26 of 38 without)&lt;/td&gt;
&lt;td&gt;5.1% on PRs, 13.9% over 40 main commits&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Cirq&lt;/strong&gt; (Python monorepo, 9 packages)&lt;/td&gt;
&lt;td&gt;not measurable (main never failed)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;37.9%&lt;/strong&gt; over 132 merged PRs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;kubo&lt;/strong&gt; (Go, ~4,100 tests, older week-2 code)&lt;/td&gt;
&lt;td&gt;19 of 19 (those runs fell back to the full suite)&lt;/td&gt;
&lt;td&gt;30.7% over 20 main commits; 0% on recent PRs (all dependency/CI bumps)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Triage:&lt;/strong&gt; on substrate, &lt;code&gt;new_regression&lt;/code&gt; verdicts held up on &lt;strong&gt;319 of 319&lt;/strong&gt; labelled failures, and only 5 of 406 labelled regressions were wrongly called flaky (4 likely label noise). On rdflib, 0 regressions were called flaky.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Speed:&lt;/strong&gt; &lt;code&gt;/select&lt;/code&gt; takes about 0.4 s on 18,000 tests.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where it falls short:&lt;/strong&gt; savings follow package boundaries. In codebases where everything imports everything (like rdflib's core), safe selection still runs ~99% of the suite. siftwise just runs it, rather than guess. These are replays of history, not live production numbers; shadow mode is how you measure it on your own repo.&lt;/p&gt;

&lt;p&gt;All the numbers and methodology: &lt;a href="https://pramodbasavarajmenasi.github.io/siftwise-blog/posts/introducing-siftwise.html" rel="noopener noreferrer"&gt;full write-up&lt;/a&gt; · &lt;a href="https://github.com/PramodBasavarajMenasi/siftwise/blob/main/docs/eval-results.md" rel="noopener noreferrer"&gt;eval results&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;v0.2: prove it and widen its reach&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Live dogfooding:&lt;/strong&gt; run siftwise on its own CI and publish live shadow-mode recall numbers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;More runners for &lt;code&gt;rerun-unknown&lt;/code&gt;:&lt;/strong&gt; Go, Jest and Java (currently pytest only).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Other MCP clients:&lt;/strong&gt; test and document Cursor and Codex.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitLab CI support&lt;/strong&gt;, beyond GitHub Actions.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;💻 GitHub: &lt;a href="https://github.com/PramodBasavarajMenasi/siftwise" rel="noopener noreferrer"&gt;https://github.com/PramodBasavarajMenasi/siftwise&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📦 PyPI: &lt;a href="https://pypi.org/project/siftwise/" rel="noopener noreferrer"&gt;https://pypi.org/project/siftwise/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🐳 Docker: &lt;code&gt;ghcr.io/pramodbasavarajmenasi/siftwise:0.1.0&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;📝 Blog: &lt;a href="https://pramodbasavarajmenasi.github.io/siftwise-blog/" rel="noopener noreferrer"&gt;https://pramodbasavarajmenasi.github.io/siftwise-blog/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it, break it, tell me
&lt;/h2&gt;

&lt;p&gt;This is v0.1.0 (alpha), built by one person in six weeks, and fully open source (Apache-2.0). The fastest way to make it better is you running it on a real repo.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;⭐ &lt;strong&gt;Star it&lt;/strong&gt; if the idea clicks: it genuinely helps a solo project get seen.&lt;/li&gt;
&lt;li&gt;🐛 &lt;strong&gt;Open an issue&lt;/strong&gt; if it picks the wrong tests. Misses are the most valuable bug reports.&lt;/li&gt;
&lt;li&gt;🛠️ &lt;strong&gt;Contribute&lt;/strong&gt;: anything on the v0.2 list (re-run runners, MCP client testing, GitLab CI) is a great place to start. See &lt;a href="https://github.com/PramodBasavarajMenasi/siftwise/blob/main/CONTRIBUTING.md" rel="noopener noreferrer"&gt;CONTRIBUTING.md&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Point it at your repo and drop your numbers in the comments. I want to know how much CI time it saves you. 👇&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>python</category>
      <category>testing</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
