Commit Graph
19 Commits
Author SHA1 Message Date
dualfroz abb1e92064 feat: hide PRs you can neither act on nor learn from
Overview and the PRs tab listed open PRs from every repo, including ones
cloned for reference (e.g. pterodactyl) whose PRs you can't merge and
didn't author — pure noise. A PR is now surfaced only when you authored
it but can't merge it (your contribution elsewhere) or someone else
authored it and you can (awaiting your review); your own PR in a repo
you control, and strangers' PRs you can't touch, are filtered out.

Providers report viewer merge permission (RepoMeta.viewer_can_merge,
from GitHub/Gitea repo permissions and GitLab access level), stamped
onto each PR at refresh. The cache keeps every PR; the filter is applied
where the hub builds state.prs, so the per-repo detail view is unchanged.
2026-07-11 22:19:59 +02:00
dualfroz 4a3fa58745 feat: show active-todo count in the tab bar and declutter the Todos title
The Todos tab now carries a count badge (4 Todos(N), N = open + in
progress) like PRs/Issues do. The panel title drops the [s]/[g]/[c]
keybind soup that read as clutter; the current view/group/code-scan
state and the keys that change it move to the bottom hint bar, where
every other keybinding already lives.
2026-07-11 22:19:59 +02:00
dualfroz 3426a28c48 feat: show local branch count in the Repos table
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.
2026-07-08 16:21:22 +02:00
dualfroz 212c55d138 feat: flag vendored clones and make the repo filter navigable
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.
2026-07-08 16:15:02 +02:00
dualfroz 2ccf69f64c fix: pass -E to git so the multi-identity author filter matches
OwnEmailMatcher::author_regex() emits an extended-regex alternation
(<email>|<noreply-address>), but scan_repo_own_commits ran `git log
--author=` without -E. git defaults --author to *basic* regex, where |
is a literal, so the pattern matched nothing for anyone with two or more
identities — silently zeroing streak, heatmap, digest and the hour/day
patterns (measured 0 -> 141 own commits across the workspace after the
fix). Add -E here and in the new has_own_commit helper.
2026-07-08 16:14:43 +02:00
dualfroz 6adc22d665 feat: redesign Todos tab — grouping, collapsible headers, cleaned columns
Implements the approved mockup from the UX debate report:

- new DisplayRow::TodoHeader variant: collapsible group headers, toggled
  by Enter/Space, tracked in HubState.todo_group_collapsed
- [g] cycles grouping: status (default) -> project -> repo -> none
- [s] cycles which statuses are included before grouping: active
  (open+in-progress, default) -> open -> done -> all
- table: dropped SRC/EST (near-always empty), code-scan todos show a dim
  'no status icon' marker instead of a separate source column, project
  name colored by the project's own color (was DarkGray, wasting the most
  useful visual identifier in the tool), age now tracks updated_at not
  created_at ('what's moving' beats 'what's old')
- right panel: added Repo/Difficulty/Estimate, previously only visible in
  the table despite the panel having more room for them
- block title shows live counts + current view/group/code-scan state

Simplified vs. the literal ASCII mockup: [s] filters the underlying set
before grouping, so e.g. the default 'active' view omits Done/Cancelled
groups entirely rather than always showing them collapsed - a simpler,
more internally consistent model. Flagged for foksiu to request the exact
mockup behavior if he wants it.

Verified via pyte: group/status-view cycling, collapse/expand (arrow flips
and child rows appear/disappear), right panel on header vs. todo row.
2026-07-07 19:17:28 +02:00
dualfroz 3eb922559c fix: provider API calls used the local folder name, not the actual repo name
Every place building an owner/repo slug for the provider API (refresh,
release, add_collaborator) used path.file_name() as the repo segment - the
local checkout's directory name. That's usually the same as the GitHub repo
name, but not always: vulp's own folder is 'vulp', its GitHub repo is 'fxg',
so every refresh silently 404'd fetching its own metadata/PRs/issues/CI (the
cache carried an empty meta block with fetched_at: 0, never actually set).

RepoSummary now also carries remote_repo_name, parsed from the git remote
URL alongside owner (same source, same parsing approach) - the segment the
provider actually knows the repo by. All four slug-building call sites now
prefer it, falling back to the folder name only when there's no remote to
parse it from. Verified: 'vulp refresh' now correctly hits dualfroz/fxg and
the cache shows real stars/issues/PRs for vulp instead of the empty stub.
2026-07-07 19:00:08 +02:00
dualfroz 4b0a6e324f feat: deploy drift via auto-discovered VPS mirror, no manual metadata
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
2026-07-07 18:55:13 +02:00
dualfroz a3658d5e97 fix: eliminate alt-screen flash when returning from repo/project detail
The hub hands its live terminal to detail/project_detail on open (already
flash-free going in), but both sub-views unconditionally tore the terminal
down on exit; the hub's next loop iteration then always called tui::setup()
again, producing a real LeaveAlternateScreen -> (plain terminal visible for
one frame) -> EnterAlternateScreen blink on every trip back.

- detail::run / project_detail::run now return the live terminal on a clean
  exit instead of tearing it down (only torn down if the inner loop itself
  errors); hub::tui_phase accepts an already-live terminal and skips its own
  setup() when given one, threading it across loop iterations
