Claude Code + Obsidian: как настроить рабочее окружение и превратить vault в долгосрочную память агента

Claude Code + Obsidian: настройка vault как памяти агента
Что получишь в конце
Claude Code забывает всё между сессиями. Obsidian хранит всё вечно в обычных .md файлах.
Связка решает проблему персистентной памяти агента: vault становится контекстом, который Claude читает при каждом старте, пишет в конце каждой сессии и использует для поиска нужных знаний.
Статья — полный гайд: структура vault, CLAUDE.md, три способа подключить MCP, скрипты для автоматизации frontmatter и поиска по vault через CLI.

Диагноз: в чём реально проблема

Каждая новая сессия Claude Code — чистый лист. Ты объясняешь контекст заново. Повторяешь решения которые уже принимал. Отвечаешь на вопросы которые уже задавал.

Это не баг Claude Code. Это архитектура LLM: нет сессии — нет памяти. Но это лечится.

Obsidian vault — это папка с .md файлами. Claude Code умеет читать файлы. Значит vault может быть памятью агента которая переживает любое количество сессий. Ты написал решение проблемы три месяца назад — Claude найдёт его и воспользуется. Принял архитектурное решение в пятницу — в понедельник агент знает о нём без повторного объяснения.

В статье разберём:

  • Как структурировать vault под работу с Claude Code
  • Что писать в CLAUDE.md чтобы агент понимал твою систему
  • Три способа подключить Obsidian к Claude Code через MCP
  • Скрипты для автоматизации frontmatter и метаданных
  • CLI-поиск по vault без запуска Obsidian
  • Хуки для автоматической записи сессий в vault

Настройка базового уровня — 30 минут. С MCP и автоматизацией — полтора часа.

Как Claude Code работает с файлами

Claude Code запускается в директории. Всё что лежит в этой директории — агент видит, читает, редактирует. Никакого API не нужно — просто файловая система.

%%{init: {
  'theme': 'base',
  'themeVariables': {
    'primaryColor': '#ffffff',
    'primaryTextColor': '#1e293b',
    'primaryBorderColor': '#94a3b8',
    'lineColor': '#64748b',
    'fontSize': '14px',
    'fontFamily': 'ui-sans-serif, system-ui, sans-serif'
  },
  'flowchart': {'curve': 'linear', 'nodeSpacing': 55, 'rankSpacing': 50}
}}%%
flowchart TD
    CC["Claude Code (терминал)"] --> CLAUDE["CLAUDE.md - правила и контекст"]
    CC --> VF["Vault файлы (.md)"]
    CC --> SC["Скрипты (.py, .sh)"]
    CC --> MCP["MCP сервер Obsidian"]
    CLAUDE --> AG["Агент понимает структуру vault"]
    VF --> AG
    MCP --> SR["Семантический поиск по vault"]
    AG --> WR["Пишет заметки, обновляет frontmatter"]
    SR --> WR
    style CC fill:#f8fafc,stroke:#3b82f6,stroke-width:2px,color:#1e40af
    style CLAUDE fill:#f8fafc,stroke:#22c55e,stroke-width:2px,color:#15803d
    style MCP fill:#f8fafc,stroke:#f97316,stroke-width:2px,color:#9a3412
    style AG fill:#f8fafc,stroke:#64748b,stroke-width:2px,color:#1e293b

Есть три режима работы Claude Code с vault:

Режим 1: Vault как рабочая директория. Запускаешь claude прямо из папки vault. Агент видит все заметки напрямую. Просто, без настройки. Минус — если vault большой, агент будет видеть и мусор.

Режим 2: Симлинки из проекта в vault. Запускаешь Claude Code в папке проекта, делаешь симлинк на нужные части vault. Агент видит код проекта и нужные заметки одновременно.

Режим 3: MCP мост. Claude Code работает в любой директории, а к vault обращается через MCP сервер. Полное разделение проект/знания. Семантический поиск, фильтрация по frontmatter, запись новых заметок.

Начнём с основы которая нужна в любом режиме — структура vault и CLAUDE.md.

Структура vault под работу с Claude Code

Плоская структура — лучшая. Каждый лишний уровень вложенности стоит токенов при обходе директорий. Держи не глубже трёх уровней.

Стандартная структура которая работает:


vault/
  00-Inbox/          <- сырые заметки, необработанные мысли
  01-Projects/       <- активные проекты, по папке на проект
  02-Areas/          <- зоны постоянной ответственности
  03-Resources/      <- справочные материалы
  04-Archive/        <- завершённые проекты, старые заметки
  _claude/           <- всё что пишет агент (не твои заметки)
    sessions/        <- логи сессий
    summaries/       <- еженедельные итоги
    decisions/       <- зафиксированные решения
  Templates/         <- шаблоны для новых заметок
  CLAUDE.md          <- главный конфиг агента

Папка _claude/ — ключевая деталь. Туда идут все выходы агента: логи сессий, черновики, генерация. Твои собственные заметки хранятся отдельно. Это важно — иначе через месяц не поймёшь где твои мысли, а где ИИ-выхлоп.

Frontmatter как язык общения с агентом

Агент может парсить YAML frontmatter в начале .md файлов. Это его API к твоим данным. Стандартизируй frontmatter с первой заметки — потом не придётся переделывать.

Минимальный фронтматтер:


---
created: 2025-06-15
modified: 2025-06-20
tags: [project, backend, in-progress]
status: active
related: [[Architecture Decision 001]], [[API Design Notes]]
---

