diff --git a/CHANGELOG.md b/CHANGELOG.md index e82cbbe..91ebaff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,12 +21,14 @@ All notable changes to this project will be documented in this file. ### Changed * Monolith de-layering (Step 11a): the network concern (status, diagnostics, - and connectivity actions — 17 functions) moved verbatim out of - `terminal/launchers/mqlaunch.sh` into `mqlaunch/lib/network.sh`, sourced back - into the launcher's scope. No behavior change; the launcher drops ~200 lines - and the concern now has a named owner. Guarded by - `tests/network-lib-delayer-smoke.sh`, which fails if a network function is - redefined in the monolith or the lib source is dropped. + and connectivity actions — 17 functions) and the fzf interactive pickers + (git log/branch, kill process/port, run snippet, recent files — 6 functions) + moved verbatim out of `terminal/launchers/mqlaunch.sh` into + `mqlaunch/lib/network.sh` and `mqlaunch/lib/fzf-pickers.sh`, sourced back into + the launcher's scope. No behavior change; the launcher drops ~310 lines and + each concern now has a named owner. Guarded by + `tests/monolith-delayer-smoke.sh`, a table-driven check that fails if any + extracted function is redefined in the monolith or a lib source is dropped. * `semantic-memory-maintainer` renamed to `vector-store-maintainer` to avoid routing collision with mq-mcp's skill of the same name. * `command-template-library` skill moved to mq-ums, where the contracts and diff --git a/docs/AUTHORITY_MAP.md b/docs/AUTHORITY_MAP.md index f0925dd..2d6b850 100644 --- a/docs/AUTHORITY_MAP.md +++ b/docs/AUTHORITY_MAP.md @@ -71,6 +71,7 @@ Reached through other live paths: | `mqlaunch/lib/recommendations/*` | `terminal/menus/recommendations-menu.sh` | | `mqlaunch/lib/mqobsidian/*` | `terminal/menus/mq-obsidian-menu.sh` | | `mqlaunch/lib/network.sh` | `terminal/launchers/mqlaunch.sh` (sourced) — network concern de-layered out of the monolith (Step 11a) | +| `mqlaunch/lib/fzf-pickers.sh` | `terminal/launchers/mqlaunch.sh` (sourced) — fzf interactive pickers de-layered out of the monolith (Step 11a) | ## Bridges diff --git a/mqlaunch/lib/fzf-pickers.sh b/mqlaunch/lib/fzf-pickers.sh new file mode 100755 index 0000000..ea59a35 --- /dev/null +++ b/mqlaunch/lib/fzf-pickers.sh @@ -0,0 +1,131 @@ +#!/usr/bin/env bash +# mqlaunch fzf pickers — interactive fuzzy finders for git, processes, ports, +# scripts, and recent files. +# +# Extracted from terminal/launchers/mqlaunch.sh as part of Step 11a (monolith +# de-layering, audit P4). Sourced into the launcher's scope, so it relies on the +# launcher's ambient UI helpers (print_header/row/print_footer/pause_enter) and +# $BASE_DIR. It defines no state of its own and changes no behavior — the +# functions are verbatim moves. Pure bash (no zsh builtins), so it carries a +# bash shebang and is covered by shellcheck. + +# fzf: bläddra git log med diff-preview +fzf_git_log() { + local fzf_bin commit + fzf_bin="$(command -v fzf 2>/dev/null || true)" + [[ -z "$fzf_bin" ]] && echo "fzf not installed." && return 1 + if ! git rev-parse --git-dir >/dev/null 2>&1; then + echo "Inte i ett git-repo." + pause_enter + return 1 + fi + commit="$( + git log --oneline --color=always | \ + "$fzf_bin" --ansi --reverse --border \ + --header='Git commits — Enter för att visa, Ctrl+C för att avbryta' \ + --prompt='commit > ' \ + --preview='git show --color=always {1}' \ + --preview-window=right:60% + )" + [[ -z "$commit" ]] && return 0 + git show "$(echo "$commit" | awk '{print $1}')" + pause_enter +} + +# fzf: byt git-branch +fzf_git_branch() { + local fzf_bin branch + fzf_bin="$(command -v fzf 2>/dev/null || true)" + [[ -z "$fzf_bin" ]] && echo "fzf not installed." && return 1 + if ! git rev-parse --git-dir >/dev/null 2>&1; then + echo "Inte i ett git-repo." + pause_enter + return 1 + fi + branch="$( + git branch --all | grep -v HEAD | sed 's/remotes\/origin\///' | sort -u | \ + "$fzf_bin" --reverse --border \ + --header='Välj branch att checka ut' \ + --prompt='branch > ' + )" + [[ -z "$branch" ]] && return 0 + branch="$(echo "$branch" | xargs)" + git checkout "$branch" + pause_enter +} + +# fzf: döda processer +fzf_kill_process() { + local fzf_bin selected pid + fzf_bin="$(command -v fzf 2>/dev/null || true)" + [[ -z "$fzf_bin" ]] && echo "fzf not installed." && return 1 + selected="$( + ps aux | tail -n +2 | \ + "$fzf_bin" --multi --reverse --border \ + --header='Välj process(er) att döda — TAB för flerval' \ + --prompt='process > ' + )" + [[ -z "$selected" ]] && return 0 + pid="$(echo "$selected" | awk '{print $2}')" + echo "$pid" | xargs kill -9 2>/dev/null + echo "Dödade PID: $(echo "$pid" | tr '\n' ' ')" + pause_enter +} + +# fzf: döda process på port +fzf_kill_port() { + local fzf_bin selected pid port + fzf_bin="$(command -v fzf 2>/dev/null || true)" + [[ -z "$fzf_bin" ]] && echo "fzf not installed." && return 1 + selected="$( + lsof -iTCP -sTCP:LISTEN -n -P 2>/dev/null | tail -n +2 | \ + "$fzf_bin" --multi --reverse --border \ + --header='Välj port(ar) att frigöra — TAB för flerval' \ + --prompt='port > ' + )" + [[ -z "$selected" ]] && return 0 + pid="$(echo "$selected" | awk '{print $2}')" + port="$(echo "$selected" | awk '{print $9}' | cut -d: -f2 | tr '\n' ' ')" + echo "$pid" | xargs kill -9 2>/dev/null + echo "Frigjorde port(ar): $port" + pause_enter +} + +# fzf: kör script från macos-scripts +fzf_run_snippet() { + local fzf_bin selected + fzf_bin="$(command -v fzf 2>/dev/null || true)" + [[ -z "$fzf_bin" ]] && echo "fzf not installed." && return 1 + selected="$( + find "$BASE_DIR" -name "*.sh" -not -path "*/.git/*" -not -path "*/backups/*" 2>/dev/null | \ + sed "s|$BASE_DIR/||" | sort | \ + "$fzf_bin" --reverse --border \ + --header='Välj script att köra' \ + --prompt='script > ' \ + --preview="bat --color=always '$BASE_DIR/{}' 2>/dev/null || cat '$BASE_DIR/{}'" + )" + [[ -z "$selected" ]] && return 0 + print_header + row "Kör: $selected" + print_footer + bash "$BASE_DIR/$selected" + pause_enter +} + +# fzf: visa och öppna nyligen ändrade filer +fzf_recent_files() { + local fzf_bin selected search_dir + fzf_bin="$(command -v fzf 2>/dev/null || true)" + [[ -z "$fzf_bin" ]] && echo "fzf not installed." && return 1 + search_dir="${1:-$HOME/repos}" + selected="$( + find "$search_dir" -type f -not -path "*/.git/*" -newer "$search_dir" -mtime -14 2>/dev/null | \ + sort | \ + "$fzf_bin" --reverse --border \ + --header='Senast ändrade filer (14 dagar) — Enter öppnar' \ + --prompt='fil > ' \ + --preview='bat --color=always {} 2>/dev/null || cat {}' + )" + [[ -z "$selected" ]] && return 0 + "${EDITOR:-code}" "$selected" 2>/dev/null || open "$selected" +} diff --git a/terminal/launchers/mqlaunch.sh b/terminal/launchers/mqlaunch.sh index a0fbb1e..75dacef 100755 --- a/terminal/launchers/mqlaunch.sh +++ b/terminal/launchers/mqlaunch.sh @@ -567,126 +567,15 @@ prompts_pick() { fi } -# fzf: bläddra git log med diff-preview -fzf_git_log() { - local fzf_bin commit - fzf_bin="$(command -v fzf 2>/dev/null || true)" - [[ -z "$fzf_bin" ]] && echo "fzf not installed." && return 1 - if ! git rev-parse --git-dir >/dev/null 2>&1; then - echo "Inte i ett git-repo." - pause_enter - return 1 - fi - commit="$( - git log --oneline --color=always | \ - "$fzf_bin" --ansi --reverse --border \ - --header='Git commits — Enter för att visa, Ctrl+C för att avbryta' \ - --prompt='commit > ' \ - --preview='git show --color=always {1}' \ - --preview-window=right:60% - )" - [[ -z "$commit" ]] && return 0 - git show "$(echo "$commit" | awk '{print $1}')" - pause_enter -} - -# fzf: byt git-branch -fzf_git_branch() { - local fzf_bin branch - fzf_bin="$(command -v fzf 2>/dev/null || true)" - [[ -z "$fzf_bin" ]] && echo "fzf not installed." && return 1 - if ! git rev-parse --git-dir >/dev/null 2>&1; then - echo "Inte i ett git-repo." - pause_enter - return 1 - fi - branch="$( - git branch --all | grep -v HEAD | sed 's/remotes\/origin\///' | sort -u | \ - "$fzf_bin" --reverse --border \ - --header='Välj branch att checka ut' \ - --prompt='branch > ' - )" - [[ -z "$branch" ]] && return 0 - branch="$(echo "$branch" | xargs)" - git checkout "$branch" - pause_enter -} - -# fzf: döda processer -fzf_kill_process() { - local fzf_bin selected pid - fzf_bin="$(command -v fzf 2>/dev/null || true)" - [[ -z "$fzf_bin" ]] && echo "fzf not installed." && return 1 - selected="$( - ps aux | tail -n +2 | \ - "$fzf_bin" --multi --reverse --border \ - --header='Välj process(er) att döda — TAB för flerval' \ - --prompt='process > ' - )" - [[ -z "$selected" ]] && return 0 - pid="$(echo "$selected" | awk '{print $2}')" - echo "$pid" | xargs kill -9 2>/dev/null - echo "Dödade PID: $(echo "$pid" | tr '\n' ' ')" - pause_enter -} - -# fzf: döda process på port -fzf_kill_port() { - local fzf_bin selected pid port - fzf_bin="$(command -v fzf 2>/dev/null || true)" - [[ -z "$fzf_bin" ]] && echo "fzf not installed." && return 1 - selected="$( - lsof -iTCP -sTCP:LISTEN -n -P 2>/dev/null | tail -n +2 | \ - "$fzf_bin" --multi --reverse --border \ - --header='Välj port(ar) att frigöra — TAB för flerval' \ - --prompt='port > ' - )" - [[ -z "$selected" ]] && return 0 - pid="$(echo "$selected" | awk '{print $2}')" - port="$(echo "$selected" | awk '{print $9}' | cut -d: -f2 | tr '\n' ' ')" - echo "$pid" | xargs kill -9 2>/dev/null - echo "Frigjorde port(ar): $port" - pause_enter -} - -# fzf: kör script från macos-scripts -fzf_run_snippet() { - local fzf_bin selected - fzf_bin="$(command -v fzf 2>/dev/null || true)" - [[ -z "$fzf_bin" ]] && echo "fzf not installed." && return 1 - selected="$( - find "$BASE_DIR" -name "*.sh" -not -path "*/.git/*" -not -path "*/backups/*" 2>/dev/null | \ - sed "s|$BASE_DIR/||" | sort | \ - "$fzf_bin" --reverse --border \ - --header='Välj script att köra' \ - --prompt='script > ' \ - --preview="bat --color=always '$BASE_DIR/{}' 2>/dev/null || cat '$BASE_DIR/{}'" - )" - [[ -z "$selected" ]] && return 0 - print_header - row "Kör: $selected" - print_footer - bash "$BASE_DIR/$selected" - pause_enter -} - -# fzf: visa och öppna nyligen ändrade filer -fzf_recent_files() { - local fzf_bin selected search_dir - fzf_bin="$(command -v fzf 2>/dev/null || true)" - [[ -z "$fzf_bin" ]] && echo "fzf not installed." && return 1 - search_dir="${1:-$HOME/repos}" - selected="$( - find "$search_dir" -type f -not -path "*/.git/*" -newer "$search_dir" -mtime -14 2>/dev/null | \ - sort | \ - "$fzf_bin" --reverse --border \ - --header='Senast ändrade filer (14 dagar) — Enter öppnar' \ - --prompt='fil > ' \ - --preview='bat --color=always {} 2>/dev/null || cat {}' - )" - [[ -z "$selected" ]] && return 0 - "${EDITOR:-code}" "$selected" 2>/dev/null || open "$selected" -} +# fzf interactive pickers (git log/branch, kill process/port, run snippet, +# recent files) live in a dedicated library (Step 11a monolith de-layering, +# audit P4). Sourced into this scope so they keep using the ambient UI helpers +# and $BASE_DIR; no behavior change — the functions are verbatim moves. +if [[ -f "$BASE_DIR/mqlaunch/lib/fzf-pickers.sh" ]]; then + source "$BASE_DIR/mqlaunch/lib/fzf-pickers.sh" +else + mq_debug "mqlaunch: fzf pickers lib missing: mqlaunch/lib/fzf-pickers.sh" +fi # Opens repo browser. open_repo_browser() { diff --git a/tests/monolith-delayer-smoke.sh b/tests/monolith-delayer-smoke.sh new file mode 100755 index 0000000..24d586b --- /dev/null +++ b/tests/monolith-delayer-smoke.sh @@ -0,0 +1,51 @@ +#!/usr/bin/env bash +# Smoke (P1 Step 11a): concerns de-layered out of the mqlaunch monolith stay +# out. For each extracted concern this guards the direction — every function is +# defined in its lib, the launcher sources that lib, and the launcher does not +# redefine any of them. A future edit that pulls a function back into +# mqlaunch.sh (or drops a lib source) fails here instead of silently +# re-monolithising. Add a row to CONCERNS as each new concern is extracted. +set -uo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +LAUNCHER="$ROOT/terminal/launchers/mqlaunch.sh" + +fail() { printf '[FAIL] %s\n' "$1" >&2; exit 1; } + +# Each concern: "lib_relpath : space-separated function names" +CONCERNS=( + "mqlaunch/lib/network.sh : network_default_interface network_service_name network_interface_ip network_dns_server network_gateway network_ssid network_ping_ms network_value network_report copy_network_info open_network_settings ping_test show_dns_gateway run_network_pulse run_network_ghost run_network_quality show_network_info" + "mqlaunch/lib/fzf-pickers.sh : fzf_git_log fzf_git_branch fzf_kill_process fzf_kill_port fzf_run_snippet fzf_recent_files" +) + +total=0 +for entry in "${CONCERNS[@]}"; do + lib_rel="${entry%% : *}" + funcs="${entry#* : }" + lib="$ROOT/$lib_rel" + + [[ -f "$lib" ]] || fail "lib missing: $lib_rel" + + # zsh-shebang libs get zsh -n; bash libs get bash -n. Both when tools exist. + shebang="$(head -1 "$lib")" + if [[ "$shebang" == *zsh* ]]; then + command -v zsh >/dev/null 2>&1 && { zsh -n "$lib" || fail "$lib_rel fails zsh -n"; } + else + bash -n "$lib" || fail "$lib_rel fails bash -n" + fi + + # The launcher must source this lib. + grep -qF "source \"\$BASE_DIR/$lib_rel\"" "$LAUNCHER" \ + || fail "launcher no longer sources $lib_rel" + + for f in $funcs; do + grep -qE "^${f}\(\) \{" "$lib" || fail "$lib_rel does not define $f" + if grep -qE "^${f}\(\) \{" "$LAUNCHER"; then + fail "launcher redefines $f — concern must stay in $lib_rel" + fi + total=$((total + 1)) + done +done + +printf '[PASS] monolith de-layering: %d functions across %d concern libs; launcher sources each, redefines none\n' \ + "$total" "${#CONCERNS[@]}" diff --git a/tests/network-lib-delayer-smoke.sh b/tests/network-lib-delayer-smoke.sh deleted file mode 100755 index f1a974b..0000000 --- a/tests/network-lib-delayer-smoke.sh +++ /dev/null @@ -1,45 +0,0 @@ -#!/usr/bin/env bash -# Smoke (P1 Step 11a): the network concern is owned by mqlaunch/lib/network.sh, -# not by the launcher monolith. Guards the de-layering direction — a future edit -# that pulls a network_* definition back into mqlaunch.sh (or drops the lib -# source) fails here instead of silently re-monolithising. -set -uo pipefail - -ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" -LIB="$ROOT/mqlaunch/lib/network.sh" -LAUNCHER="$ROOT/terminal/launchers/mqlaunch.sh" - -fail() { printf '[FAIL] %s\n' "$1" >&2; exit 1; } - -FUNCS=( - network_default_interface network_service_name network_interface_ip - network_dns_server network_gateway network_ssid network_ping_ms - network_value network_report copy_network_info open_network_settings - ping_test show_dns_gateway run_network_pulse run_network_ghost - run_network_quality show_network_info -) - -[[ -f "$LIB" ]] || fail "network lib missing: mqlaunch/lib/network.sh" - -# Syntax: the lib is zsh (uses print -r --); check with zsh when available. -if command -v zsh >/dev/null 2>&1; then - zsh -n "$LIB" || fail "network lib fails zsh -n" -fi - -# Every network function is defined in the lib. -for f in "${FUNCS[@]}"; do - grep -qE "^${f}\(\) \{" "$LIB" || fail "lib does not define $f" -done - -# The launcher must source the lib. -grep -qE 'source "\$BASE_DIR/mqlaunch/lib/network\.sh"' "$LAUNCHER" \ - || fail "launcher no longer sources mqlaunch/lib/network.sh" - -# The launcher must NOT redefine any network function (single owner). -for f in "${FUNCS[@]}"; do - if grep -qE "^${f}\(\) \{" "$LAUNCHER"; then - fail "launcher redefines $f — concern must stay in the lib" - fi -done - -printf '[PASS] network de-layering: lib owns all %d network functions; launcher sources, none redefined\n' "${#FUNCS[@]}" diff --git a/tools/scripts/test-all.sh b/tools/scripts/test-all.sh index c175bd0..bb7214a 100755 --- a/tools/scripts/test-all.sh +++ b/tools/scripts/test-all.sh @@ -18,7 +18,7 @@ echo "== Running mqlaunch headless checks ==" "$PROJECT_ROOT/tests/runtime-authority-freeze-smoke.sh" "$PROJECT_ROOT/tests/hal-args-no-eval-smoke.sh" "$PROJECT_ROOT/tests/mq-debug-smoke.sh" -"$PROJECT_ROOT/tests/network-lib-delayer-smoke.sh" +"$PROJECT_ROOT/tests/monolith-delayer-smoke.sh" "$PROJECT_ROOT/tests/mq-stack-contract-smoke.sh" "$PROJECT_ROOT/tests/gitmerge-safe-smoke.sh" "$PROJECT_ROOT/tests/dashboard-header-cache-smoke.sh"