Files
obsidian-vault/personal/projects/psychologist-app/task-obsidian-import-compat.md
T

6.1 KiB
Raw Blame History

created, updated, tags, status
created updated tags status
2026-05-25 2026-05-25
project
psychologist-app
task
ready

Задача: совместимость импорта с форматом Obsidian vault

Цель: пользователь должен выбрать папку personal/psychology/ из Obsidian и приложение подхватит профиль и все сессии без конвертации.

Репо: ~/Developer/reflect-skip Затрагиваемые файлы:

  • Sources/ReflectApp/Services/DocumentImportService.swift
  • Sources/ReflectApp/Services/ProfileManager.swift

Контекст: что сейчас есть в vault

personal/psychology/
├── profile.md        ← есть **Имя:** Алексей (добавлено), YAML frontmatter, rich markdown
├── insights.md       ← есть, формат: ## Отцовство / ## ADHD с нарративными блоками
├── prompts.md        ← не нужен для импорта
└── observations/     ← папка с еженедельными файлами
    ├── 2026-W20.md
    └── 2026-W21.md

Что ожидает приложение сейчас:

UserProfile/
├── profile.md        ← строка "**Имя:** Алексей" (теперь есть ✅)
├── insights.md       ← есть ✅
└── sessions/         ← ДРУГОЕ имя папки ❌
    └── YYYY-MM-DD.md ← ДРУГОЙ формат имени файла (еженедельные vs ежедневные)

Изменения в DocumentImportService.swift

1. Поддержка папки observations/ как алиаса sessions/

В методах validate(), countSessions(), apply() — проверять обе папки.

countSessions(in:) — текущий код:

let sessionsURL = url.appendingPathComponent("sessions")

Заменить на:

let sessionsURL = sessionsFolder(in: url)

Добавить приватный метод:

private func sessionsFolder(in url: URL) -> URL {
    let sessions = url.appendingPathComponent("sessions")
    let observations = url.appendingPathComponent("observations")
    if FileManager.default.fileExists(atPath: observations.path) { return observations }
    return sessions
}

2. Обновить validate() — убрать упоминание "sessions" из ошибки

Текущий error description (noProfileFound) упоминает "папку sessions" — заменить на нейтральное:

В выбранной папке нет файла profile.md.
Убедитесь что выбрана папка с данными профиля.

3. Обновить apply() — копировать observations/ → sessions/

В методе apply() строка:

let sessionsSrc = result.sourceURL.appendingPathComponent("sessions")

Заменить на:

let sessionsSrc = sessionsFolder(in: result.sourceURL)

Папка всегда копируется в sessions/ в app Documents (это правильно — внутреннее имя не меняем).


Изменения в ProfileManager.swift

4. Fallback для sessionCount при импортированном vault

Текущий код ищет regex - Сессий проведено: N — vault profile этого не имеет. Результат: sessionCount возвращает 0, приложение запускает "первую сессию" даже при 2+ импортированных.

Изменить sessionCount — добавить fallback:

var sessionCount: Int {
    // Сначала пробуем разобрать из текста профиля
    if let content = try? String(contentsOf: profileFileURL, encoding: .utf8),
       let range = content.range(of: #"- Сессий проведено: (\d+)"#, options: .regularExpression),
       let numRange = String(content[range]).range(of: #"\d+"#, options: .regularExpression),
       let count = Int(String(content[range])[numRange]) {
        return count
    }
    // Fallback: считаем фактические файлы в sessions/
    let files = (try? FileManager.default.contentsOfDirectory(
        at: sessionsDirectoryURL,
        includingPropertiesForKeys: nil,
        options: .skipsHiddenFiles
    )) ?? []
    return files.filter { $0.pathExtension == "md" && $0.lastPathComponent != "index.md" }.count
}

Что НЕ нужно менять

  • Формат файлов сессий (YYYY-MM-DD.md vs 2026-W20.md): BM25 поиск и loadRecentSessions() сортируют лексикографически — оба формата работают одинаково хорошо.
  • Структура profile.md: loadProfile() отдаёт raw markdown в Claude — богатый vault-формат работает лучше чем простой app-формат.
  • insights.md: loadInsights() тоже отдаёт raw markdown — vault-формат с секциями лучше flat-списка.
  • appendSession() / updateProfile() / addPattern(): создают/дополняют нужные секции — при отсутствии секции код добавляет в конец файла. OK.

Acceptance criteria

  1. Пользователь выбирает папку ~/obsidian/personal/psychology/ на iOS
  2. Validation проходит: имя "Алексей", счётчик сессий = 2
  3. После import в Documents/UserProfile/:
    • profile.md скопирован
    • sessions/ содержит файлы из observations/
    • insights.md скопирован
  4. sessionCount возвращает 2 (файлы 2026-W20.md, 2026-W21.md)
  5. generateIntro() использует hasHistory = true и показывает контекст из W20/W21

Объём работы

~30 строк кода. Затрагивает 2 файла, нет API изменений, нет UI изменений.