- project_detail's own nested repo-detail call (Enter on its Repos tab) had
  the same issue one level down, fixed the same way via Option<Tty> so the
  terminal can be taken out for the nested call and put back
- verified via raw pty byte counts: two full hub<->detail round trips now
  show zero EnterAlternateScreen/LeaveAlternateScreen pairs in between,
  vs. one pair per trip before
2026-07-07 17:58:02 +02:00
dualfroz 14ef7fb06c Sprint C: author-filtered stats scan, release cadence + repo size, weekly digest
- src/stats/: OwnEmailMatcher (config emails + GH no-reply per account) +
  git-side --author filtering (cheap even on the pterodactyl fork's 4700+
  commits); streak (current/longest, midnight-grace rule), 52-week heatmap,
  day/hour histograms — one scan feeds all of them, data layer only for now
  (visual Stats-tab redesign stays in Sprint D's mockup-first cycle)
- src/stats/cadence.rs: release cadence (median tag gap, local tags, Tier A)
- src/stats/size.rs + cache.rs: git-object vs working-tree size, persisted
  in new ~/.vulp/stats_cache.toml (max 1 sample/day), recorded only during
  [R]/vulp refresh — confirms vulp's own filter-repo cleanup (539MB->834KB)
- src/digest.rs: WeeklyDigest struct + 3 renderers (Discord embed/markdown
  archive/plaintext); vulp digest [send discord|webhook <url>] CLI, webhook
  stored via existing auth token store (~/.vulp/.tokens, 0600), never logged;
  send respects --dry-run
- TUI: [d] on Stats previews the digest in a Modal::Info before anything is
  ever sent
- fix: list_contributors used --all (double-counts after fetching remotes),
  now --branches
- config: stats_author_emails field (TUI-editable)
- verified: streak/delta/top-repo/release math against a hand-built commit
  history, webhook validation+permissions, dry-run, real refresh (51 repos,
  vulp's own entry confirms the filter-repo win), TUI preview via pyte
2026-07-06 16:47:19 +02:00
dualfroz d99660420f Sprint B.9+B.10: Dependabot alerts + CI status in table, cache and CLI
- 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
2026-07-06 16:02:49 +02:00
dualfroz 1e74de5e2e Sprint B.8: vulp brief [repo] --json — one-shot state for agents
- workspace brief: summary counts (incl. missing_license), needs-attention
  repos, open/in-progress todos, cached PR/issue lists
- repo brief: full RepoSummary + 10 recent commits + repo todos + cached
  PRs/issues; name matches display label or basename
- read-only, cache-sourced (vulp refresh for freshness); human + --json
2026-07-06 15:50:52 +02:00
dualfroz c0b282335d Sprint B.7: secret scan before commit-push (TUI + CLI)
- src/secrets.rs: scans added lines of the staged diff for GitHub/GitLab
  token shapes, AWS access keys, Discord token assignments, private key
  blocks; prefix+tail-length matching (no regex dep), matched values never
  echoed
- a hit blocks the commit and unstages (git reset); CLI override
  --allow-secrets; TUI sync flow shares the same scan
- verified: block+unstage, override commits, prose mentions don't trigger
2026-07-06 15:47:32 +02:00
dualfroz 8119cdeba7 Sprint B.6: CLI guardrails — --dry-run, protected_repos, VULP_AGENT marker
- 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
2026-07-06 15:43:14 +02:00
dualfroz f1432d1cd3 Sprint B.5: add-LICENSE action (MIT/Apache-2.0) in [m] menu + vulp repo license
- src/license/: embedded templates, holder resolution (config > git user.name
  > account username), write path shared by TUI and CLI
- TUI: bulk-aware [m] entry over action_targets(), kind select + confirm,
  point-updates has_license/is_dirty, logs to activity (new License kind)
- CLI: vulp repo license <repo>...|--all [--kind] [--holder] [--json],
  skips already-licensed repos, exit 2/4 contract kept
- config: license_holder + default_license fields (TUI-editable)
- never overwrites an existing license, never auto-commits
2026-07-06 15:37:25 +02:00
dualfroz 0468c032c6 Phases 0-1 + Sprint A: module split, CLI layer, org-refresh fix, hygiene
- LICENSE (MIT) + README truth-pass; docs/cli.md CLI contract
- CLI subcommands (src/cli_cmds/) per "TUI for users, CLI for users+AI"
- Hub/detail refactor: keymap dispatch, modal split, action enum, BgMsg bus
- Fix: org repos were never refreshed (owner==username match); refresh now
  parallel — 49 repos in ~11s, surfaced 70 previously invisible PRs
- Fix: 3 date bugs in detail/git.rs (format-local, Local::now, 1y month math)
- Stash indicator + [m] actions, session persistence, gh CLI token fallback
- Legacy UI modules removed; plan/ docs (roadmap, audit, debate, handoff)
2026-07-06 14:42:52 +02:00
dualfroz 5787b14880 update 2026-06-19 01:10:19 +02:00
dualfroz d4fdc9890a Test message 123! 2026-06-14 00:22:54 +02:00
dualfroz 4cf726709f init 2026-06-10 12:41:14 +02:00