oebb-mcp-server 0.1.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ version: 2
2
+ updates:
3
+ # Python dependencies (managed via uv / uv.lock)
4
+ - package-ecosystem: "uv"
5
+ directory: "/"
6
+ schedule:
7
+ interval: "weekly"
8
+ groups:
9
+ python-dependencies:
10
+ patterns:
11
+ - "*"
12
+
13
+ # GitHub Actions used in workflows
14
+ - package-ecosystem: "github-actions"
15
+ directory: "/"
16
+ schedule:
17
+ interval: "weekly"
18
+ groups:
19
+ github-actions:
20
+ patterns:
21
+ - "*"
@@ -0,0 +1,90 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ tags: ["v*"]
6
+
7
+ jobs:
8
+ build:
9
+ name: Build distributions
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - uses: actions/checkout@v6
13
+ with:
14
+ fetch-depth: 0 # hatch-vcs needs full history + tags to derive the version
15
+ - uses: astral-sh/setup-uv@v8.3.0
16
+ with:
17
+ python-version: "3.12"
18
+ - name: Build sdist and wheel
19
+ run: uv build
20
+ - name: Verify tag matches built version
21
+ run: |
22
+ TAG_VERSION="${GITHUB_REF_NAME#v}"
23
+ BUILT=$(ls dist/*.tar.gz | sed -E 's|.*/oebb_mcp_server-(.*)\.tar\.gz|\1|')
24
+ echo "tag=$TAG_VERSION built=$BUILT"
25
+ if [ "$TAG_VERSION" != "$BUILT" ]; then
26
+ echo "::error::Tag $TAG_VERSION does not match built version $BUILT"
27
+ exit 1
28
+ fi
29
+ - uses: actions/upload-artifact@v7
30
+ with:
31
+ name: dist
32
+ path: dist/
33
+
34
+ pypi-publish:
35
+ name: Publish to PyPI
36
+ needs: build
37
+ runs-on: ubuntu-latest
38
+ environment: pypi
39
+ permissions:
40
+ id-token: write # OIDC for PyPI Trusted Publishing
41
+ steps:
42
+ - uses: actions/download-artifact@v8
43
+ with:
44
+ name: dist
45
+ path: dist/
46
+ - uses: pypa/gh-action-pypi-publish@release/v1
47
+
48
+ github-release:
49
+ name: Create GitHub Release
50
+ needs: pypi-publish
51
+ runs-on: ubuntu-latest
52
+ permissions:
53
+ contents: write
54
+ steps:
55
+ - uses: actions/download-artifact@v8
56
+ with:
57
+ name: dist
58
+ path: dist/
59
+ - name: Create release
60
+ env:
61
+ GH_TOKEN: ${{ github.token }}
62
+ run: |
63
+ gh release create "$GITHUB_REF_NAME" \
64
+ --repo "$GITHUB_REPOSITORY" \
65
+ --title "$GITHUB_REF_NAME" \
66
+ --generate-notes \
67
+ dist/*
68
+
69
+ mcp-registry:
70
+ name: Publish to MCP Registry
71
+ needs: pypi-publish
72
+ runs-on: ubuntu-latest
73
+ permissions:
74
+ id-token: write # OIDC proves ownership of the io.github.<owner> namespace
75
+ contents: read
76
+ steps:
77
+ - uses: actions/checkout@v6
78
+ - name: Set server.json version from tag
79
+ run: |
80
+ VERSION="${GITHUB_REF_NAME#v}"
81
+ jq --arg v "$VERSION" '.version = $v | .packages[0].version = $v' \
82
+ server.json > server.tmp && mv server.tmp server.json
83
+ cat server.json
84
+ - name: Install mcp-publisher
85
+ run: |
86
+ curl -L "https://github.com/modelcontextprotocol/registry/releases/latest/download/mcp-publisher_$(uname -s | tr '[:upper:]' '[:lower:]')_$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/').tar.gz" | tar xz mcp-publisher
87
+ - name: Authenticate to MCP Registry
88
+ run: ./mcp-publisher login github-oidc
89
+ - name: Publish to MCP Registry
90
+ run: ./mcp-publisher publish
@@ -0,0 +1,49 @@
1
+ name: Validate
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ jobs:
9
+ ruff:
10
+ name: Ruff (lint + format)
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - uses: actions/checkout@v6
14
+ - uses: actions/setup-python@v6
15
+ with:
16
+ python-version: "3.12"
17
+ - run: pip install ruff
18
+ - run: ruff check .
19
+ - run: ruff format . --check
20
+
21
+ test:
22
+ name: Tests
23
+ runs-on: ubuntu-latest
24
+ steps:
25
+ - uses: actions/checkout@v6
26
+ with:
27
+ fetch-depth: 0 # hatch-vcs derives the version from git history + tags
28
+ - uses: actions/setup-python@v6
29
+ with:
30
+ python-version: "3.12"
31
+ - run: pip install hatchling && pip install -e . && pip install pytest pytest-asyncio
32
+ - run: pytest tests/ -v -m "not integration"
33
+
34
+ gate:
35
+ name: gate
36
+ needs: [ruff, test]
37
+ if: always()
38
+ runs-on: ubuntu-latest
39
+ steps:
40
+ - name: Check validation results
41
+ run: |
42
+ if [[ "${{ needs.ruff.result }}" == "success" && \
43
+ "${{ needs.test.result }}" == "success" ]]; then
44
+ echo "All checks passed"
45
+ exit 0
46
+ else
47
+ echo "One or more checks failed"
48
+ exit 1
49
+ fi
@@ -0,0 +1,22 @@
1
+ # Python
2
+ _version.py
3
+ __pycache__/
4
+ *.py[cod]
5
+ *.pyo
6
+ *.egg-info/
7
+ dist/
8
+ build/
9
+ .mypy_cache/
10
+ .ruff_cache/
11
+ .pytest_cache/
12
+
13
+ # Environment
14
+ .venv/
15
+
16
+ # macOS
17
+ .DS_Store
18
+ ._*
19
+
20
+ # IDE
21
+ .vscode/
22
+ .idea/
@@ -0,0 +1,13 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0
4
+
5
+ - Initial release
6
+ - MCP server with 4 tools: `search_station`, `station_board`, `trip_search`, `service_alerts`
7
+ - Station search by name with coordinates and IDs
8
+ - Live departures/arrivals at any OeBB station
9
+ - Trip search with time planning, departure/arrival mode, and direct-only filter
10
+ - Service alerts and disruption information with product type filtering
11
+ - First public distribution: published to [PyPI](https://pypi.org/project/oebb-mcp-server/) (installable via `uvx oebb-mcp-server`) and listed in the [official MCP Registry](https://registry.modelcontextprotocol.io)
12
+ - Tag-driven release pipeline: PyPI Trusted Publishing (OIDC), GitHub Release, and MCP Registry publish on `v*` tags
13
+ - Version single-sourced from git tags via `hatch-vcs`; added PyPI metadata (authors, URLs, classifiers, keywords) and a `py.typed` marker
@@ -0,0 +1,58 @@
1
+ # OeBB MCP Server
2
+ > MCP server for OeBB (Austrian Federal Railways) train data, usable by LLMs via the Model Context Protocol.
3
+
4
+ ## Quick Reference
5
+ - **Lint**: `ruff check .`
6
+ - **Format**: `ruff format .`
7
+ - **Test (unit)**: `pytest tests/ -v -m "not integration"`
8
+ - **Test (integration, real API)**: `pytest tests/ -v -m integration`
9
+ - **Run server**: `uvx --from . oebb-mcp-server` or `python -m oebb_mcp_server.server`
10
+ - **Validate (CI)**: Ruff + pytest unit tests (all must pass via `gate` job)
11
+
12
+ ## Architecture Overview
13
+ Thin adapter: FastMCP presentation layer wrapping a pure async OeBB API client. Purely functional, no classes. All code in `src/oebb_mcp_server/`.
14
+
15
+ - `server.py` -- FastMCP tool registration, session lifecycle, stdio entry point
16
+ - `oebb_api.py` -- pure async HTTP client for OeBB Scotty API (independently testable)
17
+ - `const.py` -- all constants (endpoint, auth, client config)
18
+
19
+ Data flow: MCP tool call -> `server.py` handler -> `oebb_api.async_oebb_*()` -> OeBB Scotty API -> JSON -> MCP tool result.
20
+
21
+ See [Architecture](docs/tech/ARCHITECTURE.md) for module boundaries and data flow detail.
22
+
23
+ ## Tech Stack
24
+ - Python 3.12+, `from __future__ import annotations` in every file
25
+ - `mcp[cli]` (FastMCP) for MCP server framework
26
+ - `aiohttp` for async HTTP
27
+ - `ruff` for linting/formatting, `pytest` + `pytest-asyncio` for testing
28
+ - `uv` for environment management, `hatchling` build backend
29
+ - GitHub Actions CI (validate on push/PR)
30
+
31
+ See [Tech Stack](docs/tech/TECH-STACK.md) for full detail.
32
+
33
+ ## Core Conventions
34
+ - All async functions use `async_` prefix; MCP tools use plain `verb_noun`
35
+ - Constants in `const.py` only -- no inline magic values
36
+ - Logger: `_LOGGER = logging.getLogger(__name__)` with `%s` formatting (not f-strings)
37
+ - Import order: `__future__` -> stdlib -> third-party -> local
38
+ - Errors signaled via sentinel dict `{"message": "..."}`, not exceptions
39
+ - OeBB Scotty API is reverse-engineered -- response structure may change without notice
40
+
41
+ See [Conventions](docs/tech/CONVENTIONS.md) for naming tables and full rules.
42
+
43
+ ## Business Domain
44
+ Read-only MCP gateway to OeBB live train data. Four tools: station search, station board (departures/arrivals), trip search (connections), and service alerts (disruptions). All data fetched live from the OeBB Scotty HAFAS API with no local caching.
45
+
46
+ See [Domain Overview](docs/domain/OVERVIEW.md) for concepts, feature boundaries, and HAFAS terminology.
47
+
48
+ ## Structural Risks
49
+ - OeBB Scotty API is reverse-engineered and undocumented -- breaking changes possible without notice
50
+ - Hardcoded auth token in `const.py` -- rotation requires code change
51
+ - Per-call `aiohttp.ClientSession` creation -- no connection pooling
52
+ - No unit tests for `server.py` tool handlers
53
+ - All data flows as untyped `dict[str, Any]` -- no compile-time shape guarantees
54
+
55
+ ## Detailed Guides
56
+ - [Technical Context](docs/tech/README.md) -- architecture, tech stack, conventions, testing
57
+ - [Domain Context](docs/domain/README.md) -- business domain, entities, terminology, integrations
58
+ - [Documentation Guide](docs/README.md) -- how to maintain these docs
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Stefan Lettmayer
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,137 @@
1
+ Metadata-Version: 2.4
2
+ Name: oebb-mcp-server
3
+ Version: 0.1.1
4
+ Summary: MCP server for OeBB (Austrian Federal Railways) train data
5
+ Project-URL: Homepage, https://github.com/slettmayer/oebb-mcp-server
6
+ Project-URL: Repository, https://github.com/slettmayer/oebb-mcp-server
7
+ Project-URL: Issues, https://github.com/slettmayer/oebb-mcp-server/issues
8
+ Project-URL: Changelog, https://github.com/slettmayer/oebb-mcp-server/blob/main/CHANGELOG.md
9
+ Author: Stefan Lettmayer
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: austria,hafas,mcp,model-context-protocol,oebb,railway,scotty,train
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Environment :: Console
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: License :: OSI Approved :: MIT License
17
+ Classifier: Operating System :: OS Independent
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
21
+ Requires-Python: >=3.12
22
+ Requires-Dist: aiohttp>=3.0.0
23
+ Requires-Dist: mcp[cli]>=1.0.0
24
+ Description-Content-Type: text/markdown
25
+
26
+ # oebb-mcp-server
27
+
28
+ <!-- mcp-name: io.github.slettmayer/oebb-mcp-server -->
29
+
30
+ [![PyPI](https://img.shields.io/pypi/v/oebb-mcp-server.svg)](https://pypi.org/project/oebb-mcp-server/)
31
+ [![Python](https://img.shields.io/pypi/pyversions/oebb-mcp-server.svg)](https://pypi.org/project/oebb-mcp-server/)
32
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
33
+
34
+ MCP server for [OeBB](https://www.oebb.at) (Austrian Federal Railways) train data. Query Austrian train stations, departures, connections, and service alerts directly from LLMs via the [Model Context Protocol](https://modelcontextprotocol.io).
35
+
36
+ ## Installation
37
+
38
+ ### Claude Desktop
39
+
40
+ Add to your Claude Desktop config (`~/Library/Application Support/Claude/claude_desktop_config.json`):
41
+
42
+ ```json
43
+ {
44
+ "mcpServers": {
45
+ "oebb": {
46
+ "command": "uvx",
47
+ "args": ["oebb-mcp-server"]
48
+ }
49
+ }
50
+ }
51
+ ```
52
+
53
+ ### Claude Code
54
+
55
+ ```bash
56
+ claude mcp add oebb -- uvx oebb-mcp-server
57
+ ```
58
+
59
+ ### From source (development)
60
+
61
+ ```json
62
+ {
63
+ "mcpServers": {
64
+ "oebb": {
65
+ "command": "uvx",
66
+ "args": ["--from", "/path/to/oebb-mcp-server", "oebb-mcp-server"]
67
+ }
68
+ }
69
+ }
70
+ ```
71
+
72
+ ## Tools
73
+
74
+ ### `search_station`
75
+
76
+ Search OeBB stations by name. Returns matching stations with IDs, coordinates, and types.
77
+
78
+ | Parameter | Type | Default | Description |
79
+ |-----------|------|---------|-------------|
80
+ | `query` | string | required | Station name (e.g. "Wien Hbf") |
81
+ | `max_results` | int | 10 | Maximum results |
82
+
83
+ ### `station_board`
84
+
85
+ Fetch live departures or arrivals at a station. Provide either `station_id` or `station_name`.
86
+
87
+ | Parameter | Type | Default | Description |
88
+ |-----------|------|---------|-------------|
89
+ | `station_id` | string | — | OeBB station ID (e.g. "1190100") |
90
+ | `station_name` | string | — | Station name (auto-resolved) |
91
+ | `board_type` | string | "DEP" | "DEP" for departures, "ARR" for arrivals |
92
+ | `max_journeys` | int | 10 | Maximum journeys |
93
+
94
+ ### `trip_search`
95
+
96
+ Search train connections between two stations. Supports time planning and direct-only filtering.
97
+
98
+ | Parameter | Type | Default | Description |
99
+ |-----------|------|---------|-------------|
100
+ | `from_station_id` | string | — | Departure station ID |
101
+ | `from_station_name` | string | — | Departure station name |
102
+ | `to_station_id` | string | — | Arrival station ID |
103
+ | `to_station_name` | string | — | Arrival station name |
104
+ | `max_connections` | int | 5 | Maximum connections |
105
+ | `time` | string | now | ISO 8601 time (e.g. "2026-04-15T08:00:00") |
106
+ | `time_mode` | string | "departure" | "departure" or "arrival" |
107
+ | `direct_only` | bool | false | Only direct connections |
108
+
109
+ ### `service_alerts`
110
+
111
+ Fetch current OeBB service alerts and disruptions.
112
+
113
+ | Parameter | Type | Default | Description |
114
+ |-----------|------|---------|-------------|
115
+ | `max_alerts` | int | 20 | Maximum alerts |
116
+ | `product_filter` | int | 65535 | Product bitmask (1=ICE/RJX, 2=IC/EC, 4=NJ, 8=D/EN, 16=REX/R, 32=S-Bahn, 64=Bus, 128=Ferry, 256=U-Bahn, 512=Tram, 4096=private operators like Westbahn/RegioJet, 65535=all) |
117
+
118
+ ## Development
119
+
120
+ ```bash
121
+ # Install dependencies
122
+ uv sync
123
+
124
+ # Lint & format
125
+ ruff check .
126
+ ruff format .
127
+
128
+ # Run unit tests
129
+ pytest tests/ -v -m "not integration"
130
+
131
+ # Run integration tests (hits real OeBB API)
132
+ pytest tests/ -v -m integration
133
+ ```
134
+
135
+ ## License
136
+
137
+ MIT
@@ -0,0 +1,112 @@
1
+ # oebb-mcp-server
2
+
3
+ <!-- mcp-name: io.github.slettmayer/oebb-mcp-server -->
4
+
5
+ [![PyPI](https://img.shields.io/pypi/v/oebb-mcp-server.svg)](https://pypi.org/project/oebb-mcp-server/)
6
+ [![Python](https://img.shields.io/pypi/pyversions/oebb-mcp-server.svg)](https://pypi.org/project/oebb-mcp-server/)
7
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
8
+
9
+ MCP server for [OeBB](https://www.oebb.at) (Austrian Federal Railways) train data. Query Austrian train stations, departures, connections, and service alerts directly from LLMs via the [Model Context Protocol](https://modelcontextprotocol.io).
10
+
11
+ ## Installation
12
+
13
+ ### Claude Desktop
14
+
15
+ Add to your Claude Desktop config (`~/Library/Application Support/Claude/claude_desktop_config.json`):
16
+
17
+ ```json
18
+ {
19
+ "mcpServers": {
20
+ "oebb": {
21
+ "command": "uvx",
22
+ "args": ["oebb-mcp-server"]
23
+ }
24
+ }
25
+ }
26
+ ```
27
+
28
+ ### Claude Code
29
+
30
+ ```bash
31
+ claude mcp add oebb -- uvx oebb-mcp-server
32
+ ```
33
+
34
+ ### From source (development)
35
+
36
+ ```json
37
+ {
38
+ "mcpServers": {
39
+ "oebb": {
40
+ "command": "uvx",
41
+ "args": ["--from", "/path/to/oebb-mcp-server", "oebb-mcp-server"]
42
+ }
43
+ }
44
+ }
45
+ ```
46
+
47
+ ## Tools
48
+
49
+ ### `search_station`
50
+
51
+ Search OeBB stations by name. Returns matching stations with IDs, coordinates, and types.
52
+
53
+ | Parameter | Type | Default | Description |
54
+ |-----------|------|---------|-------------|
55
+ | `query` | string | required | Station name (e.g. "Wien Hbf") |
56
+ | `max_results` | int | 10 | Maximum results |
57
+
58
+ ### `station_board`
59
+
60
+ Fetch live departures or arrivals at a station. Provide either `station_id` or `station_name`.
61
+
62
+ | Parameter | Type | Default | Description |
63
+ |-----------|------|---------|-------------|
64
+ | `station_id` | string | — | OeBB station ID (e.g. "1190100") |
65
+ | `station_name` | string | — | Station name (auto-resolved) |
66
+ | `board_type` | string | "DEP" | "DEP" for departures, "ARR" for arrivals |
67
+ | `max_journeys` | int | 10 | Maximum journeys |
68
+
69
+ ### `trip_search`
70
+
71
+ Search train connections between two stations. Supports time planning and direct-only filtering.
72
+
73
+ | Parameter | Type | Default | Description |
74
+ |-----------|------|---------|-------------|
75
+ | `from_station_id` | string | — | Departure station ID |
76
+ | `from_station_name` | string | — | Departure station name |
77
+ | `to_station_id` | string | — | Arrival station ID |
78
+ | `to_station_name` | string | — | Arrival station name |
79
+ | `max_connections` | int | 5 | Maximum connections |
80
+ | `time` | string | now | ISO 8601 time (e.g. "2026-04-15T08:00:00") |
81
+ | `time_mode` | string | "departure" | "departure" or "arrival" |
82
+ | `direct_only` | bool | false | Only direct connections |
83
+
84
+ ### `service_alerts`
85
+
86
+ Fetch current OeBB service alerts and disruptions.
87
+
88
+ | Parameter | Type | Default | Description |
89
+ |-----------|------|---------|-------------|
90
+ | `max_alerts` | int | 20 | Maximum alerts |
91
+ | `product_filter` | int | 65535 | Product bitmask (1=ICE/RJX, 2=IC/EC, 4=NJ, 8=D/EN, 16=REX/R, 32=S-Bahn, 64=Bus, 128=Ferry, 256=U-Bahn, 512=Tram, 4096=private operators like Westbahn/RegioJet, 65535=all) |
92
+
93
+ ## Development
94
+
95
+ ```bash
96
+ # Install dependencies
97
+ uv sync
98
+
99
+ # Lint & format
100
+ ruff check .
101
+ ruff format .
102
+
103
+ # Run unit tests
104
+ pytest tests/ -v -m "not integration"
105
+
106
+ # Run integration tests (hits real OeBB API)
107
+ pytest tests/ -v -m integration
108
+ ```
109
+
110
+ ## License
111
+
112
+ MIT
@@ -0,0 +1,72 @@
1
+ # Documentation Contributing Guide
2
+
3
+ > Rules for maintaining the AI assistant documentation. Follow these when adding, updating, or restructuring any `.md` files in this system. These rules apply to both human developers and AI sessions.
4
+
5
+ ## Why This Architecture Exists
6
+
7
+ Claude reads `CLAUDE.md` on every conversation start. If it's bloated with detail, it wastes context window on content that may not be relevant to the task. The architecture solves this:
8
+
9
+ - **`CLAUDE.md`** is loaded every time -- must stay concise (index + critical one-liners)
10
+ - **`docs/tech/*.md`** are technical documentation files loaded on demand -- only when a task touches that topic. README.md in that folder contains an index.
11
+ - **`docs/domain/*.md`** are domain documentation files loaded on demand -- only when a task touches that topic. README.md in that folder contains an index.
12
+ - **Directory-specific `CLAUDE.md`** files are loaded when working in that directory
13
+
14
+ This means the AI gets the right information at the right time without burning context on irrelevant detail.
15
+
16
+ ## File Locations and Scope
17
+
18
+ | Location | Purpose | Loaded when |
19
+ |----------|---------|-------------|
20
+ | `/CLAUDE.md` | Concise index: tech stack, critical warnings (one-liners), links to detailed docs, quick reference | Every conversation |
21
+ | `/docs/tech/README.md` | Index file for technical topic guides | AI reads the file when the task is related to a technical topic to find the correct file |
22
+ | `/docs/tech/*.md` | Detailed technical topic guides (one topic per file) | AI reads the file when the task is related to the technical topic |
23
+ | `/docs/domain/README.md` | Index file for domain topic guides | AI reads the file when the task is related to a domain topic to find the correct file |
24
+ | `/docs/domain/*.md` | Detailed domain topic guides (one topic per file) | AI reads the file when the task is related to the domain |
25
+ | `src/**/CLAUDE.md` | Conventions and documentation for a specific directory within the source code | Working in a specific folder of the source code |
26
+
27
+ ### What goes where
28
+
29
+ - **Rule in `CLAUDE.md`**: one-liner that fits in a bullet point (with a link to detail)
30
+ - **Detail in `docs/tech`**: anything technical that needs explanation, examples, tables, checklists, or code blocks
31
+ - **Detail in `docs/domain`**: anything domain specific that needs explanation, examples, tables, checklists, or code blocks
32
+ - **Directory-specific `CLAUDE.md`**: conventions that only apply when working in that specific directory
33
+
34
+ ## Naming Conventions
35
+
36
+ - `/docs` files: `UPPERCASE-KEBAB-CASE.md` (e.g., `PACKAGE-GUIDELINES.md`, `STATE-MANAGEMENT.md`)
37
+ - Directory-specific files: always named `CLAUDE.md`
38
+ - Keep names descriptive and short (2-3 words max)
39
+
40
+ ## File Size Guidelines
41
+
42
+ - **`/CLAUDE.md`**: aim for <150 lines -- if it grows beyond this, content is leaking in that should be in `docs/`
43
+ - **`/docs/**/*.md`**: aim for <300 lines per file -- if a doc grows beyond this, consider splitting into two focused files
44
+ - **Directory-specific `CLAUDE.md`**: aim for <100 lines -- these should be tightly scoped
45
+
46
+ ## Checklist: Adding a New Doc
47
+
48
+ 1. Create the file in `/docs/tech` for technology focused docs and `/docs/domain` for domain focused docs, both following the naming convention
49
+ 2. Add a new entry to the `README.md` file of the corresponding folder.
50
+ 3. If there's a critical rule, add a one-liner to the `ALWAYS` or `DO NOT USE` block in `/CLAUDE.md` with a link to the new doc
51
+ 4. Add cross-references from related docs (e.g., a new payment doc should link from `README.md`)
52
+ 5. Use relative links between `/docs/` files (e.g., `[INTEGRATIONS.md](INTEGRATIONS.md)`)
53
+ 6. Use relative links from `/CLAUDE.md` (e.g., `[PACKAGE-GUIDELINES.md](/docs/tech/PACKAGE-GUIDELINES.md)`)
54
+
55
+ ## Checklist: Updating an Existing Doc
56
+
57
+ 1. Keep changes within the doc's defined scope -- don't let a doc grow to cover unrelated topics
58
+ 2. If new content doesn't fit the existing doc's scope, create a new doc instead
59
+ 3. **Never move detailed content into `/CLAUDE.md`** -- add a one-liner + link instead
60
+ 4. Update cross-references if you rename sections or files
61
+ 5. Check that the Documentation Index entry in `/CLAUDE.md` still accurately describes the doc's purpose
62
+
63
+ ## Common Mistakes to Avoid
64
+
65
+ | Mistake | What to do instead |
66
+ |---------|-------------------|
67
+ | Inlining detailed content in `/CLAUDE.md` | Create/update a doc in `docs/`, add a one-liner + link |
68
+ | Creating a doc without indexing it | Always add to the Documentation Index table in `/CLAUDE.md` |
69
+ | Putting directory-specific rules in `/docs/` | Use the directory's `CLAUDE.md` file instead |
70
+ | Using absolute paths in links | Use relative paths so links work regardless of where the repo is cloned |
71
+ | Duplicating content across multiple docs | Put it in one place, cross-reference from others |
72
+ | Letting a doc grow unbounded | Split into focused files when it exceeds ~300 lines |