Skip to content

Surface per-workspace git status (branch / ahead-behind / dirty count) in Dashboard workspace view + IM /status #28

Description

@xiaotonng

Problem or motivation

When a workspace directory is a git repo, neither the Dashboard workspace view nor the IM /status command surfaces any VCS context. To answer "what branch am I on, am I ahead/behind the remote, how many files are dirty" you currently have to drop into a terminal.

Since pikiclaw exists to drive coding agents that constantly mutate the working tree, this is exactly the context an operator wants at a glance — and from IM there's no terminal at all. A quick "you're on feature/x, 2 ahead of origin, 5 files changed" massively improves situational awareness before you fire off another agent run.

Proposed solution

1. One shared core helper (single source of truth)

New src/core/git.ts exposing readGitStatus(dir): GitStatus | null:

interface GitStatus {
  branch: string | null;   // null when detached
  detached: boolean;
  shortSha: string | null; // for the detached case
  upstream: string | null; // e.g. "origin/main", null when no tracking branch
  ahead: number;
  behind: number;
  staged: number;
  unstaged: number;
  untracked: number;
  changed: number;         // staged + unstaged + untracked — the headline count
}
  • One git invocation per call: git status --porcelain=v2 --branch, parsed for # branch.head, # branch.upstream, # branch.ab +A -B, and counting the 1/2/u/? entry lines. Porcelain v2 is machine-stable across git versions and locales — no parsing of human output.
  • Reuse the safe-spawn pattern already in /api/git-changes (src/dashboard/routes/config.ts:472): spawnSync('git', …, { cwd, timeout: 5000, env: { …process.env, GIT_OPTIONAL_LOCKS: '0' } }).
  • Returns null for not-a-repo / git-not-installed / timeout. Never throws into the status path.

2. IM /status

StatusData already carries workdir. Compute git once in the async getStatusDataAsync (src/bot/commands.ts:636) and add git?: GitStatus to StatusData (commands.ts:619). Add a shared one-line formatter formatGitStatusLine(git) in src/bot/render-shared.ts, so all 7 channels render identical text instead of each reinventing it — each channel's cmdStatus (telegram bot.ts:448, slack bot.ts:199, plus feishu / discord / dingtalk / wecom / weixin) just pushes that one line.

Friendly output, omitted entirely when the workdir isn't a repo:

Git: main  ↑2 ↓1  ·  5 changed (3 staged · 2 untracked)
Git: feature/x  ·  clean  ·  (no upstream)
Git: (detached a1b2c3d)  ·  1 changed

3. Dashboard workspace view

  • New GET /api/workspace-git?path=<workdir> (next to the existing /api/git-changes), returning { ok, isGit, git } from the same readGitStatus.
  • Frontend WorkspaceGroup header (dashboard/src/pages/sessions/SessionWorkspace.tsx:1379) renders a compact git badge beside the workspace name: branch pill + ahead/behind arrows + a dirty-count dot, full breakdown on hover. Lazy-fetch when the workspace expands and on the existing refresh action; small TTL cache so polling doesn't hammer git.

Design notes

  • Single git read feeds both surfaces — no logic duplication, per-channel code stays thin.
  • Bounded (5s timeout, optional-locks off) so a huge repo or a stuck .git/index.lock never blocks /status or the workspace poll.
  • Graceful degrade: not-a-repo / git-missing / detached / no-upstream are all first-class states, never an error toast.

Alternatives considered

  • Extend the existing /api/git-changes: it only lists changed files vs HEAD (no branch, no ahead/behind, excludes untracked) and is per-file. The header wants a cheap summary, so a dedicated summary endpoint backed by the shared helper is cleaner — both can call readGitStatus.
  • Inline git in each channel / the SPA: rejected — would duplicate spawn+parse in 8+ places and drift over time.

Area

Dashboard (also touches Channels /status and Session management)

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions