Files
egutierrez 2a3d780347 feat(doctor): add fn doctor CLI + 14 functions for system management
Adds `fn doctor` read-only diagnostic command with subcommands artefacts,
services, sync, uses-functions, unused, and --json flag for agents.
Each subcommand wraps a registry function in functions/infra/.

New functions:
- artefact_doctor, services_status, pc_locations_drift,
  audit_uses_functions, find_unused_functions (Go diagnostics)
- backup_sqlite_db, rotate_backups, wait_for_http, wait_for_port,
  port_kill, tail_journal, pre_commit_hook_install (bash utilities)
- notify_telegram (Go HTTP)
- backup_all pipeline (tag launcher)

Plus prior session leftovers (scan_secrets_in_dirty, append_diary_entry,
git utilities, http_session_cookie_middleware, compile/full-git pipelines).

Fixes pc_locations_drift filepath.Join bug with absolute dir_path.
Documents fn doctor in CLAUDE.md, .claude/rules/fn_doctor.md (rule 23),
docs/architecture.md, CHANGELOG.md (2026-05-07), and diary entry.

First fn doctor uses-functions run found drift in 7/12 apps (deuda
para sincronizar app.md con imports reales).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 01:42:10 +02:00

80 lines
2.7 KiB
Bash

#!/usr/bin/env bash
# append_diary_entry — Añade una entrada al diario del dia en docs/diary/YYYY-MM-DD.md
append_diary_entry() {
local titulo="$1"
local cuerpo="${2:-}"
if [[ -z "$titulo" ]]; then
echo "append_diary_entry: titulo requerido" >&2
return 2
fi
local date time diary_dir diary_file
date=$(date +%Y-%m-%d)
time=$(date +%H:%M)
diary_dir="${DIARY_DIR:-docs/diary}"
diary_file="${diary_dir}/${date}.md"
# Crear directorio y cabecera del dia si el archivo no existe
if [[ ! -f "$diary_file" ]]; then
mkdir -p "$diary_dir"
printf '# %s\n' "$date" > "$diary_file"
fi
# Componer bloque de entrada
if [[ -n "$cuerpo" ]]; then
printf '\n## %s — %s\n\n%s\n' "$time" "$titulo" "$cuerpo" >> "$diary_file"
else
printf '\n## %s — %s\n' "$time" "$titulo" >> "$diary_file"
fi
echo "$diary_file"
}
# ---------------------------------------------------------------------------
# Self-test: bash bash/functions/infra/append_diary_entry.sh --test
# ---------------------------------------------------------------------------
if [ "${BASH_SOURCE[0]}" = "$0" ] && [ "${1:-}" = "--test" ]; then
set -euo pipefail
declare -i PASS=0 FAIL=0
assert_contains() {
local test_name="$1" pattern="$2" file="$3"
if grep -qF "$pattern" "$file"; then
echo "PASS: $test_name"
PASS+=1
else
echo "FAIL: $test_name — patron '$pattern' no encontrado en $file"
FAIL+=1
fi
}
# Setup: directorio temporal
tmpdir=$(mktemp -d)
export DIARY_DIR="$tmpdir/diary"
# Test 1: crear archivo nuevo con titulo y cuerpo
diary_file=$(append_diary_entry "primer bloque" "Hecho algo importante hoy.")
assert_contains "crear archivo nuevo con titulo y cuerpo" "primer bloque" "$diary_file"
assert_contains "crear archivo nuevo con titulo y cuerpo" "# $(date +%Y-%m-%d)" "$diary_file"
assert_contains "crear archivo nuevo con titulo y cuerpo" "Hecho algo importante hoy." "$diary_file"
# Test 2: append en el mismo dia sin reescribir contenido previo
append_diary_entry "segundo bloque" "Otro parrafo de trabajo." > /dev/null
assert_contains "append mismo dia conserva primera entrada" "primer bloque" "$diary_file"
assert_contains "append mismo dia anade segunda entrada" "segundo bloque" "$diary_file"
# Test 3: entrada sin cuerpo (solo header)
append_diary_entry "solo titulo sin cuerpo" "" > /dev/null
assert_contains "entrada sin cuerpo escribe header" "solo titulo sin cuerpo" "$diary_file"
# Cleanup
rm -rf "$tmpdir"
echo "---"
echo "Results: $PASS passed, $FAIL failed"
[[ $FAIL -eq 0 ]] || exit 1
fi