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:
+28
@@ -0,0 +1,28 @@
|
||||
FROM golang:1.25-bookworm AS builder
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
gcc g++ libsqlite3-dev pkg-config ca-certificates \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
WORKDIR /build
|
||||
COPY . .
|
||||
|
||||
WORKDIR /build/apps/kanban/backend
|
||||
RUN CGO_ENABLED=1 go build -ldflags='-s -w' -o /kanban .
|
||||
|
||||
# --- Runtime ---
|
||||
FROM debian:bookworm-slim
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
libsqlite3-0 libstdc++6 ca-certificates tzdata \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY --from=builder /kanban /usr/local/bin/kanban
|
||||
|
||||
WORKDIR /data
|
||||
VOLUME /data
|
||||
|
||||
EXPOSE 8095
|
||||
|
||||
ENTRYPOINT ["/usr/local/bin/kanban"]
|
||||
CMD ["--port", "8095", "--db", "/data/operations.db"]
|
||||
@@ -43,6 +43,32 @@ uses_types:
|
||||
framework: "net/http + vite + react + mantine + dnd-kit"
|
||||
entry_point: "backend/main.go"
|
||||
dir_path: "apps/kanban"
|
||||
|
||||
# Validacion end-to-end (fase 4 del bucle reactivo). Ver issue 0068.
|
||||
e2e_checks:
|
||||
- id: build_frontend
|
||||
cmd: "cd frontend && pnpm install --frozen-lockfile && pnpm build"
|
||||
timeout_s: 180
|
||||
expect_exit: 0
|
||||
- id: build_backend
|
||||
cmd: "CGO_ENABLED=1 go build -tags fts5 -o kanban ."
|
||||
timeout_s: 120
|
||||
expect_exit: 0
|
||||
- id: migrations_apply
|
||||
cmd: "rm -f /tmp/kanban_e2e.db && ./kanban --port 0 --db /tmp/kanban_e2e.db --migrate-only"
|
||||
timeout_s: 15
|
||||
expect_exit: 0
|
||||
- id: migrations_schema
|
||||
cmd: "sqlite3 /tmp/kanban_e2e.db 'SELECT version FROM schema_migrations ORDER BY version;'"
|
||||
expect_stdout_contains: "1"
|
||||
- id: smoke_api
|
||||
cmd: "./kanban --port 8195 --db /tmp/kanban_e2e.db &"
|
||||
health: "http://127.0.0.1:8195/api/board"
|
||||
timeout_s: 10
|
||||
- id: tests_go
|
||||
cmd: "go test -tags fts5 -count=1 ./..."
|
||||
timeout_s: 120
|
||||
expect_exit: 0
|
||||
---
|
||||
|
||||
## Arquitectura
|
||||
|
||||
+38
-9
@@ -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)
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
name: kanban
|
||||
|
||||
services:
|
||||
kanban:
|
||||
build:
|
||||
context: ../../
|
||||
dockerfile: apps/kanban/Dockerfile
|
||||
container_name: kanban
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "8095:8095"
|
||||
volumes:
|
||||
- kanban_data:/data
|
||||
networks:
|
||||
- coolify
|
||||
|
||||
volumes:
|
||||
kanban_data:
|
||||
|
||||
networks:
|
||||
coolify:
|
||||
external: true
|
||||
@@ -0,0 +1,37 @@
|
||||
http:
|
||||
routers:
|
||||
kanban-http:
|
||||
rule: "Host(`kanban.organic-machine.com`)"
|
||||
entryPoints:
|
||||
- "http"
|
||||
middlewares:
|
||||
- "kanban-redirect"
|
||||
service: "kanban-service"
|
||||
|
||||
kanban-https:
|
||||
rule: "Host(`kanban.organic-machine.com`)"
|
||||
entryPoints:
|
||||
- "https"
|
||||
middlewares:
|
||||
- "kanban-auth"
|
||||
- "kanban-gzip"
|
||||
service: "kanban-service"
|
||||
tls:
|
||||
certResolver: letsencrypt
|
||||
|
||||
services:
|
||||
kanban-service:
|
||||
loadBalancer:
|
||||
servers:
|
||||
- url: "http://kanban:8095"
|
||||
|
||||
middlewares:
|
||||
kanban-redirect:
|
||||
redirectScheme:
|
||||
scheme: "https"
|
||||
kanban-auth:
|
||||
basicAuth:
|
||||
users:
|
||||
- "lucas:$2y$05$67qHI0i2NiSbVC40gJvqHOb28PKgkNiKYtsdJEEgw3FxT4j4NQcrG"
|
||||
kanban-gzip:
|
||||
compress: true
|
||||
Reference in New Issue
Block a user