Поля которые реально используются в автоматизации:

  • status — active, in-progress, done, archived, review
  • tags — массив тегов, не строка. Строку агент иногда парсит неверно
  • related — wiki-ссылки на связанные заметки
  • project — к какому проекту относится
  • created / modified — даты, агент умеет фильтровать по ним

CLAUDE.md: мозговой центр агента

CLAUDE.md — файл который Claude Code читает автоматически при запуске в директории где он лежит. Это не просто readme. Это оперативная память сессии: правила, контекст, структура vault, протоколы работы.

Чем точнее CLAUDE.md описывает твою систему — тем меньше вопросов агент задаёт и тем меньше ошибок делает.

Базовый шаблон CLAUDE.md для vault:


# Vault: [Название]

## Контекст
[Две строки кто ты и зачем этот vault]
Пример: Vault разработчика-фрилансера. Хранит технические решения,
заметки по проектам и учебные материалы. Не для личного дневника.

## Структура vault
- 00-Inbox/ - необработанные заметки, проверяй первым делом
- 01-Projects/ - активные проекты. Статус active в frontmatter
- 02-Areas/ - постоянные зоны ответственности
- 03-Resources/ - справочные материалы по технологиям
- 04-Archive/ - завершённое, не трогать без запроса
- _claude/ - ТВОЯ папка. Пиши сюда все выходы
- _claude/sessions/ - YYYY-MM-DD-HH.md логи сессий
- _claude/decisions/ - зафиксированные архитектурные решения

## Frontmatter стандарт
Каждая новая заметка должна содержать:
  created: (сегодняшняя дата)
  tags: [массив, тегов]
  status: active | in-progress | done | archived
  project: (название проекта если применимо)

## Протокол старта сессии
1. Прочитай _claude/sessions/ - последние 3 лога
2. Проверь 00-Inbox/ на необработанные заметки
3. Найди заметки с тегом #needs-review

## Протокол завершения сессии
1. Запиши лог в _claude/sessions/YYYY-MM-DD-HH.md
2. Обнови frontmatter изменённых заметок (modified: дата)
3. Добавь теги к новым заметкам если их нет

## Правила
- Не трогай 04-Archive без явного запроса
- Новые заметки только с frontmatter
- Wiki-ссылки в формате [[Название заметки]]
- Не переименовывай файлы без подтверждения

Это живой документ. После первых недель работы ты добавишь туда правила которые выработались из реальных сессий. Посмотри в коммит-историю vault через месяц — CLAUDE.md будет самым часто меняющимся файлом.

Протокол сессии: почему это важно

Протокол старта — это то что агент делает в первые 30 секунд. Читает логи предыдущих сессий, смотрит Inbox, проверяет незакрытые задачи. Это и есть персистентная память.

Без протокола агент начинает сессию с нуля каждый раз. С протоколом — он знает что ты делал вчера, какие решения принял на прошлой неделе и что ещё не закрыто.

Протокол завершения — зеркально. Агент пишет структурированный лог: что сделано, какие решения приняты, что осталось открытым, что нужно сделать в следующую сессию.


# Session Log: 2025-06-20 14:00

## Что делали
- Рефакторинг auth модуля - перешли на JWT
- Написали тест для refresh token flow
- Нашли баг в middleware (см. заметку [[Auth Bug 2025-06-20]])

## Решения
- JWT secret теперь из env, не из config файла
- Refresh token TTL = 30 дней (обсудили с заказчиком)

## Открыто
- [ ] Написать миграцию для старых сессий
- [ ] Обновить документацию API

## Следующая сессия
Начать с миграции старых сессий. Файл: 01-Projects/auth-refactor/migration.md

Способ 1: Vault как рабочая директория (самый простой)

Запускаешь Claude Code прямо из папки vault. Без плагинов, без настройки.


cd ~/Documents/MyVault
claude

Агент видит всю структуру. Можешь сразу работать.

Проблема — если в vault тысяча заметок, агент при поиске будет обходить всё подряд. Для vault до 500 заметок — нормально. Больше — начинает тормозить и съедать токены.

Чтобы скрыть ненужное — используй .claudeignore в корне vault:


# .claudeignore
04-Archive/
.obsidian/
Templates/
*.png
*.jpg
*.pdf
*.mp3

Формат идентичен .gitignore. Что перечислено — агент не видит.

Способ 2: Симлинки — vault рядом с проектом

Работаешь над кодовым проектом, но хочешь чтобы агент видел и заметки из vault.


# Запускаешь Claude Code в папке проекта
cd ~/projects/my-app

# Создаёшь симлинк на нужные части vault
ln -s ~/Documents/MyVault/01-Projects/my-app ./vault-notes
ln -s ~/Documents/MyVault/_claude/decisions ./decisions
ln -s ~/Documents/MyVault/03-Resources/backend ./resources

Теперь в директории проекта лежат симлинки. Claude Code видит и код, и заметки как единое рабочее пространство.

Минус симлинков — нужно создавать их для каждого проекта. Если проектов много — это рутина. Для нескольких постоянных проектов подходит, для десятков — уже лучше MCP.

Способ 3: MCP сервер — полная интеграция

MCP (Model Context Protocol) — стандарт который позволяет Claude Code обращаться к внешним инструментам. Есть несколько вариантов MCP-сервера для Obsidian.

Вариант A: obsidian-claude-code-mcp (плагин внутри Obsidian)

github.com/iansinnott/obsidian-claude-code-mcp

Устанавливается как плагин Obsidian. Поднимает MCP сервер прямо внутри приложения. Claude Code находит его автоматически через WebSocket на порту 22360.

Установка:


# Нужен Node.js
node --version  # должно быть 18+

