Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 26 additions & 14 deletions content/docs/code-design.es.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ graph TB
Cli("cli/ y mcp/<br>comandos y herramientas MCP")
Services("services/<br>casos de uso")
Sends("sends/<br>escrituras protegidas, adjuntos")
Port("ports/messenger.ts<br>el contrato del adaptador")
Port("ports/<br>el contrato del adaptador, los rasgos")
Store("store/<br>MessageStore sobre SQLite")
Domain("domain/<br>modelos y tipos de valor")
end
Expand All @@ -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. |
Expand Down Expand Up @@ -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:
Expand All @@ -186,8 +192,9 @@ account: () => Promise<AccountKey>
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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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<MessageStore, …>`, 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)`.
Expand All @@ -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.
Expand Down
37 changes: 23 additions & 14 deletions content/docs/code-design.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ graph TB
Cli("cli/ and mcp/<br>commands and MCP tools")
Services("services/<br>use cases")
Sends("sends/<br>guarded writes, uploads")
Port("ports/messenger.ts<br>the adapter contract")
Port("ports/<br>the adapter contract, the traits")
Store("store/<br>MessageStore over SQLite")
Domain("domain/<br>models and value types")
end
Expand All @@ -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. |
Expand Down Expand Up @@ -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

Expand All @@ -181,8 +186,8 @@ account: () => Promise<AccountKey>

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.
Expand Down Expand Up @@ -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
Expand All @@ -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<MessageStore, …>`, 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)`.
Expand All @@ -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.
Expand Down
Loading
Loading