Skip to content

Latest commit

Β 

History

History
597 lines (452 loc) Β· 29.3 KB

File metadata and controls

597 lines (452 loc) Β· 29.3 KB

Contributing to trelix

Thank you for your interest in contributing! This guide covers dev setup, testing, and how to add a new language parser or LLM provider.

Development Setup

git clone https://github.com/sairam0424/trelix
cd trelix

# Create virtualenv
python -m venv .venv
source .venv/bin/activate   # Windows: .venv\Scripts\activate

# Install in editable mode with all dev + optional deps
make install-dev
# equivalent: pip install -e ".[bge-code,plaid,lance,serve,dev]"
# Optional extras:
#   [bge-code]        β€” BGE-Code embeddings (requires torch, transformers)
#   [plaid]           β€” Plaid financial data integration
#   [lance]           β€” LanceDB vector store for large-scale deployments
#   [serve]           β€” REST API server (FastAPI + Uvicorn)
#   [knowledge-graph] β€” Knowledge graph + visualization (pyvis>=0.3.2, networkx>=3.3.0)
#   [graph-viz]       β€” Alias for [knowledge-graph]
#   [watch]           β€” Multi-repo file watching (watchfiles)
#   [dev]             β€” testing, linting, type-checking (always included)

# Standard dev setup (no graph visualization)
pip install -e ".[local,dev]"

# Include graph visualization (pyvis + networkx)
pip install -e ".[local,dev,knowledge-graph]"

# Copy environment template
cp .env.example .env
# Edit .env β€” at minimum set TRELIX_EMBEDDER_PROVIDER=local

Running Tests

make test           # unit + MCP: 3,114 unit + 102 MCP = 3,216 tests (no coverage)
make test-fast      # unit tests only, 3,114 (no API calls, fast)
make test-cov       # unit tests with the coverage report
make lint           # ruff check + ruff format (auto-formats before diff-check, cross-platform safe)
make format         # ruff format
make typecheck      # mypy

Note on CI checks: The ruff format step runs as part of linting β€” files are auto-formatted before the diff-check, ensuring cross-platform consistency (Windows CRLF vs Unix LF).

Running specific test subsets

# Unit tests only β€” no credentials needed
# Collects 3,114 of 3,220 and deselects the 106 integration tests.
pytest -m "not integration"

# Live integration tests β€” require Azure or AWS credentials (104 tests)
pytest tests/integration/
# tests/integration/test_llm_e2e.py covers Azure + Bedrock chat and embeddings;
# individual tests skip gracefully when the relevant credentials are absent

You do not apply the integration marker by hand: tests/integration/conftest.py applies it to every test in that directory, so a new file there is credential-gated on arrival. The marker is registered in pyproject.toml under [tool.pytest.ini_options] markers, and addopts carries --strict-markers so a typo becomes a collection error. Both matter β€” while the marker was unregistered, -m "not integration" matched nothing and quietly ran the entire suite including the live Azure/Bedrock tests.

Branch Strategy

main          ← stable releases only (do not push directly)
  └─ develop  ← integration branch β€” open PRs here
       └─ feature/<name>  ← your work
  1. Fork the repo and create a branch from develop: git checkout -b feature/my-feature develop
  2. Make your changes with tests
  3. Open a PR targeting develop (not main)

Extension Points

trelix/graph/ β€” Knowledge Graph module

The graph module lives at src/trelix/graph/ and is organized as:

File Responsibility
code_graph.py CodeGraph β€” NetworkX MultiDiGraph over SQLite edge tables
community.py Community detection (Louvain/Girvan-Newman)
persistence.py Save/load community + centrality to graph_metadata table
concepts.py LLM semantic concept extraction (crash-safe)
builder.py GraphBuilder β€” orchestrates the full pipeline
visualizer.py Pyvis HTML export (requires trelix[knowledge-graph])
search.py BFS graph_search function (4th retrieval leg)

Tests live in tests/unit/test_graph_*.py. All graph tests can run without pyvis or any LLM configured.