# В Obsidian: Settings - Community plugins - Browse
# Ищи: "Claude Code MCP"
# Устанавливай и включай

После включения плагин стартует MCP сервер. Claude Code при запуске в любой директории автоматически обнаруживает vault через lock-файл механизм.


# Просто запусти Claude Code
claude

# Проверь что vault обнаружен
/ide
# Должно показать подключённые MCP серверы включая Obsidian
Важно: версия протокола MCP
Плагин намеренно использует старую спецификацию HTTP with SSE (2024-11-05), а не новую Streamable HTTP (2025-03-26). Новая спецификация ломает подключение в Claude Code и Claude Desktop. Это не баг плагина — это текущее ограничение клиентов. Не пытайся «починить» это обновлением.

Вариант B: mcp-obsidian через Local REST API

github.com/MarkusPfundstein/mcp-obsidian

Два компонента: плагин Obsidian (Local REST API) + MCP сервер на Python.

Шаг 1: Установи плагин Local REST API в Obsidian

Settings — Community plugins — Browse. Ищи «Local REST API». Установи, включи. Скопируй API ключ из настроек плагина.


# Плагин поднимает REST API на:
# HTTP:  http://localhost:27123
# HTTPS: https://localhost:27124 (самоподписанный сертификат)

Шаг 2: Установи mcp-obsidian


# Нужен uv (менеджер пакетов Python)
curl -LsSf https://astral.sh/uv/install.sh | sh

# Установи mcp-obsidian
uv tool install mcp-obsidian

Шаг 3: Добавь конфиг в Claude Code


# Создай или отредактируй конфиг MCP
# macOS/Linux: ~/.claude/settings.json
# Windows: %APPDATA%\Claude\settings.json

{
  "mcpServers": {
    "obsidian": {
      "command": "uvx",
      "args": ["mcp-obsidian"],
      "env": {
        "OBSIDIAN_API_KEY": "твой-api-ключ-из-плагина",
        "OBSIDIAN_HOST": "localhost",
        "OBSIDIAN_PORT": "27123"
      }
    }
  }
}

Шаг 4: Перезапусти Claude Code


claude
# В сессии: /mcp - покажет подключённые серверы

Вариант C: Filesystem MCP — самый простой

Если не хочешь ни плагинов, ни Python — есть встроенный Filesystem MCP сервер. Никаких зависимостей, просто указываешь путь к vault.


{
  "mcpServers": {
    "vault": {
      "command": "npx",
      "args": [
        "-y",
        "@modelcontextprotocol/server-filesystem",
        "/Users/username/Documents/MyVault"
      ]
    }
  }
}

Плюс: работает без запущенного Obsidian. Минус: только чтение/запись файлов, без семантического поиска.

Сравнение трёх MCP вариантов

Вариант Сложность Поиск Frontmatter Obsidian нужен
obsidian-claude-code-mcp Низкая Базовый Да Да
mcp-obsidian (REST API) Средняя Полнотекстовый Да, парсит Да
Filesystem MCP Минимальная Grep только Только как текст Нет

Архитектура взаимодействия

%%{init: {
  'theme': 'base',
  'themeVariables': {
    'primaryColor': '#ffffff',
    'primaryTextColor': '#1e293b',
    'primaryBorderColor': '#94a3b8',
    'lineColor': '#64748b',
    'fontSize': '14px',
    'fontFamily': 'ui-sans-serif, system-ui, sans-serif'
  },
  'flowchart': {'curve': 'linear', 'nodeSpacing': 50, 'rankSpacing': 55}
}}%%
flowchart TD
    DEV["Разработчик"] --> CC["Claude Code (терминал)"]
    CC --> CM["CLAUDE.md - читается автоматически"]
    CC --> MCP_A["Вариант A: obsidian-claude-code-mcp\nWebSocket :22360"]
    CC --> MCP_B["Вариант B: mcp-obsidian\nREST API :27123"]
    CC --> MCP_C["Вариант C: Filesystem MCP\nпрямо с диска"]
    MCP_A --> OB["Obsidian (запущен)"]
    MCP_B --> OB
    MCP_C --> VF["Vault файлы на диске"]
    OB --> VF
    VF --> NM["Заметки .md"]
    VF --> FM["Frontmatter YAML"]
    VF --> LG["_claude/sessions/ логи"]
    style CC fill:#f8fafc,stroke:#3b82f6,stroke-width:2px,color:#1e40af
    style OB fill:#f8fafc,stroke:#f97316,stroke-width:2px,color:#9a3412
    style VF fill:#f8fafc,stroke:#22c55e,stroke-width:2px,color:#15803d

Кейс 1: Автоматизация тегов и frontmatter

Самая частая задача — привести в порядок метаданные vault. Заметки без тегов, несогласованные статусы, битые wiki-ссылки. Вручную — часы работы. С Claude Code — скрипт плюс агент.

Скрипт валидации frontmatter

Сохрани как scripts/validate_frontmatter.py в корне vault:


#!/usr/bin/env python3
"""
validate_frontmatter.py
Проверяет frontmatter всех .md файлов в vault.
Выводит список проблемных файлов.
"""

import os
import sys
import yaml
from pathlib import Path
from datetime import datetime

VAULT_ROOT = Path(__file__).parent.parent
SKIP_DIRS = {'.obsidian', '.git', 'Templates', '.claudeignore'}

REQUIRED_FIELDS = ['tags', 'status', 'created']
VALID_STATUSES = {'active', 'in-progress', 'done', 'archived', 'review'}

issues = []

