[2026-05-25] naisei: переименование nolvu→naisei, документация obsidian-совместимости, удаление task-spec
This commit is contained in:
@@ -1,9 +1,10 @@
|
|||||||
---
|
---
|
||||||
title: "nolvu / reflect-skip — проект"
|
title: "naisei / reflect-skip — проект"
|
||||||
aliases:
|
aliases:
|
||||||
|
- "naisei"
|
||||||
- "nolvu"
|
- "nolvu"
|
||||||
- "reflect-skip"
|
- "reflect-skip"
|
||||||
- "Nolvu проект"
|
- "Naisei проект"
|
||||||
- "Reflect Skip"
|
- "Reflect Skip"
|
||||||
tags:
|
tags:
|
||||||
- personal
|
- personal
|
||||||
@@ -12,14 +13,15 @@ tags:
|
|||||||
- psychology
|
- psychology
|
||||||
- skip-tools
|
- skip-tools
|
||||||
created: '2026-05-17'
|
created: '2026-05-17'
|
||||||
updated: '2026-05-17'
|
updated: '2026-05-25'
|
||||||
related:
|
related:
|
||||||
- "[[personal/projects/psychologist-app/overview]]"
|
- "[[personal/projects/psychologist-app/overview]]"
|
||||||
- "[[personal/projects/psychologist-app/dev-setup]]"
|
- "[[personal/projects/psychologist-app/dev-setup]]"
|
||||||
---
|
---
|
||||||
# nolvu / reflect-skip — проект
|
# naisei / reflect-skip — проект
|
||||||
|
|
||||||
## Статус
|
## Статус
|
||||||
|
- Имя продукта: **naisei**
|
||||||
- Активная кодовая база: `~/Developer/reflect-skip/`
|
- Активная кодовая база: `~/Developer/reflect-skip/`
|
||||||
- Android: публикуется на `https://reflect.qentra.top` (F-Droid)
|
- Android: публикуется на `https://reflect.qentra.top` (F-Droid)
|
||||||
- iOS: собирается через `Project.xcworkspace` → `Darwin/ReflectApp.xcodeproj`
|
- iOS: собирается через `Project.xcworkspace` → `Darwin/ReflectApp.xcodeproj`
|
||||||
@@ -64,6 +66,19 @@ related:
|
|||||||
`sendBatchSummary()` отправлял весь диалог включая вопросы аналитика.
|
`sendBatchSummary()` отправлял весь диалог включая вопросы аналитика.
|
||||||
Исправлено: фильтруем только user answers.
|
Исправлено: фильтруем только user answers.
|
||||||
|
|
||||||
|
## Obsidian-совместимость
|
||||||
|
|
||||||
|
Приложение читает сессии из папки `observations/` (формат vault), а не `sessions/`.
|
||||||
|
|
||||||
|
- **Папка сессий:** `observations/` в корне импортируемой директории
|
||||||
|
- **Именование файлов:** ISO week — `2026-W20.md`, `2026-W21.md`
|
||||||
|
- **Запись сессий:** аппендит `## Сессия DD.MM.YYYY HH:mm` в текущий недельный файл
|
||||||
|
- **Счётчик сессий:** считается напрямую как количество `.md` файлов в `observations/` (не хранится в profile.md)
|
||||||
|
- **Профиль:** `profile.md` — строка `**Имя:** Алексей` нужна для `parseName()`
|
||||||
|
- **Инсайты:** `insights.md` — произвольный формат, передаётся напрямую в Claude
|
||||||
|
|
||||||
|
Vault путь для импорта: `personal/psychology/` (на Taiga).
|
||||||
|
|
||||||
## Pitfalls
|
## Pitfalls
|
||||||
|
|
||||||
- Если на iOS-симуляторе не появляется `DevSetupView` (ввод токена) — значит `dev_setup_done = true` в UserDefaults. Удалить приложение с симулятора и запустить заново.
|
- Если на iOS-симуляторе не появляется `DevSetupView` (ввод токена) — значит `dev_setup_done = true` в UserDefaults. Удалить приложение с симулятора и запустить заново.
|
||||||
|
|||||||
@@ -1,147 +0,0 @@
|
|||||||
---
|
|
||||||
created: '2026-05-25'
|
|
||||||
updated: '2026-05-25'
|
|
||||||
tags:
|
|
||||||
- project
|
|
||||||
- psychologist-app
|
|
||||||
- task
|
|
||||||
status: 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:)`** — текущий код:
|
|
||||||
```swift
|
|
||||||
let sessionsURL = url.appendingPathComponent("sessions")
|
|
||||||
```
|
|
||||||
Заменить на:
|
|
||||||
```swift
|
|
||||||
let sessionsURL = sessionsFolder(in: url)
|
|
||||||
```
|
|
||||||
|
|
||||||
Добавить приватный метод:
|
|
||||||
```swift
|
|
||||||
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()` строка:
|
|
||||||
```swift
|
|
||||||
let sessionsSrc = result.sourceURL.appendingPathComponent("sessions")
|
|
||||||
```
|
|
||||||
Заменить на:
|
|
||||||
```swift
|
|
||||||
let sessionsSrc = sessionsFolder(in: result.sourceURL)
|
|
||||||
```
|
|
||||||
|
|
||||||
Папка всегда копируется в `sessions/` в app Documents (это правильно — внутреннее имя не меняем).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Изменения в ProfileManager.swift
|
|
||||||
|
|
||||||
### 4. Fallback для `sessionCount` при импортированном vault
|
|
||||||
|
|
||||||
Текущий код ищет regex `- Сессий проведено: N` — vault profile этого не имеет.
|
|
||||||
Результат: `sessionCount` возвращает 0, приложение запускает "первую сессию" даже при 2+ импортированных.
|
|
||||||
|
|
||||||
Изменить `sessionCount` — добавить fallback:
|
|
||||||
|
|
||||||
```swift
|
|
||||||
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 изменений.
|
|
||||||
Reference in New Issue
Block a user