docs/cli.md called itself the 'complete non-interactive surface' but
never listed vulp auth add/remove/switch/test (only import) — real,
script-usable subcommands with zero documentation. Also documented
the missing --allow-secrets flag on the sync synopsis line. Separately,
'list --no-external --external' together silently returned an
always-empty result (two independent retain passes) instead of an
error; now rejected as a usage error (exit 2), matching what the
synopsis's '|' already implied.
A repo that keeps several branches (e.g. one theme per branch) only ever
showed its checked-out branch, hiding that the others exist. The Repos
table now renders the active branch plus `+N` for the other local
branches (cyan when >1), the BRANCH column widened to fit, and
`vulp status` prints "(+N more local)". The full list stays in the
detail Branches tab; RepoSummary.branch_count is in --json too.
Two Repos-tab quality-of-life changes:
- External/vendored clones (repos with zero of your own commits — e.g. an
upstream cloned for reference and never pushed to) are measured on
refresh, cached in stats_cache (RepoStatsEntry.authored), shown with a
dim ⧉ marker prefixed to the name, and hidden by the persistent
hide_external_clones config toggle (key `x`, the `S` menu, or
`vulp list --external|--no-external`). A fork you do commit to stays
yours, so only true reference clones are flagged.
- Inside the `/` filter box, ↑↓ now move through the live-filtered
results and Enter opens the highlighted repo/project directly, instead
of the filter only "locking" on Enter with no way to scroll or select.
Sprint E, redesigned from the original per-repo-metadata plan: the deploy
host already mirrors workspace_root 1:1 (documented convention), so instead
of maintaining a repo->host/compose-path mapping by hand, vulp refresh runs
one batched remote command (via a configurable ssh wrapper) listing every
mirrored repo's HEAD sha and diffs it against local HEAD.
- src/deploy.rs: DeployState (InSync/Behind(n)/Diverged), scan_deploy_heads
(one remote round-trip, retried once for this VPS's known SSH flakiness),
drift_for (path relative to workspace_root -> host lookup -> local git
rev-list --count)
- config: deploy_ssh_cmd + deploy_root, both empty by default (feature is
fully inert, no SSH attempted, until configured)
- cache: RepoMeta.deploy_status, folded into the existing refresh loop
(TUI [R] and CLI) alongside CI/dependabot rather than a second pass
- table: DEPLOY column (checkmark/behind-count/diverged/dash); CLI
list --stale-deploy and deploy_status in --json
- verified against the real VPS: 37/49 repos matched to a mirror (30 in
sync, 7 behind 1-4 commits - a real signal), everything else correctly
shows unset (no mirror there, e.g. vulp itself, a pure OSS dev tool)
- set deploy_ssh_cmd/deploy_root in the live config so this is active now,
not just theoretically configurable
- Provider::org_dependabot_counts: one org-level request per distinct owner
(cursor pagination via Link header — page= returns 400 on this endpoint);
403/404 → None (unknown) vs Some(0) = checked, none open
- Provider::latest_ci_status: latest Actions run on the repo's branch →
success/failure/running; CiStatus enum with stable string form
- cache RepoMeta: ci_status + dependabot_open replace unused ci_ok;
RepoSummary carries both, applied at startup/scan/refresh (TUI + CLI)
- Repos table: CI column (✓/✗/●) + ALERT column (count, red when >0,
'—' = unknown); CLI: list --alerts filter, scan_repos now cache-enriched
- verified against live API: 49 repos, 44 with alert counts, CI statuses
match reality; table rendering checked via pyte
- global --dry-run: sync/release/init/collab/license print 'DRY RUN: would...'
and exit 0 (no auth needed for previews where possible)
- config protected_repos (name/basename, case-insensitive): mutating commands
exit 1 on listed repos unless --force; checked before dry-run
- VULP_AGENT env: activity::log centrally appends '· via agent' to detail,
covering both CLI and TUI writers
- sync: unknown --action now rejected before touching any repo