def parse_frontmatter(filepath):
    """Парсит YAML frontmatter из .md файла."""
    content = filepath.read_text(encoding='utf-8')
    if not content.startswith('---'):
        return None, content
    try:
        end = content.index('---', 3)
        fm_raw = content[3:end].strip()
        return yaml.safe_load(fm_raw), content[end+3:]
    except (ValueError, yaml.YAMLError):
        return None, content

def check_file(filepath):
    """Проверяет один .md файл."""
    rel_path = filepath.relative_to(VAULT_ROOT)
    fm, body = parse_frontmatter(filepath)

    if fm is None:
        issues.append({
            'file': str(rel_path),
            'issue': 'NO_FRONTMATTER',
            'detail': 'Frontmatter отсутствует'
        })
        return

    # Проверяем обязательные поля
    for field in REQUIRED_FIELDS:
        if field not in fm:
            issues.append({
                'file': str(rel_path),
                'issue': 'MISSING_FIELD',
                'detail': f'Отсутствует поле: {field}'
            })

    # tags должен быть списком
    if 'tags' in fm and not isinstance(fm['tags'], list):
        issues.append({
            'file': str(rel_path),
            'issue': 'TAGS_NOT_LIST',
            'detail': f'tags должен быть списком, получили: {type(fm["tags"]).__name__}'
        })

    # статус должен быть из допустимых значений
    if 'status' in fm and fm['status'] not in VALID_STATUSES:
        issues.append({
            'file': str(rel_path),
            'issue': 'INVALID_STATUS',
            'detail': f'Недопустимый статус: {fm["status"]}. Допустимые: {VALID_STATUSES}'
        })

def main():
    for root, dirs, files in os.walk(VAULT_ROOT):
        # Пропускаем системные папки
        dirs[:] = [d for d in dirs if d not in SKIP_DIRS]
        for fname in files:
            if fname.endswith('.md'):
                check_file(Path(root) / fname)

    if not issues:
        print("OK: все frontmatter валидны")
        sys.exit(0)

    print(f"Найдено проблем: {len(issues)}\n")
    for issue in issues:
        print(f"[{issue['issue']}] {issue['file']}")
        print(f"  {issue['detail']}\n")

    sys.exit(1)

if __name__ == '__main__':
    main()

# Запусти из папки vault
python3 scripts/validate_frontmatter.py

# Или попроси агента:
# "Запусти validate_frontmatter.py и исправь все найденные проблемы"

Скрипт автоматической расстановки тегов

Находит заметки без тегов, анализирует содержимое, предлагает теги на основе ключевых слов. Агент принимает решение — скрипт исполняет.


#!/usr/bin/env python3
"""
autotag.py
Предлагает теги для заметок у которых их нет.
Работает по словарю ключевых слов.
Режим --dry-run показывает что изменится без применения.
"""

import os
import re
import sys
import yaml
import argparse
from pathlib import Path

VAULT_ROOT = Path(__file__).parent.parent
SKIP_DIRS = {'.obsidian', '.git', 'Templates', '_claude'}

# Словарь: ключевое слово -> тег
KEYWORD_TAG_MAP = {
    # Языки и технологии
    'python': 'python',
    'javascript': 'javascript',
    'typescript': 'typescript',
    'react': 'react',
    'docker': 'docker',
    'kubernetes': 'kubernetes',
    'sql': 'database',
    'postgres': 'database',
    'mysql': 'database',
    'redis': 'database',
    'api': 'api',
    'rest api': 'api',
    'graphql': 'graphql',
    'nginx': 'nginx',
    'linux': 'linux',
    # Типы контента
    'meeting': 'meeting',
    'встреча': 'meeting',
    'decision': 'decision',
    'решение': 'decision',
    'bug': 'debugging',
    'баг': 'debugging',
    'refactor': 'refactoring',
    'architecture': 'architecture',
    'архитектура': 'architecture',
    'review': 'review',
    'todo': 'todo',
}

def parse_frontmatter(filepath):
    content = filepath.read_text(encoding='utf-8')
    if not content.startswith('---'):
        return None, content, 0
    try:
        end = content.index('---', 3)
        fm_raw = content[3:end].strip()
        return yaml.safe_load(fm_raw), content[end+3:], end+3
    except (ValueError, yaml.YAMLError):
        return None, content, 0

def suggest_tags(text):
    """Предлагает теги на основе содержимого."""
    text_lower = text.lower()
    suggested = set()
    for keyword, tag in KEYWORD_TAG_MAP.items():
        if keyword in text_lower:
            suggested.add(tag)
    return list(suggested)

def update_frontmatter(filepath, new_tags, dry_run=False):
    """Добавляет теги в frontmatter файла."""
    fm, body, fm_end = parse_frontmatter(filepath)
    if fm is None:
        fm = {}

    existing_tags = fm.get('tags', [])
    if isinstance(existing_tags, str):
        existing_tags = [existing_tags]

    merged = list(set(existing_tags + new_tags))
    fm['tags'] = sorted(merged)

    if dry_run:
        return fm['tags']

    # Записываем обновлённый файл
    new_fm = yaml.dump(fm, allow_unicode=True, default_flow_style=False).strip()
    content = filepath.read_text(encoding='utf-8')
    if content.startswith('---'):
        end = content.index('---', 3)
        new_content = f"---\n{new_fm}\n---{content[end+3:]}"
    else:
        new_content = f"---\n{new_fm}\n---\n\n{content}"

    filepath.write_text(new_content, encoding='utf-8')
    return fm['tags']