Opt-in config keys (all default to off β€” zero impact when disabled):

Key Default Env var
graph_search_enabled False TRELIX_RETRIEVAL_GRAPH_SEARCH_ENABLED=true
graph_search_depth 2 β€”
graph_search_max_results 15 β€”

Adding a new graph algorithm:

  1. Add the implementation to the most appropriate existing file or create a new file under src/trelix/graph/
  2. Expose it through GraphBuilder in builder.py so the pipeline can call it
  3. If it requires a new optional dependency, add an extras group to pyproject.toml and document it here
  4. Write tests in tests/unit/test_graph_<name>.py β€” mock any LLM calls; do not require pyvis

trelix/retrieval/ β€” Query Enhancement Modules

The retrieval enhancement modules live at src/trelix/retrieval/ and are organized as:

File Responsibility
query_expansion.py HyDEExpander (synthetic snippet embedding), MultiQueryExpander (N-variant recall)
flare.py FLARELoop β€” confidence-gated re-retrieval, _contains_uncertainty phrase check
telemetry.py TelemetryWriter β€” crash-safe per-query latency/intent recorder

All three modules are crash-safe (return empty/original on any failure) and gated by config flags.

Opt-in config keys (all default to off β€” zero impact when disabled):

Key Default Env var
query_expansion_enabled False TRELIX_QUERY_EXPANSION_ENABLED=true
flare_enabled False TRELIX_FLARE_ENABLED=true
telemetry_enabled False TRELIX_TELEMETRY_ENABLED=true

Adding a new query enhancement:

  1. Add the implementation under src/trelix/retrieval/
  2. Ensure any failure path returns the original query or empty results β€” never raises
  3. Gate the feature with a config flag defaulting to False
  4. Write tests in tests/unit/test_retrieval_<name>.py β€” mock any LLM calls

trelix/eval/ β€” Evaluation Harness

The evaluation harness lives at src/trelix/eval/ and is organized as:

File Responsibility
ndcg.py Pure-Python ndcg_at_k, recall_at_k, mrr β€” no pandas dependency
harness.py EvalHarness.run(golden_path) β€” reads JSONL, retrieves, returns aggregate metrics

Usage:

trelix eval --golden .trelix/golden.jsonl

Golden file format (one line per query):

{"query": "how does auth work", "relevant_files": ["src/auth.py"]}

Adding new metrics:

  1. Add the pure-Python metric function to src/trelix/eval/ndcg.py
  2. Wire it into EvalHarness.run() in src/trelix/eval/harness.py
  3. Write tests in tests/unit/test_eval_<name>.py β€” no LLM calls required for metric functions

trelix/agent/ β€” ReAct Agentic Loop

The agent module lives at src/trelix/agent/ and implements a ReAct (Reason + Act) loop over the trelix retrieval stack:

File Responsibility
actions.py ActionType enum, AgentAction, Observation, Turn dataclasses
history.py TurnHistory, HistoryCompressor (token-budget context trimming)
tools.py OpenAI function-calling tool schemas for 4 actions
loop.py AgentLoop orchestrator — ReAct Thought→Action→Observation cycle

All agent tests live in tests/unit/test_agent_*.py. No LLM calls are needed β€” TrelixChatClient is mocked throughout the test suite.

trelix/analysis/ β€” Program Analysis

The analysis module lives at src/trelix/analysis/ and provides static program analysis on top of the indexed codebase:

File Responsibility
defuse.py DataFlowExtractor β€” tree-sitter def-use chain extraction (crash-safe)
taint.py TaintAnalyzer β€” Semgrep CLI wrapper (requires trelix[taint])

To use taint analysis, install the optional extra:

pip install -e ".[taint]"

Tests that exercise TaintAnalyzer mock the subprocess call, so the full test suite runs without Semgrep installed.

trelix/embedder/sparse.py and trelix/store/sparse_store.py β€” Sparse Embeddings

SparseEmbedder produces {token_id: weight} SPLADE-Code vectors and requires the optional extra:

