Metadata-Version: 2.4
Name: meridian-mcp
Version: 0.6.14
Summary: Meridian — Local-first AI compliance scanner. DPDPA 2023 · RBI FREE-AI · SEBI AI/ML · EU AI Act. Works with any LLM provider. AST-level code analysis across Python, TypeScript, Go, Java.
Project-URL: Homepage, https://meridiancompliance.framer.website
Project-URL: Repository, https://github.com/KNambiarDJsc/Meridian
Project-URL: Issues, https://github.com/KNambiarDJsc/Meridian/issues
License: Apache-2.0
License-File: LICENSE
Requires-Python: >=3.11
Requires-Dist: fastapi>=0.130.0
Requires-Dist: fastmcp<3.0,>=2.14.7
Requires-Dist: httpx>=0.27.0
Requires-Dist: jinja2>=3.1
Requires-Dist: keyring>=25.7.0
Requires-Dist: litellm>=1.40
Requires-Dist: networkx>=3.0
Requires-Dist: pydantic>=2.8.0
Requires-Dist: pyyaml>=6.0
Requires-Dist: questionary>=2.1.0
Requires-Dist: rich-argparse>=1.4.0
Requires-Dist: scipy>=1.10
Requires-Dist: starlette>=1.0.1
Requires-Dist: tenacity>=8.0
Requires-Dist: tree-sitter-go==0.25.0
Requires-Dist: tree-sitter-java==0.23.5
Requires-Dist: tree-sitter-javascript==0.25.0
Requires-Dist: tree-sitter-python==0.25.0
Requires-Dist: tree-sitter-typescript==0.23.2
Requires-Dist: tree-sitter==0.25.2
Requires-Dist: typer>=0.27.0
Requires-Dist: uvicorn>=0.30.0
Provides-Extra: all-providers
Requires-Dist: anthropic>=0.40; extra == 'all-providers'
Requires-Dist: boto3>=1.34; extra == 'all-providers'
Requires-Dist: groq>=0.9; extra == 'all-providers'
Requires-Dist: openai>=1.40; extra == 'all-providers'
Provides-Extra: anthropic
Requires-Dist: anthropic>=0.40; extra == 'anthropic'
Provides-Extra: bedrock
Requires-Dist: boto3>=1.34; extra == 'bedrock'
Provides-Extra: dev
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Provides-Extra: groq
Requires-Dist: groq>=0.9; extra == 'groq'
Provides-Extra: openai
Requires-Dist: openai>=1.40; extra == 'openai'
Provides-Extra: pdf
Requires-Dist: playwright>=1.40; extra == 'pdf'
Provides-Extra: terraform
Requires-Dist: tree-sitter-hcl==1.2.0; extra == 'terraform'
Description-Content-Type: text/markdown

<p align="center">
  <img src=".github/brand/logo-full.svg" alt="Meridian — AI Compliance Intelligence" width="720">
</p>

# Meridian

**A privacy-first hybrid platform that performs repository analysis locally while optionally synchronizing reports and receiving the latest intelligence from the Meridian Cloud.**

Meridian installs on your machine and analyses your repository there. Signing in is optional and adds report sync, a dashboard and scan history — it does not unlock features. Every signed-in user has access to the complete experience. See [docs/architecture/hybrid_architecture.md](docs/architecture/hybrid_architecture.md) for the Local Engine / Cloud boundary.

Meridian scans a repository deterministically, has one LLM call reason about what your business actually does, then deterministically resolves which of 19 real regulatory frameworks apply and produces an evidence-cited compliance report — DPDPA 2023, RBI FREE-AI, SEBI AI/ML, EU AI Act, GDPR, PCI DSS, SOC 2, CERT-In, HIPAA Security Rule, NIST AI RMF, and more. It answers the questions a compliance engineer would if they sat down with your CTO: **what applies, why, what's already verified, what's missing, and what to do next** — never a 1,565-row violation dump. By Karthik Nambiar

> Meridian provides automated compliance assessments and recommendations, not legal advice. Final compliance determinations should be reviewed by qualified legal, compliance, or security professionals where required. See [V1_SCOPE.md](V1_SCOPE.md) for the full V1 scope, data-handling details, and explicit non-goals.