def main():
    parser = argparse.ArgumentParser()
    parser.add_argument('--dry-run', action='store_true',
                        help='Показать изменения без применения')
    parser.add_argument('--threshold', type=float, default=0.0,
                        help='Минимальная уверенность (не используется, для совместимости)')
    args = parser.parse_args()

    changed = 0
    for root, dirs, files in os.walk(VAULT_ROOT):
        dirs[:] = [d for d in dirs if d not in SKIP_DIRS]
        for fname in files:
            if not fname.endswith('.md'):
                continue
            filepath = Path(root) / fname
            fm, body, _ = parse_frontmatter(filepath)

            existing_tags = []
            if fm:
                existing_tags = fm.get('tags', [])
                if isinstance(existing_tags, str):
                    existing_tags = [existing_tags]

            # Предлагаем теги на основе содержимого заметки
            suggested = suggest_tags(body)
            new_tags = [t for t in suggested if t not in existing_tags]

            if not new_tags:
                continue

            rel_path = filepath.relative_to(VAULT_ROOT)
            if args.dry_run:
                print(f"[DRY RUN] {rel_path}")
                print(f"  Добавить: {new_tags}")
            else:
                result = update_frontmatter(filepath, new_tags)
                print(f"[UPDATED] {rel_path}")
                print(f"  Теги: {result}")
            changed += 1

    print(f"\nИтого: {'будет изменено' if args.dry_run else 'изменено'} {changed} файлов")

if __name__ == '__main__':
    main()

# Сначала dry run - посмотри что изменится
python3 scripts/autotag.py --dry-run

# Применить изменения
python3 scripts/autotag.py

# Попросить агента:
# "Запусти autotag.py --dry-run, покажи результат,
#  и если теги выглядят разумно - применяй"

Кейс 2: Поиск и анализ по vault через CLI

Obsidian для поиска запускать не нужно. Vault — обычные файлы, значит grep, find и Python справляются не хуже.

Базовый поиск через grep/ripgrep


# Установи ripgrep - grep на стероидах
# macOS:
brew install ripgrep
# Ubuntu/Debian:
apt install ripgrep

# Поиск по содержимому
rg "аутентификация JWT" ~/Documents/MyVault

# Поиск с показом контекста (2 строки вокруг)
rg -C 2 "frontmatter" ~/Documents/MyVault

# Поиск только в .md файлах
rg --type md "TODO" ~/Documents/MyVault

# Поиск в конкретной папке
rg "docker" ~/Documents/MyVault/03-Resources/

# Поиск по frontmatter - все активные проекты
rg "^status: active" ~/Documents/MyVault/01-Projects/

Скрипт поиска с анализом frontmatter


#!/usr/bin/env python3
"""
vault_search.py
Поиск по vault с учётом frontmatter.
Фильтрует по тегам, статусу, проекту.
"""

import os
import sys
import yaml
import argparse
from pathlib import Path
from datetime import datetime, date

VAULT_ROOT = Path(__file__).parent.parent
SKIP_DIRS = {'.obsidian', '.git', 'Templates'}

def parse_frontmatter(filepath):
    content = filepath.read_text(encoding='utf-8', errors='ignore')
    if not content.startswith('---'):
        return {}, content
    try:
        end = content.index('---', 3)
        fm_raw = content[3:end].strip()
        return yaml.safe_load(fm_raw) or {}, content[end+3:]
    except (ValueError, yaml.YAMLError):
        return {}, content

def search_vault(query=None, tags=None, status=None, project=None,
                 modified_after=None):
    """Ищет заметки по критериям."""
    results = []

    for root, dirs, files in os.walk(VAULT_ROOT):
        dirs[:] = [d for d in dirs if d not in SKIP_DIRS]
        for fname in files:
            if not fname.endswith('.md'):
                continue
            filepath = Path(root) / fname
            fm, body = parse_frontmatter(filepath)

            # Фильтр по статусу
            if status and fm.get('status') != status:
                continue

            # Фильтр по проекту
            if project and fm.get('project') != project:
                continue

            # Фильтр по тегам
            if tags:
                note_tags = fm.get('tags', [])
                if isinstance(note_tags, str):
                    note_tags = [note_tags]
                if not all(t in note_tags for t in tags):
                    continue

            # Фильтр по дате изменения
            if modified_after:
                modified = fm.get('modified', fm.get('created'))
                if modified:
                    if isinstance(modified, str):
                        try:
                            modified = date.fromisoformat(modified)
                        except ValueError:
                            pass
                    if isinstance(modified, date) and modified < modified_after:
                        continue

            # Текстовый поиск
            if query:
                full_text = fname + ' ' + body
                if query.lower() not in full_text.lower():
                    continue

            rel_path = filepath.relative_to(VAULT_ROOT)
            stat = filepath.stat()
            results.append({
                'path': str(rel_path),
                'frontmatter': fm,
                'modified': stat.st_mtime,
                'size': stat.st_size
            })

    # Сортируем по дате изменения (свежие первыми)
    results.sort(key=lambda x: x['modified'], reverse=True)
    return results

def main():
    parser = argparse.ArgumentParser(description='Поиск по vault')
    parser.add_argument('query', nargs='?', help='Текстовый поиск')
    parser.add_argument('--tag', action='append', dest='tags',
                        help='Фильтр по тегу (можно несколько)')
    parser.add_argument('--status', help='Фильтр по статусу')
    parser.add_argument('--project', help='Фильтр по проекту')
    parser.add_argument('--after', help='Изменены после (YYYY-MM-DD)')
    parser.add_argument('--limit', type=int, default=20,
                        help='Максимум результатов')
    args = parser.parse_args()

    after_date = None
    if args.after:
        after_date = date.fromisoformat(args.after)

    results = search_vault(
        query=args.query,
        tags=args.tags,
        status=args.status,
        project=args.project,
        modified_after=after_date
    )

    if not results:
        print("Ничего не найдено")
        sys.exit(0)

    print(f"Найдено: {len(results)} (показываем {min(len(results), args.limit)})\n")
    for r in results[:args.limit]:
        fm = r['frontmatter']
        tags = fm.get('tags', [])
        status = fm.get('status', '-')
        modified = datetime.fromtimestamp(r['modified']).strftime('%Y-%m-%d')
        print(f"  {r['path']}")
        print(f"  status={status} | tags={tags} | modified={modified}\n")

