mirror of
https://github.com/pese-git/cherrypick.git
synced 2026-03-25 13:01:14 +00:00
Add OpenSpec system specification
This commit is contained in:
2
openspec/changes/cherrypick-system-spec/.openspec.yaml
Normal file
2
openspec/changes/cherrypick-system-spec/.openspec.yaml
Normal file
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-02-27
|
||||
40
openspec/changes/cherrypick-system-spec/design.md
Normal file
40
openspec/changes/cherrypick-system-spec/design.md
Normal file
@@ -0,0 +1,40 @@
|
||||
## Context
|
||||
|
||||
CherryPick — монорепозиторий DI‑экосистемы для Dart/Flutter, включающий ядро рантайма, аннотации, генератор и Flutter‑интеграцию, а также адаптер логирования для Talker. Требуется системная спецификация модулей и их контрактов на русском языке.
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
- Зафиксировать архитектурные границы и контракты модулей.
|
||||
- Согласовать терминологию и жизненные циклы ключевых сущностей.
|
||||
- Дать проверяемые требования с однозначными сценариями.
|
||||
|
||||
**Non-Goals:**
|
||||
- Изменение или рефакторинг кода.
|
||||
- Детализация низкоуровневых алгоритмов генератора.
|
||||
- Документация внешних библиотек (Flutter, Talker).
|
||||
|
||||
## Decisions
|
||||
|
||||
- Разбить спецификацию по четырем capability: `di-runtime`, `annotations-and-codegen`, `flutter-integration`, `talker-logging-adapter`.
|
||||
- Все требования формулировать нормативно (MUST) и снабжать сценариями WHEN/THEN.
|
||||
- Описывать сущности, жизненный цикл, ошибки и точки расширения в каждом capability.
|
||||
|
||||
**Альтернативы:**
|
||||
- Единый монолитный spec.md: отклонено из-за ухудшения навигации и поддержки.
|
||||
- Использовать SHOULD/MAY: отклонено для уменьшения двусмысленности.
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
- [Risk] Спецификация может расходиться с фактическим поведением кода → Mitigation: привязка требований к публичным API и README, дальнейшая валидация при реализации.
|
||||
- [Risk] Перекрывающиеся требования между capability → Mitigation: строгие границы и минимальный повтор.
|
||||
|
||||
## Migration Plan
|
||||
|
||||
- Спецификация вводится как документ без влияния на runtime.
|
||||
- При необходимости последующих изменений — через отдельные change‑запросы OpenSpec.
|
||||
|
||||
## Open Questions
|
||||
|
||||
- Нужно ли фиксировать требования по производительности (O(1) lookup) как отдельный раздел?
|
||||
- Нужны ли отдельные сценарии для edge‑cases (например, singleton + params)?
|
||||
23
openspec/changes/cherrypick-system-spec/proposal.md
Normal file
23
openspec/changes/cherrypick-system-spec/proposal.md
Normal file
@@ -0,0 +1,23 @@
|
||||
## Why
|
||||
|
||||
CherryPick is a multi-package DI ecosystem. A system-level spec will make module boundaries, expected behaviors, and integration contracts explicit for contributors and downstream users.
|
||||
|
||||
## What Changes
|
||||
|
||||
- Define system specifications for the core DI runtime, annotations/codegen, Flutter integration, and Talker logging adapter.
|
||||
- Document expected behaviors, inputs, and outputs for each module and its public integration points.
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
- `di-runtime`: Core dependency injection runtime (scopes, modules, bindings, resolution, lifecycle).
|
||||
- `annotations-and-codegen`: Annotation vocabulary and code generation behavior for DI wiring.
|
||||
- `flutter-integration`: Flutter-specific provider and scope access integration.
|
||||
- `talker-logging-adapter`: Observer adapter that routes DI events to Talker.
|
||||
|
||||
### Modified Capabilities
|
||||
|
||||
## Impact
|
||||
|
||||
- Documentation and contributor guidance for module behaviors.
|
||||
- Clarifies runtime and codegen expectations for users and maintainers.
|
||||
@@ -0,0 +1,84 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Сущности аннотаций
|
||||
Пакет аннотаций MUST предоставлять аннотации `@module`, `@provide`, `@instance`, `@singleton`, `@named`, `@params`, `@inject`, `@injectable`, `@scope`.
|
||||
|
||||
#### Scenario: Наличие аннотаций
|
||||
- **WHEN** разработчик импортирует пакет аннотаций
|
||||
- **THEN** все перечисленные аннотации доступны для использования
|
||||
|
||||
### Requirement: Семантика module/provide/instance/singleton
|
||||
Генератор MUST трактовать аннотации `@module`, `@provide`, `@instance`, `@singleton` как источники bindings и жизненного цикла.
|
||||
|
||||
#### Scenario: Генерация bindings для @module
|
||||
- **WHEN** класс помечен `@module` и содержит методы с `@provide`
|
||||
- **THEN** сгенерированный модуль регистрирует эти методы как DI‑bindings
|
||||
|
||||
#### Scenario: Singleton vs Instance
|
||||
- **WHEN** метод помечен `@singleton`
|
||||
- **THEN** binding создается как singleton
|
||||
- **WHEN** метод помечен `@instance` или не имеет lifecycle‑аннотации
|
||||
- **THEN** binding создается как factory/instance
|
||||
|
||||
#### Scenario: Обязательность provide/instance
|
||||
- **WHEN** публичный метод в `@module` не помечен `@instance` или `@provide`
|
||||
- **THEN** генератор завершает сборку с ошибкой валидации
|
||||
|
||||
### Requirement: Инъекция полей
|
||||
Генератор MUST создавать миксин для класса с `@injectable` и инъектировать все поля, помеченные `@inject`.
|
||||
|
||||
#### Scenario: Инъекция полей без квалификаторов
|
||||
- **WHEN** поле помечено `@inject`
|
||||
- **THEN** миксин вызывает resolve для типа поля
|
||||
|
||||
#### Scenario: Инъекция с @named и @scope
|
||||
- **WHEN** поле помечено `@inject` и `@named` или `@scope`
|
||||
- **THEN** миксин использует соответствующий named/scope при резолве
|
||||
|
||||
### Requirement: Параметры выполнения
|
||||
`@params` MUST обозначать runtime‑параметры, которые передаются при резолве.
|
||||
|
||||
#### Scenario: Параметризованный provider
|
||||
- **WHEN** provider‑метод имеет параметр с `@params`
|
||||
- **THEN** генерируется binding с параметрами и соответствующий API резолва
|
||||
|
||||
#### Scenario: Запрет @params с @instance
|
||||
- **WHEN** `@params` используется вместе с `@instance`
|
||||
- **THEN** генератор завершает сборку с ошибкой валидации
|
||||
|
||||
### Requirement: Поддержка async
|
||||
Генератор MUST корректно обрабатывать `Future<T>` и асинхронные зависимости.
|
||||
|
||||
#### Scenario: Async provider
|
||||
- **WHEN** provider возвращает `Future<T>`
|
||||
- **THEN** генерируется async‑binding и используется `resolveAsync`
|
||||
|
||||
### Requirement: Обработка ошибок и валидация
|
||||
Генератор MUST валидировать корректность применения аннотаций и сообщать об ошибках на стадии build.
|
||||
|
||||
#### Scenario: Некорректная цель аннотации
|
||||
- **WHEN** аннотация применена к неподдерживаемому элементу
|
||||
- **THEN** генератор завершает сборку с понятной ошибкой
|
||||
|
||||
#### Scenario: Взаимоисключающие аннотации
|
||||
- **WHEN** метод помечен одновременно `@instance` и `@provide`
|
||||
- **THEN** генератор завершает сборку с ошибкой валидации
|
||||
|
||||
#### Scenario: Требования к @named
|
||||
- **WHEN** `@named` использует пустую строку или некорректный идентификатор
|
||||
- **THEN** генератор завершает сборку с ошибкой валидации
|
||||
|
||||
#### Scenario: Валидность @module
|
||||
- **WHEN** класс с `@module` не имеет публичных методов
|
||||
- **THEN** генератор завершает сборку с ошибкой валидации
|
||||
|
||||
#### Scenario: Валидность @injectable полей
|
||||
- **WHEN** поле с `@inject` не является `final`
|
||||
- **THEN** генератор завершает сборку с ошибкой валидации
|
||||
|
||||
### Requirement: Точки расширения
|
||||
Система MUST позволять расширять DI‑контракт через новые модули/классы без изменения генератора, используя стандартные аннотации.
|
||||
|
||||
#### Scenario: Новый модуль
|
||||
- **WHEN** разработчик добавляет новый класс `@module`
|
||||
- **THEN** генератор автоматически включает его в DI‑регистрации
|
||||
137
openspec/changes/cherrypick-system-spec/specs/di-runtime/spec.md
Normal file
137
openspec/changes/cherrypick-system-spec/specs/di-runtime/spec.md
Normal file
@@ -0,0 +1,137 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Сущности ядра DI
|
||||
Ядро DI MUST определять сущности `Scope`, `Module`, `Binding`, `BindingResolver`, `Disposable`, `CherryPickObserver` и их роли.
|
||||
|
||||
#### Scenario: Декларация базовых сущностей
|
||||
- **WHEN** разработчик использует публичный API ядра
|
||||
- **THEN** сущности `Scope`, `Module`, `Binding`, `Disposable`, `CherryPickObserver` доступны и описывают контракт DI
|
||||
|
||||
### Requirement: Жизненный цикл Scope
|
||||
`Scope` MUST поддерживать жизненный цикл открытия, использования и корректного закрытия с освобождением ресурсов.
|
||||
|
||||
#### Scenario: Открытие root scope
|
||||
- **WHEN** вызывается открытие root scope
|
||||
- **THEN** создается корневой контейнер для регистраций и резолва
|
||||
|
||||
#### Scenario: Открытие named subscope
|
||||
- **WHEN** открывается именованный subscope
|
||||
- **THEN** subscope наследует bindings родителя и может переопределять зависимости
|
||||
|
||||
#### Scenario: Закрытие subscope
|
||||
- **WHEN** вызывается закрытие subscope
|
||||
- **THEN** subscope удаляется из дерева, а связанные ресурсы освобождаются
|
||||
|
||||
#### Scenario: Путь scope и разделитель
|
||||
- **WHEN** scope открывается по иерархическому пути с разделителем
|
||||
- **THEN** создается цепочка subscopes по каждому сегменту пути
|
||||
|
||||
#### Scenario: Пустой scopeName
|
||||
- **WHEN** scope открывается с пустым именем
|
||||
- **THEN** возвращается root scope
|
||||
|
||||
### Requirement: Установка и удаление модулей
|
||||
`Scope` MUST поддерживать установку и удаление `Module`, а их bindings MUST становиться доступными сразу после установки.
|
||||
|
||||
#### Scenario: Установка модуля
|
||||
- **WHEN** модуль установлен в scope
|
||||
- **THEN** его bindings доступны для resolve/tryResolve
|
||||
|
||||
#### Scenario: Сброс модулей
|
||||
- **WHEN** модули сброшены
|
||||
- **THEN** bindings модулей не доступны для резолва в этом scope
|
||||
|
||||
### Requirement: Типы bindings
|
||||
`Binding` MUST поддерживать прямые инстансы, синхронные провайдеры, асинхронные провайдеры и провайдеры с параметрами, а также именованные bindings.
|
||||
|
||||
#### Scenario: Именованные bindings
|
||||
- **WHEN** зарегистрированы два bindings одного типа с разными именами
|
||||
- **THEN** resolve с указанным именем возвращает соответствующую реализацию
|
||||
|
||||
#### Scenario: Параметризованный provider
|
||||
- **WHEN** binding зарегистрирован с провайдером, принимающим параметры
|
||||
- **THEN** resolve с параметрами использует переданные аргументы для создания экземпляра
|
||||
|
||||
### Requirement: Семантика резолва и fallback
|
||||
Резолв MUST искать зависимость в текущем scope и, при отсутствии, подниматься к родительским scope.
|
||||
|
||||
#### Scenario: Fallback к родителю
|
||||
- **WHEN** зависимость отсутствует в текущем scope, но есть в родительском
|
||||
- **THEN** резолв выполняется через родительский binding
|
||||
|
||||
### Requirement: Синхронный и асинхронный резолв
|
||||
`Scope` MUST предоставлять синхронный и асинхронный резолв и их nullable‑варианты.
|
||||
|
||||
#### Scenario: Resolve (sync)
|
||||
- **WHEN** вызывается `resolve<T>()` для существующей зависимости
|
||||
- **THEN** возвращается экземпляр или выбрасывается ошибка при отсутствии
|
||||
|
||||
#### Scenario: TryResolve (sync)
|
||||
- **WHEN** вызывается `tryResolve<T>()` для отсутствующей зависимости
|
||||
- **THEN** возвращается `null` без исключения
|
||||
|
||||
#### Scenario: Resolve (async)
|
||||
- **WHEN** вызывается `resolveAsync<T>()` для зависимости с async‑провайдером
|
||||
- **THEN** возвращается `Future<T>` с экземпляром
|
||||
|
||||
#### Scenario: TryResolve (async)
|
||||
- **WHEN** вызывается `tryResolveAsync<T>()` для отсутствующей зависимости
|
||||
- **THEN** возвращается `null` без исключения
|
||||
|
||||
### Requirement: Ошибки несоответствия sync/async
|
||||
Резолв MUST выбрасывать ошибки при несоответствии sync/async режима.
|
||||
|
||||
#### Scenario: ResolveSync для async‑инстанса
|
||||
- **WHEN** binding зарегистрирован как async‑инстанс или async‑provider, а вызывается `resolveSync`
|
||||
- **THEN** выбрасывается ошибка с указанием использовать async‑резолв
|
||||
|
||||
### Requirement: Управление Disposable
|
||||
`Scope` MUST отслеживать экземпляры, реализующие `Disposable`, и вызывать `dispose()` при закрытии scope.
|
||||
|
||||
#### Scenario: Автоматическое освобождение
|
||||
- **WHEN** scope закрывается
|
||||
- **THEN** все `Disposable` в scope и дочерних scope освобождаются
|
||||
|
||||
### Requirement: Детекция циклов зависимостей
|
||||
Ядро DI MUST поддерживать локальную и глобальную детекцию циклов.
|
||||
|
||||
#### Scenario: Локальный цикл
|
||||
- **WHEN** локальная детекция включена и резолв обнаруживает цикл
|
||||
- **THEN** резолв завершается ошибкой цикла
|
||||
|
||||
#### Scenario: Глобальный цикл
|
||||
- **WHEN** глобальная детекция включена и цикл пересекает scope
|
||||
- **THEN** резолв завершается ошибкой цикла
|
||||
|
||||
### Requirement: Наблюдатель событий (Observer)
|
||||
Ядро DI MUST предоставлять наблюдателя, получающего события регистрации, резолва, ошибок, lifecycle и диагностики.
|
||||
|
||||
#### Scenario: События lifecycle
|
||||
- **WHEN** scope открывается, модули устанавливаются и зависимости резолвятся
|
||||
- **THEN** observer получает соответствующие уведомления
|
||||
|
||||
### Requirement: Ошибки и сообщения об ошибках
|
||||
При критических сбоях резолва ядро MUST выбрасывать ошибку с понятным сообщением, а для tryResolve MUST не бросать исключения.
|
||||
|
||||
#### Scenario: Ошибка отсутствующей зависимости
|
||||
- **WHEN** вызывается `resolve<T>()` для незарегистрированной зависимости
|
||||
- **THEN** выбрасывается ошибка с сообщением о невозможности резолва
|
||||
|
||||
#### Scenario: Ошибка цикла
|
||||
- **WHEN** обнаружен цикл зависимостей
|
||||
- **THEN** выбрасывается ошибка с указанием цепочки
|
||||
|
||||
#### Scenario: Отсутствующие параметры для параметризованного binding
|
||||
- **WHEN** binding зарегистрирован с `toProvideWithParams`, но резолв вызывается без `params`
|
||||
- **THEN** выбрасывается ошибка о необходимости передать параметры
|
||||
|
||||
### Requirement: Точки расширения
|
||||
Ядро DI MUST предоставлять точки расширения через пользовательский `Module` и `CherryPickObserver`.
|
||||
|
||||
#### Scenario: Пользовательский Module
|
||||
- **WHEN** разработчик создает свой `Module`
|
||||
- **THEN** он может зарегистрировать bindings и контролировать регистрацию
|
||||
|
||||
#### Scenario: Пользовательский Observer
|
||||
- **WHEN** разработчик передает кастомный observer
|
||||
- **THEN** он получает все DI‑события и может интегрировать внешние логеры/метрики
|
||||
@@ -0,0 +1,58 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Сущности Flutter‑интеграции
|
||||
Flutter‑интеграция MUST предоставлять `CherryPickProvider` как `InheritedWidget` для доступа к DI‑scope в дереве виджетов.
|
||||
|
||||
#### Scenario: Публичная сущность провайдера
|
||||
- **WHEN** разработчик импортирует Flutter‑пакет
|
||||
- **THEN** `CherryPickProvider` доступен и может быть помещен в дерево виджетов
|
||||
|
||||
### Requirement: Жизненный цикл провайдера
|
||||
`CherryPickProvider` MUST быть статическим проводником к scope и не владеть их жизненным циклом.
|
||||
|
||||
#### Scenario: Stateless поведение
|
||||
- **WHEN** `CherryPickProvider` пересоздается с тем же child
|
||||
- **THEN** он не инициирует изменения DI‑состояния и не уведомляет зависимые виджеты
|
||||
|
||||
### Requirement: Доступ к root scope
|
||||
Провайдер MUST предоставлять доступ к root scope через `openRootScope`.
|
||||
|
||||
#### Scenario: Открытие root scope
|
||||
- **WHEN** вызывается `openRootScope`
|
||||
- **THEN** возвращается root scope DI‑контейнера
|
||||
|
||||
### Requirement: Доступ к subscope
|
||||
Провайдер MUST предоставлять доступ к subscope через `openSubScope` с именем и разделителем.
|
||||
|
||||
#### Scenario: Открытие named subscope
|
||||
- **WHEN** вызывается `openSubScope` с именем
|
||||
- **THEN** возвращается subscope с указанным именем
|
||||
|
||||
#### Scenario: Пустое имя scope
|
||||
- **WHEN** вызывается `openSubScope` без имени
|
||||
- **THEN** возвращается root scope
|
||||
|
||||
### Requirement: Доступ через BuildContext
|
||||
`CherryPickProvider.of(context)` MUST возвращать провайдер из ближайшего ancestor.
|
||||
|
||||
#### Scenario: Успешный lookup
|
||||
- **WHEN** вызов происходит внутри поддерева провайдера
|
||||
- **THEN** возвращается экземпляр провайдера
|
||||
|
||||
#### Scenario: Ошибка lookup
|
||||
- **WHEN** вызов происходит вне поддерева провайдера
|
||||
- **THEN** происходит assertion‑ошибка
|
||||
|
||||
### Requirement: Ошибки и сообщения
|
||||
При отсутствии провайдера в дереве MUST быть диагностируемая ошибка.
|
||||
|
||||
#### Scenario: Диагностика отсутствия провайдера
|
||||
- **WHEN** `CherryPickProvider.of(context)` не находит провайдер
|
||||
- **THEN** сообщение об ошибке указывает на отсутствие провайдера
|
||||
|
||||
### Requirement: Точки расширения
|
||||
Flutter‑интеграция MUST позволять использовать собственные DI‑scope стратегии поверх `CherryPickProvider`.
|
||||
|
||||
#### Scenario: Кастомная организация scope
|
||||
- **WHEN** приложение использует собственные правила создания subscope
|
||||
- **THEN** провайдер остается совместимым и не ограничивает стратегию
|
||||
@@ -0,0 +1,47 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Сущность адаптера логирования
|
||||
Адаптер MUST предоставлять `TalkerCherryPickObserver`, реализующий `CherryPickObserver`.
|
||||
|
||||
#### Scenario: Доступность адаптера
|
||||
- **WHEN** разработчик импортирует пакет адаптера
|
||||
- **THEN** `TalkerCherryPickObserver` доступен для использования
|
||||
|
||||
### Requirement: Жизненный цикл наблюдателя
|
||||
Наблюдатель MUST подключаться при создании scope и работать в течение жизненного цикла этого scope.
|
||||
|
||||
#### Scenario: Подключение наблюдателя
|
||||
- **WHEN** observer передан при открытии scope
|
||||
- **THEN** все DI‑события в scope направляются в Talker
|
||||
|
||||
### Requirement: Маппинг уровней логирования
|
||||
Адаптер MUST маршрутизировать DI‑события в Talker с корректными уровнями (info/warning/verbose/handle).
|
||||
|
||||
#### Scenario: Диагностика
|
||||
- **WHEN** DI‑ядро генерирует диагностическое событие
|
||||
- **THEN** адаптер логирует его как verbose
|
||||
|
||||
#### Scenario: Ошибка резолва
|
||||
- **WHEN** DI‑ядро генерирует ошибку
|
||||
- **THEN** адаптер логирует ее через `handle` и включает stack trace при наличии
|
||||
|
||||
### Requirement: Ненавязчивость поведения
|
||||
Адаптер MUST быть наблюдателем и не изменять DI‑поведение, жизненный цикл или кэширование.
|
||||
|
||||
#### Scenario: Разрешение с адаптером
|
||||
- **WHEN** адаптер подключен
|
||||
- **THEN** резолв и поведение DI не изменяются, кроме появления логов
|
||||
|
||||
### Requirement: Ошибки логирования
|
||||
Адаптер MUST передавать ошибки логирования как есть и не содержит собственной обработки исключений.
|
||||
|
||||
#### Scenario: Исключение в Talker
|
||||
- **WHEN** Talker выбрасывает исключение при логировании
|
||||
- **THEN** исключение пробрасывается вызывающему коду
|
||||
|
||||
### Requirement: Точки расширения
|
||||
Адаптер MUST позволять использовать пользовательские настройки Talker без изменения адаптера.
|
||||
|
||||
#### Scenario: Кастомная конфигурация Talker
|
||||
- **WHEN** пользователь передает кастомный Talker
|
||||
- **THEN** адаптер использует переданную конфигурацию для всех DI‑событий
|
||||
28
openspec/changes/cherrypick-system-spec/tasks.md
Normal file
28
openspec/changes/cherrypick-system-spec/tasks.md
Normal file
@@ -0,0 +1,28 @@
|
||||
## 1. Системная структура спецификации
|
||||
|
||||
- [x] 1.1 Проверить корректность capability‑разделения и согласованность терминов
|
||||
- [x] 1.2 Уточнить cross‑cutting требования (терминология, общие lifecycle‑понятия)
|
||||
|
||||
## 2. DI Runtime (ядро)
|
||||
|
||||
- [x] 2.1 Сверить требования по сущностям и lifecycle со структурой кода `cherrypick`
|
||||
- [x] 2.2 Уточнить сценарии ошибок и сообщений об ошибках
|
||||
- [x] 2.3 Проверить сценарии расширения (Module/Observer) на полноту
|
||||
|
||||
## 3. Аннотации и генератор
|
||||
|
||||
- [x] 3.1 Проверить полноту словаря аннотаций и их назначений
|
||||
- [x] 3.2 Сверить требования по codegen (module/field injection/params/async)
|
||||
- [x] 3.3 Уточнить сценарии валидации и типовых ошибок генератора
|
||||
|
||||
## 4. Flutter‑интеграция
|
||||
|
||||
- [x] 4.1 Сверить требования `CherryPickProvider` с фактическим API
|
||||
- [x] 4.2 Уточнить сценарии lookup и диагностических ошибок
|
||||
- [x] 4.3 Проверить корректность заявленных точек расширения
|
||||
|
||||
## 5. Talker‑адаптер
|
||||
|
||||
- [x] 5.1 Сверить маппинг уровней логирования с фактическим поведением
|
||||
- [x] 5.2 Уточнить поведение при ошибках логирования
|
||||
- [x] 5.3 Проверить сценарии ненавязчивости и расширения
|
||||
Reference in New Issue
Block a user