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>
312 lines
9.6 KiB
Bash
312 lines
9.6 KiB
Bash
#!/usr/bin/env bash
|
|
# tbd_branch_finish — integra rama TBD a master/main y publica
|
|
#
|
|
# Uso:
|
|
# tbd_branch_finish [<merge_title>]
|
|
#
|
|
# Opciones especiales:
|
|
# --test ejecutar suite de tests internos y salir
|
|
|
|
set -euo pipefail
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# helpers
|
|
# ---------------------------------------------------------------------------
|
|
|
|
_tbd_finish_detect_base() {
|
|
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_finish_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
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# funcion principal
|
|
# ---------------------------------------------------------------------------
|
|
|
|
tbd_branch_finish() {
|
|
local merge_title="${1:-}"
|
|
|
|
# Verificar repo git
|
|
_tbd_finish_require_git_repo
|
|
|
|
# Detectar rama actual
|
|
local branch
|
|
branch=$(git branch --show-current)
|
|
|
|
# Validar que es una rama TBD (issue/* o quick/*)
|
|
if [[ "$branch" != issue/* && "$branch" != quick/* ]]; then
|
|
if [[ "$branch" == "master" || "$branch" == "main" ]]; then
|
|
echo "ERROR: ya estas en ${branch}, nada que mergear." >&2
|
|
return 1
|
|
else
|
|
echo "ERROR: la rama actual '${branch}' no es una rama TBD (issue/* o quick/*)." >&2
|
|
return 1
|
|
fi
|
|
fi
|
|
|
|
# Verificar working tree limpio
|
|
local dirty
|
|
dirty=$(git status --porcelain)
|
|
if [[ -n "$dirty" ]]; then
|
|
echo "ERROR: hay cambios sin commitear. Haz commit antes de mergear." >&2
|
|
return 1
|
|
fi
|
|
|
|
# Detectar rama base
|
|
local base
|
|
base=$(_tbd_finish_detect_base)
|
|
if [[ -z "$base" ]]; then
|
|
echo "ERROR: no se encontro rama 'master' ni 'main' en el repo local." >&2
|
|
return 1
|
|
fi
|
|
|
|
# Componer titulo del merge commit
|
|
local title
|
|
if [[ -n "$merge_title" ]]; then
|
|
title="merge: ${branch} — ${merge_title}"
|
|
else
|
|
title="merge: ${branch}"
|
|
fi
|
|
|
|
# Cambiar a base y actualizar
|
|
echo "Cambiando a ${base}..."
|
|
git checkout "$base"
|
|
|
|
echo "Actualizando ${base}..."
|
|
git pull --rebase
|
|
|
|
# Merge --no-ff
|
|
echo "Mergeando ${branch} en ${base}..."
|
|
local merge_exit=0
|
|
git merge --no-ff "$branch" -m "$title" || merge_exit=$?
|
|
|
|
if [[ $merge_exit -ne 0 ]]; then
|
|
echo "merge conflict: resolver manualmente y continuar (git add + git commit)." >&2
|
|
return 2
|
|
fi
|
|
|
|
# Push
|
|
echo "Publicando ${base}..."
|
|
local push_exit=0
|
|
git push || push_exit=$?
|
|
if [[ $push_exit -ne 0 ]]; then
|
|
echo "ERROR: git push fallo (exit ${push_exit}). La rama '${branch}' NO fue eliminada." >&2
|
|
return 1
|
|
fi
|
|
|
|
# Eliminar rama local
|
|
git branch -d "$branch"
|
|
|
|
echo "Rama '${branch}' integrada a ${base} y publicada. Rama local eliminada."
|
|
return 0
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# modo --test
|
|
# ---------------------------------------------------------------------------
|
|
|
|
_tbd_branch_finish_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 de trabajo
|
|
# ---------------------------------------------------------------------------
|
|
local tmpdir
|
|
tmpdir=$(mktemp -d)
|
|
trap "rm -rf '$tmpdir'" EXIT
|
|
|
|
local bare="$tmpdir/remote.git"
|
|
local work="$tmpdir/work"
|
|
|
|
# Crear remote bare
|
|
git -c init.defaultBranch=master init --bare -q "$bare"
|
|
|
|
# Clonar para tener origin
|
|
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: merge issue branch sin titulo
|
|
# ---------------------------------------------------------------------------
|
|
(
|
|
cd "$work"
|
|
git checkout -q -b issue/0001-add-feature
|
|
echo "feature" > feature.txt
|
|
git add feature.txt
|
|
git commit -q -m "feat: add feature"
|
|
)
|
|
|
|
local finish_exit=0
|
|
(
|
|
cd "$work"
|
|
tbd_branch_finish
|
|
) > /dev/null 2>&1 || finish_exit=$?
|
|
|
|
local current
|
|
current=$(cd "$work" && git branch --show-current)
|
|
_assert_eq "finish lands on base branch" "master" "$current"
|
|
|
|
local branch_gone=0
|
|
(cd "$work" && git show-ref --verify --quiet refs/heads/issue/0001-add-feature) && branch_gone=1 || branch_gone=0
|
|
_assert_eq "issue branch deleted locally" "0" "$branch_gone"
|
|
|
|
_assert_eq "finish exits 0" "0" "$finish_exit"
|
|
|
|
# Verificar merge commit en remote
|
|
local remote_log
|
|
remote_log=$(cd "$work" && git log --oneline -2 origin/master)
|
|
local has_merge=0
|
|
[[ "$remote_log" == *"merge: issue/0001-add-feature"* ]] && has_merge=1 || has_merge=0
|
|
_assert_eq "merge commit pushed to remote" "1" "$has_merge"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Test: merge quick branch con titulo
|
|
# ---------------------------------------------------------------------------
|
|
(
|
|
cd "$work"
|
|
git checkout -q -b quick/fix-typo
|
|
echo "fix" > fix.txt
|
|
git add fix.txt
|
|
git commit -q -m "fix: typo"
|
|
)
|
|
|
|
(
|
|
cd "$work"
|
|
tbd_branch_finish "corregir typo en README"
|
|
) > /dev/null 2>&1
|
|
|
|
current=$(cd "$work" && git branch --show-current)
|
|
_assert_eq "quick branch finish lands on master" "master" "$current"
|
|
|
|
remote_log=$(cd "$work" && git log --oneline -2 origin/master)
|
|
local has_title=0
|
|
[[ "$remote_log" == *"merge: quick/fix-typo — corregir typo en README"* ]] && has_title=1 || has_title=0
|
|
_assert_eq "merge title included in commit" "1" "$has_title"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Test: dirty tree → exit 1
|
|
# ---------------------------------------------------------------------------
|
|
(
|
|
cd "$work"
|
|
git checkout -q -b quick/dirty-test
|
|
)
|
|
echo "dirty" > "$work/dirty.txt"
|
|
|
|
_assert_exit "dirty tree exits 1" 1 bash -c "
|
|
cd '$work'
|
|
source '${BASH_SOURCE[0]}'
|
|
tbd_branch_finish
|
|
"
|
|
rm -f "$work/dirty.txt"
|
|
(cd "$work" && git checkout -q master)
|
|
(cd "$work" && git branch -D quick/dirty-test 2>/dev/null || true)
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Test: ya en master → exit 1
|
|
# ---------------------------------------------------------------------------
|
|
_assert_exit "on master exits 1" 1 bash -c "
|
|
cd '$work'
|
|
source '${BASH_SOURCE[0]}'
|
|
tbd_branch_finish
|
|
"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Test: rama no TBD → exit 1
|
|
# ---------------------------------------------------------------------------
|
|
(cd "$work" && git checkout -q -b feature/non-tbd 2>/dev/null)
|
|
_assert_exit "non-TBD branch exits 1" 1 bash -c "
|
|
cd '$work'
|
|
source '${BASH_SOURCE[0]}'
|
|
tbd_branch_finish
|
|
"
|
|
(cd "$work" && git checkout -q master && git branch -D feature/non-tbd 2>/dev/null || true)
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Test: funciona con 'main' como rama base
|
|
# ---------------------------------------------------------------------------
|
|
local tmpdir2
|
|
tmpdir2=$(mktemp -d)
|
|
local bare2="$tmpdir2/remote.git"
|
|
local work2="$tmpdir2/work"
|
|
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
|
|
git checkout -q -b quick/use-main
|
|
echo "x" > x.txt
|
|
git add x.txt
|
|
git commit -q -m "feat: x"
|
|
tbd_branch_finish
|
|
) > /dev/null 2>&1
|
|
current=$(cd "$work2" && git branch --show-current)
|
|
_assert_eq "works with main as base" "main" "$current"
|
|
rm -rf "$tmpdir2"
|
|
|
|
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_finish_tests
|
|
else
|
|
tbd_branch_finish "$@"
|
|
fi
|
|
fi
|