{
  "markdown": "<p align=\"center\">\n  <img src=\"doc/ariadne-banner.svg\" alt=\"ariadne banner\" width=\"100%\">\n</p>\n\n<p align=\"center\">\n  <h1 align=\"center\">ariadne</h1>\n  <p align=\"center\">\n    <strong>MCP Server for Affected Test Selection</strong>\n  </p>\n  <p align=\"center\">\n    <a href=\"https://github.com/MikhailHal/ariadne/releases\"><img src=\"https://img.shields.io/github/v/release/MikhailHal/ariadne?style=flat-square&color=success\" alt=\"Release\"></a>\n    <a href=\"https://registry.modelcontextprotocol.io/v0/servers?search=io.github.MikhailHal/ariadne\"><img src=\"https://img.shields.io/badge/MCP%20Registry-listed-6E56CF.svg?style=flat-square\" alt=\"MCP Registry\"></a>\n    <a href=\"https://github.com/MikhailHal/homebrew-tap\"><img src=\"https://img.shields.io/badge/homebrew-mikhailhal%2Ftap-FBB040.svg?style=flat-square&logo=homebrew&logoColor=white\" alt=\"Homebrew\"></a>\n    <a href=\"https://github.com/MikhailHal/ariadne/pkgs/container/ariadne\"><img src=\"https://img.shields.io/badge/ghcr.io-ariadne-2496ED.svg?style=flat-square&logo=docker&logoColor=white\" alt=\"Container image\"></a>\n    <a href=\"LICENSE\"><img src=\"https://img.shields.io/badge/License-Apache%202.0-blue.svg?style=flat-square\" alt=\"License\"></a>\n    <a href=\"https://kotlinlang.org\"><img src=\"https://img.shields.io/badge/Kotlin-2.3.0-7F52FF.svg?style=flat-square&logo=kotlin&logoColor=white\" alt=\"Kotlin\"></a>\n  </p>\n</p>\n\n<br>\n\n**ariadne** is an MCP (Model Context Protocol) server that provides AI agents with the ability to identify affected tests. Powered by [sazanami](https://github.com/MikhailHal/sazanami), it analyzes code changes and returns only the tests that need to be run.\n\n<p align=\"center\">\n  <a href=\"https://glama.ai/mcp/servers/MikhailHal/ariadne\">\n    <img src=\"https://glama.ai/mcp/servers/MikhailHal/ariadne/badges/card.svg\" alt=\"ariadne MCP server\" />\n  </a>\n</p>\n\n> [!IMPORTANT]\n> ## ⚠️ MUST READ: Speed over Completeness\n>\n> ariadne is built for the agent inner loop — edit, verify, commit — where fast\n> feedback matters more than exhaustive selection. Static analysis cannot trace\n> every execution path: reflection, DI frameworks, and data-flow indirection\n> (e.g., Flux/MVI dispatch) can hide dependencies from **any**\n> affected-test-selection tool, not just ariadne.\n>\n> **Always keep a final line of defense in CI.** Run the full test suite (or a\n> conservative selection) before merging. ariadne narrows what an agent runs\n> while iterating; it is not a replacement for CI.\n>\n> When ariadne detects changes it cannot analyze (build scripts, resources,\n> unscanned source sets), it says so explicitly in the tool response instead of\n> silently reporting \"no affected tests\".\n\n## Features\n\n- **MCP Integration** — Works with Claude Code, Claude Desktop, and other MCP-compatible clients\n- **Automatic Git Diff** — No need to pass diff manually; ariadne runs `git diff` internally\n- **Powered by sazanami** — Uses Kotlin Analysis API for accurate static analysis\n\n## Installation\n\n### Homebrew (recommended)\n\n```bash\nbrew install mikhailhal/tap/ariadne\n```\n\nThen register it with your MCP client — for Claude Code:\n\n```bash\nclaude mcp add ariadne -- ariadne\n```\n\nOr add to your MCP client configuration manually (e.g., Claude Desktop):\n\n```json\n{\n  \"mcpServers\": {\n    \"ariadne\": {\n      \"command\": \"ariadne\"\n    }\n  }\n}\n```\n\n### Docker\n\n```bash\ndocker run -i --rm -v /path/to/project:/workspace ghcr.io/mikhailhal/ariadne\n```\n\nMount the project you want analyzed and pass `/workspace` as `project_path`.\nThe image is also listed in the [official MCP Registry](https://registry.modelcontextprotocol.io/v0/servers?search=io.github.MikhailHal/ariadne) as `io.github.MikhailHal/ariadne`.\n\n### Manual (release JAR)\n\nDownload `ariadne-<version>-all.jar` from [Releases](https://github.com/MikhailHal/ariadne/releases) (requires JDK 21+) and configure your client with `\"command\": \"java\", \"args\": [\"-jar\", \"/path/to/ariadne-<version>-all.jar\"]`.\n\n### Build from Source\n\n```bash\ngit clone --recursive https://github.com/MikhailHal/ariadne.git\ncd ariadne\n./gradlew shadowJar   # fat JAR: build/libs/ariadne-<version>-all.jar\n```\n\n## Usage\n\nOnce configured, AI agents can use the `get_affected_tests` tool:\n\n### Tool: `get_affected_tests`\n\n**Parameters:**\n- `project_path` (required) — Path to the Kotlin project\n- `scope` (optional, `deep` | `shallow`, default `deep`) — how far back to look:\n  - **`deep`** — all changes since `base_branch` (committed *and* uncommitted). Safest; the whole branch is covered so nothing you already committed slips through unverified. May select more tests.\n  - **`shallow`** — uncommitted changes only (diff against `HEAD`). Fastest, for the tight edit loop. Verifying already-committed work is left to the caller. `base_branch` is ignored.\n- `base_branch` (optional, `deep` scope only) — Branch to compare against. When omitted, ariadne uses the repository's default branch (`origin/HEAD`). If that is not set (e.g. a repo with no remote), it returns an error asking you to pass `base_branch` explicitly rather than guessing.\n\n**Returns:**\n- List of affected test FQNs (fully qualified names), sorted\n- If the diff contains changes outside the analyzed Kotlin sources (build scripts,\n  resources, unscanned source sets), a note is appended recommending a full test run\n  for those changes\n- Analysis is bounded by a 120s timeout; on timeout an explicit error is returned\n\n### Example\n\nAgent request:\n```json\n{\n  \"name\": \"get_affected_tests\",\n  \"arguments\": {\n    \"project_path\": \"/path/to/your/kotlin/project\"\n  }\n}\n```\n\nResponse:\n```\ncom.example.UserServiceTest.testCreateUser\ncom.example.UserRepositoryTest.testSave\n```\n\n## How It Works\n\n```\n┌─────────────┐     ┌─────────────┐     ┌─────────────┐\n│  MCP Client │ ──▶ │   ariadne   │ ──▶ │  sazanami   │\n│  (Agent)    │     │ (MCP Server)│     │  (Analysis) │\n└─────────────┘     └─────────────┘     └─────────────┘\n                           │\n                           ▼\n                    ┌─────────────┐\n                    │  git diff   │\n                    └─────────────┘\n```\n\n1. **Agent calls tool** — Passes project path to ariadne\n2. **Run git diff** — ariadne executes `git diff --unified=0` against base branch\n3. **Analyze with sazanami** — Build call graph and find affected tests\n4. **Return results** — List of test FQNs returned to agent\n\n## Real-World Validation: Now in Android\n\nMeasured against [Now in Android](https://github.com/android/nowinandroid)\n(Google's reference Android app — 34 modules, ~268 Kotlin files):\n\n| Metric | Result |\n|---|---|\n| Recall audit — 19 target functions across all layers | **18/18 valid targets detected** (the 19th had no exercising unit test; correctly not selected) |\n| End-to-end response time | **~4s** (module discovery + call-graph build + BFS) |\n| Module discovery | 34 modules via `settings.gradle.kts`, incl. nested modules and type-safe accessor dependencies |\n| Source sets | `main`, `debug`, `prod`, `benchmark`, `testDemo`, … discovered per module (`androidTest*` excluded by design) |\n\nVerified patterns include repositories behind project interfaces, a library-interface\noverride (`androidx.datastore.Serializer`), `operator fun invoke` use cases,\n`@Composable` functions, extension mappers, ViewModel property-initializer chains,\nand callable references. Two representative results:\n\n- Changing `core:common`'s `asResult()` selects **14 tests across three modules**,\n  including ViewModel tests reachable only through `val uiState = ...stateIn(...)`\n- Changing the mapper `PopulatedNewsResource.asExternalModel()` selects **14 tests**,\n  including 11 repository tests reachable only through `.map(Type::mapper)` chains\n\n### Test-class selection rate\n\nEvery unit-test class in Now in Android was measured by changing a function in the\nclass it tests and checking whether that test class was selected:\n\n| Test style | Selected |\n|---|---|\n| Plain unit tests (construct the object, call it) | 13 / 13 valid targets |\n| Robolectric / Compose screenshot tests | **12 / 12** |\n| Framework-dispatched callbacks (lint `Detector`) | 0 / 2 — see below |\n\nRobolectric turned out **not** to be a barrier: those tests call the composable\nthemselves (`setContent { NiaTheme { ... } }`), so the call exists in the source.\nWhat decides coverage is not the test runner but whether the test's own code\ncontains the call.\n\n### What ariadne cannot see\n\nThe rule of thumb: **if the framework calls your code instead of your test calling\nit, ariadne cannot connect them.** These are limits of static analysis, not bugs —\nplan your CI safety net around them:\n\n| Pattern | Status |\n|---|---|\n| Framework-invoked callbacks — Fragment/Activity lifecycle (`launchFragmentInContainer`), lint `Detector` methods, `Application.onCreate` | Not traced: no call written in the test |\n| Reflection / DI-container wiring | Not traced |\n| UDF dispatch (Flux/MVI) | `dispatch → collect` is never an edge, but wiring in `init` (or a `start()` the test calls) is covered conservatively via constructor chains. Subscriptions started by DI/lifecycle are **not** covered ([sazanami#38](https://github.com/MikhailHal/sazanami/issues/38)) |\n| `stateIn` / `shareIn` chains (`map`, `onEach`, `flatMapLatest`, `combine`) | Covered — verified with exact selection |\n| Instrumented tests (`androidTest*`) | Out of scope by design |\n| Build scripts, resources, unscanned source sets | Not analyzed — reported explicitly in the tool response |\n| Same-name top-level extensions in one package | Over-selected (receiver types are not part of top-level FQNs) — safe direction |\n| KMP source sets (`commonMain`, `expect`/`actual`) | Enumerated, but resolution quality unverified ([#1](https://github.com/MikhailHal/ariadne/issues/1)) |\n\nFull audit notes: [sazanami#29](https://github.com/MikhailHal/sazanami/issues/29),\n[sazanami#38](https://github.com/MikhailHal/sazanami/issues/38).\n\n## Requirements\n\n- **JDK 21** or later\n- **Git** — For diff detection\n\n## Limitations\n\n- Module discovery is convention-based: it parses `settings.gradle(.kts)` includes,\n  enumerates `src/<sourceSet>/{kotlin,java}` layouts, and reads `project(\":x\")` /\n  type-safe accessor dependencies from build files. Dynamic includes,\n  `projectDir` remapping, custom `srcDirs`, and dependencies injected by convention\n  plugins are not detected — see [#1](https://github.com/MikhailHal/ariadne/issues/1)\n- Full graph rebuild on each request (no caching yet); analysis is capped at 120s\n- See [What ariadne cannot see](#what-ariadne-cannot-see) for analysis-level gaps\n\n## License\n\n```\nCopyright 2025 ariadne contributors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n```\n\nSee [LICENSE](LICENSE) for the full text.\n",
  "bytes": 10848,
  "sha": "3a96e903a199abc19537bf58d9a32738066ccfcd364f989adfb5e9938b5fd162",
  "repo_slug": "mikhailhal/ariadne",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_mikhailhal_ariadne_4781276d/readme"
}