[![Python 3.11+](https://img.shields.io/badge/python-3.11%2B-0F2A4A.svg)](https://www.python.org/downloads/)
[![License: Apache 2.0](https://img.shields.io/badge/License-Apache_2.0-0F2A4A.svg)](LICENSE)
[![Zero telemetry](https://img.shields.io/badge/telemetry-zero-0B7A4B.svg)](#privacy)

---

## How it works

Meridian is **one assessment pipeline**, not a deterministic scanner with an optional LLM bolted on. Exactly one LLM call happens per assessment — everything before and after it is deterministic and evidence-cited:

```
Repository
  ↓ (deterministic — tree-sitter parsing, no LLM)
Discovery → IR → Repository Graph → Fact Extraction → Capability Detection → Evidence Ledger
  ↓
Repository Context   (aggregated tech stack / behavioral signals — no detection of its own)
  ↓
Repository Context Interpreter        ← the ONE LLM call
  ↓
Repository Understanding + up to 5 Clarification Questions
  ↓ (a human — or an AI client on your behalf — answers)
Business Profile                       (deterministic lookup from the answers, no LLM)
  ↓ (deterministic from here on — zero LLM calls)
Framework Recommendation  →  Framework Loader  →  Objective Applicability
  ↓
Scoped Control Verdicts  →  Framework Summaries  →  Report
```

If the Interpreter is already confident about your business context, it asks zero questions and the assessment completes immediately — no human interaction required. `--offline` skips the Interpreter entirely and evaluates all 1,565 controls across all 19 frameworks deterministically, no LLM/API key needed at all.

**The compliance database** (`database/meridian.db`) is 19 real frameworks / 1,565 controls / 50 objectives / 49 capabilities, all shipped as data — not hardcoded in Python.

---

## Installation

**Recommended — [pipx](https://pipx.pypa.io):**

```bash
pipx install meridian-mcp
meridian configure   # pick a provider, paste your API key — once
meridian scan .      # done
```

`pipx` installs meridian-mcp into its own isolated environment automatically, so it can never conflict with (or be broken by) whatever else is already in your Python setup — no other installer can guarantee that. This matters in practice: a `pip install meridian-mcp` into an existing environment that already has other tools installed (data-science stacks in particular — `mlflow`, `streamlit`, `instructor`, etc. all pin old, narrow versions of shared dependencies like `rich`/`packaging`/`openai`) will print a wall of `ERROR: pip's dependency resolver...` conflict warnings. Those warnings don't mean the install failed — pip still installs meridian-mcp successfully — but `pipx` avoids the noise, and the risk, entirely.

**Alternative — plain pip**, if you're already inside a project's own virtualenv:

```bash
pip install meridian-mcp
python3 -m meridian.cli configure   # pick a provider, paste your API key — once
python3 -m meridian.cli scan .      # done
```

`python3 -m meridian.cli ...` works identically on every OS with zero setup, because it never depends on the `meridian` console script being discoverable on `PATH` — only on `python3` itself, which is already guaranteed the moment `pip` ran successfully. If your shell already has the `meridian`/`meridian-mcp` commands on `PATH` (most installs do), you can drop the `python3 -m meridian.cli` prefix and just run `meridian configure` / `meridian scan .` directly.

If `meridian configure` reports "command not found" even though `pip install` succeeded, that's a `PATH` issue, not a broken install — pip's console script lands in a folder that isn't always on `PATH` by default (especially on macOS without a venv). The `python3 -m meridian.cli` form above sidesteps it entirely; switching to `pipx install meridian-mcp` is the more permanent fix, since it also isolates meridian-mcp's dependencies from anything else already installed.

The compliance database and all runtime metadata ship **inside the package** and are copied to `~/.meridian/meridian.db` automatically on first use. Nothing else to set up.

Installing gives you three console scripts on your `PATH` (once it resolves):

| Command | What it runs |
|---|---|
| `meridian` | The interactive CLI (`scan` / `report` / `compare` / `profile` / `configure`) |
| `meridian-ci` | Identical to `meridian` — same Typer app, kept as a separate alias for CI pipeline clarity |
| `meridian-mcp` | The MCP server (13 tools) for Claude Desktop / Cursor / Windsurf / any MCP client |

Optional extras: `pip install 'meridian-mcp[pdf]'` for PDF export (Playwright), `pip install 'meridian-mcp[terraform]'` for AST-based Terraform/HCL parsing (a regex-based fallback covers it otherwise). With `pipx`, install extras with `pipx install 'meridian-mcp[pdf,terraform]'` or add them after the fact with `pipx inject meridian-mcp <extra-deps>`.

**From source (contributors):**

```bash
git clone https://github.com/KNambiarDJsc/Meridian.git
cd Meridian
python -m venv .venv
.venv\Scripts\activate        # Windows
source .venv/bin/activate     # macOS / Linux
pip install -e ".[dev]"
```

A source checkout resolves the database and metadata from the repo's own `database/` and `data/` directories automatically — same zero-flag commands, no copying either way. `--db-path` / `MERIDIAN_DB_PATH` still exist for pointing at a non-default database deliberately.

---

## Configure an LLM (first run only)

`meridian scan` needs an LLM for the one mandatory Interpreter call. On first run, if no provider is already configured, an interactive wizard walks you through it:

```
$ meridian scan .

No AI model configured.

Select Provider

1. OpenAI
2. Anthropic
3. Google Gemini
4. OpenRouter
5. Groq
6. Azure OpenAI
7. Show all providers

Enter a number: 2
```

("Show all providers" expands the list to all 15 — Ollama, AWS Bedrock, Vertex AI, Fireworks AI, Together AI, DeepSeek, xAI, Mistral, Cohere. Every provider stays fully supported; the short list is just the first screen.)

```

Select Model
  (live list from litellm — the models actually available for that provider today)

1. claude-haiku-4-5
2. claude-opus-4-8
3. claude-sonnet-4-6
...
21. Custom (enter a model name)

Enter a number: 2

Enter API Key: ****************

Save this configuration? [Y/n]: y
Saved to /home/you/.meridian/config.toml
```

The wizard writes `provider`/`model`/`api_key` to `~/.meridian/config.toml` (never inside the scanned repository) so it never runs again. **It's skipped entirely if you already have a provider's API key set as an environment variable** (`ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `GROQ_API_KEY`, etc. — see the full table below) or an existing `~/.meridian/config.toml` — Meridian never asks twice, and never re-asks if credentials are already present.

**To switch providers/models, fix a broken saved config, or just re-run the wizard on demand**, either command forces it regardless of what's already configured:

```bash
meridian configure                    # standalone — just (re)runs the wizard, then exits
meridian scan . --reconfigure         # runs the wizard first, then proceeds with the scan
```

If you don't want an LLM at all, use `--offline` (below) and skip this section entirely.

---

## CLI commands

### `meridian scan` — run a full assessment (the main command)

```bash
meridian scan <repository_path> [OPTIONS]
```

| Flag | Default | Meaning |
|---|---|---|
| `--db-path TEXT` | `~/.meridian/meridian.db` | Path to `meridian.db` |
| `--offline` | off | Skip the LLM entirely — deterministic scan only, all 1,565 controls, no questions asked |
| `--format TEXT` | `markdown` | Report format once the assessment completes: `json`, `markdown`, `sarif`, `html`, `pdf` |
| `--output TEXT` / `-o` | stdout | Write the report to a file instead of printing it (required for `pdf`) |
| `--fail-on TEXT` | `none` | Exit non-zero if any FAILED (code-verified) finding meets this severity: `none`, `low`, `medium`, `high`, `critical` — Needs Documentation/Attestation never trips it |
| `--reconfigure` | off | Force the provider/model/API key wizard to run first, even if already configured |

**Full interactive assessment** (asks clarification questions, produces a scoped report against only the frameworks that actually apply):

```bash
meridian scan .
```

You'll see a repository summary, then each clarification question with numbered options:

```
Question 1 of 3
Does the application store personal data?
1. Yes
2. No
3. Unsure
Enter a number (or press Enter to skip): 1
```

Answer each one (or press Enter to skip — that field just stays unanswered, never guessed), and the assessment completes: Framework Recommendation → Framework Loader → Objective Applicability → scoped verdicts → report, printed to stdout as markdown.

**Deterministic scan, no LLM, no questions, all 1,565 controls** (same as any earlier Meridian version's behavior):

```bash
meridian scan . --offline
# or, equivalently:
MERIDIAN_OFFLINE=1 meridian scan .
```

**Save the report to a file instead of printing it:**

```bash
meridian scan . --format json --output report.json
meridian scan . --format pdf --output report.pdf
```

**CI/CD gate — fail the build on Critical violations:**

```bash
meridian scan . --offline --fail-on critical
echo $?   # 0 = clean, 1 = violations at/above threshold found, 2 = bad arguments
```

```yaml
# .github/workflows/compliance.yml
- name: Run Meridian compliance gate
  run: meridian scan . --offline --fail-on critical
```

### `meridian report` — regenerate a report without re-answering questions

Always deterministic-only (re-runs the scan for full evidence fidelity, no LLM/Interpreter involved) — useful once you already know which frameworks matter and just want the full 1,565-control report:

```bash
meridian report <repository_path> [--format markdown|json|sarif|html|pdf] [--output FILE] [--db-path PATH]
```

### `meridian compare` — diff two historical scans

```bash
meridian compare <repository_path> <older_scan_hash> <newer_scan_hash>
```

Every `meridian scan` prints a `Scan hash:` you can use here. Shows newly-violated controls, fixed controls, and capability status changes.

### `meridian profile` — read the last scan's Repository Intelligence Profile

```bash
meridian profile <repository_path>
```

### `meridian configure` — (re)run the provider/model/API key wizard on demand

```bash
meridian configure
```

Runs the same wizard `meridian scan` runs automatically on first use, but unconditionally — even if a provider is already configured — so you can switch providers/models or overwrite a broken saved config. Equivalent to `meridian scan <path> --reconfigure` except it doesn't also run a scan afterward.

Reads persisted history — does not scan on its own; run `meridian scan` (with or without `--offline`) first.

---

## MCP server (Claude Desktop, Cursor, Windsurf, Antigravity)

`meridian-mcp` exposes 13 tools. The two that matter for the full assessment flow:

- **`start_scan(repository_path)`** — runs the deterministic scan + the Interpreter. Returns either a completed assessment (if the LLM was already confident) or a `session_id` + `repository_summary` + `questions` for the calling AI client to ask the human.
- **`continue_scan(session_id, answers)`** — resume with `{question_id: answer_text}`, completes the assessment and returns the scoped report.

**`repository_path` must already exist on the same machine as the MCP server** (your own computer, if you're using Claude Desktop). If the AI client you're talking to has its own separate code-execution/sandbox tool, cloning a repo there does **not** make it visible to this server — they're different filesystems. For a repo you only have a URL for, use the `_from_url` variants instead, which clone it locally on this machine themselves:

- **`scan_repository_from_url(repo_url, ref=None)`** / **`start_scan_from_url(repo_url, ref=None)`** (2026-07-23) — same as `scan_repository`/`start_scan`, but clone `repo_url` (shallow, HTTPS-only, `github.com`/`gitlab.com`/`bitbucket.org` by default — see `MERIDIAN_ALLOWED_GIT_HOSTS`) into a temp directory on this machine first, then clean up automatically once the scan/assessment finishes. `continue_scan` resumes a `start_scan_from_url` session exactly like a local-path one.

Plus `scan_repository`/`generate_report` (the deterministic-only, no-LLM path), `repository_profile`, `technology_inventory`, `risk_summary`, `repository_summary`, `compare_scans`, `explain_control`, and `ai_governance_summary`.

### Claude Desktop (`%APPDATA%\Claude\claude_desktop_config.json`)

**If you've already run `meridian configure`** (or set a provider key as an environment variable — see the table below), no `env` block is needed at all; the MCP server picks up the same saved config the CLI uses:

```json
{
  "mcpServers": {
    "meridian": {
      "command": "meridian-mcp"
    }
  }
}
```

**If you haven't configured a provider yet**, or want this MCP connection to use a different one than your CLI default, pass it explicitly:

```json
{
  "mcpServers": {
    "meridian": {
      "command": "meridian-mcp",
      "env": {
        "ANTHROPIC_API_KEY": "YOUR_KEY_HERE"
      }
    }
  }
}
```

### Cursor / Windsurf / Antigravity (`~/.cursor/mcp.json`, etc.)

Same two forms as above — same JSON, different file.

**`MERIDIAN_DB_PATH` is optional in both cases** — omit it and Meridian resolves to `~/.meridian/meridian.db` (auto-populated from the bundled database on first use, per install). Only set it explicitly if you deliberately want to point at a different database file, e.g. a source checkout's own `database/meridian.db`:

```json
"env": { "MERIDIAN_DB_PATH": "/absolute/path/to/your/database/meridian.db" }
```

Use the absolute path to `meridian-mcp` if it isn't globally on your `PATH` (e.g. inside a venv: `/path/to/.venv/Scripts/meridian-mcp.exe` on Windows, `/path/to/.venv/bin/meridian-mcp` on macOS/Linux).

**If `meridian-mcp` already had a database at its default path before an upgrade** (e.g. from an earlier install), it is deliberately never auto-refreshed — Meridian never overwrites a database file that already exists there, since it might contain a user's own working copy. After upgrading to a version with new/changed frameworks, delete or move the old `~/.meridian/meridian.db` first (back it up if you want to keep it) so the next run re-copies the current bundled one; otherwise every tool call keeps reading the older framework set silently.

---

## REST API + background worker

`src/meridian/api/app.py` (FastAPI) exposes `POST /scan`, `POST /scan/start` + `POST /scan/continue` (the same two-call assessment protocol as MCP), `POST /report`, `POST /compare`, `GET /findings`, `GET /controls`, `GET /profile`, plus `/admin/api-keys` and `/admin/audit-log`. It isn't installed as a console script — run it directly:

`POST /scan/remote/start` + `POST /scan/remote/continue` (2026-07-23) are the same two-call assessment protocol again, but for a caller that only has a repo URL and no access to this server's filesystem — the intended way for a cloud-based AI agent to trigger a real Meridian assessment. The server clones `repo_url` itself into an isolated, request-scoped temp directory (shallow, `github.com`/`gitlab.com`/`bitbucket.org` HTTPS only by default — see `MERIDIAN_ALLOWED_GIT_HOSTS` below) instead of trusting a client-supplied path; `/scan`, `/scan/start`, `/report`, and `/scan/async` still accept a `repository_path` for trusted self-hosted use, but that can be turned off entirely for a public deployment via `MERIDIAN_ALLOW_LOCAL_PATH_SCAN=false`. `db_path` is no longer accepted on any request — the server's own `MERIDIAN_DB_PATH` is always used.

**`/scan/remote/start` is bring-your-own-key**: `llm_model` (a litellm-routable string, e.g. `"openrouter/anthropic/claude-opus-4-8"`) and `llm_api_key` are required request fields — the *caller's* key, not the server's. A hosted, multi-caller API has no single "the person running this" to fund every remote caller's LLM usage the way the local CLI does, so each caller pays for their own. The key is used for exactly the one Interpreter call that request makes and is never logged, persisted, or returned in any response. `/scan/remote/continue` needs no LLM credentials at all — it never calls the interpreter a second time.

```bash
uvicorn meridian.api.app:app --host 0.0.0.0 --port 8000
```

Or via the bundled Docker image, which also supports a background worker mode for async scan jobs:

```bash
docker build -f deploy/docker/Dockerfile -t meridian .
docker run -p 8000:8000 -e MERIDIAN_DB_PATH=/data/meridian.db -v $(pwd)/database:/data meridian          # api mode (default)
docker run -e MERIDIAN_DB_PATH=/data/meridian.db -v $(pwd)/database:/data meridian python -m meridian.docker_entrypoint worker
```

The repo-root `Dockerfile` instead runs only `meridian-mcp` (the MCP server), for a minimal MCP-only image.

**First-login bootstrap** (no shell access assumed): set `MERIDIAN_BOOTSTRAP_ADMIN_KEY` to a high-entropy value in your deployment environment before first boot. On first boot, Meridian mints it as a real `admin`-role API key named `bootstrap-admin` — but only if `api_keys.db` is empty, so this never overwrites real key material on a later redeploy. Then `POST /admin/api-keys` with that key to mint a real permanent one, `DELETE /admin/api-keys/bootstrap-admin` to revoke the bootstrap one, and remove the env var.

**Hard constraint if you do host this**: every store under `MERIDIAN_HOME` (scan history, API keys, audit log, job queue, vector store) is plain SQLite — safe with exactly one writer. Keep instance count and `MERIDIAN_API_WORKERS` at `1` — `/scan/start`↔`/scan/continue` and `/scan/remote/start`↔`/scan/remote/continue` also hold session state in an in-memory dict local to one process, so a second worker or instance would 404 on a `/continue` call that landed on the wrong process.

---

## Environment variables

| Variable | Default | Meaning |
|---|---|---|
| `MERIDIAN_HOME` | `~/.meridian` | Base directory for the compliance database default below |
| `MERIDIAN_DB_PATH` | `$MERIDIAN_HOME/meridian.db` | Path to the compliance database |
| `MERIDIAN_OFFLINE` | unset | Set to `1`/`true`/`yes` to force the deterministic-only path (same as `meridian scan --offline`) |
| `MERIDIAN_INTERPRETER_MODEL` | resolved from `~/.meridian/config.toml` or provider default | Model for the one mandatory LLM call |
| `MERIDIAN_EVALUATOR_MODEL`, `MERIDIAN_CRITIC_MODEL` | provider defaults | Reserved for the optional LLM-augmentation layer (`evaluator/`/`critic/`) — not wired into any scan path today |
| `MERIDIAN_MAX_CONCURRENT` | `5` | Max concurrent LLM requests |
| `MERIDIAN_RPM_LIMIT`, `MERIDIAN_TPM_LIMIT` | provider-inferred | Override the auto-detected rate limits |
| `MERIDIAN_API_PORT`, `MERIDIAN_API_WORKERS` | `8000`, `1` | REST API server (Docker `api` mode). Keep workers at `1` — see the hard constraint noted just above |
| `MERIDIAN_WORKER_COUNT` | `1` | Background worker count (Docker `worker` mode) |
| `MERIDIAN_LEDGER_DIR` | `~/.meridian` | Where `scan_history.db` and the tamper-evident audit ledger live. Does **not** default from `MERIDIAN_HOME` — set both explicitly in any container deployment, or these silently miss the persistent volume |
| `MERIDIAN_ALLOW_LOCAL_PATH_SCAN` | `true` | Set to `false` to disable `repository_path`-based scanning (`/scan`, `/scan/start`, `/report`, `/scan/async`) entirely — for a public deployment where no caller should be able to make the server read an arbitrary path on its own host |
| `MERIDIAN_ALLOWED_GIT_HOSTS` | `github.com,gitlab.com,bitbucket.org` | Git hosts the `_from_url` MCP tools / `/scan/remote/start` are allowed to clone from |
| `MERIDIAN_CLONE_TIMEOUT_SECONDS` | `90` | Timeout for a `_from_url` MCP tool's / `/scan/remote/start`'s clone |
| `MERIDIAN_MAX_CLONE_MB` | `500` | Size limit (megabytes) for a repo cloned via a `_from_url` MCP tool / `/scan/remote/start` |
| `MERIDIAN_REMOTE_ASSESSMENT_TIMEOUT_SECONDS` | `300` | Timeout for the assessment itself in `/scan/remote/start` / `/scan/remote/continue` |
| `MERIDIAN_REMOTE_SESSION_TTL_SECONDS` | `1800` | How long an unfinished `/scan/start`\|`/scan/remote/start` session (waiting on `/scan/continue`\|`/scan/remote/continue`) is kept before being reclaimed |
| `MERIDIAN_BOOTSTRAP_ADMIN_KEY` | unset | Mints a one-time `admin` API key on first boot if `api_keys.db` is empty (self-hosted REST API deployments only) |
| `MERIDIAN_MAX_REQUEST_BODY_BYTES` | `2097152` (2MB) | REST API request body size limit |
| `MERIDIAN_REQUEST_TIMEOUT_SECONDS` | `600` | REST API per-request timeout (coarse outer bound — the clone/assessment timeouts above are the tighter, real limits) |
| Provider keys | — | `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `GEMINI_API_KEY`, `GROQ_API_KEY`, `OPENROUTER_API_KEY`, `AZURE_API_KEY`, `AWS_ACCESS_KEY_ID`+`AWS_SECRET_ACCESS_KEY` (Bedrock), `GOOGLE_APPLICATION_CREDENTIALS` (Vertex AI), `FIREWORKS_AI_API_KEY`, `TOGETHER_AI_API_KEY`, `DEEPSEEK_API_KEY`, `XAI_API_KEY`, `MISTRAL_API_KEY`, `COHERE_API_KEY` — any one of these, if set, skips the setup wizard |

---

## Project structure

```
src/meridian/
├── cli.py                     # meridian / meridian-ci — scan, report, compare, profile
├── cli_setup.py                # Interactive LLM setup wizard + clarification-question prompting
├── config.py                    # Provider/model config, live litellm model catalogue, ~/.meridian/config.toml
├── engine.py                     # MeridianEngine — the one orchestration layer every transport calls
├── scan_state.py                  # ScanPaused / ScanCompleted / ScanFailed — the engine's execution contract
├── scan_orchestrator.py            # run_scan() / run_and_persist_scan() — the M1-M6 deterministic pipeline
│
├── discovery.py, parsers/, ir/, ir_builder.py, graph/     # Repository → IR → Repository Graph
├── facts/, detectors/, evidence/, inventory/               # Fact extraction → capability detection → evidence
├── compliance/, verdicts/                                    # meridian.db access, control verdict resolution
├── profile/, ai_governance/                                    # Repository Intelligence Profile, AI-BOM/governance
│
├── context/, interpreter/, assessment/                     # Sprint 1-2: Repository Context + the one LLM call
├── framework_recommendation/, framework_loader/              # Sprint 2-3: which frameworks apply
├── objective_applicability/                                    # Sprint 4: which objectives/controls apply
│
├── report/                     # ReportContext + markdown/json/sarif/html/pdf exporters
├── scan_ledger/, ledger.py      # Scan history, comparison, tamper-evident audit chain
├── search/, embeddings/          # Semantic search index (rebuilt explicitly, not on every scan)
│
├── mcp/server.py                # FastMCP server, 13 tools
├── api/                           # FastAPI REST server
└── workers/                        # Background async scan job queue
```

`evaluator/`, `critic/`, `augmentation/`, `clustering/`, `cache_engine/`, `annotation/` are real, tested subsystems not currently wired into any scan path — see `CLAUDE.md` for the full picture of what's live vs. built-but-disconnected.

---

## Running tests

```bash
pytest tests/ -v
pytest tests/test_cli.py -v                    # a single file
pytest tests/test_cli.py::test_scan_real_counts -v   # a single test
```

Most e2e tests need the local `database/meridian.db` (present in the repo) — they skip cleanly if it's missing. A handful of older tests additionally expect a real Flask checkout at `/tmp/flask-repo` and skip without it.

---

## Privacy

Meridian is local-first, and the claim is verifiable in the code — not marketing copy:

- **Your code never leaves your machine.** The entire scan pipeline (discovery, parsing, fact extraction, capability detection, evidence, verdicts, reporting) runs locally against the bundled compliance database. Meridian has no servers in any code path — there is nowhere to upload your repository *to*.
- **Zero telemetry.** Meridian sends no usage data anywhere, and LiteLLM's own telemetry is explicitly disabled in code (`litellm.telemetry = False` in `evaluator/provider.py`).
- **Keys never leave your machine.** API keys are read from your environment or `~/.meridian/config.toml` and passed directly to the provider SDK via [litellm](https://github.com/BerriAI/litellm) — no Meridian-run proxy, no logging of credentials.
- **Exactly one LLM call per assessment**, made directly from your machine to the provider you chose, with your own key. Every control verdict, every framework applicability decision, every objective evaluation is deterministic — nothing about compliance reasoning is ever sent to or decided by an LLM.
- **`--offline` needs no network access and no API key at all.**
- No other network access exists anywhere in the codebase beyond the one LLM call above and the one-time package install itself — grep-verified, not just asserted.

---

## License

Apache 2.0 — see [LICENSE](LICENSE). All 19 frameworks are available unconditionally; there is no tiered/licensed feature gate in the current codebase.

---
<!-- mcp-name: io.github.KNambiarDJsc/meridian-mcp -->