if __name__ == '__main__':
    main()

# Поиск по тексту
python3 scripts/vault_search.py "JWT аутентификация"

# Все активные проекты
python3 scripts/vault_search.py --status active

# Все заметки с тегом debugging изменённые после 1 июня
python3 scripts/vault_search.py --tag debugging --after 2025-06-01

# Все заметки проекта my-app
python3 scripts/vault_search.py --project my-app

# Комбинация
python3 scripts/vault_search.py "docker" --tag backend --status in-progress

Добавь эти скрипты в CLAUDE.md с описанием когда какой использовать:


## Скрипты
- scripts/validate_frontmatter.py - проверка метаданных vault
- scripts/autotag.py --dry-run - предложить теги без изменений
- scripts/autotag.py - применить теги
- scripts/vault_search.py [запрос] [--tag X] [--status Y] - поиск

Кейс 3: MCP сервер — запросы к vault в реальном времени

Когда MCP настроен, Claude Code может обращаться к vault прямо в ходе работы — не читая все файлы целиком, а делая точечные запросы.

Примеры того что становится возможным:


# В диалоге с агентом:

"Что я уже знаю про Redis? Поищи в vault."
-> агент вызывает vault_search("redis"), читает топ-5 результатов,
   синтезирует ответ из твоих же заметок

"Создай заметку по нашему сегодняшнему решению про JWT"
-> агент создаёт файл в _claude/decisions/ с правильным frontmatter,
   добавляет ссылки на связанные заметки

"Какие у меня открытые задачи по проекту auth-refactor?"
-> агент ищет заметки с project: auth-refactor и status: in-progress,
   выводит список TODO из их содержимого

"Найди все заметки с тегом debugging за последний месяц"
-> frontmatter-aware запрос, возвращает только релевантное

Пример реального диалога с настроенным MCP


> Что я решал по поводу архитектуры базы данных в последние 2 недели?

Claude Code: Нашёл 3 заметки в _claude/decisions/ за этот период:

1. decisions/2025-06-10-db-schema-v2.md
   Решили перейти на UUID вместо integer ID.
   Причина: масштабируемость при шардировании.
   Статус: applied

2. decisions/2025-06-14-indexing-strategy.md
   Составные индексы на (user_id, created_at) для timeline queries.
   Бенчмарк показал x4 ускорение на 1M строк.
   Статус: active

3. decisions/2025-06-18-connection-pooling.md
   PgBouncer в transaction mode, pool size = CPU * 2.
   Открыто: нужно проверить под нагрузкой > 500 concurrent connections.

Хочешь развернуть по любому из этих решений?

Хуки Claude Code: автозапись сессий в vault

Claude Code поддерживает хуки — скрипты которые запускаются автоматически при событиях: старт сессии, конец сессии, после каждого инструмента.

Stop хук — самый полезный. Запускается когда сессия завершается. Можно автоматически записывать итоги в vault.

Настройка Stop хука


# Создай файл настроек хуков
# macOS/Linux: ~/.claude/settings.json (глобально)
# или .claude/settings.json в папке проекта/vault (локально)

{
  "hooks": {
    "Stop": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "python3 /Users/username/Documents/MyVault/scripts/session_capture.py"
          }
        ]
      }
    ]
  }
}

Скрипт захвата сессии


#!/usr/bin/env python3
"""
session_capture.py
Stop хук Claude Code.
Читает stdin с данными сессии, пишет лог в vault.
"""

import sys
import json
import os
from pathlib import Path
from datetime import datetime

VAULT_PATH = Path.home() / "Documents" / "MyVault"
SESSIONS_DIR = VAULT_PATH / "_claude" / "sessions"
SESSIONS_DIR.mkdir(parents=True, exist_ok=True)

def main():
    # Читаем JSON payload от Claude Code
    try:
        payload = json.loads(sys.stdin.read())
    except (json.JSONDecodeError, ValueError):
        sys.exit(0)

    session_id = payload.get('session_id', 'unknown')
    project_dir = payload.get('cwd', os.getcwd())
    project_name = Path(project_dir).name

    # Собираем инструменты которые использовались
    tool_calls = payload.get('tool_calls', [])
    files_touched = set()
    for call in tool_calls:
        tool = call.get('tool', '')
        if tool in ('write_file', 'edit_file', 'create_file'):
            path = call.get('input', {}).get('path', '')
            if path:
                files_touched.add(path)

    now = datetime.now()
    date_str = now.strftime('%Y-%m-%d')
    time_str = now.strftime('%H-%M')
    fname = f"{date_str}-{time_str}-{project_name}.md"

    frontmatter = f"""---
date: {date_str}
time: {now.strftime('%H:%M')}
project: {project_name}
session_id: {session_id[:8]}
tags: [session-log, claude-code]
---
"""

    body = f"""# Session Log: {project_name} {now.strftime('%Y-%m-%d %H:%M')}

## Проект
{project_dir}

## Файлы затронуты
"""
    if files_touched:
        for f in sorted(files_touched):
            body += f"- {f}\n"
    else:
        body += "- (нет данных)\n"

    body += """
## Итоги сессии
(заполни вручную или попроси агента)

## Открытые задачи
- [ ] 

## Следующая сессия
"""

    log_path = SESSIONS_DIR / fname
    log_path.write_text(frontmatter + body, encoding='utf-8')

