chore: auto-commit (5 archivos)

- app.md
- backend/chat.go
- Dockerfile
- docker-compose.yml
- traefik-dynamic.yml

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-09 18:11:24 +02:00
parent ce807ec2ee
commit f1ee116d3b
5 changed files with 151 additions and 9 deletions
+38 -9
View File
@@ -15,18 +15,17 @@ import (
"nhooyr.io/websocket"
)
const chatSystemPrompt = `Eres el asistente del tablero kanban. Responde al usuario y, cuando pida cambios, modifica el tablero llamando a tools nativas (MCP).
const chatSystemPrompt = `Asistente del tablero kanban. Modifica el tablero llamando a tools MCP cuando el usuario pida cambios. Responde texto en markdown cuando solo informe.
Tools disponibles via MCP server "kanban":
- list_board / find_cards / card_history / list_users — lectura
- create_column / update_column / delete_column / reorder_columns — columnas
- create_card / update_card / delete_card / move_card / assign_card — tarjetas
Tools (MCP server "kanban"):
- Lectura: list_board, find_cards, card_history, list_users
- Columnas: create_column, update_column, delete_column, reorder_columns
- Tarjetas: create_card, update_card, delete_card, move_card, assign_card
Llama directamente a las tools cuando necesites mutar el tablero. Usa list_board al principio si necesitas resolver nombres a IDs. NUNCA inventes IDs.
El estado actual del tablero viene en <board_state> al final del mensaje. Usa esos IDs directamente — NO llames list_board si ya tienes lo que necesitas. NUNCA inventes IDs.
Cuando termines, responde texto natural en markdown (sin llamadas extra) — eso señala el fin de la conversacion.`
Cuando termines, responde texto natural sin mas llamadas — eso cierra la conversacion.`
const claudeModel = "claude-sonnet-4-6"
const claudeTimeout = 300 * time.Second
func claudeBinary() string {
@@ -36,6 +35,13 @@ func claudeBinary() string {
return "claude"
}
func claudeModel() string {
if m := os.Getenv("KANBAN_CLAUDE_MODEL"); m != "" {
return m
}
return "claude-haiku-4-5-20251001"
}
type chatMessage struct {
Role string `json:"role"`
Content string `json:"content"`
@@ -125,13 +131,18 @@ func streamChat(ctx context.Context, conn *websocket.Conn, db *DB, workdir, toke
defer os.Remove(mcpPath)
prompt := flattenMessages(msgs)
if board, err := boardSnapshot(db); err == nil && board != "" {
prompt += "\n\n<board_state>\n" + board + "\n</board_state>\n"
}
stdin := strings.NewReader(prompt)
events, err := core.StreamClaude(ctx, core.ClaudeStreamOpts{
Bin: claudeBinary(),
Args: []string{
"--model", claudeModel,
"--model", claudeModel(),
"--no-session-persistence",
"--mcp-config", mcpPath,
"--strict-mcp-config",
"--system-prompt", chatSystemPrompt,
"--allowedTools",
"mcp__kanban__list_board,mcp__kanban__create_column,mcp__kanban__update_column,mcp__kanban__rename_column,mcp__kanban__delete_column,mcp__kanban__reorder_columns,mcp__kanban__create_card,mcp__kanban__update_card,mcp__kanban__delete_card,mcp__kanban__move_card,mcp__kanban__card_history,mcp__kanban__find_cards,mcp__kanban__list_users,mcp__kanban__assign_card",
@@ -215,6 +226,24 @@ func flattenMessages(msgs []chatMessage) string {
return b.String()
}
// boardSnapshot returns a JSON dump of columns + cards to inject in the
// initial prompt, saving a list_board round-trip.
func boardSnapshot(db *DB) (string, error) {
cols, err := db.ListColumns()
if err != nil {
return "", err
}
cards, err := db.ListCardsWithTime()
if err != nil {
return "", err
}
b, err := json.Marshal(map[string]any{"columns": cols, "cards": cards})
if err != nil {
return "", err
}
return string(b), nil
}
// chatWorkdir resolves an absolute working directory for `claude -p`.
func chatWorkdir(dbPath string) string {
abs, err := filepath.Abs(dbPath)