pip install -e ".[sparse]"

SparseStore is a SQLite inverted index β€” no external service or vector database is needed. Tests run without torch: SparseEmbedder returns {} automatically when _TORCH_AVAILABLE=False, so the sparse test suite passes in any environment.

Adding a New Language Parser

  1. Create src/trelix/indexing/parser/extractors/<language>.py
  2. Subclass BaseParser from src/trelix/indexing/parser/base.py
  3. Implement parse(source: str, file_id: int) -> ParseResult
  4. Register in src/trelix/indexing/parser/registry.py: add Language.YOURLANG: YourParser()
  5. Add file extensions to EXTENSION_MAP in src/trelix/indexing/walker.py
  6. Add Language.YOURLANG to WalkerConfig.languages default list in src/trelix/core/config.py
  7. Write tests in tests/unit/test_parser_<language>.py with fixture files

Embedder Providers

trelix ships with built-in support for multiple embedding backends:

  • Local embeddings (local) β€” Uses transformers library (default, no API keys needed)
  • BGE-Code-v1 (bge-code) β€” BAAI General Embedding for code. Experimental: the wrapper pools with CLS while BAAI publishes pooling_mode_lasttoken: true, so retrieval quality is unverified (see docs/PROVIDERS.md)
  • Nomic CodeRankEmbed (nomic-code) β€” Open-source embeddings specialized for code ranking
  • Azure OpenAI Embeddings β€” Enterprise deployment via Azure; set TRELIX_EMBEDDER_PROVIDER=azure
  • Bedrock β€” AWS-hosted embeddings via Bedrock

To use BGE-Code or CodeRank embeddings, install the optional extra:

pip install -e ".[bge-code]"
# Then set TRELIX_EMBEDDER_PROVIDER=bge-code in .env

Adding a New LLM Provider

trelix uses a provider-agnostic TrelixChatClient ABC (src/trelix/llm/client.py). All five built-in backends (OpenAIBackend, AnthropicBackend, BedrockBackend, VertexBackend, LiteLLMBackend) implement the same three methods: complete(), stream(), and tool_call(). Adding a new provider requires zero changes to business logic (chunker, synthesizer, planner, graph_rag).

  1. Create src/trelix/llm/providers/<name>_backend.py
  2. Subclass TrelixChatClient and implement complete(), stream(), tool_call(). In complete(), translate the provider's stop field through a table in src/trelix/llm/finish_reasons.py (normalise, or classify_chat_choice for an OpenAI-shaped choices[0]): a value nobody has classified, or a missing one, is unknown, never stop, because providers return refusals, filtered replies and truncations as ordinary successful responses. Add the provider's values to tests/unit/test_llm_finish_reason_matrix.py. The "not configured" placeholder a backend returns when it has no credentials keeps finish_reason="stop" and is recognised by model == UNCONFIGURED_MODEL, so check that, not the finish reason
  3. Add a case "<name>": branch to src/trelix/llm/factory.py (build_chat_client())
  4. Add credential fields to LLMConfig in src/trelix/core/config.py
  5. Add "<name>" to the Literal type of LLMConfig.provider
  6. Add an optional dep group to pyproject.toml if the provider SDK is not already a dependency
  7. Write unit tests in tests/unit/test_llm_<name>_backend.py β€” mock the provider SDK, no real API calls

No changes are needed in chunker.py, synthesizer.py, planner/agent.py, or graph_rag.py.

Adding federation cache configuration

FederatedRetriever (src/trelix/federation/retriever.py) ships with a SHA-256 keyed, thread-safe TTL cache:

from trelix.federation.retriever import FederatedRetriever

# Default: 120-second TTL
fed = FederatedRetriever(registry)

# Custom TTL
fed = FederatedRetriever(registry, cache_ttl=300.0)

# Disable caching entirely
fed = FederatedRetriever(registry, cache_ttl=0)

# Inspect cache performance
stats = fed.cache_stats()   # -> {"hits": int, "misses": int, "size": int}

# Invalidate all cached results
fed.clear_cache()

