diff --git a/app.md b/app.md index f3dfc95..d08151c 100644 --- a/app.md +++ b/app.md @@ -2,6 +2,7 @@ name: call_monitor lang: go domain: infra +version: 0.1.0 description: "Telemetria de invocaciones del agente al fn_registry. Persiste eventos (calls, code_writes, test_runs, e2e_runs_fn, violations, patterns, sessions) en su propia operations.db. Vista agregada function_stats por function_id alimenta el bucle reactivo (proposals automaticas). Issue 0085." tags: [service, telemetry, monitoring, registry, sqlite] uses_functions: @@ -14,6 +15,20 @@ framework: "stdlib" entry_point: "main.go" dir_path: "projects/fn_monitoring/apps/call_monitor" repo_url: "" +service: + port: null + health_endpoint: null + health_timeout_s: 3 + systemd_unit: call_monitor.service + systemd_scope: user + restart_policy: always + runtime: systemd-user + pc_targets: + - aurgi-pc + - home-wsl + is_local_only: true +# Runtime: subcomando `daemon` mantiene loop cada `--interval` (default 5m) +# llamando snapshot + sequences --detect --propose. Ver daemon.go. e2e_checks: - id: build cmd: "CGO_ENABLED=1 go build -tags fts5 -o call_monitor ." @@ -143,3 +158,13 @@ Para correr manualmente fuera del schedule: `systemctl --user start call_monitor - BD vive **junto al binario** (`/operations.db`) por defecto, no en el cwd del agente. Hook puede pasar `--db` explicito si conviene. - `operations.db` gitignored — telemetria es local por PC, no se sincroniza. - Sin `repo_url`: aun no se ha hecho `gitea_create_repo`. Se inicializara con `/full-git-push` cuando este la fase 0085b lista para evitar repos vacios. + + +## Capability growth log + +Una linea por bump SemVer. Bump-type segun `.claude/commands/version.md`: +- `major`: breaking observable (CLI args, schema BBDD propia, formato wire). +- `minor`: feature aditiva (nuevo panel, endpoint, opcion). +- `patch`: bugfix sin cambio observable. + +- v0.1.0 (2026-05-18) — baseline. diff --git a/call_monitor b/call_monitor index f00fce3..a05753d 100755 Binary files a/call_monitor and b/call_monitor differ diff --git a/daemon.go b/daemon.go new file mode 100644 index 0000000..204400e --- /dev/null +++ b/daemon.go @@ -0,0 +1,71 @@ +package main + +import ( + "context" + "log" + "os" + "os/signal" + "syscall" + "time" +) + +// runDaemon mantiene call_monitor vivo como systemd service. Cada `interval` +// ejecuta los pasos batch en orden (snapshot + sequences --detect --propose). +// Idempotente: cada uno se salta trabajo ya hecho. Errores se loguean y no +// abortan el daemon (siguiente tick lo reintenta). +func runDaemon(dbPath, registryRoot string, interval time.Duration) { + log.Printf("call_monitor daemon: db=%s registry=%s interval=%s", + dbPath, registryRoot, interval) + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + // Signal handling — clean exit on SIGTERM/SIGINT. + sigCh := make(chan os.Signal, 1) + signal.Notify(sigCh, syscall.SIGTERM, syscall.SIGINT) + go func() { + s := <-sigCh + log.Printf("call_monitor daemon: received %s, shutting down", s) + cancel() + }() + + // Initial cycle immediately, then on interval. + runCycleSafe(dbPath, registryRoot) + + tick := time.NewTicker(interval) + defer tick.Stop() + + for { + select { + case <-ctx.Done(): + log.Printf("call_monitor daemon: stopped") + return + case <-tick.C: + runCycleSafe(dbPath, registryRoot) + } + } +} + +func runCycleSafe(dbPath, registryRoot string) { + defer func() { + if r := recover(); r != nil { + log.Printf("call_monitor daemon: cycle panic recovered: %v", r) + } + }() + + // Snapshot: registra (function_id, content_hash) en function_versions. + log.Printf("call_monitor daemon: cycle start") + runSnapshot(dbPath, registryRoot) + + // Sequences detection: persiste candidatas + proposals new_pipeline. + cfg := SequenceConfig{ + WindowSecs: 30, + LookbackDays: 30, + MinOccurrences: 5, + MinSessions: 2, + MinSuccessRate: 0.9, + } + runSequences(dbPath, true, false, true, false, registryRoot, cfg) + + log.Printf("call_monitor daemon: cycle done") +} diff --git a/main.go b/main.go index 7b0e0c0..316eb89 100644 --- a/main.go +++ b/main.go @@ -9,6 +9,7 @@ import ( "os" "path/filepath" "text/tabwriter" + "time" ) const defaultDBName = "operations.db" @@ -75,6 +76,11 @@ func main() { LookbackDays: *lookback, Tools: tools, }, *persist, *formatJSON) + case "daemon": + registry := fs.String("registry", "", "Path to registry.db (default: walk up from cwd until found).") + interval := fs.Duration("interval", 5*time.Minute, "How often to run snapshot+sequences cycle.") + fs.Parse(os.Args[2:]) + runDaemon(resolveDB(*dbPath), *registry, *interval) case "-h", "--help", "help": usage() default: diff --git a/operations.db b/operations.db index 139310b..d5f20ed 100644 Binary files a/operations.db and b/operations.db differ diff --git a/operations.db-shm b/operations.db-shm index 63dc174..61a4871 100644 Binary files a/operations.db-shm and b/operations.db-shm differ diff --git a/operations.db-wal b/operations.db-wal index 50d4e09..aa13b9d 100644 Binary files a/operations.db-wal and b/operations.db-wal differ