+

+
Cursor VPet
+
+ Digimon virtual pet for Cursor that evolves as you use Agent — shares progress with OpenCode VPet
+
+
+[](https://open-vsx.org/extension/sbugallo/cursor-vpet)
+[](https://open-vsx.org/extension/sbugallo/cursor-vpet)
+[](https://open-vsx.org/extension/sbugallo/cursor-vpet)
+[](LICENSE)
+
+[Features](#-features) • [Installation](#-installation) • [Usage](#-usage) •
+[Commands](#-commands) • [Settings](#%EF%B8%8F-settings) • [Storage](#-storage) •
+[How It Works](#-how-it-works) • [Authors](#-authors)
+
+[](https://open-vsx.org/extension/sbugallo/cursor-vpet)
+
+[](https://github.com/sbugallo/opencode-vpet/releases)
+
+---
+
+
+
+## 📖 About
+
+**Cursor Agent** turns every prompt into real work — but what if that effort also fed a partner
+that grows with you? **Cursor VPet** adds a Digimon virtual pet to the Explorer sidebar. Your
+partner gains experience from Agent token usage and evolves through classic Digimon stages.
+
+It shares the same `pet.db` database as
+[@sbugallo/opencode-vpet](https://www.npmjs.com/package/@sbugallo/opencode-vpet), so progress
+carries over between Cursor and OpenCode.
+
+
+
+**Built with ❤️ for the developer community**
+
+[⬆ Back to Top](#cursor-vpet)
+
+
diff --git a/packages/cursor-vpet/docs/REFACTORING-IMPLEMENTATION-SPEC.md b/packages/cursor-vpet/docs/REFACTORING-IMPLEMENTATION-SPEC.md
new file mode 100644
index 0000000..1c2f92e
--- /dev/null
+++ b/packages/cursor-vpet/docs/REFACTORING-IMPLEMENTATION-SPEC.md
@@ -0,0 +1,1682 @@
+# Especificación técnica de refactorización — `cursor-vpet`
+
+| Campo | Valor |
+|-------|-------|
+| **Paquete** | `packages/cursor-vpet` |
+| **Versión del documento** | 1.1 |
+| **Fecha** | 2026-09-11 |
+| **Estado** | En progreso — ~15 % del plan de refactor completado; feature de batalla/evolución ya mergeada en ambos hosts |
+| **Alcance** | Refactorización de `cursor-vpet`, cambios coordinados en `vpet-core`, `vpet-animation` (nuevo) y `opencode-vpet` |
+| **Último commit relevante** | `3b91b30` — *feat: evolve Digitama directly and split battle from reveal animations* |
+
+---
+
+## Tabla de contenidos
+
+1. [Resumen ejecutivo](#1-resumen-ejecutivo)
+ - [1.4 Estado de implementación (snapshot)](#14-estado-de-implementación-snapshot)
+2. [Contexto y arquitectura actual](#2-contexto-y-arquitectura-actual)
+3. [Diferencias entre hosts (OpenCode vs Cursor)](#3-diferencias-entre-hosts-opencode-vs-cursor)
+4. [Inventario de problemas por subsistema](#4-inventario-de-problemas-por-subsistema)
+5. [Decisiones de diseño](#5-decisiones-de-diseño)
+6. [Arquitectura objetivo](#6-arquitectura-objetivo)
+7. [Plan de implementación por PR](#7-plan-de-implementación-por-pr)
+8. [Especificaciones de módulos nuevos](#8-especificaciones-de-módulos-nuevos)
+9. [Estructura de carpetas objetivo](#9-estructura-de-carpetas-objetivo)
+10. [Plan de pruebas](#10-plan-de-pruebas)
+11. [Riesgos y mitigaciones](#11-riesgos-y-mitigaciones)
+12. [Criterios de aceptación globales](#12-criterios-de-aceptación-globales)
+13. [Apéndices](#13-apéndices)
+
+---
+
+## 1. Resumen ejecutivo
+
+### 1.1 Objetivo
+
+Refactorizar `cursor-vpet` para:
+
+- Eliminar duplicación de código (interna y cross-package con `opencode-vpet`).
+- Aplicar patrones modernos: responsabilidad única, inyección de dependencias, composición explícita.
+- Mejorar rendimiento (lectura SQLite, coordinación de refrescos).
+- Mantener paridad funcional y compatibilidad con `pet.db` compartida.
+
+### 1.2 Principio rector: separar lógica pura de presentación host-specific
+
+| Capa | Compartir entre hosts | Mantener host-specific |
+|------|----------------------|------------------------|
+| Dominio y use cases | ✅ `vpet-core` (ya existe) | — |
+| Animación idle (state machine) | ✅ Nuevo `vpet-animation` | — |
+| Render ASCII posicionado → `string` | ✅ `vpet-animation` | — |
+| Queries SQLite de lectura | ✅ `vpet-core` | Drivers (`sql.js` / `bun:sqlite`) |
+| Batalla/evolución/derrota visual (ASCII) | ✅ Ambos hosts (duplicado) | Entrega: OpenTUI vs Webview |
+| Entrega al usuario | ❌ | OpenTUI vs Webview |
+| HTML / componentes UI | ❌ | Por host |
+
+### 1.3 Estimación
+
+| Fase | PRs | Duración estimada |
+|------|-----|-------------------|
+| Fundamentos | PR-0, PR-1 | 5–6 días |
+| Hooks y animación compartida | PR-2, PR-3 | 7–8 días |
+| UI Cursor y artworks | PR-4, PR-5 | 8–10 días |
+| Bootstrap y calidad | PR-6, PR-7 | 4–5 días |
+| **Total** | **7 PRs** | **~4–6 semanas** |
+
+Cada PR debe ser mergeable de forma independiente, con tests pasando.
+
+### 1.4 Estado de implementación (snapshot)
+
+**Fecha del snapshot:** 2026-09-11
+**Progreso global del plan de refactor (PR-0 … PR-7):** ~15 %
+
+#### Matriz de PRs
+
+| PR | Título | Estado | Notas |
+|----|--------|--------|-------|
+| PR-0 | Fundamentos y utilidades (`shared/`, eliminar `sqljs-runtime`) | ❌ Pendiente | `sqljs-runtime.ts` sigue existiendo; `CompletedUsage` sigue duplicado en `types.ts` |
+| PR-1 | Capa SQLite | 🟡 Parcial | **Hecho:** repository único expone `getSidebarSnapshot()` desde executor en memoria; `extension.ts` pasa `repository` como reader y battle repo. **Pendiente:** `sqljs-statement-executor`, queries en `vpet-core`, deprecar lectura por archivo en runtime |
+| PR-2 | Capa Cursor — hooks y usage | ❌ Pendiente | Sin `hook-events-reader`, sin `onActivity`, sin caché de token |
+| PR-3 | Paquete `vpet-animation` | ❌ Pendiente | Paquete no creado; duplicación monster-animation intacta en ambos hosts |
+| PR-4 | Artworks Cursor-only (refactor interno) | ❌ Pendiente | Sin carpeta `webview/artwork/`; `evolution-battle-artwork.ts` sigue en 510 líneas |
+| PR-5 | Webview y sidebar — descomposición | 🟡 Parcial | **Hecho:** `evolution-battle-session.ts`, `evolution-reveal-session.ts`, refresh con cola (`refreshInFlight` / `pendingRefresh`), sin recursión. **Pendiente:** presenter, animation-host, orchestrator, escape HTML, provider < 100 líneas |
+| PR-6 | Extension bootstrap | ❌ Pendiente | `extension.ts` sigue en 136 líneas, monolítico |
+| PR-7 | Calidad y architecture boundaries | ❌ Pendiente | Sin `architecture-boundary.test.ts` en cursor-vpet |
+
+#### Trabajo mergeado fuera del plan de refactor
+
+Desde el borrador inicial se incorporó la **feature de batalla de evolución** en ambos hosts (commits `093550d` … `3b91b30`):
+
+| Módulo | `cursor-vpet` | `opencode-vpet` | Duplicación |
+|--------|---------------|-----------------|-------------|
+| `evolution-battle-session.ts` | `webview/` | `tui/` | ~100 % |
+| `evolution-reveal-session.ts` | `webview/` | `tui/` | ~100 % |
+| `evolution-battle-artwork.ts` | `webview/` (510 líneas) | `tui/` (510 líneas) | ~100 % |
+| `evolution-artwork.ts` | `webview/` | `tui/` | Alta |
+| `defeat-artwork.ts` | `webview/` | `tui/` | Alta |
+
+OpenCode ya **no** resuelve batallas solo con toast: `tui.tsx` orquesta `runEvolutionBattleSession` con animación ASCII en sidebar, igual que Cursor con webview.
+
+#### Problemas del inventario §4 — resueltos o mitigados
+
+| ID | Estado | Evidencia |
+|----|--------|-----------|
+| EXT-03 | ✅ Mitigado | `extension.ts` usa un solo `repository` para escritura, snapshot y batalla |
+| EXT-08 | 🟡 Parcial | "Not now" no marca hooks instalados; aún se marca `true` sin verificar éxito de `installVpetHooks` |
+| SQL-04 | 🟡 Parcial | Runtime del sidebar lee del executor en memoria; `readSidebarSnapshot` / paneles siguen abriendo DB en disco |
+| SQL-13 | 🟡 Parcial | Watcher llama `reloadFromDisk()`; snapshot coherente vía mismo executor tras reload |
+| WV-02 | ✅ Mitigado | `refresh()` usa bucle `do…while` + cola en lugar de recursión |
+| WV-01 | 🟡 Parcial | Lógica de batalla/reveal extraída a sessions; provider sigue con 238 líneas y 6+ responsabilidades |
+
+#### Tests actuales (`packages/cursor-vpet/tests/`)
+
+| Archivo | Cubre |
+|---------|-------|
+| `evolution-battle-session.test.ts` | Sesión de batalla (nuevo) |
+| `evolution-reveal-session.test.ts` | Sesión de reveal (nuevo) |
+| `evolution-battle-artwork.test.ts` | Snapshots ASCII batalla |
+| `evolution-artwork.test.ts` | Snapshots ASCII evolución |
+| `defeat-artwork.test.ts` | Snapshots ASCII derrota |
+| `monster-animation.test.ts` | Animación idle (local, pendiente migrar a `vpet-animation`) |
+| `persistence/sqljs-vpet-repository.persistence.test.ts` | Persistencia sql.js |
+| `sidebar-render.test.ts`, `cursor-*`, `database-change-watcher.test.ts` | Render, hooks mapper, watcher |
+
+**Ausentes respecto al plan:** `architecture-boundary`, `sidebar-presenter`, `evolution-battle-orchestrator`, `escape-html`, `hook-events-reader`, tests de `VpetSidebarProvider`.
+
+---
+
+## 2. Contexto y arquitectura actual
+
+### 2.1 Estructura del paquete (31 archivos `.ts` en `src/`)
+
+```
+packages/cursor-vpet/
+├── src/
+│ ├── extension.ts # Composition root (monolítico, 136 líneas)
+│ ├── config/
+│ │ └── extension-settings.ts
+│ ├── adapters/
+│ │ ├── cursor/ # Hooks, API watermark, auth (7 archivos)
+│ │ └── sqlite/ # sql.js driver, readers, watcher (7 archivos)
+│ └── webview/ # Sidebar, artworks, sessions, paneles (14 archivos)
+│ ├── vpet-sidebar-provider.ts
+│ ├── evolution-battle-session.ts # NUEVO — orquestación batalla + reveal/defeat
+│ ├── evolution-reveal-session.ts # NUEVO — animación evolución reutilizable
+│ ├── evolution-battle-artwork.ts
+│ ├── evolution-artwork.ts
+│ ├── defeat-artwork.ts
+│ ├── monster-animation.ts # Pendiente mover a vpet-animation
+│ └── …
+├── tests/ # 11 archivos de test
+├── hook-bridge.js
+├── scripts/build.ts
+└── package.json
+```
+
+### 2.2 Dependencias
+
+| Dependencia | Uso |
+|-------------|-----|
+| `@sbugallo/vpet-core` | Dominio, use cases, view models, catálogo |
+| `sql.js` | SQLite en memoria (VS Code extension, sin native modules) |
+| `vscode` | API de extensión (external en build) |
+
+### 2.3 Flujo de datos actual
+
+```
+Cursor Hooks (hook-bridge.js)
+ → vpet-hook-events.jsonl
+ → cursor-usage-event-source (file watcher)
+ → recordUsage (vpet-core)
+ → sqlite repository (sql.js, writable, única conexión)
+ → database-change-watcher (reloadFromDisk + refresh si no hay presentación)
+ → refresh sidebar
+
+Sidebar refresh (VpetSidebarProvider.refresh):
+ → repository.getSidebarSnapshot() # executor en memoria, sin readFileSync
+ → resolvePendingBattleIfNeeded
+ → runEvolutionBattleSession
+ → runEvolutionBattleAnimation
+ → runEvolutionRevealSession | runDefeatAnimation
+ → resolveEvolutionBattleForPartner
+ → playPendingEvolutionReveal # evolución directa (p. ej. Digitama)
+ → runEvolutionRevealSession
+ → publishSidebarModel
+ → postMessage → webview
+
+Usage con evolución directa (sin batalla):
+ → onApplied callback en extension.ts
+ → sidebarProvider.queueEvolutionReveal(evolution)
+ → refresh → playPendingEvolutionReveal
+
+Animación idle:
+ → MonsterAnimationController (tick 500ms, pausado durante presentación)
+ → renderPositionedArtwork
+ → postMessage { type: "animation-frame" }
+
+Lectura one-shot (paneles Dex/History, tests persistencia):
+ → readArchive / readSidebarSnapshot / createSqliteSidebarSnapshotReader
+ → abre sql.js desde disco en cada llamada # pendiente de eliminar en runtime
+```
+
+### 2.4 Lo que ya está bien
+
+- Dominio delegado correctamente a `vpet-core`.
+- Puertos/adaptadores: `UsageLedger`, `SidebarSnapshotReader`, `EvolutionBattleRepository`.
+- `MonsterAnimationController` como máquina de estados pura.
+- Tests unitarios sólidos en artwork, animación, mappers y persistencia sql.js.
+- Separación `adapters/` vs `webview/`.
+- Repository único en `extension.ts` (escritura + snapshot + batalla).
+- Sesiones de batalla/reveal extraídas (`evolution-battle-session`, `evolution-reveal-session`) con tests propios.
+- Refresh del sidebar con cola y sin recursión; presentaciones bloquean watcher y tick idle.
+- Sincronización multi-ventana de `pet.db` vía `database-change-watcher` + `reloadFromDisk`.
+
+---
+
+## 3. Diferencias entre hosts (OpenCode vs Cursor)
+
+### 3.1 Tabla comparativa tecnológica
+
+| Aspecto | OpenCode (`opencode-vpet`) | Cursor (`cursor-vpet`) |
+|---------|---------------------------|------------------------|
+| **Runtime** | Bun + `bun:sqlite` | Node 24 + `sql.js` (WASM) |
+| **UI** | SolidJS + OpenTUI (`