9.8 KiB
title, aliases, tags, created, updated, related
| title | aliases | tags | created | updated | related | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| naisei / reflect-skip — проект |
|
|
2026-05-17 | 2026-05-28 |
|
naisei / reflect-skip — проект
Статус
- Имя продукта: naisei
- Активная кодовая база:
~/Developer/reflect-skip/ - Android: публикуется на
https://reflect.qentra.top(F-Droid) - iOS: собирается через
Project.xcworkspace→Darwin/ReflectApp.xcodeproj - Текущая версия: v0.0.92 (Android APK), v0.0.25 (iOS)
Структура проекта
Два источника Swift-кода в reflect-skip
Sources/ReflectApp/— актуальный Skip-источник. Компилируется Darwin-проектом. Используется для iOS и Android.ReflectApp/— legacy iOS-only код, существовавший до перехода на Skip. КомпилируетсяReflectApp.xcodeprojв корне. Не используется в продакшне.
Как открывать iOS-проект
Всегда через Project.xcworkspace, не через ReflectApp.xcodeproj напрямую.
Как собирается Android
build-and-publish.sh → Skip компилирует Sources/ReflectApp/ → APK → F-Droid repo.
Прокси
- LaunchAgent:
ai.reflect-proxy→localhost:8765 - Cloudflare tunnel:
ai.qentra.top→localhost:8765 - Бэкенд:
claude-proxyнаlocalhost:3456 - Лог сессий:
~/Developer/reflect-skip/sessions.log
Агенты
- NarratorBot — нейтрализует ввод пользователя (убирает эмоциональную окраску, 3-е лицо)
- AnalystBot — задаёт 2-3 глубоких вопроса по профилю + контексту сессии
- SessionManager — управляет историей сессий
Исправления (v0.0.92, 28 мая 2026) — Android vault validation
Веха: Android onboarding работает
✅ Алерт валидации vault отображается корректно
✅ Кнопки появились после выбора папки
✅ Дошли до экрана "Доброе утро"
Fix — @AppStorage вместо @State для UI-обновлений на Android
Проблема: @State var folderPickerError: String? и @State var vaultFound: Int? не триггерили Compose recompose в SkipFuse bridge.
Симптом: алерт не появлялся, кнопки не обновлялись, хотя логика SAF выполнялась.
Исправление: заменили на @AppStorage("_vaultErr") и @AppStorage("_vaultFound") — идут через SharedPreferences → всегда триггерят Compose recompose.
Файл: Sources/naisei/Views/OnboardingView.swift
Открытые проблемы (v0.0.92)
- ❌ На главном экране "0 сессий", "0 дней"
- ❌ Профиль и Инсайты пустые
- ❌ Агент при начале сессии говорит "нет данных" (не видит vault)
- Гипотеза: vault URI сохраняется, но ProfileManager/SessionManager читают данные не через SAF URI, а по прямому пути — на Android без SAF доступа путь недоступен
Исправления (v0.0.25, май 2026)
Fix 1 — дублирование нарратора
buildSessionContext() вызывался после addMessage() → нарратор видел своё же сообщение в контексте.
Исправлено: поменяли порядок в Sources/SessionManager.swift.
Fix 2 — AnalystBot не видел текущий диалог
ProfileManager.loadRecentSessions(count: 3) загружал прошлые ФАЙЛЫ, не текущую сессию.
Исправлено: добавили buildCurrentSessionDialogue() (последние 8 пар user/analyst), передаём в analyze().
Fix 3 — нарратору уходили вопросы аналитика
sendBatchSummary() отправлял весь диалог включая вопросы аналитика.
Исправлено: фильтруем только 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
- Если на iOS-симуляторе не появляется
DevSetupView(ввод токена) — значитdev_setup_done = trueв UserDefaults. Удалить приложение с симулятора и запустить заново. ReflectApp/в корне — legacy, не трогать. Реальные правки только вSources/ReflectApp/.
Plan: Session Pacing & Closing (не реализовано)
Проблема
SessionViewModel.endSession() существует и реализует полный closing-флоу (инсайт → warm phrase → oneLastThing → vault save), но нигде не вызывается из UI.
dismissSession() (кнопка X) отменяет сессию без сохранения.
Пейсинга нет: агент не знает сколько оборотов прошло, нет правил завершения в systemPrompt, нет сигнала session_complete в AnalystJSON.
Решение: Вариант Б + hard limit
Шаг 1 — Quick-fix: X-кнопка вызывает endSession() (даёт рабочий closing уже сейчас)
Вместо dismissSession() при нажатии X — вызывать endSession().
Пользователь сам решает когда нажать, но closing-флоу отработает корректно.
Шаг 2 — Turn count в SessionManager
Добавить var userTurnCount: Int = 0, инкрементировать в processUserInput().
Передавать в AnalystBot.analyze() как параметр turnCount.
Шаг 3 — session_complete в AnalystJSON
struct AnalystJSON: Codable {
let questions: [String]
let observation: String?
let crisisSignal: Bool
let suggestedInsight: String?
let sessionComplete: Bool // новое поле
}
Шаг 4 — Пейсинг-правила в systemPrompt AnalystBot
Правила ведения сессии (параметр turn_count передаётся в каждом запросе):
- После 4+ оборотов: если пользователь пришёл к пониманию, тема исчерпана
или повторяется — установи session_complete: true
- Если открылась новая важная тема — продолжай (session_complete: false)
- После 7+ оборотов: session_complete: true независимо от темы
- session_complete: true — предложение закрыть, не принудительное завершение
Шаг 5 — .readyToClose в ProcessingResult
enum ProcessingResult {
case questions([String], observation: String?)
case readyToClose([String]?, observation: String?)
case crisis(CrisisDetectionResult)
case error(String)
}
SessionManager возвращает .readyToClose когда analystResponse.sessionComplete == true ИЛИ userTurnCount >= 8.
Шаг 6 — UI карточка в SessionViewModel
При .readyToClose: показать вопросы (если есть) → после ответа добавить карточку
"Кажется, мы хорошо прошлись по теме. Завершить сессию?"
Кнопки: "Завершить" → endSession() | "Продолжить" → сбросить сигнал на текущий turn.
Шаг 7 — Интеграционные тесты (TDD: писать до реализации шагов 2-6)
testAnalystReturnsSessionComplete_afterExhaustedTopic()testSessionManager_readyToClose_onSessionCompleteSignal()testSessionManager_hardLimit_at8Turns()testSessionViewModel_showsCompletionCard_onReadyToClose()testSessionViewModel_continueAfterReadyToClose_resetsSignal()
Порядок реализации
- Тесты (шаг 7) — все RED
- Quick-fix X-кнопки (шаг 1) — немедленно даёт рабочий closing
- Шаги 2-5 — данные + логика
- Шаг 6 — UI карточка
- Все тесты GREEN