diff --git a/content/docs/code-design.es.mdx b/content/docs/code-design.es.mdx index 2b58295e..80eb0cf2 100644 --- a/content/docs/code-design.es.mdx +++ b/content/docs/code-design.es.mdx @@ -69,7 +69,7 @@ graph TB Cli("cli/ y mcp/
comandos y herramientas MCP") Services("services/
casos de uso") Sends("sends/
escrituras protegidas, adjuntos") - Port("ports/messenger.ts
el contrato del adaptador") + Port("ports/
el contrato del adaptador, los rasgos") Store("store/
MessageStore sobre SQLite") Domain("domain/
modelos y tipos de valor") end @@ -89,10 +89,10 @@ graph TB | Capa | Su función | Tamaño | Qué no puede importar | | --- | --- | --- | --- | -| `domain/` | Tipos independientes del mensajero: `Chat`, `Message`, `Person`, localizadores, tramos de formato. Ningún comportamiento que necesite red o archivos. | 24 archivos | Ninguna biblioteca de mensajero ni adaptador. | -| `ports/messenger.ts` | El contrato que implementa cada mensajero. | 1 archivo, 663 líneas | Lo mismo. | +| `domain/` | Tipos y valores independientes del mensajero: `Chat`, `Message`, `Person`, localizadores, tramos de formato, los conjuntos de valores de hechos, memoria y tareas. Ningún comportamiento que necesite red o archivos. Está casi abajo del todo: toma dos tipos de `search/` y los tipos de tarea de `tasks/`. | 29 archivos | `cli/`, ninguna biblioteca de mensajero ni adaptador. | +| `ports/` | El contrato que implementa cada mensajero (`messenger.ts`), lo que un servicio lee del mensajero (`traits.ts`) y quién es la aplicación (`app.ts`). | 3 archivos | Lo mismo. | | `store/` | El archivo local: una interfaz `MessageStore`, abierta por `openStore`, sobre módulos SQLite, migraciones e índices de búsqueda. | 101 archivos | Nada fuera de `store/` puede importar un módulo de `store/sqlite/`. | -| `services/` | Casos de uso: mensajes, chats, personas, búsqueda, vectores, menciones, moderación. | 81 archivos | Commander, un módulo de comando, un módulo SQLite. | +| `services/` | Casos de uso: mensajes, chats, personas, búsqueda, vectores, menciones, moderación. | 87 archivos | Commander, cualquier cosa de `cli/`, un módulo SQLite. Lo mismo vale para `replies/`, `attachments/`, `moderation/`, `models/` y `analysis/`. | | `sends/` | Toda escritura: la protección de envíos, los identificadores de operación, el registro de envíos, los adjuntos. | 14 archivos | Lo mismo que los servicios. | | `cli/`, `mcp/` | Presentación: comandos, el sobre JSON, configuración y perfiles; herramientas MCP sobre los mismos servicios. | 168 + 51 archivos | Un módulo SQLite. | | `meetings/`, `tasks/` | Módulos autónomos con sus propios puertos. | 46 + 4 archivos | Cualquier otra parte de cli-core, una base de datos o el sistema de archivos. | @@ -164,11 +164,17 @@ misma manera, nunca se descarta en silencio. ### La descripción -Cada herramienta entrega también al núcleo un objeto `Messenger` de 47 campos: el nombre de la aplicación, -cómo conectarse, cómo paginar el historial y opciones como `pollQuiz`, `topicShow` y `stickers`. Los -comandos leen esas opciones al registrar sus parámetros, así que `polls create --quiz` solo existe en una +Cada herramienta entrega también al núcleo un objeto `Messenger`: el nombre de la aplicación, cómo +conectarse, cómo paginar el historial y opciones como `pollQuiz`, `topicShow` y `stickers`. Los comandos +leen esas opciones al registrar sus parámetros, así que `polls create --quiz` solo existe en una herramienta cuyo mensajero tiene cuestionarios. +Los servicios ven solo una parte. [`MessengerTraits`](https://github.com/WireCatLabs/cli-core/blob/80616b3813ca8264d8f9edca3e8e0fcce7a1e81e/src/ports/traits.ts), en `ports/traits.ts`, reúne los 14 campos que lee un +servicio, como el nombre de la aplicación, cómo se pagina el historial y si el mensajero busca en su +servidor. `Messenger` lo amplía con lo que solo usa la CLI: `connect`, la configuración, el gancho de la +protección de envíos y el diagnóstico de `doctor`. Un servicio no puede llegar a un comando, a un objeto de +commander ni a una opción que no se le haya dado. + ### Los servicios y sus dependencias Un servicio es una función que recibe un objeto de dependencias y devuelve el caso de uso: @@ -186,8 +192,9 @@ account: () => Promise Nada se abre hasta que un servicio lo pide, así que una lectura que responde el almacén local nunca se conecta. [`servicesFor(deps)`](https://github.com/WireCatLabs/cli-core/blob/a3b5b3c47792d5e2df6529642e907ee32783e66a/src/services/index.ts#L145) -crea todos los servicios una vez; los comandos y las herramientas MCP los toman de ahí, así que dan la -misma respuesta y el mismo error para la misma entrada. +crea los servicios sobre un objeto de dependencias: un comando los crea para su ejecución, una herramienta +MCP para su llamada. Ambos llaman a las mismas funciones de servicio, así que un caso de uso tiene una sola +implementación para los dos. Una herramienta puede sustituir un servicio sin copiar el núcleo. `Messenger.services` recibe los servicios compartidos y devuelve sustitutos, por ejemplo su propia forma de encontrar un chat para la @@ -265,7 +272,7 @@ y una regla de lint o una prueba lo mantiene así. | Lectores de documentos, descriptores de imágenes, modelos de vectores | Pequeño | Registrar un lector o proveedor; elegir un modelo local o remoto. | | Un proveedor de reuniones | Pequeño | Implementar `MeetingSource` en el paquete del proveedor. | | Un mensajero nuevo | Moderado | Implementar `MessengerCore` y los grupos que tenga el mensajero, y escribir una descripción. Los comandos, las herramientas MCP, el almacén, la búsqueda y la protección de envíos vienen con el núcleo. | -| Una interfaz nueva junto a la CLI y MCP | Moderado | Llamar a `servicesFor(deps)` y mostrar la respuesta. Los servicios aún importan algunas utilidades de `cli/` (configuración, utilidades de errores, consultas de listas), que tendrían que moverse con ella. | +| Una interfaz nueva junto a la CLI y MCP | Moderado | Construir `ServiceDeps`, llamar a `servicesFor(deps)` y mostrar la respuesta. Los servicios no importan nada de `cli/`; la interfaz nueva escribe su propia forma de conectarse y de leer la configuración. | | El motor de almacenamiento | Difícil | Reimplementar `MessageStore`: unos 140 métodos, incluidas la búsqueda de texto completo, las raíces de palabras y la búsqueda por vectores. El límite se respeta, pero la interfaz es una sola superficie amplia. | ## Cómo se mantiene el diseño @@ -293,8 +300,12 @@ Convenciones para el código nuevo: 1. **Una función nueva del mensajero** es un grupo opcional en `ports/messenger.ts`, al que se accede con `capability()` y que se ofrece mediante una opción de la descripción. `MessengerCore` no crece. -2. **Un caso de uso nuevo** es un servicio que recibe `ServiceDeps`. Lo llaman el comando y la herramienta - MCP; un archivo de comando no contiene lógica de negocio. +2. **Un caso de uso nuevo** es un servicio que recibe la dependencia más estrecha que necesita. Un caso de uso + que solo lee el almacén recibe la parte que lee (`Pick`, o un almacén y una cuenta), como + la mayoría de los servicios de reuniones y de búsqueda: es más fácil de probar y de mover. Uno que además + necesita el mensajero, la protección de envíos o la apertura perezosa recibe `ServiceDeps`. De los 87 + archivos de servicios, 25 reciben solo el almacén y 36 reciben `ServiceDeps`. Lo llaman el comando y la + herramienta MCP; un archivo de comando no contiene lógica de negocio. 3. **Una consulta nueva al almacén** es un módulo en `store/sqlite/`, expuesto solo a través de `store.ts`. Un servicio lee con `accountStore(deps)`; una lectura deliberada de varias cuentas se identifica con `crossAccount(deps, reason)`. @@ -311,8 +322,9 @@ Convenciones para el código nuevo: - **El almacén es una sola interfaz amplia.** `MessageStore` tiene unos 140 métodos sobre un único `openStore`, y `store.ts` tiene 2042 líneas. Casi todas las funciones lo tocan. Dividirlo solo sirve siguiendo una costura real: recursos que se abren y cierran por separado. -- **Los servicios aún saben un poco de la CLI.** Importan la descripción `Messenger` como tipo y algunas - utilidades de `cli/` en ejecución. +- **Las partes de `Messenger` que solo usa la CLI reciben un `Command` de commander.** `connect`, + `accountOf`, `guard` y `diagnose` leen ellas mismas las opciones del comando. Los servicios no las ven, + pero una segunda interfaz tendría que escribir las suyas. - **El adaptador de tg es una clase grande**: 2312 líneas y unos 110 métodos, legible método a método, pero es el lugar donde acaba cada función de Telegram. - **`client/values.ts` de max** tiene 59 exportaciones que usan 20 módulos. diff --git a/content/docs/code-design.mdx b/content/docs/code-design.mdx index 31c88dcd..e08b9e92 100644 --- a/content/docs/code-design.mdx +++ b/content/docs/code-design.mdx @@ -68,7 +68,7 @@ graph TB Cli("cli/ and mcp/
commands and MCP tools") Services("services/
use cases") Sends("sends/
guarded writes, uploads") - Port("ports/messenger.ts
the adapter contract") + Port("ports/
the adapter contract, the traits") Store("store/
MessageStore over SQLite") Domain("domain/
models and value types") end @@ -88,10 +88,10 @@ graph TB | Layer | Its job | Size | What it may not import | | --- | --- | --- | --- | -| `domain/` | Messenger-neutral types: `Chat`, `Message`, `Person`, locators, formatting spans. No behaviour that needs a network or a file. | 24 files | Any messenger library or adapter. | -| `ports/messenger.ts` | The contract every messenger implements. | 1 file, 663 lines | Same as above. | +| `domain/` | Messenger-neutral types and values: `Chat`, `Message`, `Person`, locators, formatting spans, the fact, memory and task value sets. No behaviour that needs a network or a file. It sits almost at the bottom: it reads two types from `search/` and the task kinds from `tasks/`. | 29 files | `cli/`, any messenger library or adapter. | +| `ports/` | The contract every messenger implements (`messenger.ts`), what a service reads of the messenger (`traits.ts`) and who the app is (`app.ts`). | 3 files | Same as above. | | `store/` | The local archive: one `MessageStore` interface, opened by `openStore`, over SQLite modules, migrations and the search indexes. | 101 files | Nothing outside `store/` may import a module under `store/sqlite/`. | -| `services/` | Use cases: messages, chats, people, search, embeddings, mentions, moderation. | 81 files | Commander, a command module, a SQLite module. | +| `services/` | Use cases: messages, chats, people, search, embeddings, mentions, moderation. | 87 files | Commander, anything under `cli/`, a SQLite module. The same holds for `replies/`, `attachments/`, `moderation/`, `models/` and `analysis/`. | | `sends/` | Every write: the send guard, operation ids, the send journal, uploads. | 14 files | Same as services. | | `cli/`, `mcp/` | Presentation: commands, the JSON envelope, settings and profiles; MCP tools over the same services. | 168 + 51 files | A SQLite module. | | `meetings/`, `tasks/` | Self-contained modules with their own ports. | 46 + 4 files | Anything else in cli-core, a database or the file system. | @@ -162,9 +162,14 @@ never dropped. ### The descriptor -Each tool also hands the core a `Messenger` object of 47 fields: the app's name, how to connect, how to -page history, and flags such as `pollQuiz`, `topicShow` and `stickers`. Commands read the flags when -they register their options, so `polls create --quiz` exists only in a tool whose messenger has quizzes. +Each tool also hands the core a `Messenger` object: the app's name, how to connect, how to page history, +and flags such as `pollQuiz`, `topicShow` and `stickers`. Commands read the flags when they register their +options, so `polls create --quiz` exists only in a tool whose messenger has quizzes. + +Services see only part of it. [`MessengerTraits`](https://github.com/WireCatLabs/cli-core/blob/80616b3813ca8264d8f9edca3e8e0fcce7a1e81e/src/ports/traits.ts) in `ports/traits.ts` holds the 14 fields a service reads, +such as the app's name, how history is paged and whether the messenger searches on its server. `Messenger` +extends it with what only the CLI uses: `connect`, settings, the guard hook and `doctor`'s diagnosis. A +service cannot reach a command, a commander object or a setting it was not given. ### Services and their dependencies @@ -181,8 +186,8 @@ account: () => Promise Nothing opens until a service asks, so a read answered from the local store never connects. [`servicesFor(deps)`](https://github.com/WireCatLabs/cli-core/blob/a3b5b3c47792d5e2df6529642e907ee32783e66a/src/services/index.ts#L145) -builds every service once; the commands and the MCP tools take them from there, so both give the same -answer and the same error for the same input. +builds the services over one dependency object: a command builds them for its run, an MCP tool for its +call. Both call the same service functions, so a use case has one implementation for both. A tool can replace one service without forking the core. `Messenger.services` receives the shared services and returns replacements, for example its own way of finding a chat for moderation. @@ -259,7 +264,7 @@ rule or a test keeps it that way. | Document readers, image describers, embedding models | Small | Register a reader or provider; choose a local or remote model. | | A meeting provider | Small | Implement `MeetingSource` in the provider's package. | | A new messenger | Moderate | Implement `MessengerCore` and the groups the messenger has, and write a descriptor. The commands, MCP tools, store, search and send guard come with the core. | -| A new front end beside the CLI and MCP | Moderate | Call `servicesFor(deps)` and render. Services still import some helpers from `cli/` (settings, failure helpers, list queries), so those move with it. | +| A new front end beside the CLI and MCP | Moderate | Build `ServiceDeps`, call `servicesFor(deps)` and render. Services import nothing from `cli/`; the front end writes its own way to connect and resolve settings. | | The storage engine | Hard | Reimplement `MessageStore`: about 140 methods, including full-text, stemming and vector search. The boundary holds, but the interface is one wide surface. | ## Keeping the shape @@ -286,8 +291,11 @@ Conventions for new code: 1. **A new messenger feature** is an optional group in `ports/messenger.ts`, reached through `capability()` and offered through a descriptor flag. `MessengerCore` does not grow. -2. **A new use case** is a service that takes `ServiceDeps`. The command and the MCP tool both call it; a - command file holds no business logic. +2. **A new use case** is a service that takes the narrowest dependency it needs. A use case that only reads the + store takes the store part it reads (`Pick`, or a store and an account), as most meeting + and search services do: it is the easier one to test and to move. One that also needs the messenger, the + send guard or lazy opening takes `ServiceDeps`. Of the 87 service files, 25 take only the store and 36 take + `ServiceDeps`. The command and the MCP tool both call the service; a command file holds no business logic. 3. **A new store query** is a module under `store/sqlite/`, exposed only through `store.ts`. A service reads through `accountStore(deps)`; a read across accounts on purpose names itself with `crossAccount(deps, reason)`. @@ -303,8 +311,9 @@ Conventions for new code: - **The store is one wide interface.** `MessageStore` has about 140 methods over one `openStore`, and `store.ts` is 2,042 lines. Most features touch it. A split is useful only along a real seam: resources that open and close separately. -- **Services still know a little about the CLI.** They import the `Messenger` descriptor as a type and a - handful of runtime helpers from `cli/`. +- **The CLI-only parts of `Messenger` take a commander `Command`.** `connect`, `accountOf`, `guard` and + `diagnose` read the command's options themselves. Services never see them, but a second front end would + write its own versions. - **The tg adapter is one large class**: 2,312 lines and about 110 methods, readable per method but the place every Telegram feature lands. - **max's `client/values.ts`** holds 59 exports used by 20 modules. diff --git a/content/docs/code-design.ru.mdx b/content/docs/code-design.ru.mdx index b01c8be3..7b7ae6f0 100644 --- a/content/docs/code-design.ru.mdx +++ b/content/docs/code-design.ru.mdx @@ -68,7 +68,7 @@ graph TB Cli("cli/ и mcp/
команды и инструменты MCP") Services("services/
сценарии") Sends("sends/
защищённая запись, вложения") - Port("ports/messenger.ts
контракт адаптера") + Port("ports/
контракт адаптера, свойства") Store("store/
MessageStore поверх SQLite") Domain("domain/
модели и типы значений") end @@ -88,10 +88,10 @@ graph TB | Слой | Задача | Размер | Что ему нельзя импортировать | | --- | --- | --- | --- | -| `domain/` | Типы, не привязанные к мессенджеру: `Chat`, `Message`, `Person`, локаторы, участки форматирования. Никакого поведения, которому нужна сеть или файл. | 24 файла | Библиотеку мессенджера или адаптер. | -| `ports/messenger.ts` | Контракт, который реализует каждый мессенджер. | 1 файл, 663 строки | То же. | +| `domain/` | Типы и значения, не привязанные к мессенджеру: `Chat`, `Message`, `Person`, локаторы, участки форматирования, наборы значений фактов, памяти и задач. Никакого поведения, которому нужна сеть или файл. Слой почти нижний: он берёт два типа из `search/` и виды задач из `tasks/`. | 29 файлов | `cli/`, библиотеку мессенджера или адаптер. | +| `ports/` | Контракт, который реализует каждый мессенджер (`messenger.ts`), то, что сервис читает о мессенджере (`traits.ts`), и кто такое приложение (`app.ts`). | 3 файла | То же. | | `store/` | Локальный архив: один интерфейс `MessageStore`, который открывает `openStore`, поверх модулей SQLite, миграций и поисковых индексов. | 101 файл | Ничему вне `store/` нельзя импортировать модуль из `store/sqlite/`. | -| `services/` | Сценарии: сообщения, чаты, люди, поиск, векторы, упоминания, модерация. | 81 файл | Commander, модуль команды, модуль SQLite. | +| `services/` | Сценарии: сообщения, чаты, люди, поиск, векторы, упоминания, модерация. | 87 файлов | Commander, что угодно из `cli/`, модуль SQLite. То же верно для `replies/`, `attachments/`, `moderation/`, `models/` и `analysis/`. | | `sends/` | Любая запись: защита отправки, идентификаторы операций, журнал отправок, вложения. | 14 файлов | То же, что сервисам. | | `cli/`, `mcp/` | Представление: команды, конверт JSON, настройки и профили; инструменты MCP поверх тех же сервисов. | 168 + 51 файл | Модуль SQLite. | | `meetings/`, `tasks/` | Самостоятельные модули со своими портами. | 46 + 4 файла | Что-либо ещё из cli-core, базу данных или файловую систему. | @@ -163,10 +163,14 @@ cannot react to a message», если у адаптера его нет. Пар ### Описание инструмента -Каждый инструмент передаёт ядру ещё и объект `Messenger` из 47 полей: имя приложения, как подключаться, -как листать историю, и флаги вроде `pollQuiz`, `topicShow` и `stickers`. Команды читают флаги, когда -регистрируют свои параметры, поэтому `polls create --quiz` есть только в инструменте, где мессенджер -умеет викторины. +Каждый инструмент передаёт ядру ещё и объект `Messenger`: имя приложения, как подключаться, как листать +историю, и флаги вроде `pollQuiz`, `topicShow` и `stickers`. Команды читают флаги, когда регистрируют свои +параметры, поэтому `polls create --quiz` есть только в инструменте, где мессенджер умеет викторины. + +Сервисы видят только часть этого объекта. [`MessengerTraits`](https://github.com/WireCatLabs/cli-core/blob/80616b3813ca8264d8f9edca3e8e0fcce7a1e81e/src/ports/traits.ts) в `ports/traits.ts` — 14 полей, которые читает +сервис: имя приложения, как листается история, ищет ли мессенджер на своём сервере и другие. `Messenger` +добавляет к ним то, что нужно только CLI: `connect`, настройки, хук защиты отправки и диагностику для +`doctor`. Сервис не может дотянуться до команды, объекта commander или настройки, которую ему не передали. ### Сервисы и их зависимости @@ -184,8 +188,8 @@ account: () => Promise Ничего не открывается, пока сервис не попросит, поэтому чтение, на которое отвечает локальное хранилище, никогда не подключается к сети. [`servicesFor(deps)`](https://github.com/WireCatLabs/cli-core/blob/a3b5b3c47792d5e2df6529642e907ee32783e66a/src/services/index.ts#L145) -собирает все сервисы один раз; команды и инструменты MCP берут их оттуда, поэтому на одинаковый ввод дают -одинаковый ответ и одинаковую ошибку. +собирает сервисы над одним объектом зависимостей: команда — на свой запуск, инструмент MCP — на свой +вызов. Оба вызывают одни и те же функции сервисов, поэтому у сценария одна реализация на двоих. Инструмент может заменить один сервис, не копируя ядро. `Messenger.services` получает общие сервисы и возвращает замены, например свой способ находить чат для модерации. @@ -262,7 +266,7 @@ sequenceDiagram | Чтение документов, описание картинок, модели векторов | Мало | Зарегистрировать читатель или поставщика; выбрать локальную или удалённую модель. | | Поставщик встреч | Мало | Реализовать `MeetingSource` в пакете поставщика. | | Новый мессенджер | Средне | Реализовать `MessengerCore` и те группы, что есть у мессенджера, и написать описание. Команды, инструменты MCP, хранилище, поиск и защита отправки приходят вместе с ядром. | -| Новый интерфейс рядом с CLI и MCP | Средне | Вызвать `servicesFor(deps)` и отрисовать ответ. Сервисы всё ещё импортируют несколько помощников из `cli/` (настройки, помощники ошибок, запросы списков), и они переезжают вместе. | +| Новый интерфейс рядом с CLI и MCP | Средне | Собрать `ServiceDeps`, вызвать `servicesFor(deps)` и отрисовать ответ. Сервисы ничего не импортируют из `cli/`; новый интерфейс пишет свой способ подключаться и читать настройки. | | Движок хранилища | Сложно | Заново реализовать `MessageStore`: около 140 методов, включая полнотекстовый поиск, основы слов и поиск по векторам. Граница держится, но интерфейс — одна широкая поверхность. | ## Как сохранить устройство @@ -289,8 +293,12 @@ Biome строит граф импортов для `noImportCycles`, тольк 1. **Новая возможность мессенджера** — необязательная группа в `ports/messenger.ts`, доступная через `capability()` и предложенная через флаг описания. `MessengerCore` не растёт. -2. **Новый сценарий** — сервис, который принимает `ServiceDeps`. Его вызывают и команда, и инструмент MCP; - в файле команды нет бизнес-логики. +2. **Новый сценарий** — сервис, который принимает самую узкую зависимость, какая ему нужна. Сценарий, который + только читает хранилище, принимает ту часть, которую читает (`Pick` или хранилище и + аккаунт), как большинство сервисов встреч и поиска: такой проще проверить и перенести. Сценарий, которому + нужны ещё мессенджер, защита отправки или ленивое открытие, принимает `ServiceDeps`. Из 87 файлов сервисов + 25 принимают только хранилище, 36 — `ServiceDeps`. Сервис вызывают и команда, и инструмент MCP; в файле + команды нет бизнес-логики. 3. **Новый запрос к хранилищу** — модуль в `store/sqlite/`, доступный только через `store.ts`. Сервис читает через `accountStore(deps)`; намеренное чтение по нескольким аккаунтам называет себя через `crossAccount(deps, reason)`. @@ -306,8 +314,9 @@ Biome строит граф импортов для `noImportCycles`, тольк - **Хранилище — один широкий интерфейс.** У `MessageStore` около 140 методов над одним `openStore`, а `store.ts` — 2042 строки. Его затрагивает большинство функций. Делить его полезно только по настоящему шву: ресурсам, которые открываются и закрываются отдельно. -- **Сервисы всё ещё немного знают о CLI.** Они импортируют описание `Messenger` как тип и несколько - помощников из `cli/` во время работы. +- **Части `Messenger`, нужные только CLI, принимают `Command` из commander.** `connect`, `accountOf`, + `guard` и `diagnose` сами читают параметры команды. Сервисы их не видят, но второму интерфейсу пришлось бы + написать свои. - **Адаптер tg — один большой класс**: 2312 строк и около 110 методов; каждый метод читается легко, но сюда попадает каждая функция Telegram. - **`client/values.ts` в max** содержит 59 экспортов, которые используют 20 модулей.