The cache achieves ~90% hit rate for typical debugging sessions. Set cache_ttl=0 in tests to avoid stale results across test cases.

Adding multi-repo watchers

MultiRepoWatcher (src/trelix/indexing/multi_watcher.py) drives a single watchfiles.awatch() call over all repos registered in RepoRegistry. A SHA-256 hash guard prevents re-indexing unchanged files; deleted files are removed from both SQLite and the vector store.

# Install the watch extra
pip install -e ".[watch]"

# Watch all registered repos
trelix watch-all

To integrate programmatically:

from trelix.indexing.multi_watcher import MultiRepoWatcher

watcher = MultiRepoWatcher(registry=repo_registry, indexer=indexer)
await watcher.run()   # streams per-repo stats; graceful on KeyboardInterrupt

GitHub PR review integration

GitHubPRClient (src/trelix/review/github.py) fetches PR diffs via the GitHub REST API and posts batched review comments back. Requires the GITHUB_TOKEN environment variable.

# Review a PR diff locally
trelix review --pr owner/repo#42

# Review and post findings as a GitHub review
trelix review --pr owner/repo#42 --post-comments

All seven GitHub file status values (added, modified, removed, renamed, copied, changed, unchanged) are handled. PRs with more than 3,000 files emit a truncation warning.

To call DiffReviewer directly with a raw unified diff string (skipping file I/O):

from trelix.review.diff import DiffReviewer

reviewer = DiffReviewer(llm_client=client)
findings = await reviewer.review(diff_text=raw_unified_diff)

parse_pr_ref("owner/repo#42") is the canonical helper for parsing --pr argument values.

Coding Standards

  • Python 3.12+ type hints everywhere
  • Line length: 100 chars (ruff enforced)
  • No mutable default arguments
  • New objects, never mutate in-place
  • Functions > 50 lines should be split

Reporting Issues

Use the GitHub issue templates:

  • Bug report β€” include Python version, OS, trelix version, minimal reproduction
  • Feature request β€” describe the use case, not just the solution

Questions

Open a GitHub Discussion for questions.

Versioning & Stability Policy

trelix follows Semantic Versioning 2.0.0. Read the current version from the tree rather than from this sentence:

python -c "import trelix; print(trelix.__version__)"

This section deliberately no longer carries a copy of the number. It said "Current version: 2.12.0" while v3.0.0, v3.0.1, v3.1.0, v3.1.1 and v3.1.2 shipped β€” five releases of rot inside the very section that tells you to keep versions in sync. The doc-stamp grep in the release checklist below would now catch it, but a prose stamp no release step has to touch is better deleted than monitored.

Stable public API (guaranteed not to change without a major version bump)

  • CLI commands and flags: trelix index, trelix search, trelix ask, trelix query, trelix stats, trelix watch, trelix update-index, trelix migrate-vectors and all documented flags
  • Python API: IndexConfig, EmbedderConfig, LLMConfig, Indexer, Retriever, TrelixChatClient, ChatMessage, ChatResponse, ToolCallResponse, build_chat_client, BaseEmbedder, make_embedder
  • Sub-package interfaces: TrelixRetriever (trelix-langchain), TrelixIndexRetriever (trelix-llama-index), MCP tool signatures (trelix-mcp) β€” note: search_code return type changed to {results, next_cursor, total_available} envelope in v2.4.0 (see Breaking Changes in CHANGELOG)
  • Environment variable names: all TRELIX_* env vars documented in .env.example

What counts as a breaking change

  • Removing or renaming a public class, method, or CLI flag
  • Changing a method signature in an incompatible way
  • Changing the SQLite schema in a way that requires re-indexing
  • Removing a previously supported Python version

CLI command renames (e.g. trelix graph β†’ trelix call-graph in v2.0.0) are breaking changes and must be documented under a ### Breaking Changes heading in CHANGELOG.md for the relevant release, alongside a migration note showing the old and new invocation.

