Files
factory/lib/projects-meta-mcp/docs/superpowers/specs/2026-04-29-projects-meta-mcp-design.md
vitya ca669d96e1 feat(lib): bundle MCP servers from .common/lib into factory/lib/
Copies source (no node_modules, dist, .tasks, .wiki, __pycache__) for:
- projects-meta-mcp v2.25.0 (TypeScript/Node)
- wiki-graph v0.3.1 (TypeScript/Node)
- interns-mcp v0.3.3 (Python/FastMCP)

.gitignore: exclude lib build artefacts (node_modules, dist, .venv, __pycache__, *.pyc)
bootstrap.ps1: add MCP build step — npm install+build for TS servers, venv+pip for Python

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-11 13:17:29 +03:00

16 KiB
Raw Blame History

projects-meta-mcp — Design

Date: 2026-04-29 Status: Approved (брейнсторм завершён, спека к ревью) Owner: vitya

Цель

Дать Claude (и любому MCP-клиенту) единый, дешёвый и offline-устойчивый доступ к двум видам мета-информации, разбросанным по множеству проектов и нескольких машин:

  1. Статусы задач — свод .tasks/STATUS.md всех проектов в Gitea.
  2. Общие знания — поиск и чтение страниц общей вики (projects-wiki), отфильтрованных по домену текущего проекта.

Не цели (явный YAGNI)

  • Удалённый MCP-процесс на Synology (HTTP/SSE-транспорт). Откладывается до момента, когда «truly always-on» станет реальной болью.
  • Карантин _pending/ для projects-wiki. Решение по промоушену принимается в моменте.
  • Sparse-checkout всех проектов на каждую машину. API + кэш проще.
  • Серверный агрегатор / git-hook на стороне Gitea. Клиентский sync проще.
  • Embedding/семантический поиск projects-wiki. Достаточно поиска по тексту/тегам.
  • Управление зависимостями между проектами, GANTT, deadlines. Только статусы.

Архитектура

                       ┌──────────────────┐
                       │  Gitea (Synology)│
                       │  - все проекты   │
                       │  - projects-wiki   │
                       │  - projects-meta-│
                       │    mcp (этот код)│
                       └────────┬─────────┘
                                │ pull / API
                                ▼
┌────────────────── каждая машина ─────────────────┐
│                                                  │
│  ~/projects/.wiki/   ← git clone          │
│  ~/.cache/projects-mcp/      ← JSON-кэш STATUS   │
│                                                  │
│  sync-script (cron / руками):                    │
│    - git pull в projects-wiki                      │
│    - Gitea API → собрать STATUS.md → JSON-кэш    │
│    - offline → no-op, кэш остаётся               │
│                                                  │
│  mcp-server (локальный, stdio):                  │
│    Tools:                                        │
│      tasks.aggregate()                           │
│      tasks.search(q)                             │
│      knowledge.search(q, domain?)                │
│      knowledge.get(slug)                         │
│      knowledge.suggest_promote()                 │
└──────────────────────────────────────────────────┘

Принцип: MCP в hot path читает только локальные файлы. Сетевые вызовы — только в sync-script.

Компоненты

1. projects-wiki — отдельный git-репозиторий на Gitea

Имя репо: projects-wiki. Клонируется на каждую машину под ~/projects/.wiki/.

Структура:

~/projects/.wiki/
├── CLAUDE.md            ← правила промоушена + список доменов
├── README.md
├── cross/               ← кросс-доменные знания (git, vscode, claude-code, общие гочи)
├── node/                ← Node.js / TypeScript / npm / yarn
├── embedded/            ← микроконтроллеры, PlatformIO, STM32, Arduino
├── web/                 ← фронтенд, браузер, HTTP, Nginx
└── ...                  ← новые домены добавляются по мере появления проектов

Frontmatter каждой страницы:

---
title: Windows yarn requires exec()
domain: node              # обязательное: node | embedded | web | cross | ...
shared_at: 2026-04-29     # дата промоушена
applies_to: [windows]     # опц. — платформенные ограничения
source_project: npm-mcp   # опц. — откуда промоутили
---

Тело — стандартный concept: Why + Когда / Как чинить.

