2a3d780347
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>
313 lines
10 KiB
Bash
313 lines
10 KiB
Bash
#!/usr/bin/env bash
|
|
# tbd_branch_create — crea rama TBD desde master/main actualizado
|
|
#
|
|
# Uso:
|
|
# tbd_branch_create issue <NNNN> <slug>
|
|
# tbd_branch_create quick <slug>
|
|
#
|
|
# Opciones especiales:
|
|
# --test ejecutar suite de tests internos y salir
|
|
|
|
set -euo pipefail
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# helpers
|
|
# ---------------------------------------------------------------------------
|
|
|
|
_tbd_detect_base() {
|
|
# Retorna 'master' o 'main' — el primero que exista localmente
|
|
if git show-ref --verify --quiet refs/heads/master 2>/dev/null; then
|
|
echo "master"
|
|
elif git show-ref --verify --quiet refs/heads/main 2>/dev/null; then
|
|
echo "main"
|
|
else
|
|
echo ""
|
|
fi
|
|
}
|
|
|
|
_tbd_require_git_repo() {
|
|
if ! git rev-parse --git-dir > /dev/null 2>&1; then
|
|
echo "ERROR: el directorio actual no es un repositorio git." >&2
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
_tbd_pull_rebase() {
|
|
# Hace pull --rebase solo si hay upstream configurado; si no, es no-op.
|
|
local has_upstream
|
|
has_upstream=$(git rev-parse --abbrev-ref --symbolic-full-name '@{u}' 2>/dev/null || echo "")
|
|
if [[ -n "$has_upstream" ]]; then
|
|
git pull --rebase
|
|
else
|
|
echo "(sin remote upstream — saltando pull)"
|
|
fi
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# funcion principal
|
|
# ---------------------------------------------------------------------------
|
|
|
|
tbd_branch_create() {
|
|
local mode="${1:-}"
|
|
|
|
if [[ -z "$mode" ]] || [[ "$mode" != "issue" && "$mode" != "quick" ]]; then
|
|
echo "Uso: tbd_branch_create issue <NNNN> <slug>" >&2
|
|
echo " tbd_branch_create quick <slug>" >&2
|
|
return 1
|
|
fi
|
|
|
|
# Verificar repo git
|
|
_tbd_require_git_repo
|
|
|
|
# Detectar rama base
|
|
local base
|
|
base=$(_tbd_detect_base)
|
|
if [[ -z "$base" ]]; then
|
|
echo "ERROR: no se encontro rama 'master' ni 'main' en el repo local." >&2
|
|
return 1
|
|
fi
|
|
|
|
# Construir nombre de rama segun modo
|
|
local branch_name
|
|
if [[ "$mode" == "issue" ]]; then
|
|
local num="${2:-}"
|
|
local slug="${3:-}"
|
|
if [[ -z "$num" || -z "$slug" ]]; then
|
|
echo "Uso: tbd_branch_create issue <NNNN> <slug>" >&2
|
|
return 1
|
|
fi
|
|
if [[ ! "$num" =~ ^[0-9]{4}$ ]]; then
|
|
echo "ERROR: <NNNN> debe ser exactamente 4 digitos numericos (ej: 0042)." >&2
|
|
return 1
|
|
fi
|
|
if [[ ! "$slug" =~ ^[a-z0-9][a-z0-9-]*$ ]]; then
|
|
echo "ERROR: slug debe ser kebab-case ASCII (ej: fix-typo, add-auth)." >&2
|
|
return 1
|
|
fi
|
|
branch_name="issue/${num}-${slug}"
|
|
else
|
|
# quick
|
|
local slug="${2:-}"
|
|
if [[ -z "$slug" ]]; then
|
|
echo "Uso: tbd_branch_create quick <slug>" >&2
|
|
return 1
|
|
fi
|
|
if [[ ! "$slug" =~ ^[a-z0-9][a-z0-9-]*$ ]]; then
|
|
echo "ERROR: slug debe ser kebab-case ASCII (ej: fix-typo, update-readme)." >&2
|
|
return 1
|
|
fi
|
|
branch_name="quick/${slug}"
|
|
fi
|
|
|
|
# Verificar si la rama ya existe
|
|
if git show-ref --verify --quiet "refs/heads/${branch_name}" 2>/dev/null; then
|
|
echo "ERROR: la rama '${branch_name}' ya existe localmente." >&2
|
|
return 1
|
|
fi
|
|
|
|
# Asegurarse de estar en la rama base
|
|
local current
|
|
current=$(git branch --show-current)
|
|
if [[ "$current" != "$base" ]]; then
|
|
echo "Cambiando a ${base}..."
|
|
git checkout "$base"
|
|
fi
|
|
|
|
# Verificar working tree limpio
|
|
local dirty
|
|
dirty=$(git status --porcelain)
|
|
if [[ -n "$dirty" ]]; then
|
|
echo "ERROR: working tree dirty. Commit o stash los cambios antes de crear la rama." >&2
|
|
return 1
|
|
fi
|
|
|
|
# Actualizar base desde remote si hay upstream
|
|
echo "Actualizando ${base}..."
|
|
_tbd_pull_rebase
|
|
|
|
# Crear la rama
|
|
git checkout -b "$branch_name"
|
|
|
|
echo "Rama '${branch_name}' creada desde ${base} actualizada."
|
|
return 0
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# modo --test
|
|
# ---------------------------------------------------------------------------
|
|
|
|
_tbd_branch_create_tests() {
|
|
local PASS=0
|
|
local FAIL=0
|
|
|
|
_assert_eq() {
|
|
local name="$1" expected="$2" got="$3"
|
|
if [[ "$expected" == "$got" ]]; then
|
|
echo "PASS: $name"
|
|
PASS=$((PASS + 1))
|
|
else
|
|
echo "FAIL: $name — expected '${expected}', got '${got}'"
|
|
FAIL=$((FAIL + 1))
|
|
fi
|
|
}
|
|
|
|
_assert_exit() {
|
|
local name="$1" expected_exit="$2"
|
|
shift 2
|
|
local got_exit=0
|
|
"$@" > /dev/null 2>&1 || got_exit=$?
|
|
if [[ "$expected_exit" == "$got_exit" ]]; then
|
|
echo "PASS: $name (exit ${got_exit})"
|
|
PASS=$((PASS + 1))
|
|
else
|
|
echo "FAIL: $name — expected exit ${expected_exit}, got ${got_exit}"
|
|
FAIL=$((FAIL + 1))
|
|
fi
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Setup: bare remote + repo clonado (master)
|
|
# ---------------------------------------------------------------------------
|
|
local tmproot
|
|
tmproot=$(mktemp -d)
|
|
|
|
local bare="$tmproot/remote.git"
|
|
local work="$tmproot/work"
|
|
|
|
git -c init.defaultBranch=master init --bare -q "$bare"
|
|
git clone -q "$bare" "$work"
|
|
(
|
|
cd "$work"
|
|
git config user.email "test@test.com"
|
|
git config user.name "Test"
|
|
echo "init" > README.md
|
|
git add README.md
|
|
git commit -q -m "chore: init"
|
|
git push -q -u origin master
|
|
)
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Test: rama issue valida
|
|
# ---------------------------------------------------------------------------
|
|
(
|
|
cd "$work"
|
|
tbd_branch_create issue 0042 add-auth
|
|
) > /dev/null 2>&1
|
|
local result
|
|
result=$(cd "$work" && git branch --show-current)
|
|
_assert_eq "issue branch created" "issue/0042-add-auth" "$result"
|
|
|
|
# Volver a master
|
|
(cd "$work" && git checkout -q master)
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Test: rama quick valida
|
|
# ---------------------------------------------------------------------------
|
|
(
|
|
cd "$work"
|
|
tbd_branch_create quick fix-typo
|
|
) > /dev/null 2>&1
|
|
result=$(cd "$work" && git branch --show-current)
|
|
_assert_eq "quick branch created" "quick/fix-typo" "$result"
|
|
|
|
(cd "$work" && git checkout -q master)
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Test: numero de issue invalido (3 digitos)
|
|
# ---------------------------------------------------------------------------
|
|
_assert_exit "issue number must be 4 digits" 1 bash -c "
|
|
cd '$work'
|
|
source '${BASH_SOURCE[0]}'
|
|
tbd_branch_create issue 042 fix
|
|
"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Test: slug invalido (mayusculas)
|
|
# ---------------------------------------------------------------------------
|
|
_assert_exit "slug must be kebab-case" 1 bash -c "
|
|
cd '$work'
|
|
source '${BASH_SOURCE[0]}'
|
|
tbd_branch_create issue 0001 Fix-Typo
|
|
"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Test: modo invalido
|
|
# ---------------------------------------------------------------------------
|
|
_assert_exit "invalid mode exits 1" 1 bash -c "
|
|
cd '$work'
|
|
source '${BASH_SOURCE[0]}'
|
|
tbd_branch_create hotfix my-slug
|
|
"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Test: sin argumentos
|
|
# ---------------------------------------------------------------------------
|
|
_assert_exit "no args exits 1" 1 bash -c "
|
|
cd '$work'
|
|
source '${BASH_SOURCE[0]}'
|
|
tbd_branch_create
|
|
"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Test: working tree dirty → error
|
|
# ---------------------------------------------------------------------------
|
|
echo "dirty" > "$work/dirty.txt"
|
|
_assert_exit "dirty tree exits 1" 1 bash -c "
|
|
cd '$work'
|
|
source '${BASH_SOURCE[0]}'
|
|
tbd_branch_create quick clean-slug
|
|
"
|
|
rm -f "$work/dirty.txt"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Test: rama ya existe → error
|
|
# ---------------------------------------------------------------------------
|
|
(cd "$work" && git checkout -q -b issue/0099-existing && git checkout -q master)
|
|
_assert_exit "existing branch exits 1" 1 bash -c "
|
|
cd '$work'
|
|
source '${BASH_SOURCE[0]}'
|
|
tbd_branch_create issue 0099 existing
|
|
"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Test: funciona con 'main' como rama base
|
|
# ---------------------------------------------------------------------------
|
|
local bare2="$tmproot/remote2.git"
|
|
local work2="$tmproot/work2"
|
|
git -c init.defaultBranch=main init --bare -q "$bare2"
|
|
git clone -q "$bare2" "$work2"
|
|
(
|
|
cd "$work2"
|
|
git config user.email "test@test.com"
|
|
git config user.name "Test"
|
|
echo "init" > README.md
|
|
git add README.md
|
|
git commit -q -m "chore: init"
|
|
git push -q -u origin main
|
|
tbd_branch_create quick use-main
|
|
) > /dev/null 2>&1
|
|
result=$(cd "$work2" && git branch --show-current)
|
|
_assert_eq "works with main as base" "quick/use-main" "$result"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Cleanup y resultado
|
|
# ---------------------------------------------------------------------------
|
|
rm -rf "$tmproot"
|
|
|
|
echo "---"
|
|
echo "Results: ${PASS} passed, ${FAIL} failed"
|
|
[[ $FAIL -eq 0 ]] || exit 1
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# entry point
|
|
# ---------------------------------------------------------------------------
|
|
|
|
if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
|
|
if [[ "${1:-}" == "--test" ]]; then
|
|
_tbd_branch_create_tests
|
|
else
|
|
tbd_branch_create "$@"
|
|
fi
|
|
fi
|