Deprecation policy

  • Deprecated features are marked with DeprecationWarning and noted in the CHANGELOG
  • The grace period is minimum 2 minor versions and minimum 3 months, whichever lands later, with removal only on a major bump. docs/BACKWARDS_COMPATIBILITY.md is authoritative β€” go there for the reasoning and the current deprecation table
  • The CLI will print a deprecation notice on first use of deprecated flags

This file previously said "at least one minor version", contradicting the policy doc's "2 minor versions". The stricter number won: trelix shipped eight minor releases in the 30 days from v2.4.0 to v2.12.0, so a one-minor grace period can be over in days.

Python version support

  • Supported: Python 3.12, 3.13, 3.14
  • Dropped versions are announced one minor release in advance

Release checklist β€” the twelve version stamps

Eleven files carry the release version, in twelve stamps β€” server.json carries it twice. All twelve are gated. Bump them together. Missing one ships a package whose --version disagrees with its metadata, or a Helm chart that advertises one version and deploys another; neither is hypothetical. helm/trelix/values.yaml's image.tag sat on 2.12.0 while Chart.yaml advertised appVersion: 3.1.2, and both adapters sat at 2.4.0 while core reached 3.1.2 β€” which skip-existing: true turned into a silent 2-of-4 publish on every tag.

# Site Key Gated by verify-version
1 pyproject.toml [project] version yes
2 src/trelix/__init__.py __version__ yes
3 packages/trelix-mcp/pyproject.toml [project] version yes
4 packages/trelix-mcp/src/trelix_mcp/__init__.py __version__ yes
5 helm/trelix/Chart.yaml appVersion: yes
6 helm/trelix/values.yaml image.tag yes
7 packages/trelix-mcp/server.json version and packages[0].version yes (both)
8 packages/trelix-langchain/pyproject.toml [project] version yes
9 packages/trelix-langchain/src/trelix_langchain/__init__.py __version__ yes
10 packages/trelix-llama-index/pyproject.toml [project] version yes
11 packages/trelix-llama-index/src/trelix_llama_index/__init__.py __version__ yes

.github/workflows/release.yml's verify-version job now fails the release if a v* tag disagrees with any of those stamps, which is what turns a missed bump from a silent mis-publish into a red build. It runs twelve check calls β€” one per stamp above, with no exceptions. Site 4 used to be one: it was documented as "verify it by hand until that check is added", which is the same silent-mis-publish risk as the adapters had, so it is now checked like the rest.

Sites 4 and 8–11 are newly gated. docs/BACKWARDS_COMPATIBILITY.md has always put all three integration packages on the core version; both adapters sat at 2.4.0 anyway, across the seventeen releases that shipped after it, and nothing in CI noticed. Read the jump as a re-alignment to that line rather than as adapter change: git diff v2.4.0..HEAD -- 'packages/trelix-*/src/*/retriever.py' is 13 insertions and 3 deletions, all type annotations, so 3.1.2 adds no feature and breaks nothing the adapters exposed at 2.4.0.

Two more files hold the number without being sites of their own: packages/trelix-langchain/tests/test_retriever.py and packages/trelix-llama-index/tests/test_retriever.py each assert their own package's __version__. Bump those literals too β€” but they are assertions on sites 9 and 11, not independent stamps, so they stay out of the table and out of the count. Skipping them turns a suite red rather than shipping anything wrong, and release.yml's test job now runs both adapter suites, so that red arrives on the tag and not only in ci.yml (which never fires on one).