projects-wiki/CLAUDE.md содержит:

  • Полный список валидных доменов и что в каждый кладётся.
  • Эвристику промоушена (см. ниже).
  • Чёрный список (имена проектов, бизнес-доменов — никогда не упоминаются в shared).

2. sync-script

Bash или Node-скрипт. На каждой машине, запускается cron'ом / руками / git-хуком.

Шаги:

  1. Pull projects-wiki:

    git -C ~/projects/.wiki pull --quiet
    

    Offline → silently fail (exit 0), кэш не трогается.

  2. Скачать список репозиториев пользователя через Gitea API:

    GET https://git.kzntsv.site/api/v1/users/OpeItcLoc03/repos?limit=50
    

    С токеном (в ~/.config/projects-mcp/auth.toml).

  3. Параллельно (limit 10) скачать STATUS.md каждого:

    GET https://git.kzntsv.site/api/v1/repos/OpeItcLoc03/{repo}/raw/.tasks/STATUS.md
    

    404 → у проекта нет тасок, пропустить. Сетевая ошибка → пропустить, оставить старое значение в кэше.

  4. Собрать в ~/.cache/projects-mcp/tasks.json атомарно (write to tasks.json.tmp, rename):

    {
      "synced_at": "2026-04-29T12:00:00Z",
      "synced_from": "https://git.kzntsv.site",
      "machine": "vitya-desktop",
      "projects": [
        {
          "name": "npm-mcp",
          "default_branch": "main",
          "fetched_at": "2026-04-29T12:00:01Z",
          "active_tasks": [
            {"slug": "wiki-self-ingest", "status": "active", "next": "..."}
          ],
          "all_tasks_count": 5,
          "raw": "<полный STATUS.md>"
        }
      ],
      "errors": [{"project": "books", "reason": "network"}]
    }
    
  5. Exit 0 даже при частичных ошибках. Логи в ~/.cache/projects-mcp/sync.log.

3. mcp-server

Stateless Node-процесс, stdio-транспорт MCP. Читает только локальные файлы. Не делает сетевых вызовов.

Tool surface

Tool Аргументы Возвращает
tasks.aggregate() (filter?: { status?, project? }) Список активных задач по всем проектам. Только мета (slug, project, status, next-action), не полный текст.
tasks.search(query) query: string Поиск по STATUS.md-кэшу (substring + per-task title).
tasks.get(project) project: string Полный STATUS.md одного проекта.
knowledge.search(query, opts?) query: string, opts?: { domain?: string|"all", limit?: number } До 10 заголовков + 1-строчное описание. По умолчанию фильтр зависит от detected domain текущего проекта (см. ниже).
knowledge.get(slug) slug: string (например node/windows-yarn-exec) Полная страница (frontmatter + тело).
knowledge.suggest_promote() () Возвращает кандидатов на промоушен из текущего проекта. См. логику ниже.
meta.status() () Возвращает диагностику: когда был последний sync, кэш-stale-возраст, сколько проектов, сколько shared-страниц.

Two-step паттерн

search всегда возвращает только индекс (заголовок + 1 строка). get возвращает полный текст. Минимизирует токены — модель сама решает что углублять.

Detect domain текущего проекта

При запуске MCP получает cwd от клиента. Алгоритм:

  1. Если в cwd есть package.jsonnode.
  2. Если есть platformio.ini / *.ino / CMakeLists.txt с упоминанием arm-none-eabi или embedded SDK → embedded.
  3. Если есть next.config.* / vite.config.* / index.html без package.json (статика) → web.
  4. Иначе → unknown.

Логика фильтрации knowledge.search:

  • Detected domain — конкретный (node / embedded / web) → показываем domain == detected || domain == "cross".
  • Detected domain unknown → фильтра нет (видны все домены).
  • Опциональный аргумент opts.domain пользователя/Claude переопределяет: "all" снимает фильтр явно, конкретное имя меняет таргет.

cross в frontmatter — это тег страницы (универсальное знание). Detected unknown — это режим поиска (фильтра нет). Не путать.

Логика выносится в отдельный модуль domain-detector (тестируется юнит-тестами).

knowledge.suggest_promote()

