# dl4rce/flaiwheel [Health: Active]

**Category:** 🧠 Knowledge & Memory  
**Repository:** https://github.com/dl4rce/flaiwheel  
**GitHub Stars:** 6  
**Views:** 0  
**Installs:** 0  
**Upvotes:** 0  
**Directory Page:** https://allmcps.com/mcp/dl4rce-flaiwheel

## Description
Self-hosted memory and governance layer for AI coding agents. 28 MCP tools with structured knowledge capture, hybrid search (semantic + BM25 + cross-encoder reranking), behavioral documentation nudges, cold-start codebase analyzer, and git-native storage. Single Docker container, zero cloud dependencies.

## Claude Desktop Quick Installation
Install path detected from listing signals. Uses `npx` (confidence: high):

```json
"mcpServers": {
  "flaiwheel": {
    "command": "npx",
    "args": ["-y","mcp-remote","http://localhost:8081/sse"]
  }
}
```

## Documentation & README

# Flaiwheel

[![flaiwheel MCP server](https://glama.ai/mcp/servers/dl4rce/flaiwheel/badges/score.svg)](https://glama.ai/mcp/servers/dl4rce/flaiwheel)
[![Available on Glama](https://img.shields.io/badge/Available%20on-Glama-6366f1?style=flat)](https://glama.ai/mcp/servers/dl4rce/flaiwheel)

> Self-hosted memory & governance layer for AI coding agents.
> Turn every bug fix into permanent knowledge. Zero cloud. Zero lock-in.

## 🚀 Why Flaiwheel Exists

AI coding agents forget everything between sessions.
That leads to repeated bugs, lost architectural decisions, and knowledge decay.

Flaiwheel ensures:
- Agents search before coding
- Agents document after fixing
- Commits automatically capture knowledge
- Memory compounds over time

**Every bug fixed makes the next bug cheaper.**

## 🧠 How Flaiwheel Is Different

- **Persistent AI Memory That Compounds** — knowledge doesn't reset between sessions.
- **Git-Native Automation** — commits automatically become structured knowledge.
- **Governance, Not Just Storage** — quality gates + enforced documentation.
- **Hybrid Search + Reranking** — high-precision context for real codebases.
- **Fully Self-Hosted** — single Docker container, no external infrastructure.
- **Zero Lock-In** — all knowledge stored as structured flat files in Git.

## ✅ Who Flaiwheel Is For

- Engineering teams using AI coding assistants in real projects
- Codebases where repeated bugs are expensive
- Teams requiring full data control
- AI-native development environments

## ❌ Not For

- Small hobby projects under a few thousand lines
- Developers who just want better autocomplete
- Pure SaaS workflows with no interest in self-hosting

## 🆚 Where Flaiwheel Fits

- AI coding tools generate code.
- RAG tools retrieve documents.
- **Flaiwheel governs and compounds structured engineering knowledge inside your own infrastructure.**

It does not replace your AI assistant. **It makes it reliable at scale.**

**📄 [Whitepaper (PDF)](https://flaiwheel.app/flaiwheel_whitepaper_volker_geith_v1.6.pdf)** — Vision, architecture, and design in depth.

---

## ⚙️ Key Technical Features

Flaiwheel is a self-contained Docker service that operates on three levels:
**Pull** — agents search before they code (`search_docs`, `get_file_context`)  
**Push** — agents document as they work (`write_bugfix_summary`, `write_architecture_doc`, …)  
**Capture** — git commits auto-capture knowledge via a post-commit hook, even without an AI agent

- **Indexes** your project documentation (`.md`, `.pdf`, `.html`, `.docx`, `.rst`, `.txt`, `.json`, `.yaml`, `.csv`) into a vector database
- **Provides an MCP server** that AI agents (Cursor, Claude Code, VS Code Copilot) connect to
- **Hybrid search** — combines semantic vector search with BM25 keyword search via Reciprocal Rank Fusion (RRF) for best-of-both-worlds retrieval
- **Cross-encoder reranker** — optional reranking step that rescores candidates with a cross-encoder model for significantly higher precision on vocabulary-mismatch queries
- **Behavioral Directives** — AI agents silently search Flaiwheel before every response, auto-document after every task, and reuse before recreating — all without being asked
- **`get_file_context(filename)`** — pre-loads spatial knowledge for any file the agent is about to edit (complements `get_recent_sessions` for full temporal + spatial context)
- **post-commit git hook** — captures every `fix:`, `feat:`, `refactor:`, `perf:`, `docs:` commit as a structured knowledge doc automatically
- **Living Architecture** — AI agents are instructed to maintain self-updating Mermaid.js diagrams for system components and flows
- **Executable Test Flows** — test scenarios are documented in machine-readable BDD/Gherkin format (`Given`, `When`, `Then`) for QA automation
- **Learns from bugfixes** — agents write bugfix summaries that are instantly indexed
- **Structured write tools** — 7 category-specific tools (bugfix, architecture, API, best-practice, setup, changelog, test case) that enforce quality at the source
- **Structured relations (v1)** — `relations()` and `timeline()` derive a per-project knowledge graph from optional YAML frontmatter on existing docs (`id`, `replaces`, `depends_on`, `fixes`, `implements`, `status`). No second store — markdown stays canonical and Git history is the validity window
- **Pre-commit validation** — `validate_doc()` checks freeform markdown before it enters the knowledge base, including unknown-relation-key warnings
- **Ingest quality gate** — files with critical issues are automatically skipped during indexing (never deleted — you own your files)
- **Auto-syncs via Git** — pulls AND pushes to a dedicated knowledge repo
- **Tool telemetry (persistent)** — tracks every MCP call per project (searches, writes, misses, patterns), detects knowledge gaps, and nudges agents to document — persisted across restarts and visible in the Web UI
- **Impact metrics API** — `/api/impact-metrics` computes estimated time saved + regressions avoided; CI pipelines can post guardrail outcomes to `/api/telemetry/ci-guardrail-report`
- **Proactive quality checks** — automatically validates knowledge base after every reindex
- **Knowledge Bootstrap** — "This is the Way": analyse messy repos, classify files, detect duplicates, propose a cleanup plan, execute with user approval (never deletes files)
- **Cold-Start Codebase Analyzer** — `analyze_codebase(path)` scans a source code directory entirely server-side (zero tokens, zero cloud). Uses Python's built-in `ast` module for Python, regex for TypeScript/JavaScript, the existing MiniLM embedding model for classification and duplicate detection. Returns a single `bootstrap_report.md` with language distribution, category map, top 20 files to document first ranked by documentability score, duplicate pairs, and coverage gaps. Reduces cold-start token cost by ~90% on legacy codebases.
- **Multi-project support** — one container manages multiple knowledge repos with per-project isolation
- **Includes a Web UI** for configuration, monitoring, and testing

---

## What’s New in v3.15.3 — Plain HTTP remains the default

- A new installation uses `http://host:8081/sse`. This is the recommended configuration until the Web UI provides TLS activation, CA download, and operating-system installation instructions. An update preserves its existing mode unless `FLAIWHEEL_TLS_AUTO=0` or `1` is set explicitly.
- Automatic TLS remains available as an opt-in feature. The generated certificate is valid, but Cursor/Electron and other clients may require the Flaiwheel CA to be installed in the operating-system trust store.
- `FLAIWHEEL_TLS_AUTO=0` now explicitly returns an existing TLS-enabled installation to HTTP while preserving its volumes and other `MCP_*` settings.
- Leaving `FLAIWHEEL_TLS_AUTO` unset during an update preserves the currently deployed mode.

## v3.15.2 TLS caveat

`v3.15.2` corrected endpoint schemes and certificate persistence, but its direct-SSE client guidance was incomplete. An `env` block containing `NODE_EXTRA_CA_CERTS` does not necessarily control TLS for a direct Cursor/Electron SSE connection. Direct clients may require the generated CA in the operating-system trust store. Use the default HTTP endpoint unless you have completed that trust step.

## What’s New in v3.15.0 — Flaiwheel issues its own TLS certificate

- **A LAN deployment can now be encrypted without the operator touching a certificate tool.** No public CA will issue for `192.168.178.230`, so the only previous options were `openssl` by hand or a proxy. Set `MCP_SSE_TLS_AUTO=true` and Flaiwheel generates a private CA plus a server certificate on first start, covering the configured hosts and loopback.
- **Everything on Flaiwheel's side is automatic, and repeated starts are safe.** Material is persisted in `/data/tls` and **reused** while it is valid and still covers the requested names — an upgrade does not change the fingerprint, so a client that pinned the CA stays valid. A certificate that has gone stale, stopped covering a host, or lost its matching key is re-issued, with the reason logged.
- **One thing it genuinely cannot do: make another machine trust the CA.** The container has no access to a client's trust store — the same boundary that forces `mkcert` to run a per-machine step. So the startup log prints the fingerprint and the exact `NODE_EXTRA_CA_CERTS=...` line for the client's env block. That is the floor, and it is real TLS: encryption *and* identity, with the CA pinned by trust-on-first-use.
- **No `NODE_TLS_REJECT_UNAUTHORIZED=0` anywhere in this path.** The easy way to "make HTTPS work" is to disable verification, which defends against nothing; it is not offered as an option here.
- **Opt-in, because turning it on is a protocol change.** Auto-TLS flips the endpoint from `http://` to `https://`, and doing that unasked during an upgrade would break every existing client — the same class of failure as an installer that refuses to upgrade. It **fails closed** too: if provisioning fails, startup aborts rather than quietly serving cleartext.
- **Verified with a real TLS stack, not a fixture.** A client trusting only the generated CA completes a TLSv1.3 handshake and gets a `200`; an untrusting client is rejected. Both directions asserted.
- **Tests: 370 → 439**, covering SAN derivation, signature chain verification, idempotence, stale/mismatched material, key permissions and every fail-closed path.

## What’s New in v3.14.0 — Remote MCP without a reverse proxy

- **Serve MCP to a LAN or remote client without adding a component.** Until now the SSE endpoint was reachable only from `localhost`: the socket bound `0.0.0.0` while the application layer answered `HTTP 421 Invalid Host header` to every other `Host`. The workaround was a proxy that rewrites `Host: localhost` — a moving part, and a boot-ordering dependency. Set `MCP_SSE_ALLOWED_HOSTS=flaiwheel.example.com` and connect directly.
- **The root cause was not the bind address.** Flaiwheel builds `FastMCP` itself and passed neither `host` nor `transport_security`, so the SDK's loopback-only guard always applied. `FASTMCP_HOST` looks like the fix and is **silently inert** — init arguments outrank environment variables in pydantic-settings. Verified empirically before and after.
- **The guard stays ON.** The tempting one-liner (`host='0.0.0.0'`) makes the SDK skip its auto-enable and turn DNS-rebinding protection **off** entirely. Instead, protection remains enabled with a wider allowlist — `Host` and `Origin` are still validated, and lookalike suffixes like `flaiwheel.example.com.evil.com` are still rejected.
- **Nothing changes for existing installs.** An unconfigured Flaiwheel is loopback-only, exactly as before, and still rejects remote hosts. Loopback entries are never dropped, so container health checks, `ssh -L` forwards and existing Host-rewriting proxies keep working.
- **Native TLS — a remote deployment needs no proxy at all.** Set `MCP_SSE_TLS_CERTFILE` + `MCP_SSE_TLS_KEYFILE` and the endpoint is served over HTTPS directly. Verified against a real TLS listener: TLSv1.3, `200` over HTTPS for the allowlisted host, `421` still enforced for `evil.example.com`, and a plain HTTP request to the same port rejected.
- **TLS fails closed.** A half-configured or unreadable certificate **aborts startup** instead of quietly serving plaintext — an operator who asked for encryption must never silently get cleartext.
- **Startup tells you when you are no longer loopback-only**, and `SECURITY.md` carries the deployment table so you can see which layer encrypts what.
- **Tests: 335 → 370**, including the guard exercised through the SDK's real middleware and every fail-closed TLS path.

### Previous: v3.13.0 — Observability

- **Flaiwheel now knows whether its knowledge repo is still connected to its remote.** Everything before this reported on pushes that were *attempted*. The failure that hid 325 documents in a Docker volume for 2.5 months attempted nothing: the clone had drifted from its remote, so there was never anything to commit, so no push could fail, so nothing went red. `check_divergence()` compares `HEAD` against `@{u}` and classifies the result as `synced` / `ahead` / `behind` / `diverged` / `no-upstream`.
- **The "nothing to push" path is where this matters.** That branch used to return an unconditional *"already in sync"*. It now verifies the claim. Divergence is also checked after every successful push (did the commit actually land?), after a rejected push (a rejection is the classic symptom — now named instead of leaving you to read a git error), and on every pull.
- **A repo that indexes perfectly and pushes nothing is no longer "healthy".** `/health` gains `divergence_status`, `commits_ahead`, `commits_behind` and `last_divergence_at`, and reports `degraded` on `diverged`, `ahead` or `no-upstream`. Being `behind` is the normal state between two pulls and deliberately does *not* alarm.
- **The agent is told directly.** `write_*` results append an explicit warning when the repo has diverged — including on "nothing to push". A warning in an endpoint nobody polls does not exist; the agent that just wrote the document is the one that needs to know it never left the machine.
- **Tests: 316 → 335**, against real temp repos including a force-pushed rewritten upstream — the real-world trigger, where a secret purge or a squash silently desynchronises every clone.
- **This closes the 2026-08-19 incident completely.** The one item that looked outstanding — "watcher path scoping" — was retracted as a misdiagnosis after checking the running container. Its only evidence was the log line `knowledge: update flaiwheel/telemetry.json`, read as one project's file being committed into all 11 repos. The real path is `.flaiwheel/telemetry.json`, *with a leading dot*: the signature of the porcelain off-by-one already fixed in v3.12.2. Every project owns an identically-named telemetry file, so all 11 watchers logged the same mangled string at once — uniformity caused by shared *code*, mistaken for shared *state*.

### Previous: v3.12.3

- **Every dependency is capped below the next major.** Eleven requirements were unbounded `>=X`. That fails silently: the breaking release lands, existing installs keep working off a stale resolve, and it only bites on the next *fresh* install — CI, a Docker rebuild, a new contributor. Exactly how `mcp` 2.0.0 broke CI and the Docker build together three weeks after release while every dev machine stayed green. A clean install resolves to identical versions as before, so this constrains the future without moving anything today.
- **Sustained push failure now degrades `/health`.** `HealthTracker` kept only `last_push_ok` — a single boolean the next attempt overwrites — so one blip and a repo failing for weeks looked the same. `push_failures_consecutive` escalates past 3 consecutive failures. A single failure deliberately does *not* degrade; crying wolf on transients is how alerts get ignored.
- **`/health` names the failing projects.** Adds `last_push_ok`, `last_push_error`, `push_failures_consecutive` and `degraded_projects` — previously the endpoint could say `degraded` while showing only the default project's numbers, with no way to tell *which* repo was broken.
- **Pre-deploy image smoke test documented.** An image can build cleanly and still fail every import at runtime. The README now verifies `from flaiwheel.server import create_mcp_server` inside the image before starting a container, and renames rather than removes the previous container so rollback is instant.
- **Tests: 308 → 316.**

### Previous: v3.12.2

- **Auto-commit no longer drops the first worktree-modified file.** `git status --porcelain` emits `XY <path>` where a leading space is *data* (`" M file"`). Stripping the whole output before splitting ate that space on the first line only, so `line[3:]` truncated the filename's first character — `.flaiwheel/telemetry.json` became `flaiwheel/telemetry.json`, `git add` failed, and the commit aborted. Intermittent and file-order dependent, which is why it survived so long.
- **Renamed and copied files are staged correctly.** Porcelain reports `old -> new`; the whole string was passed to `git add`, so renames were never committed.

### Previous: v3.12.1

- **Pinned `mcp[cli]<2.0.0`.** `mcp` 2.0.0 removed `mcp.server.fastmcp` (`FastMCP` → `mcp.server.mcpserver`), breaking every import of the server on a fresh resolve.

### Previous: v3.12.0

- **Auto-push now reports what actually happened.** `push_pending()` returns a structured result (`ok` / `noop` / `disabled` / `failed` / `blocked`) and every `write_*` tool renders that outcome. Previously the success line was derived from *configuration* (`git_auto_push and bool(git_repo_url)`), so it read `Auto-pushed to remote: True` even when every push was being rejected. A failed push now says **"Auto-push: FAILED — this doc is NOT on the remote"** with the git error attached.
- **Push errors are no longer swallowed.** The bare `except` in `push_pending()` that only wrote to the diagnostic log now records to `HealthTracker` and returns the error to the caller. A failing `git commit` is reported instead of raising through an unchecked `check=True`.
- **gitleaks runs inside the container, on the write path.** Flaiwheel's commits are machine-generated and never human-reviewed, so secret scanning now happens in `_push_local_changes()` before the commit — not as a per-clone git hook that gets lost on re-clone. `MCP_GITLEAKS_MODE=block` (default) refuses to commit and reports the findings through the MCP result; `warn` commits and reports; `off` disables. Honours a `.gitleaks.toml` in the knowledge repo for allowlisting. **A missing or broken scanner is reported explicitly, never silently skipped.**
- **Tests: 300 → 308** (`tests/test_watcher_push.py`: push success/failure/noop/disabled reporting, gitleaks block/warn/clean, unavailable-scanner visibility).

### Previous: v3.11.0

- **Telemetry now survives `docker volume rm flaiwheel-data`.** A per-project summary slice is mirrored from the Docker volume into each knowledge repo at `<docs_path>/.flaiwheel/telemetry.json`. On the next cold start, `hydrate_from_mirrors()` rebuilds the in-memory state from these files so the Tool Telemetry dashboard does not reset to zero. Hot tier wins when both exist; mirror writes are rate-limited to 60s/project to avoid one Git commit per tool call. Events stay in the volume only (too noisy for the knowledge repo). Don't want it committed? Add `.flaiwheel/` to your knowledge repo's `.gitignore` — Flaiwheel will still read/write the file locally.
- **Reset Telemetry button on every per-project tile in the Web UI.** Zeroes summary counters across both storage tiers via the new `POST /api/telemetry/reset?project=<name>` endpoint. The 30-day impact-metrics window keeps working because events history is preserved.
- **Agent instructions taught the relations workflow.** `AGENTS.md` and both install.sh templates now include a "Structured Relations Workflow" section with three concrete rules (when to add `fixes`, when to add `replaces`, when to add `depends_on`) so agents actually use the v3.10.x graph machinery instead of ignoring it.
- **Client Configuration's "VS Code" tab is now "VS Code + Copilot"** with explicit help text pointing at GitHub Copilot agent mode. The `.vscode/mcp.json` file Flaiwheel emits already works for Copilot — no separate snippet needed.
- **Tests: 292 → 300** (8 new tests in `test_telemetry.py` for mirror writes, rate limiting, cold-start hydration, hot-tier authority, and reset semantics).

### Previous: v3.10.1

- **Every structured writer now auto-emits frontmatter.** `write_bugfix_summary`, `write_architecture_doc`, `write_api_doc`, `write_best_practice`, `write_setup_doc`, `write_changelog_entry`, and `write_test_case` prepend `id` / `type` / `status: active` + empty relation lists to every new doc. Every doc you create from now on is automatically a graph node — no manual frontmatter editing required. IDs are derived from the existing filename slugs (e.g. `adr-2026-05-22-payment-service-architecture`, `bugfix-2026-05-22-fix-race-condition`, `api-create-user-endpoint`).
- **New helper `flaiwheel.frontmatter.emit()`** with stable, deterministic key order so same-day overwrites produce minimal diffs.

### Previous: v3.10.0

- **Structured relations (v1)** — two new read-only MCP tools, `relations(entity_id)` and `timeline(entity_id)`, derive a per-project knowledge graph from YAML frontmatter on existing markdown docs. No new persistent store and no `graph_add` / `invalidate` writes: markdown stays the single source of truth and Git history is the validity window. Recognised relation keys: `replaces`, `depends_on`, `fixes`, `implements`. Scalar keys: `id`, `type`, `status`, `superseded_at`.
- **Frontmatter-aware quality checks** — `validate_doc()` now warns on unknown relation keys (info severity) and invalid `status` values (warning severity); heading-structure checks strip the leading `---` block first so frontmatter does not confuse the "first heading is h1" rule.
- **`GitWatcher.log_for_file()`** — read-only helper returning newest-first commits (`hash`, `author`, ISO `date`, `subject`); backs the `timeline()` tool.
- **Zero new dependencies** — frontmatter parsing is stdlib-only (`flaiwheel.frontmatter`). No `python-frontmatter` / `PyYAML` added.
- **Total tools: 28 → 30.**

> Note: the SQLite ER store (`graph_add` / `graph_invalidate` / `valid_from` / `valid_to` columns) originally proposed for this feature is deferred as v2, gated on a real query becoming measurably too slow on v1. AST-driven code↔symbol edges (v3) remain merged with the `feature_ideas_backlog` #13 track.

### Previous: v3.9.40

- **Installer: `claude-md` no longer fails on repeat runs** — `claude mcp add` non-zero exits (e.g. MCP already registered) no longer abort the parallel phase under `set -e`; registration output is captured safely.
- **Installer: correct release version from GitHub** — `_FW_VERSION` is refreshed from `main` `pyproject.toml` when reachable so Docker rebuild / version checks stay aligned with the package even if raw `install.sh` on `main` lags at the CDN.

### Previous: v3.9.29

- **Glama tool detection fix** — `AuthManager` crashed on read-only `/data` before the MCP server could start (the real reason Glama saw 0 tools). Skipped in stdio cold-start mode.
- **Zero print() on stdout** — 36 remaining `print()` in watcher, indexer, readers, bootstrap replaced with `diag()` (stderr). Verified: full MCP handshake returns all 28 tools over stdio.
- **`config.save()` resilient** — read-only filesystem logs warning instead of crashing.

### Previous: v3.9.28

- **Glama / MCP stdio fix** — all diagnostic output moved to stderr; stdout is now JSON-RPC only. Glama Inspector now detects all 28 tools correctly.
- **Improved cold-start detection** — stdio cold-start logic handles empty Docker volumes correctly (no bootstrap / model download during Glama inspection).

### Previous: v3.9.27

- **License cleanup** — one `LICENSE` file (BSL 1.1) for correct GitHub/Glama detection; all docs and headers point to `LICENSE` (not `LICENSE.md`).
- **Glama / stdio inspection** — optional `[inspect]` deps and cold-start stdio path for lightweight MCP directory builds.

### Previous: v3.9.26

- **Claude Cowork skill** — the Flaiwheel workflow is now distributed as a native Claude skill. The installer writes `.skills/skills/flaiwheel/SKILL.md` to your project. When you open the project in Claude (Cowork), the skill is auto-available — no extra setup needed. The skill drives session-start context restore, pre-coding knowledge search, mandatory post-bugfix documentation, and session-end summarisation.
- Skill source also committed to `skills/flaiwheel/SKILL.md` in this repo for reference and manual install.

### Previous: v3.9.25

- **WSL2 automatic pre-flight setup** — WSL2 is now detected automatically and a dedicated pre-flight block runs before the main installer flow. No manual steps required:
  1. Switches `iptables` to legacy backend (fixes Docker networking / DNAT errors)
  2. Adds the current user to the `docker` group (no more `permission denied`)
  3. Starts the Docker daemon via `service` (no systemd on WSL2)
  4. Adds a Docker auto-start snippet to `~/.bashrc` (idempotent, runs on every WSL2 login)
- Scattered WSL2 checks throughout the script consolidated into the single pre-flight block.

### Previous: v3.9.24

- **Fix: auto-install python3 if missing** — the installer uses `python3` extensively for JSON manipulation. On minimal Linux/WSL2 systems without python3, config file writes silently failed (`/dev/fd/63: line N: python3: command not found`). python3 is now checked as prerequisite #0 and auto-installed via apt/dnf/yum/pacman/brew if missing.

### Previous: v3.9.23

- **Fix: Docker daemon start on WSL2 with iptables-legacy** — Docker on WSL2 often fails to start silently because the default `iptables-nft` backend is not supported. The installer now switches to `iptables-legacy` via `update-alternatives` before starting Docker. Also adds the current user to the `docker` group automatically.
- **All install commands updated to `bash <(curl ...)`** — every displayed install/re-run command throughout the script (error messages, AGENTS.md, Cursor rules, etc.) now uses process substitution to avoid WSL2 pipe issues.

### Previous: v3.9.22

- **Fix: `curl | bash` pipe write failures on WSL2** — `curl | bash` can fail with `curl: (23) Failure writing output` on WSL2 due to pipe/tmp permission issues. The primary install command in README is now `bash <(curl ...)` (process substitution), which avoids the pipe entirely. The re-exec block also tries `$HOME` as a fallback temp dir when `/tmp` writes fail. Error message explicitly recommends the `bash <(curl ...)` form.

### Previous: v3.9.21

- **Fix: sudo guard moved before re-exec block** — when `sudo curl | bash` was used, the `curl: (23)` pipe error truncated the script before the previous sudo guard (which was after colors/functions) was ever reached. The guard is now the very first executable line (`set -euo pipefail` aside), so it fires even on a truncated download. Duplicate guard after colors removed.

### Previous: v3.9.20

- **Fix: Docker daemon startup poll on WSL2** — instead of a fixed 5-second sleep, the installer now polls `docker info` every 2 seconds for up to 30 seconds after `service docker start`. Also shows the actual output of `service docker start` so startup errors are visible instead of silently swallowed.

### Previous: v3.9.19

- **Fix: Docker daemon start on WSL2** — WSL2 typically has no `systemd`, so `systemctl start docker` silently failed. The installer now detects WSL2 via `/proc/version` and uses `sudo service docker start` instead. If Docker still isn't running after install, a clear WSL2-specific error is shown with the exact fix command and a tip to add it to `~/.bashrc` for auto-start on login.

### Previous: v3.9.18

- **Fix: block `sudo curl | bash` and `sudo bash install.sh`** — running the installer as root via `sudo` breaks GitHub CLI authentication: `gh auth` stores credentials in `/root/.config/gh/` instead of the real user's home, making every subsequent `gh` call fail. Also caused `curl: (23) Failure writing output` pipe errors on WSL. The installer now detects `SUDO_USER` at startup and exits immediately with a clear message telling the user to re-run without `sudo`. Privilege escalation for package installs is handled internally.

### Previous: v3.9.17

- **Fix: `gh auth login` must not be run with sudo** — after auto-installing `gh` on Linux/WSL, the installer now explicitly tells the user to run `gh auth login` **without** `sudo`. If auth was previously done with `sudo`, credentials ended up in `/root/.config/gh/` and were invisible to the current user, causing the auth check to fail. The error messages at both the post-install and the auth-check step now clearly warn: do not use sudo for `gh auth`.

### Previous: v3.9.16

- **Fix: installer works on WSL and non-root Linux** — all Linux package manager commands (`apt-get`, `dnf`, `yum`, `zypper`, `pacman`), Docker convenience script, and `systemctl` calls now automatically use `sudo` when the installer is not running as root. Root installs are unaffected. Fixes `Permission denied` / lock file errors on WSL and standard Linux desktop users.

### Previous: v3.9.15

- **Cold-start report cached in `/data/`** — `analyze_codebase()` saves the report to `/data/coldstart-<project>.md` after the first run. Subsequent calls return the cached report instantly (<1s). The installer also writes the cache during install so the very first MCP call by any agent is instant. Call with `force=True` to regenerate after major codebase changes.
- **`analyze_codebase()` in all agent Session Setup templates** — `AGENTS.md`, `.cursor/rules/flaiwheel.mdc`, `CLAUDE.md`, and `.github/copilot-instructions.md` all now include it as step 3 of Session Setup. Agents automatically get the codebase overview before starting work.
- **Cold-start prompt asked before Docker rebuild** — all interactive questions (embedding model + cold-start) are now batched upfront, then the rebuild runs unattended.
- **Fix: used `docker exec` for cold-start** — replaced broken HTTP calls to the MCP SSE endpoint with direct `docker exec python3`. Analysis now works reliably in ~20s.

### Previous: v3.9.14
- **Fix: fast-path always prompts for cold-start** — no more silent skip when cached report exists.

### Previous: v3.9.13
- **Improved cold-start classification** — two-pass classifier: path heuristics first, code-specific embedding templates as fallback.

### Previous: v3.9.12
- **Fix: y/n answer respected before cache check** — explicit `y` now always re-runs analysis even when cached report exists.

### Previous: v3.9.11
- **Fix: coldstart functions in global scope** — moved `_run_coldstart`/`_do_coldstart_analysis` to top of script so fast-path can call them.

### Previous: v3.9.10
- **Fix: version check** — `LATEST_VERSION` now uses `_FW_VERSION` directly, no CDN fetch.

### Previous: v3.9.9
- **Fix: cold-start on all paths** — `_run_coldstart()` called from fast-path, update, and fresh install. Smart cache detection.

### Previous: v3.9.8
- **Cold-start report caching** — `analyze_codebase()` cached to `/data/coldstart-<project>.md` for instant reads. New `force=True` param.

### Previous: v3.9.7
- **Agent Session Setup** — all instruction templates now include `analyze_codebase()` as a first-session step.

### Previous: v3.9.6
- **Fix: use docker exec** — replaced broken HTTP calls to MCP SSE endpoint with direct `docker exec python3` invocation. Cold-start report now actually works (~20s).

### Previous: v3.9.5
- **Fix: warm up embedding model** — added model warm-up before cold-start analysis (superseded by v3.9.6).

### Previous: v3.9.4
- **Fix: cold-start retries while model loads** — installer now retries `analyze_codebase()` for up to 90s after container starts.

### Previous: v3.9.3
- **Fix: update detection always checks `main`** — `LATEST_VERSION` now fetched from `main` branch so stale cached installers no longer silently skip updates.

### Previous: v3.9.2
- **Cold-start prompt moved before Docker rebuild** — all interactive questions now batched upfront.

### Previous: v3.9.1
- **Cold-start prompt moved upfront** — the `install.sh` cold-start question is now asked right after the embedding model selection (before the Docker rebuild), so all interactive questions are gathered first and the user never misses the prompt after a long rebuild.

### Previous: v3.9.0
- **`analyze_codebase(path)`** — new 28th MCP tool for zero-token cold-start analysis of legacy codebases. Runs entirely server-side in Docker. Uses Python `ast`, regex, MiniLM embeddings, and nearest-centroid classification. Returns a ranked `bootstrap_report.md` with language distribution, category map, top 20 files by documentability score, near-duplicate pairs, and recommended next steps. Reduces cold-start token cost by ∼90%.

### Previous: v3.8.3
- **No auto-index on project add** — adding a project via the web UI no longer immediately pulls and embeds the knowledge repo. Indexing is now deferred until explicitly triggered (“Git Pull + Reindex” or `reindex()` MCP tool), keeping the vector DB clean until the repo has been reviewed.

### Previous: v3.6.x
- VS Code / GitHub Copilot support — installer writes `.vscode/mcp.json` and `.github/copilot-instructions.md`.
- Claude Desktop support — installer auto-configures Claude Desktop via `mcp-remote`.
- Web UI Client Configuration panel — VS Code and Claude Code CLI tabs added.

### Previous: v3.5.x
- Claude Desktop + Claude Code CLI support added.
- README strategically rewritten with positioning, target audience, and competitive framing.

### Previous: v3.4.x
- Search miss rate fix — `search_bugfixes` calls no longer inflate miss rate above 100%.
- Classification consistency — `_path_category_hint` unified token-based approach across all categories.
- `CHANGELOG.md` added to repo root.

---

## Quick Start — One Command (recommended)

**Prerequisites:** [GitHub CLI](https://cli.github.com) authenticated (`gh auth login`), [Docker](https://docs.docker.com/get-docker/) running.

**Platform support:** macOS and Linux work out of the box. On **Windows**, run the installer from [WSL](https://learn.microsoft.com/en-us/windows/wsl/install) or [Git Bash](https://gitforwindows.org/) (Docker Desktop must be running with WSL 2 backend enabled).

Run this from inside your project directory:

```bash
bash <(curl -sSL https://raw.githubusercontent.com/dl4rce/flaiwheel/main/scripts/install.sh)
```

> **WSL2 / Linux note:** Use the `bash <(curl ...)` form above — it avoids `curl: (23)` pipe write errors that occur with `curl | bash` on some WSL2 setups. Never prefix with `sudo`.

**That's it.** The installer automatically:

1. Detects your project name and GitHub org from the git remote
2. Creates a private `<project>-knowledge` repo with the standard folder structure
3. Starts the Flaiwheel Docker container pointed at that repo
4. Configures **Cursor** — writes `.cursor/mcp.json` and `.cursor/rules/flaiwheel.mdc`
5. Configures **VS Code / GitHub Copilot** — writes `.vscode/mcp.json` (native SSE, VS Code 1.99+) and `.github/copilot-instructions.md`
6. Configures **Claude Desktop** (macOS app) — writes `claude_desktop_config.json` via `mcp-remote` bridge (requires Node.js)
7. Configures **Claude Code CLI** — writes `.mcp.json` + `CLAUDE.md` and runs `claude mcp add` automatically if the CLI is on PATH
8. Installs **Claude Cowork skill** — writes `.skills/skills/flaiwheel/SKILL.md` so the full Flaiwheel workflow is available as a native Claude skill
9. Writes `AGENTS.md` for all other agents
10. If existing `.md` docs are found, creates a migration guide — the AI will offer to organize them into the knowledge repo

**After install:**

| Agent | What to do |
|-------|------------|
| **Cursor** | Restart Cursor → Settings → MCP → enable `flaiwheel` toggle |
| **Claude Desktop** (macOS app) | Quit and reopen Claude for Mac — hammer icon appears when connected |
| **Claude Code CLI** | Already registered automatically — run `/mcp` inside Claude Code to verify |
| **VS Code** | Open project → Command Palette → **MCP: List Servers** → start `flaiwheel` |
| **Claude (Cowork)** | Skill auto-loads from `.skills/skills/flaiwheel/SKILL.md` — no further action needed |

The installer also sets up a **post-commit git hook** that automatically captures every `fix:`, `feat:`, `refactor:`, `perf:`, and `docs:` commit as a structured knowledge doc — no agent or manual action required.

Once connected, the AI has access to all Flaiwheel tools. If you have existing docs, tell the AI: *"migrate docs"*.

---

## Optional: Open Terminal Local Daemon (Open WebUI)

If you also use Open WebUI's Open Terminal integration, this repo includes helper installers for a local `open-terminal` daemon.

Third-party write-ups (for example [AI·Collab — Open Terminal](https://aicollab.app/blog/open-terminal/)) may mirror only the Linux script; **macOS** uses `scripts/macos/install-open-terminal-launchagent.sh` below. After any mirror update, re-check the file with `shasum -a 256` against the same revision on GitHub.

### One-liner install via `curl` (no git clone)

Use **`main`** or pin a **commit SHA** / **tag** in the URL for reproducible bytes.

**Linux / WSL2** (`systemd --user`):

```bash
curl -fsSL -o install-open-terminal-systemd-user.sh \
  https://raw.githubusercontent.com/dl4rce/flaiwheel/main/scripts/install-open-terminal-systemd-user.sh
/bin/chmod +x install-open-terminal-systemd-user.sh
/bin/bash ./install-open-terminal-systemd-user.sh
```

**macOS** (LaunchAgent; do **not** use `sudo`):

```bash
curl -fsSL -o install-open-terminal-launchagent.sh \
  https://raw.githubusercontent.com/dl4rce/flaiwheel/main/scripts/macos/install-open-terminal-launchagent.sh
/bin/chmod +x install-open-terminal-launchagent.sh
/bin/bash ./install-open-terminal-launchagent.sh
```

If `chmod` or `bash` are “not found”, your `PATH` is broken (often Conda `base`); the `/bin/…` paths above still work.

### Linux / WSL2 (`systemd --user`)

```bash
./scripts/install-open-terminal-systemd-user.sh
```

- Service name: `com.flaiwheel.open-terminal-local.service`
- Default endpoint: `http://localhost:8000`
- The script auto-generates an API key and prints it after install/reset.
- On WSL2, make sure `systemd=true` is enabled in `/etc/wsl.conf`.

Useful commands:

```bash
systemctl --user status com.flaiwheel.open-terminal-local.service
journalctl --user -u com.flaiwheel.open-terminal-local.service -f
```

### macOS (`launchctl` LaunchAgent)

```bash
./scripts/macos/install-open-terminal-launchagent.sh
```

- LaunchAgent label: `com.flaiwheel.open-terminal-local`
- Default endpoint: `http://localhost:8000`
- The script auto-generates an API key and prints it after install/reset.
- The LaunchAgent sets **WorkingDirectory** to `$HOME` by default so Open Terminal does not start in `/private/tmp`.
- **PATH:** `launchd` does not load `~/.zshrc`, so the daemon used to see only `/usr/bin:/bin:…` and miss Homebrew / Supabase CLI. The generated wrapper prepends `/opt/homebrew/bin`, `/usr/local/bin`, `~/.local/bin`, and `~/.npm-global/bin`. Re-run the installer (menu **→1 Update**) after pulling this change so the wrapper is regenerated.
- **Persisted custom folder:** the installer can save a path in `~/.config/flaiwheel/open-terminal-working-directory` (fresh-install prompt, or menu **→5** when re-running the script). **Update (menu →1)** keeps using that saved path. One-off override: set `OPEN_TERMINAL_WORKING_DIRECTORY` for that run only.

Environment overrides (both scripts):

```bash
HOST=127.0.0.1 PORT=8000 OPEN_TERMINAL_CORS_ALLOWED_ORIGINS='https://your-openwebui.example' ./scripts/install-open-terminal-systemd-user.sh
```

```bash
HOST=127.0.0.1 PORT=8000 OPEN_TERMINAL_CORS_ALLOWED_ORIGINS='https://your-openwebui.example' ./scripts/macos/install-open-terminal-launchagent.sh
```

macOS only — custom initial folder for Open Terminal (must exist before you save it):

```bash
OPEN_TERMINAL_WORKING_DIRECTORY="$HOME/projects/my-repo" ./scripts/macos/install-open-terminal-launchagent.sh
```

Re-run the same script and choose **5** to change or clear the saved folder (or edit `~/.config/flaiwheel/open-terminal-working-directory`). Non-interactive install: set the env var above or create that file with a single line (path); use `AUTO_INSTALL_DEPS=1` to skip the first-run path prompt.

---

## Updating

Run the same install command again from your project directory:

```bash
bash <(curl -sSL https://raw.githubusercontent.com/dl4rce/flaiwheel/main/scripts/install.sh)
```

The installer detects the existing container, asks for confirmation, then:
- Verifies the target host ports are actually free **before** touching anything (and refuses if a non-Flaiwheel process owns them)
- Rebuilds the Docker image with the latest code **before** stopping the old container, so a failed build is not an outage
- Recreates the container, preserving both volumes (`/data` **and** `/docs`), the host port bindings, and all `MCP_*` settings
- Refreshes all agent configs and guides

Your knowledge base, index, and credentials are preserved — only the code is updated.

### Shared hosts, custom ports, and proxies

If `8080`/`8081` are already taken (e.g. by a fronting proxy), point the installer at free ports instead of fighting over the defaults:

```bash
FLAIWHEEL_WEB_PORT=18080 FLAIWHEEL_SSE_PORT=18081 \
FLAIWHEEL_WEB_BIND=127.0.0.1 FLAIWHEEL_SSE_BIND=127.0.0.1 \
  bash <(curl -sSL https://raw.githubusercontent.com/dl4rce/flaiwheel/main/scripts/install.sh)
```

| Variable | Default | Purpose |
|----------|---------|---------|
| `FLAIWHEEL_WEB_PORT` | `8080` | Host port for the Web UI |
| `FLAIWHEEL_SSE_PORT` | `8081` | Host port for the MCP SSE endpoint |
| `FLAIWHEEL_WEB_BIND` | `0.0.0.0` | Host bind address for the Web UI |
| `FLAIWHEEL_SSE_BIND` | `0.0.0.0` | Host bind address for MCP SSE |
| `FLAIWHEEL_AGGRESSIVE_CLEANUP` | `0` | Allow host-wide Docker pruning (`docker image prune -af`, `docker container prune -f`, `systemctl stop docker`) |
| `FLAIWHEEL_CLIENT_CA_PATH` | `$HOME/.flaiwheel/ca.pem` | Where the installer exports the TLS CA so clients on this host can trust it |

On a **shared** host, leave `FLAIWHEEL_AGGRESSIVE_CLEANUP` unset. Those commands affect every project and container on the machine — `systemctl stop docker` stops *all* containers — so they are off by default and only run when you explicitly opt in.

> **Which ports does the installer change?** `FLAIWHEEL_SSE_PORT` changes the container's published port only. Client config files generated for other tools still reference the default `8081`, so update those yourself if you remap it.

---

## Manual Setup

<details>
<summary>Click to expand manual steps</summary>

### 1. Create a knowledge repo

```bash
# On GitHub, create: <your-project>-knowledge (private repo)
mkdir -p architecture api bugfix-log best-practices setup changelog
echo "# Project Knowledge Base" > README.md
git add -A && git commit -m "init" && git push
```

### 2. Build and start Flaiwheel

```bash
git clone https://github.com/dl4rce/flaiwheel.git /tmp/flaiwheel-build
docker build -t flaiwheel:latest /tmp/flaiwheel-build

# Smoke-test the image BEFORE starting or replacing a container.
# A broken transitive dependency only surfaces on a fresh resolve, so an
# image can build cleanly and still fail every import at runtime.
docker run --rm --entrypoint sh flaiwheel:latest -c \
  'python -c "from flaiwheel.server import create_mcp_server; import flaiwheel; print(flaiwheel.__version__)"'

docker run -d \
  --name flaiwheel \
  -p 8080:8080 \
  -p 8081:8081 \
  -e MCP_GIT_REPO_URL=https://github.com/you/yourproject-knowledge.git \
  -e MCP_GIT_TOKEN=ghp_your_token \
  -v flaiwheel-data:/data \
  flaiwheel:latest
```

> **Upgrading an existing container?** Run the smoke test above first, then rename
> the old container instead of removing it (`docker rename flaiwheel flaiwheel-rollback`)
> so you can restore it instantly if the new one fails its health check. Confirm every
> knowledge repo is pushed (`git rev-list --count @{u}..HEAD` → `0`) before swapping.

### 3. Connect your AI agent

**Cursor** — add to `.cursor/mcp.json`:
```json
{
  "mcpServers": {
    "flaiwheel": {
      "type": "sse",
      "url": "http://localhost:8081/sse"
    }
  }
}
```

**VS Code / GitHub Copilot** (1.99+) — add to `.vscode/mcp.json`:
```json
{
  "servers": {
    "flaiwheel": {
      "type": "sse",
      "url": "http://localhost:8081/sse"
    }
  }
}
```
Then: Command Palette → **MCP: List Servers** → start `flaiwheel`.

**Claude Desktop** (macOS app) — add to `~/Library/Application Support/Claude/claude_desktop_config.json`:
```json
{
  "mcpServers": {
    "flaiwheel": {
      "command": "npx",
      "args": ["-y", "mcp-remote", "http://localhost:8081/sse"]
    }
  }
}
```
Requires Node.js. Restart Claude for Mac after editing.

**Claude Code CLI** — run once in your project directory:
```bash
claude mcp add --transport sse --scope project flaiwheel http://localhost:8081/sse
```

### 3b. Remote / LAN deployment (serving MCP beyond localhost)

By default the MCP endpoint is **loopback-only**: the transport guard accepts
only `localhost`, `127.0.0.1` and `[::1]`, and any other client gets
`HTTP 421 Invalid Host header`. Binding is not the problem — the allowlist is,
and it is enforced at the application layer, so it is invisible from inside the
container.

To serve a shared or LAN instance, allowlist the hostname you connect with:

```bash
docker run -d --name flaiwheel -p 8080:8080 -p 8081:8081 \
  -v flaiwheel-data:/data -v flaiwheel-docs:/docs \
  -e MCP_SSE_ALLOWED_HOSTS=flaiwheel.example.com,flaiwheel.lan \
  flaiwheel:latest
```

Both forms work: comma-separated (`host1,host2`) or JSON (`["host1"]`). Each
host matches with **or without** a port, so the same entry covers
`flaiwheel.example.com` and `flaiwheel.example.com:8081`. Loopback entries are
always kept, so an SSH tunnel or an existing Host-rewriting proxy keeps working
unchanged.

Then point your client at that hostname:

```json
{ "mcpServers": { "flaiwheel": { "type": "sse",
  "url": "http://flaiwheel.example.com:8081/sse" } } }
```

> **Default and recommended:** use the HTTP endpoint on port `8081`. This avoids
> client trust-store setup and is the configuration produced by the normal
> installer command. HTTP traffic is not encrypted, so use it only on a trusted
> LAN, localhost, an SSH tunnel, or an encrypted network such as WireGuard or
> Tailscale.

#### Optional automatic TLS — client trust required

Automatic TLS is available, but it is **not the default recommendation yet**.
Flaiwheel generates and persists a private CA and a server certificate. Each
client computer must trust that CA. Direct Cursor/Electron SSE connections use
the operating-system trust store; an `env` object in a direct remote-server
entry does not establish certificate trust.

New installation, with TLS off by default (an update preserves its current mode):

```bash
bash <(curl -sSL https://raw.githubusercontent.com/dl4rce/flaiwheel/main/scripts/install.sh)
```

Explicitly return an existing TLS-enabled installation to HTTP:

```bash
FLAIWHEEL_TLS_AUTO=0 bash <(curl -sSL https://raw.githubusercontent.com/dl4rce/flaiwheel/main/scripts/install.sh)
```

Enable automatic TLS only when the clients have been prepared to trust its CA:

```bash
FLAIWHEEL_TLS_AUTO=1 bash <(curl -sSL https://raw.githubusercontent.com/dl4rce/flaiwheel/main/scripts/install.sh)
```

The generated files are stored in `/data/tls` in the persistent data volume.
The installer exports the public CA certificate to
`${HOME}/.flaiwheel/ca.pem`; override that path with
`FLAIWHEEL_CLIENT_CA_PATH`. Flaiwheel reuses the generated CA across container
restarts and upgrades.

After enabling TLS, install the exported CA into the trust store used by the
client operating system, restart the client, and use an HTTPS endpoint such as:

```json
{ "mcpServers": { "flaiwheel": { "type": "sse",
  "url": "https://192.168.178.230:8081/sse" } } }
```

##### macOS: verified direct Cursor/Electron setup

The following procedure was verified against the live `v3.15.3` deployment on
2026-09-11. It uses the current user's login keychain and does not require
`NODE_EXTRA_CA_CERTS` for the direct SSE entry.

1. Copy the CA from the Flaiwheel server to the Mac and verify its SHA-256
   against the server copy:

```bash
mkdir -p "$HOME/.flaiwheel"
scp user@flaiwheel-host:/path/to/exported/ca.pem "$HOME/.flaiwheel/ca.pem"
chmod 644 "$HOME/.flaiwheel/ca.pem"
shasum -a 256 "$HOME/.flaiwheel/ca.pem"
```

1. Add the verified CA to the login keychain as a trusted root:

```bash
security add-trusted-cert \
  -r trustRoot \
  -k "$HOME/Library/Keychains/login.keychain-db" \
  "$HOME/.flaiwheel/ca.pem"
```

1. Configure the direct SSE endpoint with `https://` and the hostname or IP
   covered by the certificate. Do not add an `env` block:

```json
{
  "mcpServers": {
    "flaiwheel": {
      "type": "sse",
      "url": "https://flaiwheel.example.com:8081/sse"
    }
  }
}
```

1. Fully quit and restart Cursor after installing the CA or changing
   `.cursor/mcp.json`.

1. Verify system trust without `-k`, `--cacert`, or an environment override:

```bash
curl -i --max-time 5 https://flaiwheel.example.com:8081/sse
```

Expected: `HTTP/1.1 200 OK`, `content-type: text/event-stream`, and an
`event: endpoint` payload. `curl` exits after the timeout because an SSE stream
remains open; receiving the `200` and event proves the TLS and SSE connection.

Do not use `NODE_TLS_REJECT_UNAUTHORIZED=0`; it disables certificate
verification.

#### Native TLS — your own certificate, no proxy at all

Set a certificate and key and the endpoint is served over HTTPS directly:

```bash
docker run -d --name flaiwheel -p 8080:8080 -p 8081:8081 \
  -v flaiwheel-data:/data -v flaiwheel-docs:/docs \
  -v /etc/ssl/flaiwheel:/certs:ro \
  -e MCP_SSE_ALLOWED_HOSTS=flaiwheel.example.com \
  -e MCP_SSE_TLS_CERTFILE=/certs/fullchain.pem \
  -e MCP_SSE_TLS_KEYFILE=/certs/privkey.pem \
  flaiwheel:latest
```

```json
{ "mcpServers": { "flaiwheel": { "type": "sse",
  "url": "https://flaiwheel.example.com:8081/sse" } } }
```

Obtain the certificate from your CA (e.g. `certbot certonly --standalone -d
flaiwheel.example.com`), or generate an internal-CA cert for a LAN hostname.
Use `fullchain.pem`, not the bare leaf certificate, or clients will reject it
for an incomplete chain.

**TLS fails closed.** If only one of the two paths is set, or the file is
unreadable, Flaiwheel **refuses to start** rather than quietly falling back to
plain HTTP — an operator who asked for encryption must never silently get
cleartext. Check the logs for `FATAL: MCP_SSE_TLS_...` if the container exits
immediately. Note the Web UI on `MCP_WEB_PORT` is still plain HTTP; terminate
TLS for the UI at a proxy if you expose it beyond localhost.

**`FASTMCP_HOST` will not help.** It looks like the obvious knob and is
silently ignored: `FastMCP.__init__` passes `host` and `transport_security`
into its `Settings(...)` as explicit init arguments, and pydantic-settings
gives init arguments precedence over environment variables. Use
`MCP_SSE_HOST` (bind address, default `0.0.0.0`) and `MCP_SSE_ALLOWED_HOSTS`
(allowlist) instead.

### 4. Done. Start coding.

</details>

---

## Knowledge Repo Structure

```
yourproject-knowledge/
├── README.md              ← overview / index
├── architecture/          ← system design, decisions, diagrams
├── api/                   ← endpoint docs, contracts, schemas
├── bugfix-log/            ← auto-generated bugfix summaries
│   └── 2026-02-25-fix-payment-retry.md
├── best-practices/        ← coding standards, patterns
├── setup/                 ← deployment, environment setup
├── changelog/             ← release notes
└── tests/                 ← test cases, scenarios, regression patterns
```

---

## Supported Input Formats

Flaiwheel indexes 9 file formats. All non-markdown files are converted to markdown-like text in memory at index time — no generated files on disk, no repo clutter.

| Format | Extension(s) | How it works |
|--------|-------------|--------------|
| **Markdown** | `.md` | Native (pass-through) |
| **Plain text** | `.txt` | Wrapped in `# filename` heading |
| **PDF** | `.pdf` | Text extracted per page via `pypdf` |
| **HTML** | `.html`, `.htm` | Headings/lists/code converted to markdown, scripts stripped |
| **reStructuredText** | `.rst` | Heading underlines converted to `#` levels, code blocks preserved |
| **Word** | `.docx` | Paragraphs + heading styles mapped to markdown |
| **JSON** | `.json` | Pretty-printed in fenced `json` code block |
| **YAML** | `.yaml`, `.yml` | Wrapped in fenced `yaml` code block |
| **CSV** | `.csv` | Converted to markdown table |

Quality checks (structure, completeness, bugfix format) apply only to `.md` files. Other formats are indexed as-is.

---

## Configuration

All config via environment variables (`MCP_` prefix), Web UI (http://localhost:8080), or `.env` file.

| Variable | Default | Description |
|----------|---------|-------------|
| `MCP_DOCS_PATH` | `/docs` | Path to .md files inside container |
| `MCP_EMBEDDING_PROVIDER` | `local` | `local` (free, private) or `openai` |
| `MCP_EMBEDDING_MODEL` | `all-MiniLM-L6-v2` | Embedding model name |
| `MCP_CHUNK_STRATEGY` | `heading` | `heading`, `fixed`, or `hybrid` |
| `MCP_RERANKER_ENABLED` | `false` | Enable cross-encoder reranker for higher precision |
| `MCP_RERANKER_MODEL` | `cross-encoder/ms-marco-MiniLM-L-6-v2` | Reranker model name |
| `MCP_RRF_K` | `60` | RRF k parameter (lower = more weight on top ranks) |
| `MCP_RRF_VECTOR_WEIGHT` | `1.0` | Vector search weight in RRF fusion |
| `MCP_RRF_BM25_WEIGHT` | `1.0` | BM25 keyword search weight in RRF fusion |
| `MCP_MIN_RELEVANCE` | `0` | Minimum relevance % to return (0 = no filter) |
| `MCP_GIT_REPO_URL` | | Knowledge repo URL (enables git sync) |
| `MCP_GIT_BRANCH` | `main` | Branch to sync |
| `MCP_GIT_TOKEN` | | GitHub token for private repos |
| `MCP_GIT_SYNC_INTERVAL` | `300` | Pull interval in seconds (0 = disabled) |
| `MCP_GIT_AUTO_PUSH` | `true` | Auto-commit + push bugfix summaries |
| `MCP_GITLEAKS_MODE` | `block` | Secret scan before auto-commit: `block` (refuse), `warn` (commit + report), `off` |
| `MCP_WEBHOOK_SECRET` | | GitHub webhook secret (enables `/webhook/github` HMAC verification) |
| `MCP_TRANSPORT` | `sse` | MCP transport: `sse` or `stdio` |
| `MCP_SSE_PORT` | `8081` | MCP SSE endpoint port |
| `MCP_SSE_HOST` | `0.0.0.0` | SSE **bind address** (an IP, not a hostname). Set `127.0.0.1` for loopback-only |
| `MCP_SSE_ALLOWED_HOSTS` | | Extra `Host` headers accepted by the transport guard, comma-separated or JSON. Required to serve remote/LAN clients directly |
| `MCP_SSE_ALLOWED_ORIGINS` | | Extra `Origin` headers (browser-based MCP clients only) |
| `MCP_SSE_DNS_REBINDING_PROTECTION` | `true` | Keep on. `false` disables the `Host` **and** `Origin` guard for all clients |
| `MCP_SSE_TLS_CERTFILE` | | PEM certificate — with the key, serves MCP over **HTTPS** natively (no proxy) |
| `MCP_SSE_TLS_KEYFILE` | | PEM private key. TLS is all-or-nothing: set both or neither |
| `MCP_SSE_TLS_AUTO` | `false` | Let Flaiwheel issue its own CA + certificate when none is configured. Opt-in: it flips the endpoint to HTTPS, so existing clients must trust the new CA |
| `MCP_SSE_TLS_DIR` | `/data/tls` | Where generated certificates live. **Must be persistent** — regenerating the CA invalidates every client that trusts it |
| `MCP_WEB_PORT` | `8080` | Web UI port |

### Multi-Repo Support

A single Flaiwheel container can manage multiple knowledge repositories — one per project. Each project gets its own ChromaDB collection, git watcher, index lock, health tracker, and quality checker, while sharing one embedding model in RAM and one MCP/Web endpoint.

**How it works:**
- The first `install.sh` run creates the Flaiwheel container with project A
- Subsequent `install.sh` runs from other project directories detect the running container and register the new project via the API — no additional containers
- All MCP tools accept an optional `project` parameter (e.g., `search_docs("query", project="my-app")`)
- Call `set_project("my-app")` at the start of every conversation to bind all subsequent calls to that project (sticky session)
- Without an explicit `project` parameter, the active project (set via `set_project`) is used; if none is set, the first project is used
- The Web UI has a project selector dropdown to switch between projects
- Use `list_projects()` via MCP to see all registered projects (shows active marker)

**Adding/removing projects:**
- **Via AI agent:** call `setup_project(name="my-app", git_repo_url="...")` — registers, clones, indexes, and auto-binds
- **Via install script:** run `install.sh` from a new project directory (auto-registers)
- **Via Web UI:** click "Add Project" in the project selector bar
- **Via API:** `POST /api/projects` with `{name, git_repo_url, git_branch, git_token}`
- **Remove:** `DELETE /api/projects/{name}` or the "Remove" button in the Web UI

**Backward compatibility:** existing single-project setups continue to work without changes. If no `projects.json` exists but `MCP_GIT_REPO_URL` is set, Flaiwheel auto-creates a single project from the env vars.

### Embedding Model Hot-Swap

When you change the embedding model via the Web UI, Flaiwheel re-embeds all documents in the background using a shadow collection. Search remains fully available on the old model while the migration runs. Once complete, the new index atomically replaces the old one — zero downtime.

The Web UI shows a live progress bar with file count and percentage. You can cancel at any time.

### Embedding Models (local, free)

| Model | RAM | Quality | Best for |
|-------|-----|---------|----------|
| `all-MiniLM-L6-v2` | 90MB | 78% | Large repos, low RAM |
| `nomic-ai/nomic-embed-text-v1.5` | 520MB | 87% | Best English quality |
| `BAAI/bge-m3` | 2.2GB | 86% | Multilingual (DE/EN) |

Select via Web UI or `MCP_EMBEDDING_MODEL` env var. Full list in the Web UI.

### Cross-Encoder Reranker (optional)

The reranker is a second-stage model that rescores the top candidates from hybrid search. It reads the full `(query, document)` pair together, which produces much more accurate relevance scores than independent embeddings — especially for vocabulary-mismatch queries where the user and the document use different words for the same concept.

**How it works:**
1. Hybrid search (vector + BM25) retrieves a wider candidate pool (`top_k × 5`)
2. RRF merges and ranks the candidates
3. The cross-encoder rescores the top candidates and returns only the best `top_k`

**Enable via Web UI** (Search & Retrieval card) or environment variable:
```bash
docker run -d \
  -e MCP_RERANKER_ENABLED=true \
  -e MCP_RERANKER_MODEL=cross-encoder/ms-marco-MiniLM-L-6-v2 \
  ...
```

| Reranker Model | RAM | Speed | Quality |
|----------------|-----|-------|---------|
| `cross-encoder/ms-marco-MiniLM-L-6-v2` | 90MB | Fast | Good — best speed/quality balance |
| `cross-encoder/ms-marco-MiniLM-L-12-v2` | 130MB | Medium | Better — higher precision |
| `BAAI/bge-reranker-base` | 420MB | Slower | Best — state-of-the-art accuracy |

The reranker is **off by default** (zero overhead). When enabled, it adds ~50ms latency per search but typically improves precision by 10-25% on vocabulary-mismatch queries.

### GitHub Webhook (instant reindex)

Instead of waiting for the 300s polling interval, configure a GitHub webhook for instant reindex on push:

1. In your knowledge repo on GitHub: **Settings → Webhooks → Add webhook**
2. **Payload URL:** `http://your-server:8080/webhook/github`
3. **Content type:** `application/json`
4. **Secret:** set the same value as `MCP_WEBHOOK_SECRET`
5. **Events:** select "Just the push event"

The webhook endpoint verifies the HMAC signature if `MCP_WEBHOOK_SECRET` is set. Without a secret, any POST triggers a pull + reindex.

### CI Guardrail Telemetry (ROI tracking)

Track non-vanity engineering impact directly in Flaiwheel:

- **POST** `/api/telemetry/ci-guardrail-report` — CI reports guardrail findings/fixes per PR
- **GET** `/api/impact-metrics?project=<name>&days=30` — returns estimated time saved + regressions avoided

Example payload:

```json
{
  "project": "my-app",
  "violations_found": 4,
  "violations_blocking": 1,
  "violations_fixed_before_merge": 2,
  "cycle_time_baseline_minutes": 58,
  "cycle_time_actual_minutes": 43,
  "pr_number": 127,
  "branch": "feature/payment-fix",
  "commit_sha": "abc1234",
  "source": "github-actions"
}
```

Flaiwheel persists telemetry on disk (`<vectorstore>/telemetry`) so metrics survive container restarts and updates.

### Diff-aware Reindexing

Reindexing is incremental by default — only files whose content changed since the last run are re-embedded. On a 500-file repo, this means a typical reindex after a single-file push takes <1s instead of re-embedding everything.

Use `reindex(force=True)` via MCP or the Web UI "Reindex" button to force a full rebuild (e.g. after changing the embedding model).

---

## Architecture

```
┌─────────────────────────────────────────────────────────────┐
│  Docker Container (single process, N projects)               │
│                                                              │
│  ┌───────────────────────────────────────────────────────┐  │
│  │  Web-UI (FastAPI)                        Port 8080    │  │
│  │  Project CRUD, config, monitoring, search, health     │  │
│  └─────────────────────┬─────────────────────────────────┘  │
│                         │ shared state (ProjectRegistry)     │
│  ┌─────────────────────┴─────────────────────────────────┐  │
│  │  MCP Server (FastMCP)                    Port 8081    │  │
│  │  30 tools (search, write, classify, manage, projects,│  │
│  │           relations, timeline)                        │  │
│  └─────────────────────┬─────────────────────────────────┘  │
│                         │                                    │
│  ┌─────────────────────┴─────────────────────────────────┐  │
│  │  Shared Embedding Model (1× in RAM)                   │  │
│  └─────────────────────┬─────────────────────────────────┘  │
│                         │                                    │
│  ┌──────────────────────┴────────────────────────────────┐  │
│  │  Per-Project Contexts (isolated)                      │  │
│  │  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐   │  │
│  │  │  Project A  │  │  Project B  │  │  Project C  │   │  │
│  │  │  collection │  │  collection │  │  collection │   │  │
│  │  │  watcher    │  │  watcher    │  │  watcher    │   │  │
│  │  │  lock       │  │  lock       │  │  lock       │   │  │
│  │  │  health     │  │  health     │  │  health     │   │  │
│  │  │  quality    │  │  quality    │  │  quality    │   │  │
│  │  └─────────────┘  └─────────────┘  └─────────────┘   │  │
│  └───────────────────────────────────────────────────────┘  │
│                                                              │
│  /docs/{project}/  ← per-project knowledge repos             │
│  /data/            ← shared vectorstore + config + projects  │
└─────────────────────────────────────────────────────────────┘
```

### Search Pipeline

```
query
  │
  ├──► Vector Search (ChromaDB/HNSW, cosine similarity)
  │         fetch top_k (or top_k×5 if reranker enabled)
  │
  ├──► BM25 Keyword Search (bm25s, English stopwords)
  │         fetch top_k (or top_k×5 if reranker enabled)
  │
  ├──► RRF Fusion (configurable k, vector/BM25 weights)
  │         merge + rank candidates
  │
  ├──► [optional] Cross-Encoder Reranker
  │         rescore (query, doc) pairs for higher precision
  │
  ├──► Min Relevance Filter (configurable threshold)
  │
  └──► Return top_k results with relevance scores
```

---

## Web UI

Access at **http://localhost:8080** (HTTP Basic Auth — credentials shown on first start).

Features:
- System health panel: last index, last git pull, git commit, version, search metrics, quality score, skipped files count
- Index status and statistics (including reranker status)
- Embedding model selection (visual picker)
- **Search & Retrieval tuning**: cross-encoder reranker toggle + model picker, RRF weights, minimum relevance threshold
- Chunking strategy configuration
- Git sync settings (URL, branch, auto-push toggle)
- Test search interface
- Knowledge quality checker (also runs automatically after every reindex)
- Search metrics (hits/total, miss rate, per-tool breakdown)
- Skipped files indicator (files excluded from indexing due to critical quality issues)
- **"This is the Way" — Knowledge Bootstrap**: agent-driven project classification + in-repo cleanup (Web UI shows guidance + advanced scan)
- Multi-project switcher (manage multiple repos from one instance)
- Client configuration snippets (Cursor, Claude Desktop, Docker)
- Password management

---

## Development

```bash
# Clone
git clone https://github.com/dl4rce/flaiwheel.git
cd flaiwheel

# Install
pip install -e ".[dev]"

# Run tests (259 tests covering readers, quality checker, indexer, reranker, health tracker, MCP tools, model migration, multi-project, bootstrap, classification, file-context, cold-start analyzer)
pytest

# Run locally (needs /docs and /data directories)
mkdir -p /tmp/flaiwheel-docs /tmp/flaiwheel-data
MCP_DOCS_PATH=/tmp/flaiwheel-docs MCP_VECTORSTORE_PATH=/tmp/flaiwheel-data python -m flaiwheel
```

---

## License

**Business Source License 1.1 (BSL 1.1)**

Flaiwheel is source-available under the [Business Source License 1.1](https://mariadb.com/bsl11/).

**You may use Flaiwheel for free** if:
- Your use is **non-commercial** (personal, educational, no revenue), or
- Your organization has **no more than 10 individuals** using it

**Commercial use beyond these limits** (e.g., teams of 11+ or commercial deployment) requires a paid license.

- Effective **2030-02-25**, this version converts to **Apache License 2.0** (fully open source)
- Commercial licenses: [info@4rce.com](mailto:info@4rce.com) | [https://4rce.com](https://4rce.com)

See [LICENSE](https://github.com/dl4rce/flaiwheel/blob/HEAD/LICENSE) for full terms.