Two things that are not version sites, and must not be bumped with them:

  • helm/trelix/Chart.yaml has both version: (the chart's own version, independent of trelix; the comment in that file says when it moves) and appVersion: (which tracks trelix). Only appVersion is a version stamp.
  • Both adapters' dependencies = ["trelix>=3.0.0", ...]. Lockstep governs the version stamp β€” identity β€” not the dependency floor, which is a compatibility contract that moves only when an import demands it. Each pyproject.toml:35 records why it reads 3.0.0: "the lowest published core verified to expose every name used here." An adapter stamped 3.1.2 that declares trelix>=3.0.0 is saying something true. Raising the floor to match a release is the mistake CHANGELOG v2.7.1 already reverted ("Unjustified dependency-floor bumps reverted") on these same two packages, where it had been raised on an unverified assumption about API usage.

Verify by printing what each site says β€” never by grepping for a version string

Both directions of that grep fail silently, which is how image.tag stayed on 2.12.0 across five releases:

  • Grepping for the new version lists only the sites already bumped. A stale site produces no line, and a missing line is not a signal you will notice. Reproduced on a tree with values.yaml and server.json left at 2.12.0: a grep for the new version over the five previously-listed files printed five clean hits and exited 0.
  • Grepping for the previous version cannot see a site that skipped a release. The same tree, grepped for the previous version, returned zero hits β€” the stale sites read 2.12.0, not the previous version. A site stranded on 2.x is structurally invisible to this check β€” which is why verify-version, not any grep in this checklist, is the actual gate. Every site that had really drifted was of that class: image.tag, both of server.json's fields, and both adapters at 2.4.0.

So print the value each site actually holds and collapse them:

python - <<'PY'
import json, re, tomllib
from pathlib import Path
def toml(p): return tomllib.load(open(p, "rb"))["project"]["version"]
def rx(p, pat): return re.search(pat, Path(p).read_text(), re.M).group(1)
sj = json.load(open("packages/trelix-mcp/server.json"))
for label, got in [
    ("pyproject.toml", toml("pyproject.toml")),
    ("src/trelix/__init__.py", rx("src/trelix/__init__.py", r'__version__ = "([^"]+)"')),
    ("trelix-mcp/pyproject.toml", toml("packages/trelix-mcp/pyproject.toml")),
    ("trelix_mcp/__init__.py", rx("packages/trelix-mcp/src/trelix_mcp/__init__.py", r'__version__ = "([^"]+)"')),
    ("Chart.yaml appVersion", rx("helm/trelix/Chart.yaml", r'^appVersion:\s*"?([^"\s]+)')),
    ("values.yaml image.tag", rx("helm/trelix/values.yaml", r'^\s+tag:\s*"?([^"\s]+)')),
    ("server.json version", sj["version"]),
    ("server.json packages[0]", sj["packages"][0]["version"]),
    ("trelix-langchain/pyproject.toml", toml("packages/trelix-langchain/pyproject.toml")),
    ("trelix_langchain/__init__.py", rx("packages/trelix-langchain/src/trelix_langchain/__init__.py", r'__version__ = "([^"]+)"')),
    ("trelix-llama-index/pyproject.toml", toml("packages/trelix-llama-index/pyproject.toml")),
    ("trelix_llama_index/__init__.py", rx("packages/trelix-llama-index/src/trelix_llama_index/__init__.py", r'__version__ = "([^"]+)"')),
]:
    print(f"{got:<10} {label}")
PY

Twelve lines out, all the same version. Pipe it through | awk '{print $1}' | sort -u and you should get exactly one line β€” more than one means a stale site, named rather than merely absent. This mirrors verify-version's own extraction site for site, so a clean local run predicts a green tag. tests/unit/test_release_version_gate.py asserts the same agreement in CI, which is the part that catches drift before a tag exists at all β€” the gate itself can only fail once someone has cut one.

Doc version stamps

Doc stamps rot every release, and they rot to arbitrary old versions β€” this file's own "Current version" line reached 2.12.0-vs-3.1.2, five releases behind β€” so grepping for the previous version misses exactly the worst cases. Grep for the assertions that must name the current version, whatever number they currently hold:

grep -rnE '[Cc]urrent version|trelix(-mcp|-langchain|-llama-index)?==|^\*\*Version|image: .*trelix:' \
    docs/*.md *.md packages/*/README.md \
  | grep -v '^CHANGELOG.md' \
  | grep -viE "new in|fixed in|added in|since v|what's new|removed in|deprecated in"

That returns 15 readable lines β€” several of which are this section quoting its own pattern β€” rather than the 251 that "every semver-shaped token that isn't the current version" produces over the same files. It catches the stamps a previous-version grep cannot: docs/CLI_REFERENCE.md's **Version:** header, the adapter == install pins in docs/FAQ.md's pin-your-requirements answer, a Helm image: …trelix: tag. The pattern finds those pins by package name, not by number, so it keeps working across bumps β€” naming the number here would only rot this sentence. docs/LANGCHAIN_LLAMAINDEX_GUIDE.md no longer has any: its install lines are deliberately unpinned, since a version hardcoded into a doc's own install command is what rotted them last time. CHANGELOG.md is excluded on purpose β€” it is an append-only historical record, never a stamp to bump.

packages/*/README.md is in scope because each distribution's pyproject.toml sets readme = "README.md", making those files the PyPI long description β€” the project page readers copy commands from. The glob was docs/*.md *.md until this release, which never descended into packages/, and that blind spot is exactly where the worst rot was: packages/trelix-mcp/README.md hard-pinned trelix-mcp==2.12.0 in seven places, including the primary command under its own ## Install heading, while the package shipped 3.1.2. A grep with a blind spot reads as coverage. tests/unit/test_readme_install_commands.py now asserts the same properties in CI, so this does not depend on anyone remembering to run the grep.

Read every hit before editing. A blind sed over docs/ will silently rewrite "New in v3.0.0" and the shipped-version table in ROADMAP.md, turning accurate history into a false claim.

Every stamp above is a property of the source tree. Once the tag is pushed, .github/workflows/verify-release.yml runs scripts/verify_release.py for you automatically β€” it waits for release.yml's Release workflow AND docker-publish.yml's Docker Publish workflow to both go green for that tag, then runs every check and posts the same PASS/FAIL summary. Watch the "Verify Release" run in the Actions tab before announcing the release. The manual command remains available for ad-hoc re-verification β€” e.g. after fixing a bug in the script itself, or re-checking an older release: python scripts/verify_release.py --version X.Y.Z (confirm every check passes) β€” it verifies the published artifacts themselves (PyPI, the Docker images, the Helm chart at the tag, the GitHub Release binaries), which is a different claim than "the stamps agreed with the tag."

Publish trelix-mcp to the MCP registry

A green "Verify Release" run is not the end of a release: nothing in CI updates the MCP registry, so that entry only moves when someone runs the publish by hand. That is how the io.github.sairam0424/trelix entry came to read 0.5.1 when PyPI already had 3.4.2. Once PyPI shows the new trelix-mcp version and "Verify Release" is green, publish the same version to the registry. It has to come after PyPI, because the registry checks the package named in server.json against PyPI, where it looks for the mcp-name: line that packages/trelix-mcp/README.md carries (keep it).

cd packages/trelix-mcp
mcp-publisher validate      # server.json against the registry schema; both of its version fields are site 7 above
mcp-publisher login github  # GitHub device flow, as the account that owns the io.github.sairam0424 namespace
mcp-publisher publish       # publishes ./server.json
curl -s 'https://registry.modelcontextprotocol.io/v0/servers?search=io.github.sairam0424/trelix' | python -m json.tool

mcp-publisher installs with brew install mcp-publisher. The curl is the check, not the publish: its output must name the version you just released, in both servers[0].server.version and servers[0].server.packages[0].version. An old version there means the publish did not land.


Working on Sub-packages

trelix ships three integration packages. To work on them:

# Install a sub-package in editable mode
pip install -e packages/trelix-mcp/
pip install -e packages/trelix-langchain/
pip install -e packages/trelix-llama-index/

# Run tests for a specific package
python -m pytest packages/trelix-mcp/tests/ --override-ini="testpaths=packages" -v
python -m pytest packages/trelix-langchain/tests/ --override-ini="testpaths=packages" -v
python -m pytest packages/trelix-llama-index/tests/ --override-ini="testpaths=packages" -v

Each package has its own pyproject.toml and tests/ directory. The src/ layout mirrors the main package.