devmemomcp
Memoria de trabajo volátil para desarrollo asistido por LLM, expuesta como servidor MCP. Mantiene en RAM —como grafo con textos vectorizados— todo lo que queda pendiente de codificar entre sesiones, para que el modelo no tenga que recordarlo en contexto ni escribirlo en archivos de notas.
Construido sobre proMCP 0.5.1
(convención triádica can_do / read_* / do_*) con FastMCP como transporte.
pip install -e .
python -m devmemomcp # servidor stdio
Sobre el BREAKING de proMCP 0.5.0. Renombra
TMCPError→ProMCPErrory__tmcp_*→__promcp_*. No afecta a este servidor: solo consume los decoradores,Registry,ProMCPServerylint_registry, y ninguno cambió de nombre. Comprobado con la suite completa contrav0.5.1en un venv limpio.
Por qué volátil
La memoria muere con el proceso, a propósito. No es un registro permanente: es memoria de trabajo. Lo que deba sobrevivir a la sesión se escribe en el repo. Lo que vive aquí es el estado mental de la codificación —qué estabas haciendo, qué te bloqueaba, qué decidiste y por qué— que hoy se pierde entre una sesión de LLM y la siguiente.
Consecuencia de diseño que atraviesa todo el código: una memoria vacía se
reporta con quality: degraded, no good. Vacío puede significar "reinició el
servidor", y eso no es lo mismo que "no queda trabajo pendiente".
Los 8 tools
| Tool | Categoría | Para qué |
|---|---|---|
can_do |
precondición | ¿es viable esta intención ahora, y qué la bloquea? |
read_pending |
observación | qué está abierto, ordenado por énfasis |
read_related |
observación | qué sé ya sobre este tema (vectores + grafo) |
read_item |
observación | un item con vecinos, bloqueadores e historial |
do_remember |
mutación | grabar un pendiente y auto-enlazarlo |
do_update_item |
mutación | cambiar estado/prioridad/texto, reforzar énfasis |
do_link |
mutación | declarar dependencias y bloqueos entre items |
do_forget |
mutación | borrar un item de verdad (no solo archivarlo) |
Los ocho que proMCP recomienda como máximo por servidor. do_forget ocupa el
slot que se dejó libre a propósito en la primera versión de este servidor —
ver FUN-1 en TODO.md para el porqué de esa elección sobre las otras
candidatas.
Protocolo de sesión
1. Al abrir la sesión → read_pending recupera el énfasis anterior
2. Antes de codificar algo → read_related decisiones y bloqueos del tema
3. Al detectar un pendiente → do_remember grábalo en cuanto aparezca
4. Al empezar / terminar → do_update_item status=active / status=done
5. Antes de cerrar algo dudoso → can_do ¿quedan bloqueadores?
Arquitectura
Outside-in, con el transporte en la frontera exterior. Cada capa solo conoce a la de adentro:
devmemomcp/
├── domain/ invariantes puras · no sabe que MCP existe
│ ├── models.py MemoryNode, Edge, salience
│ ├── embedding.py Embedder (Protocol) + HashingEmbedder
│ ├── graph.py grafo dirigido en RAM, difusión por saltos
│ ├── store.py casos de uso, RLock, idempotencia
│ └── errors.py
├── surface/ superficie triádica · conoce proMCP, no FastMCP
│ ├── reads.py read_pending, read_related, read_item
│ ├── mutations.py do_remember, do_update_item, do_link
│ └── feasibility.py can_do
└── transport/ única frontera con FastMCP
└── server.py create_server()
transport/server.py es el único archivo que menciona el transporte. Cambiar
de FastMCP a otra cosa toca un archivo, no cuarenta.
El grafo
Nodos de seis tipos (task, blocker, decision, question, insight,
artifact) y aristas dirigidas de seis tipos (blocks, depends_on,
subtask_of, relates_to, supersedes, touches). Las adyacencias se
mantienen en espejo (entrantes y salientes) para que recorrer el vecindario
sea O(grado); al leer hacia atrás se reporta la inversa semántica —
blocks se lee como blocked_by— para que el modelo entienda la relación tal
como aplica al nodo que consultó.
Los vectores
HashingEmbedder por defecto: hashing vectorizer con unigramas y bigramas,
tf sublineal y normalización L2. Sin modelo, sin red, sin descarga,
determinista entre procesos. Vectores dispersos (dict[int, float])
porque un título produce ~20 dimensiones no nulas de 4096, y el coseno disperso
es O(min(|a|,|b|)) en Python puro.
No entiende sinónimos. Para eso hay un embedder denso opcional
(pip install devmemomcp[semantic]), o se implementa el Protocol
Embedder propio y se pasa al store:
from devmemomcp.domain.dense_embedder import SentenceTransformerEmbedder
create_server(embedder=SentenceTransformerEmbedder())
Nada más en el codebase cambia. Umbral de auto-enlace por embedder, no
global: AUTOLINK_THRESHOLD = 0.15 está afinado para el embedder léxico
(pares relacionados 0.17–0.59, no relacionados exactamente 0.0). Un modelo
denso da coseno > 0 a casi todo par, así que ese mismo umbral saturaría el
grafo de aristas falsas. Por eso cada Embedder declara su propio
autolink_threshold (HashingEmbedder usa 0.15,
SentenceTransformerEmbedder usa 0.35 — ver el docstring de
devmemomcp/domain/dense_embedder.py para cómo se calibró ese 0.35) y
MemoryStore lo toma como default; se puede seguir forzando uno explícito
con MemoryStore(embedder=..., autolink_threshold=...).
El énfasis (salience)
Es lo que hace que la memoria "mantenga en énfasis" lo pendiente en lugar de devolver una lista plana:
salience = peso_estado × (0.45·prioridad + 0.35·recencia + 0.20·centralidad + boost)
La recencia decae exponencialmente (vida media 24 h por defecto) desde el
último toque: lo que nadie mira se apaga solo. La centralidad hace subir a
los items muy conectados. done y dropped colapsan a 0 — siguen en el grafo
como contexto histórico, pero dejan de competir por atención. bump=true
refuerza manualmente (+0.1, tope 0.5).
Los tres canales de can_do
proMCP separa desde 0.4.0 tres respuestas que antes se mezclaban, y la distinción no es cosmética:
| Canal | Significa | Ejemplo aquí |
|---|---|---|
candidates[] |
hay ruta y es transitable | "puedes grabar el pendiente" |
blocked[] |
la ruta existe y no es transitable ahora | cerrar un item con bloqueadores abiertos |
unroutable[] |
no hay ruta al resultado — no hay capacidad que nombrar | el item que citas ya no existe |
Antes de adoptarlo, este servidor cometía los dos abusos que el spec nombra:
metía un item inexistente en blocked[] —inventando una ruta viva— y bloqueaba
los read_* con la memoria vacía, cuando esas lecturas sí son transitables
(devuelven cero items con quality: degraded, que ya es la señal correcta). La
memoria fría es ahora contexto, no un bloqueo.
Los tres motivos de unroutable están cubiertos: requirements_unmet (el item
no existe), malformed_request (el intent no declara nada que enrutar — antes
devolvía las seis capacidades con confianza 0.5, ruido disfrazado de respuesta)
y no_producer (se pidió algo que este servidor no hace, que antes salía como
un feasible: false sin explicar).
Invariante que impone el builder: unroutable[] no vacío obliga a
feasible: false. Por eso, cuando el resultado pedido es inalcanzable no se
emite ningún candidato — ofrecer do_remember a quien preguntó "¿puedo cerrar
el item X?" es ruido, no ayuda.
Recuperación híbrida
read_related no es solo búsqueda vectorial. Vectoriza la consulta, saca
semillas por coseno, y las difunde por el grafo con decaimiento por salto.
Un nodo alcanzable por varios caminos se queda con el mejor, no con la suma —
sumar premiaría a los hubs por ser hubs, no por ser relevantes. El puntaje final
mezcla semántica (0.55), proximidad en el grafo (0.25) y salience (0.20). Así
aparece lo que está conectado a lo que estás mirando aunque su texto no se
parezca en nada.
Decisiones que conviene no re-descubrir
Las lecturas no mutan nada. Ni touched_at, ni contadores de acceso. Es lo
que hace que los read_* sean paralelizables y cacheables de verdad, como
promete proMCP. Refrescar la atención es un acto explícito:
do_update_item(bump=true). Hay un test que lo vigila.
El store serializa con RLock. FastMCP 3.x corre los tools en hilos de
worker (run_in_thread=True por defecto), así que el estado en RAM se toca
concurrentemente. El lock vive en MemoryStore y ninguna capa de arriba se
entera.
Nada de from __future__ import annotations en surface/ (vivo: el fix
está propuesto pero no ha entrado en 0.4.0, 0.4.1, 0.5.0 ni 0.5.1 — ver promcp-pr/). Bajo PEP 563
las anotaciones quedan como strings y promcp._infer_input_schema —que compara
contra los tipos reales— emite un inputSchema vacío, lo que hace fallar al
propio linter de proMCP (I002/I003). FastMCP resuelve los strings por su cuenta,
así que el síntoma solo aparece del lado del registry y es fácil de perseguir.
valid_until por bandas, no por constante plana (§11.3). Enrutar una
intención genérica depende solo del catálogo, que no cambia → banda estática,
240 s. Responder sobre un item concreto depende de un estado que cualquier do_*
invalida → banda dinámica, 20 s.
Las descripciones de los do_* explican cómo construir el idempotency_key
(AP-9). Importa especialmente aquí: el store deduplica por clave durante 24 h, así
que una clave derivada del tipo de operación en vez de la instancia haría que
el segundo do_remember devolviera el nodo del primero y nunca grabara nada —
sin error visible, con el servidor comportándose correctamente.
Registry explícito, no el singleton de módulo. Permite dos servidores en
el mismo proceso (tests, multi-tenant) sin disparar CanDoSingletonError.
Los do_* fallidos devuelven un compensation_hint no nulo. proMCP exige
el campo cuando compensable=True, incluso en el camino de error; devolver
None dispara un ContractViolation que enmascara el error real.
Verificación
pip install -e ".[dev]"
pytest -q # tests: dominio, contratos, linter y transporte real
pytest --cov=devmemomcp --cov-report=term-missing -q # cobertura, umbral 85% (CAL-7)
ruff format --check devmemomcp tests # formato (CAL-6)
ruff check devmemomcp tests # lint (CAL-6)
mypy --strict devmemomcp # tipos (CAL-5)
El servidor pasa el linter de proMCP 0.5.1 con 0 errores y 0 warnings, y hay
tests que lo comprueban en CI en lugar de confiar en que siga siendo cierto.
El test end-to-end recorre el patrón canónico completo — can_do → read_*
→ do_* → read_* — incluyendo que can_do impida cerrar un item que
todavía tiene bloqueadores abiertos.
tests/test_stdio.py (CAL-8) es el único test que no usa el cliente
in-memory de FastMCP: lanza python -m devmemomcp como proceso real, habla
MCP por stdin/stdout con el SDK cliente oficial (mcp), completa el
handshake y ejecuta un do_remember + read_pending de verdad — la única
forma de detectar algo que escriba basura en stdout y corrompa el protocolo.
Los cuatro pasos de arriba, más promcp-lint sobre el registry en tiempo de
ejecución, corren en CI (.github/workflows/ci.yml) en cada push y PR sobre
Python 3.10, 3.11 y 3.12 — ver el badge al principio de este README.
Configuración en un cliente MCP
Instalación previa en el entorno donde vive el intérprete que se invoque abajo (una vez por proyecto, o global si prefieres tenerlo siempre disponible):
pip install -e /ruta/a/devmemomcp # o: pip install -e . estando dentro del repo
El bloque de configuración es el mismo para cualquier cliente MCP por stdio —
lo que cambia es dónde se pega. command/args es literalmente
python -m devmemomcp, tal como lo arranca CAL-8 en el test de proceso
real:
{
"mcpServers": {
"devmemomcp": {
"command": "python",
"args": ["-m", "devmemomcp"]
}
}
}
Claude Code
Verificado en este mismo entorno (Windows, Claude Code 2.1.234) con el comando real, no de memoria:
claude mcp add devmemomcp -s local -- python -m devmemomcp
-s locallo registra solo para este proyecto (el scope por defecto); usa-s userpara tenerlo disponible en todos tus proyectos, o-s projectpara versionarlo en.mcp.jsony compartirlo con quien clone el repo.- Todo lo que va después de
--es el comando literal que Claude Code ejecuta por stdio: exactamentepython -m devmemomcp.
Comprobado con claude mcp get devmemomcp inmediatamente después de añadirlo:
devmemomcp:
Scope: Local config (private to you in this project)
Status: ✔ Connected
Type: stdio
Command: python
Args: -m devmemomcp
claude mcp list lo muestra ✔ Connected junto al resto de servidores MCP
configurados, confirmando el handshake — el mismo handshake que
tests/test_stdio.py (CAL-8) automatiza contra un proceso real. Tras
verificarlo se retiró con claude mcp remove devmemomcp -s local para no
dejar un servidor de más en la configuración de quien corrió esta
verificación; repite el add de arriba para instalarlo de verdad.
Para confirmar que salen los 8 tools dentro de una sesión de Claude Code,
usa /mcp (o pregúntale a Claude "qué tools expone devmemomcp") una vez
conectado.
Claude Desktop
No verificado en este entorno — no hay forma de abrir Claude Desktop (aplicación de escritorio) desde una sesión de Claude Code headless en esta máquina, así que esto son instrucciones de edición de archivo, no una captura de pantalla ni una sesión probada:
-
Localiza
claude_desktop_config.json:- macOS:
~/Library/Application Support/Claude/claude_desktop_config.json - Windows:
%APPDATA%\Claude\claude_desktop_config.json
- macOS:
-
Añade la entrada
devmemomcpdentro demcpServers(créalo si el archivo está vacío o no existe todavía):{ "mcpServers": { "devmemomcp": { "command": "python", "args": ["-m", "devmemomcp"] } } }
-
Reinicia Claude Desktop por completo (no basta con cerrar la ventana).
-
Los 8 tools deberían aparecer bajo el icono de herramientas (🔨) de la conversación, con el prefijo
devmemomcpdelante de cada nombre.
Si python no resuelve al intérprete correcto (varias instalaciones, un
venv específico), sustituye "command": "python" por la ruta absoluta al
intérprete donde se instaló el paquete — Claude Desktop no hereda el PATH
de una shell interactiva en todos los sistemas.
Decisiones de diseño
Elecciones que condicionan el diseño y que no conviene reabrir sin un motivo nuevo. Cada una fue evaluada, no asumida.
¿Persistencia? No, por defecto — y tampoco un export/import a mano
La memoria es volátil por diseño, no por descuido: muere con el proceso a
propósito, y esa decisión atraviesa el código entero, desde el RLock en
MemoryStore hasta el quality: degraded que reporta una memoria vacía (que
puede significar "reinició el servidor", no "no queda trabajo pendiente"). Se
evaluó el punto intermedio obvio — un export/import explícito, invocado a
mano por el usuario, nunca autoguardado — y se descarta también, no solo el
autoguardado:
- No resuelve ningún problema que no resuelva ya el repo. Lo que debe
sobrevivir a la sesión se escribe donde ya vive el resto del proyecto: un
commit, un
TODO.md, un comentario. Un export en un formato propio de devmemomcp es una copia paralela de eso, no una mejora sobre eso. - Cambia lo que es este proyecto. En cuanto existe un archivo que un usuario puede reabrir la semana siguiente, deja de ser memoria de trabajo y pasa a ser una base de datos de tareas con pasos extra — justo lo que el diseño evita a propósito (ver "Por qué volátil" arriba).
- No es gratis mantenerlo. Un formato de export es superficie a versionar y a la que hay que darle compatibilidad hacia atrás, por una función que nadie pidió resolviendo un problema que no existe: si un LLM necesita que algo persista, ya tiene un tool para eso — escribir en el repo.
Cerrado: no se añade ni autoguardado ni export/import manual. Si esto se replantea, que sea porque cambió el problema (por ejemplo, sesiones que necesitan sobrevivir a un crash, no a un cierre limpio), no porque "persistir siempre es mejor".
El peso del body en el vector: se truncó, un cambio de una línea
searchable_text() repetía el título para pesar el doble y concatenaba el
body completo (hasta MAX_BODY_BYTES, 8 KB). Con un body largo esa
repetición no basta: unos pocos miles de caracteres de prosa dominan por
volumen a un título de un puñado de palabras en un hashing vectorizer, que no
sabe de posición ni de importancia estructural — solo cuenta n‑gramas.
No hay corpus real en este entorno para medir la mejora de recall empíricamente, así que la decisión se tomó por el argumento de diseño, no por benchmark, y se dejó lo más barata y reversible posible:
- Se truncó el
bodyaSEARCHABLE_BODY_CHARS(500 caracteres) solo para el texto que ve el embedder. Elbodyalmacenado y el que devuelvenread_item/read_pending/read_relatedno cambian ni un carácter — el corte vive únicamente dentro deMemoryNode.searchable_text(). - Se truncó por caracteres al principio, no por una ventana ni un resumen,
porque el principio de un
bodysuele concentrar la señal (qué es, por qué importa) y truncar es una operación sin estado, determinista y de coste cero — cualquier alternativa (TF‑IDF sobre el body para quedarse con los términos más informativos, un resumen) es más código para un problema que 500 caracteres ya mitiga. - 500 es una elección razonada, no calibrada: dos o tres frases suelen bastar
para que el hashing vectorizer capture el tema; comparado con un título de
≤200 caracteres repetido dos veces (~400 caracteres de peso), deja al body
con un peso comparable al del título en vez de aplastarlo por longitud.
No está medido contra un corpus real — si se observa en uso que 500 es
demasiado poco (temas que solo se distinguen entrada varios párrafos) o
demasiado (ruido que sigue colando temas ajenos), es el primer número a
mover, y un buen candidato para volver a medir cuando
FUN-3traiga un embedder denso con semántica distinta a la de n‑gramas. - Alternativa considerada y descartada por ahora: ponderar el body aparte en vez de truncarlo (dos vectores, uno de título y otro de body, combinados con pesos distintos en el coseno). Es estrictamente más flexible, pero es más superficie — dos vectores por nodo, una fórmula de combinación que calibrar — para un problema que truncar ya resuelve razonablemente. Si el truncado demuestra ser insuficiente, esa es la vía natural de seguir.
Test: test_searchable_text_trunca_el_body_pero_to_dict_lo_devuelve_completo
en tests/test_domain.py.
El grafo como recurso MCP (devmemo://graph): no, por ahora
MCP separa tools de resources. Un recurso devmemo://graph leería el
grafo (o un digest) sin gastar una llamada a tool, y en abstracto encaja: el
grafo es un dato que un cliente podría querer leer directamente. Se descarta
por ahora, no por principio:
- No hay ningún cliente MCP en uso que consuma
resourcesfrente a este servidor — es superficie sin consumidor. Construir un recurso que nadie lee no valida nada; valida que el código compila. - Ya existe una vía de lectura completa y sin efectos secundarios:
read_pending,read_relatedyread_itemcubren "todo lo abierto", "lo relacionado con X" y "un nodo con su vecindario" respectivamente. Un recursodevmemo://graphsería un cuarto camino a la misma información, con su propio formato de serialización que mantener sincronizado con los otros tres. - Añade una segunda superficie a la que
transport/server.py— hoy el único archivo que menciona FastMCP — tendría que declarar y versionar junto a los 8 tools, por un ahorro (una llamada a tool menos) que no se ha demostrado que importe: todas las lecturas hoy están medidas en milisegundos incluso a 2000 nodos (verTODO.md, bloque ESC).
Cerrado. Se reabre el día que exista un cliente MCP real hablando con este
servidor que sí distinga resources de tools en su UX y para el que ese
ahorro de una llamada importe.
Multi-proyecto: (a) un servidor por proyecto, no un campo project
Hoy un servidor MCP = una memoria. Con tres repos abiertos, la alternativa a
levantar tres servidores sería (b) un campo project en cada nodo y filtrado
en todos los read_*. Se elige (a):
- El aislamiento por proceso ya es completo y gratis: cada servidor tiene su
propio
MemoryStore, su propio grafo, su propio índice de vectores. No hay ningún caso en que la memoria de un proyecto se filtre a otro, porque no comparten proceso, y eso vale sin escribir una línea. - (b) no compra aislamiento — ya existe — compra la posibilidad de ver varios
proyectos a la vez desde un único servidor. Nadie ha pedido eso, y
encaja mal con lo que ya existe:
sessionya distingue "sesión de trabajo dentro de un proyecto"; añadirprojectcomo una segunda dimensión de filtrado obligaría a tocar los cuatroread_*ycan_do(que agrega sobre el store entero en sucontext) para que ninguno se salte el filtro — el mismo tipo de superficie nueva que sí importaría vigilar con tests, por un caso de uso hipotético. - Los clientes MCP (Claude Desktop, Claude Code) ya soportan de forma nativa varios servidores declarados a la vez con nombres distintos — es exactamente el mecanismo de (a), no algo que este proyecto tenga que inventar.
Aislamiento por proceso basta; no se justifica (b). Documentado como la forma
correcta de usarlo: un devmemomcp por proyecto, cada uno con su propia
entrada en la configuración del cliente MCP (ver "Configuración en un cliente
MCP" arriba), con un command/args/cwd distinto por repo si se necesita
que cada proceso arranque en su propio directorio.
start_* + read_status_* para operaciones largas: no aplica hoy
Es el patrón de proMCP para mutaciones que no terminan en la misma llamada.
Hoy no aplica: con HashingEmbedder, todo — vectorizar, auto-enlazar,
recorrer el grafo — está medido en milisegundos incluso a 2000 nodos (ver
TODO.md, bloque ESC). Introducir start_do_remember + read_status_* para
una operación de 1–3 ms sería puro ceremonial: dos tools más ocupando el
límite de 8, y un cliente que tendría que hacer polling de algo que ya
terminó cuando le llega la respuesta del start_*.
Condición de reapertura, ya anotada: si FUN-3 (embedder denso opcional)
llega a producción, vectorizar puede pasar de milisegundos a segundos por
llamada a un modelo. Ese es el momento de medir de nuevo do_remember con el
embedder denso puesto y decidir si cruza el umbral en el que vale la pena
start_*/read_status_* — no antes.
stream_* para observación continua: no
Es el patrón de proMCP para telemetría en vivo — "avísame cuando cambie el
énfasis". Se descarta: un LLM conversando por turnos no está escuchando un
canal entre turnos; lo primero que hace al volver es leer el estado actual
con read_pending/read_related, no reproducir un histórico de eventos que
se perdió mientras no había ningún turno en curso. Un stream_* de cambios
de salience serviría a un observador humano con un dashboard abierto, que
no es el consumidor que tiene este servidor. Cerrado sin condición de
reapertura explícita: reabrir esto exigiría que apareciera un consumidor con
esa forma de trabajar, no una mejora incremental del servidor actual.
¿Publicar en PyPI? No, todavía — bloqueado por una dependencia externa
Hoy devmemomcp solo se instala desde git (pip install -e . sobre un
clon, o pip install git+https://github.com/alexlqi/devmemomcp). Publicarlo
en PyPI no es una decisión de diseño de este repo, es un bloqueo técnico:
pyproject.tomlfijapromcpcomopromcp[transport] @ git+https://github.com/alexlqi/promcp.git@v0.5.1— una dependencia por URL de git, no por nombre de paquete de índice.- PyPI rechaza subir un paquete que declare una dependencia por URL git
en
[project.dependencies](lo bloqueatwine upload/ el propio índice, no es una regla de estilo que se pueda saltar). No hay forma de publicardevmemomcpen PyPI mientraspromcpno esté también en PyPI. promcpen sí mismo tampoco está publicado en PyPI hoy — solo existe como repo de GitHub con tags (v0.3.0→v0.5.1, ver el historial de este mismo README yCHANGELOG.md).
Decisión: no se persigue publicar en PyPI hasta que promcp se publique
primero ahí. Es una dependencia externa bloqueante, no algo que este repo
pueda resolver por su cuenta (cambiar a una copia vendorizada de promcp
para poder publicar sería peor que no publicar: divergiría en silencio de
la fuente real). Si promcp llega a PyPI, esta tarea se reabre reemplazando
el @ git+... por un rango de versión normal (promcp[transport]>=0.5.1) y
es, a partir de ahí, un python -m build && twine upload estándar — nada
más de este repo tiene que cambiar para que sea publicable.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file devmemomcp-0.1.0.tar.gz.
File metadata
- Download URL: devmemomcp-0.1.0.tar.gz
- Upload date:
- Size: 82.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/6.2.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
888f411afa0f72ad8e0ed558ed90c058dade1a579b3b03a9fbd76433f9b54c0f
|
|
| MD5 |
557f9d916478ff523e308a271a069132
|
|
| BLAKE2b-256 |
4dbda16920fd69fdc077e851a17de4edb6810b4ec960f443b4aeaa7abd9df675
|
File details
Details for the file devmemomcp-0.1.0-py3-none-any.whl.
File metadata
- Download URL: devmemomcp-0.1.0-py3-none-any.whl
- Upload date:
- Size: 63.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/6.2.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
17fb7870f079e1aa3cb321c5f38c68495de33d665e0c760c556106f20184a9de
|
|
| MD5 |
befe8faa8d1a32fe5c88646b19ef70d7
|
|
| BLAKE2b-256 |
c13f7ad213aef8c68c6f8008ae85650fe86a8d1eed92b7d71ef40d28381ffe3b
|