if __name__ == '__main__':
    main()

Скрипт создаёт базовую структуру лога. Агент дополняет содержательную часть если попросить его в конце сессии написать итоги.

Поиск сломанных wiki-ссылок

Один из самых раздражающих артефактов большого vault — [[Ссылки на несуществующие заметки]]. Возникают когда файл переименовали или удалили. Находятся скриптом.


#!/usr/bin/env python3
"""
find_broken_links.py
Находит битые [[wiki-ссылки]] в vault.
"""

import os
import re
from pathlib import Path

VAULT_ROOT = Path(__file__).parent.parent
SKIP_DIRS = {'.obsidian', '.git', 'Templates'}

# Строим индекс всех заметок
def build_index():
    index = {}
    for root, dirs, files in os.walk(VAULT_ROOT):
        dirs[:] = [d for d in dirs if d not in SKIP_DIRS]
        for fname in files:
            if fname.endswith('.md'):
                # Имя без расширения - ключ для wiki-ссылок
                name = Path(fname).stem.lower()
                index[name] = Path(root) / fname
    return index

def find_wikilinks(text):
    """Извлекает все [[ссылки]] из текста."""
    pattern = r'\[\[([^\]|#]+?)(?:\|[^\]]+)?\]\]'
    return re.findall(pattern, text)

def main():
    index = build_index()
    broken = []

    for root, dirs, files in os.walk(VAULT_ROOT):
        dirs[:] = [d for d in dirs if d not in SKIP_DIRS]
        for fname in files:
            if not fname.endswith('.md'):
                continue
            filepath = Path(root) / fname
            content = filepath.read_text(encoding='utf-8', errors='ignore')
            links = find_wikilinks(content)

            for link in links:
                # Нормализуем: убираем путь, берём только имя файла
                link_name = Path(link).stem.lower()
                if link_name not in index:
                    broken.append({
                        'source': str(filepath.relative_to(VAULT_ROOT)),
                        'broken_link': link
                    })

    if not broken:
        print("OK: битых ссылок не найдено")
        return

    print(f"Найдено битых ссылок: {len(broken)}\n")
    for b in broken:
        print(f"  [{b['source']}]")
        print(f"    [[{b['broken_link']}]] - файл не существует\n")

if __name__ == '__main__':
    main()

python3 scripts/find_broken_links.py

# Или агенту:
# "Запусти find_broken_links.py и исправь ссылки которые
#  можно исправить автоматически (фузи-матч по имени файла)"

Типичные проблемы и решения

Claude Code не видит vault через MCP

Симптом: /mcp показывает пустой список или ошибку подключения.


# Проверь что Obsidian запущен (для вариантов A и B)
# Проверь что плагин включён
# Settings - Community plugins - убедись что плагин активен

# Для obsidian-claude-code-mcp: проверь порт
curl http://localhost:22360/health
# Должно вернуть JSON с ok: true

# Для mcp-obsidian: проверь Local REST API
curl http://localhost:27123/
# Должно вернуть ошибку 401 (не авторизован) - значит сервер работает

# Посмотри логи MCP
tail -f ~/Library/Logs/Claude/mcp-server-obsidian.log  # macOS
# Windows: %APPDATA%\Claude\logs\

Агент пишет заметки без frontmatter

Причина: Правило не прописано явно в CLAUDE.md или прописано нечётко.

Решение: Добавь конкретный шаблон в CLAUDE.md:


## Шаблон новой заметки (ОБЯЗАТЕЛЬНО для каждого нового файла)
---
created: YYYY-MM-DD
modified: YYYY-MM-DD
tags: []
status: active
---

Конфликт Node.js версий при запуске MCP


# Проверь версию
node --version  # нужно 18+

# Если старая - обнови через nvm
nvm install 20
nvm use 20

# Или через brew на macOS
brew upgrade node

Агент меняет заметки которые не должен трогать

Явно пропиши запреты в CLAUDE.md:


## Запрещено без явного запроса
- Изменять файлы в 04-Archive/
- Переименовывать существующие файлы
- Удалять теги из frontmatter
- Изменять поле created в frontmatter

Слишком много токенов тратится на обход vault

Причина: Vault большой, агент читает всё подряд.

Решение:

Создай _claude/hot.md — файл быстрого доступа. Обновляй его еженедельно. Это краткое резюме актуального состояния vault: текущие проекты, свежие решения, открытые задачи. Агент читает его первым, к полному vault обращается только если нужно.


# Hot Cache (обновлён: 2025-06-20)

## Активные проекты
- auth-refactor (01-Projects/auth-refactor/) - рефакторинг JWT, 80%
- api-v2 (01-Projects/api-v2/) - на паузе до согласования

## Последние решения
- UUID вместо int ID (_claude/decisions/2025-06-10-db-schema-v2.md)
- PgBouncer transaction mode (_claude/decisions/2025-06-18-connection-pooling.md)

## Открыто
- [ ] Тест нагрузки PgBouncer > 500 connections
- [ ] Миграция старых сессий в новую схему

## Ресурсы которые часто нужны
- 03-Resources/backend/postgres-tuning.md
- 03-Resources/backend/jwt-patterns.md