Логика:

  1. Прочесть все concepts/*.md в <cwd>/.wiki/concepts/.
  2. Для каждого применить эвристику:
    • Включить как кандидата, если страница: упоминает платформу/тулинг (Windows, yarn, Node, MCP, git, Docker), не упоминает доменные имена из чёрного списка projects-wiki/CLAUDE.md.
    • Усилить сигнал, если в projects-wiki есть страница с близким заголовком в другом домене (= знание подтверждено повторно).
  3. Вернуть список кандидатов: { slug, current_path, suggested_domain, confidence, reason }.
  4. Решение принимает пользователь (Claude в чате предлагает строкой _Кандидат на shared: ... Промоутить?_, ждёт y/n).

Промоушен ≠ автомат. Tool только возвращает кандидатов, ничего не пишет.

4. Bootstrap

На новой машине (пример Windows + Git Bash; на других ОС адаптировать пути):

# один раз:
GITEA=https://git.kzntsv.site
OWNER=OpeItcLoc03
git clone $GITEA/$OWNER/projects-meta-mcp ~/.local/projects-meta-mcp
cd ~/.local/projects-meta-mcp && npm install && npm run build
git clone $GITEA/$OWNER/projects-wiki ~/projects/.wiki

mkdir -p ~/.config/projects-mcp
cp ~/.local/projects-meta-mcp/auth.toml.example ~/.config/projects-mcp/auth.toml
# отредактировать auth.toml — заполнить gitea_token

node ~/.local/projects-meta-mcp/dist/sync.js   # первый sync
# зарегистрировать MCP в claude code config (см. ниже)

Формат auth.toml:

gitea_url   = "https://git.kzntsv.site"
gitea_user  = "OpeItcLoc03"
gitea_token = "..."   # personal access token, scope: read:repository

Регистрация в Claude Code (~/.claude.json или per-project) — пути в Windows-формате с прямыми слэшами:

{
  "mcpServers": {
    "projects-meta": {
      "command": "node",
      "args": ["C:/Users/vitya/.local/projects-meta-mcp/dist/server.js"]
    }
  }
}

Поведение в offline

Ситуация Что работает Что не работает
Сеть есть, синк свежий Всё
Сеть есть, sync не запускался N часов Всё (кэш отдаёт старое) Свежесть других машин
Сеть пропала tasks.*, knowledge.search/get — всё работает knowledge.suggest_promote работает; git push после промоушена откладывается
Кэш отсутствует (новая машина без bootstrap) Только meta.status() (отдаёт diagnostic с пустым кэшем) Всё остальное

meta.status() всегда возвращает возраст кэша, чтобы Claude мог предупредить пользователя «данные старше 24 часов».

Авторизация

  • Gitea API token: scope read:repositorywrite:repository для пуша projects-wiki).
  • Хранится в ~/.config/projects-mcp/auth.toml (gitignore'd).
  • MCP-сервер сам не читает токен — только sync-script. MCP оперирует уже скачанными локальными файлами.

Тестирование

  • Unit: domain-detector, парсер STATUS.md, эвристика промоушена.
  • Integration: sync-script против локального Gitea (Docker fixture). Проверка: rate limit, 404 на отсутствующий STATUS.md, частичный отказ.
  • MCP smoke test: запустить сервер, дёрнуть каждый tool через MCP-клиент-fixture.

Открытые вопросы (не блокеры)

  1. Cron vs git-hook vs руками — определится при первом использовании. Стартовать с «руками + npm script», добавить cron когда станет лень.
  2. Уведомление о stale-кэше — порог в часах? Стартовать с 24, корректировать.
  3. Multi-user — пока однопользовательский (OpeItcLoc03). Если кто-то ещё начнёт пользоваться — переделать users/{owner}/repos в параметр из auth.toml.
  4. projects-wiki GC — раз в полгода аудит, какие страницы не использовались. Будет отдельный tool meta.audit_shared(months) в v2.

Дальнейшие шаги

  1. Создать репозиторий projects-meta-mcp на Gitea, запушить этот скелет.
  2. Создать репозиторий projects-wiki на Gitea с минимальной структурой (CLAUDE.md, cross/, node/).
  3. Перейти к фазе writing-plans: расписать пошаговый план реализации (sync-script сначала, MCP-сервер вторым, эвристика промоушена третьим).