126 lines
7.6 KiB
Markdown
126 lines
7.6 KiB
Markdown
# Skill global: cargar contexto de Docmost al iniciar conversación
|
|
|
|
## Contexto
|
|
|
|
El usuario quiere que, al iniciar cualquier conversación de Claude Code, se cargue automáticamente el
|
|
contexto del proyecto actual desde Docmost (MCP `mcp__docmost__*`): listar los spaces, identificar cuál
|
|
corresponde al proyecto en el que se está trabajando, y leer el contenido completo de todas sus páginas.
|
|
|
|
Investigación realizada (en modo solo-lectura):
|
|
- No existe ningún CLAUDE.md global ni hook `SessionStart` hoy en `~/.claude/settings.json` (solo hay un
|
|
hook `Notification` para email vía Gmail).
|
|
- Las **skills se activan por coincidencia de descripción**, no automáticamente al iniciar sesión. Para que
|
|
algo corra *siempre* al inicio, el único mecanismo real es un **hook `SessionStart`**, cuyo `stdout` se
|
|
inyecta como contexto adicional (esto es lo que documenta `hooks-guide.md` de Claude Code, ej. "Re-inject
|
|
context after compaction"). El hook (bash) no puede llamar herramientas MCP directamente — eso solo lo
|
|
puede hacer el modelo. Por eso el diseño es: **hook (bash) → inyecta instrucción → skill (markdown) →
|
|
modelo ejecuta las llamadas MCP reales**.
|
|
- Ya existe en `background-orchestrator/SKILL.md` una lógica de detección de proyecto reutilizable
|
|
(`package.json` → `pyproject.toml` → basename del repo git), que se reutiliza aquí tal cual.
|
|
- Verifiqué en vivo el MCP de Docmost: `list_spaces` devuelve `{id, name, slug, description, ...}`;
|
|
`list_pages({spaceId})` devuelve el árbol de páginas (`id, title, parentPageId`) sin contenido;
|
|
`get_page({pageId})` sí devuelve el contenido completo (`content`, markdown). Los nombres de space no
|
|
siempre calzan literal con el nombre del repo (ej. space "Create React SSR App" vs. un repo en
|
|
kebab-case), así que el match debe ser normalizado (minúsculas, sin espacios/guiones), no exacto.
|
|
Algunos spaces ya tienen 30+ páginas con contenido extenso.
|
|
|
|
Decisiones confirmadas con el usuario:
|
|
1. **Leer todo siempre** — al detectar el space, leer el contenido completo de todas sus páginas (sin
|
|
recortar ni resumir antes de cargar).
|
|
2. **Si no hay match** — preguntar al usuario (mostrar los spaces disponibles y dejar que elija uno o
|
|
indique que no corresponde ninguno).
|
|
3. **Alcance** — el hook solo actúa si el cwd está dentro de un repo git; fuera de un repo no intenta nada.
|
|
|
|
## Diseño
|
|
|
|
### 1. Script del hook — `~/.claude/hooks/docmost-session-start.sh` (nuevo, `chmod +x`)
|
|
|
|
Bash puro, sin llamadas MCP (no puede hacerlas). Solo detecta el proyecto y, si aplica, imprime a stdout
|
|
una instrucción para que el modelo invoque la skill. Si no está en un repo git, no imprime nada (`exit 0`
|
|
silencioso → no se inyecta contexto ni se gasta turno en esto).
|
|
|
|
```bash
|
|
#!/bin/bash
|
|
# Solo actuar dentro de un repo git (decisión confirmada con el usuario)
|
|
git rev-parse --show-toplevel >/dev/null 2>&1 || exit 0
|
|
|
|
PROJECT_NAME=$(jq -r '.name // empty' package.json 2>/dev/null)
|
|
[ -z "$PROJECT_NAME" ] && PROJECT_NAME=$(grep -oP '^\s*name\s*=\s*"\K[^"]+' pyproject.toml 2>/dev/null | head -1)
|
|
[ -z "$PROJECT_NAME" ] && PROJECT_NAME=$(basename "$(git rev-parse --show-toplevel)")
|
|
|
|
cat <<EOF
|
|
[docmost-context] Nueva conversación detectada en el proyecto git "$PROJECT_NAME" ($(pwd)).
|
|
Antes de responder al primer mensaje del usuario, invoca la skill "docmost-context" (Skill tool,
|
|
skill: docmost-context, args: "$PROJECT_NAME") para cargar el contexto del proyecto desde Docmost.
|
|
Hazlo una sola vez al inicio de esta conversación; no la vuelvas a invocar en turnos posteriores salvo
|
|
que el usuario lo pida explícitamente.
|
|
EOF
|
|
```
|
|
|
|
### 2. Registrar el hook en `~/.claude/settings.json` (editar)
|
|
|
|
Añadir la clave `SessionStart` dentro de `hooks` (se conserva el `Notification` existente). Se registran
|
|
tres matchers por separado (`startup`, `resume`, `clear`) — deliberadamente **sin** `compact`, para no
|
|
releer 30+ páginas completas cada vez que el contexto se compacta a mitad de conversación:
|
|
|
|
```json
|
|
"SessionStart": [
|
|
{ "matcher": "startup", "hooks": [{ "type": "command", "command": "/home/aleleba/.claude/hooks/docmost-session-start.sh" }] },
|
|
{ "matcher": "resume", "hooks": [{ "type": "command", "command": "/home/aleleba/.claude/hooks/docmost-session-start.sh" }] },
|
|
{ "matcher": "clear", "hooks": [{ "type": "command", "command": "/home/aleleba/.claude/hooks/docmost-session-start.sh" }] }
|
|
]
|
|
```
|
|
|
|
También añadir a `permissions.allow`: `"mcp__docmost__list_spaces"` (los demás —`list_pages`, `get_page`,
|
|
`search`— ya están permitidos desde `background-orchestrator`).
|
|
|
|
### 3. Skill global — `~/.claude/skills/docmost-context/SKILL.md` (nuevo)
|
|
|
|
```yaml
|
|
---
|
|
name: docmost-context
|
|
description: >-
|
|
Carga el contexto del proyecto actual desde Docmost (MCP) al inicio de una conversación: lista los
|
|
spaces, identifica cuál corresponde al proyecto del directorio actual, y lee el contenido completo
|
|
de todas sus páginas. Se activa automáticamente vía el hook SessionStart (ver
|
|
~/.claude/hooks/docmost-session-start.sh) — no requiere que el usuario la invoque a mano, aunque
|
|
también responde a "carga el contexto de Docmost", "lee el proyecto en Docmost", "/docmost-context".
|
|
effort: medium
|
|
argument-hint: "[nombre del proyecto, si ya se conoce]"
|
|
---
|
|
```
|
|
|
|
Cuerpo (workflow):
|
|
1. **Determinar el nombre del proyecto**: usar el argumento recibido si viene; si no, repetir la misma
|
|
detección de `background-orchestrator` (`package.json` → `pyproject.toml` → basename del repo git).
|
|
2. **Listar spaces**: `mcp__docmost__list_spaces`.
|
|
3. **Buscar coincidencia** comparando el nombre del proyecto contra `name` y `slug` de cada space,
|
|
normalizando (minúsculas, sin espacios/guiones/guiones bajos) — match exacto normalizado, no
|
|
substring débil.
|
|
- **Una sola coincidencia** → usarla directamente, sin confirmar con el usuario.
|
|
- **Cero coincidencias** → usar `AskUserQuestion` mostrando la lista de spaces disponibles, dejando
|
|
elegir uno existente o responder que ninguno corresponde (en ese caso, no cargar nada y continuar
|
|
la conversación normalmente).
|
|
- **Varias coincidencias ambiguas** → preguntar igual, mostrando solo los candidatos ambiguos.
|
|
4. **Leer el space completo**: `mcp__docmost__list_pages({spaceId})` para obtener todas las páginas, y
|
|
luego `mcp__docmost__get_page({pageId})` para **cada una** (contenido completo, sin omitir ninguna).
|
|
5. **No repetir el dump al usuario**: una vez cargado el contenido al contexto, responder con un mensaje
|
|
breve (1-2 líneas) indicando qué space y cuántas páginas se cargaron — no transcribir el contenido de
|
|
las páginas en el chat.
|
|
6. Ejecutar esto **una sola vez** por conversación (al primer turno, disparado por el hook); no repetir
|
|
en turnos siguientes salvo pedido explícito del usuario.
|
|
|
|
## Verificación
|
|
|
|
1. `chmod +x ~/.claude/hooks/docmost-session-start.sh` y ejecutarlo manualmente dentro de un repo git y
|
|
fuera de uno, confirmando que imprime la instrucción solo en el primer caso.
|
|
2. Iniciar una conversación nueva (`startup`) dentro de un repo con space conocido (ej. `Ro-ut`) y
|
|
confirmar que el modelo invoca la skill sin que el usuario lo pida, y que reporta el space/páginas
|
|
cargadas en 1-2 líneas.
|
|
3. Probar dentro de un repo **sin** space coincidente y confirmar que se dispara `AskUserQuestion` con la
|
|
lista de spaces.
|
|
4. Si en la prueba real el `stdout` plano del hook no llega como contexto, usar como fallback el formato
|
|
JSON documentado: `{"hookSpecificOutput": {"hookEventName": "SessionStart", "additionalContext": "..."}}`.
|
|
5. Confirmar que `/clear` también dispara la carga, y que `compact` (compactación automática) **no** la
|
|
repite.
|