Профилактика: как не превратить vault в помойку

  • Папка _claude/ — только для выходов агента. Твои мысли и заметки там не живут. Это разграничение исчезает удивительно быстро если его не блюсти.
  • Раз в неделю: запускай validate_frontmatter.py. Дрейф метаданных происходит постепенно и незаметно, но накапливается.
  • CLAUDE.md должен меняться. Если ты работаешь с агентом месяц а CLAUDE.md такой же как в день создания — что-то не так.
  • .claudeignore актуален. Добавляй туда всё что агент не должен индексировать: PDF, изображения, архив.
  • Git для vault — не опция, обязательство. Агент пишет в файлы. Нужна возможность откатиться.

Системные требования

Компонент Версия Примечание
Claude Code актуальная npm install -g @anthropic-ai/claude-code
Node.js 18+ Для MCP серверов на JS
Python 3.9+ Для скриптов и mcp-obsidian
uv / pip любая Для mcp-obsidian через uvx
Obsidian 1.4+ Для MCP вариантов A и B
Git 2.x Для версионирования vault
ripgrep (rg) любая Опционально, для быстрого поиска

На момент публикации актуальна версия Claude Code 1.x. Перед установкой проверь свежие релизы: docs.anthropic.com/en/docs/claude-code

Таблица портов

Сервис Протокол Порт по умолчанию Изменить
obsidian-claude-code-mcp WebSocket WebSocket 22360 Настройки плагина
obsidian-claude-code-mcp HTTP/SSE HTTP 22361 Настройки плагина
Obsidian Local REST API (HTTP) HTTP 27123 Настройки плагина
Obsidian Local REST API (HTTPS) HTTPS 27124 Настройки плагина

Порты выше 1024, открывать в файрволе не нужно если только не работаешь удалённо.

FAQ

Нужен ли Obsidian открытым постоянно для работы с Claude Code?

Зависит от метода. Filesystem MCP работает без Obsidian — читает файлы напрямую с диска. Для obsidian-claude-code-mcp и mcp-obsidian (Local REST API) — Obsidian должен быть запущен. В режиме прямого запуска claude из папки vault — Obsidian вообще не нужен, агент работает с файлами напрямую.

Что написать в CLAUDE.md для первого запуска?

Минимум: структура папок vault и протокол сессии (что делать в начале и конце). Запусти claude из папки vault и попроси: «Просмотри структуру этого vault и сгенерируй стартовый CLAUDE.md». Агент проанализирует что есть и создаст разумный черновик. Потом дорабатывай.

Как Claude Code понимает wiki-ссылки [[вот такие]]?

Агент видит их как текст. Если ты описал в CLAUDE.md что [[ссылки]] — это внутренние ссылки на заметки по имени файла, он понимает логику и может переходить по ним через чтение нужного файла. Но автоматически по ним не переходит — только по явному запросу.

Можно ли использовать несколько vault одновременно?

Для obsidian-claude-code-mcp — каждый vault нужно запускать на отдельном порту. Для Filesystem MCP — добавь несколько путей в конфиг. Практически — удобнее держать один vault с хорошей структурой, чем несколько.

Claude Code пишет файлы в vault — это безопасно?

Относительно. Инициализируй vault как git репозиторий. Тогда любое изменение можно откатить. Запрети в CLAUDE.md изменение архивных файлов. И периодически запускай git diff чтобы видеть что агент наделал.

Как обновлять скрипты автоматизации?

Держи их в scripts/ внутри vault и коммить в git. Когда хочешь улучшить скрипт — просто скажи агенту что именно хочешь изменить. Он видит скрипт, редактирует, тестирует. Итоговая версия остаётся в файле.

Итог

Claude Code без памяти — мощный инструмент который каждый раз начинает с нуля. Obsidian vault как персистентный контекст превращает его в агента который знает твою историю, помнит решения и умеет работать с накопленными знаниями.

Базовый уровень — просто запусти claude из папки vault с хорошим CLAUDE.md. Работает сразу, ничего устанавливать не нужно. Добавь скрипты для фронтматтера и поиска — получишь инструмент который можно попросить «приведи в порядок теги в Inbox» и он справится без объяснений.

MCP сервер нужен когда vault вырос до 500+ заметок и нужен семантический поиск вместо grep. Или когда хочешь чтобы агент работал в коде и параллельно тянул контекст из vault не переключая директорию.

Хуки — финальный шаг. Vault начинает расти сам: каждая сессия оставляет структурированный след, агент следующей сессии читает его и продолжает с того места где остановился.

Не завелось?
Опиши в комментариях: какой вариант настраиваешь, на чём застрял, что в логах. Разберёмся.
Андрей Анатольевич
Author: Андрей Анатольевич

Руководитель ИТ / Кризис-менеджер 25 лет в IT: от инженера в МегаФоне до руководителя отдела. Знаю, как выглядит бардак: нестабильные сети, устаревшая инфраструктура, конфликты в команде, раздутые сроки. Помогаю бизнесу выходить из кризиса: навожу порядок в легаси, стабилизирую то, что разваливается, выстраиваю прогнозируемые процессы. Не раз возвращал к жизни ИТ-структуры — знаю цену хаосу. 📍 Ищу проект для полной реорганизации / стабилизации. 📬 Telegram: @over_dude ✉️ mail@it-apteka.com

Оставайтесь на связи

Рецепты от IT-боли. Без воды, без рекламы, без маркетинговой шелухи.

Подписаться на IT-Аптеку →

Мы ВКонтакте

IT-Аптека — советы, новости и помощь рядом.

Вступить в группу ВКонтакте →
Поделитесь:

Оставьте комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Прокрутить вверх