Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,6 @@ git-scope-test
git-scope-final
docs/images/fallback_pic
docs/images/mobile
docs/images/desktop
docs/images/desktop
coverage.out
PROJECT-STRATEGY-AND-CAREER-PLAYBOOK.md
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ git-scope -h # Show help

## ✨ Features

* **🎯 Attention Summary** — At-a-glance verdict of your whole workspace: `▲ to push · ▼ behind · ● dirty · ✓ clean`. Repos are ranked into **Action / Watch / Clean** tiers and sorted by default so what needs you lands at the top.
* **📁 Workspace Switch** — Switch root directories without quitting (`w`). Supports `~`, relative paths, and **symlinks**.
* **🔍 Fuzzy Search** — Find any repo by name, path, or branch (`/`).
* **🛡️ Dirty Filter** — Instantly show only repos with uncommitted changes (`f`).
Expand Down Expand Up @@ -142,7 +143,7 @@ Typical git workflows involve "tunnel vision"—working deep inside one reposito
| `w` | **Switch Workspace** (with Tab completion) |
| `/` | **Search** repositories (Fuzzy) |
| `f` | **Filter** (Cycle: All / Dirty / Clean) |
| `s` | Cycle **Sort** Mode |
| `s` | Cycle **Sort** Mode (Attention / Dirty / Name / Branch / Recent) |
| `1`–`4` | Sort by: Dirty / Name / Branch / Recent |
| `[` / `]` | **Page Navigation** (Previous / Next) |
| `Enter` | **Open** repo in Editor |
Expand All @@ -154,6 +155,8 @@ Typical git workflows involve "tunnel vision"—working deep inside one reposito
| `t` | Toggle **Timeline** view |
| `q` | Quit |

> The dashboard sorts by **Attention** on launch (Action repos first). Press `s` to cycle to the classic sorts, or `1`–`4` to jump directly.

-----

## ⚙️ Configuration
Expand Down Expand Up @@ -200,6 +203,7 @@ I built `git-scope` to solve the **"Multi-Repo Blindness"** problem. It gives me
- [x] Symlink resolution for devcontainers/Codespaces
- [x] Background file watcher (real-time updates)
- [x] Bulk fetch all remotes (`F`)
- [x] Attention summary + tiered scoring (at-a-glance verdict)
- [ ] Quick actions (bulk pull / stash, with confirmation)
- [ ] Repo grouping (Service / Team / Stack)
- [ ] Custom team dashboards
Expand Down
94 changes: 94 additions & 0 deletions docs/specs/attention-summary-scoring-plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# Plan: Attention Summary + Scoring

Status: **Accepted**
Spec: `docs/specs/attention-summary-scoring.md`

## Components & dependencies

```
┌─────────────────────────────┐
│ internal/attention (NEW) │ pure logic, no TUI import
│ Tier, Classify, SubScore, │ depends only on: internal/model, time
│ Summary, Summarize, Less │
└──────────────┬──────────────┘
│ consumed by
┌───────┴────────────────────────────┐
▼ ▼
┌───────────────────┐ ┌──────────────────────┐
│ tui/model.go │ │ tui/view.go │
│ SortByAttention │ │ renderStats() rollup│
│ default sortMode │ │ Status-col glyph │
│ sortRepos() case │ │ (+ tui/styles.go) │
└───────────────────┘ └──────────────────────┘
```

Dependency direction is one-way: TUI → attention → model. `attention` never
imports `tui`. This is what keeps it unit-testable and reusable for a future
CLI/`--json` exit-code path.

## Build order (dependency-ordered)

1. **`internal/attention` package + tests first** (TDD). Nothing depends on TUI
here, so it's fully verifiable in isolation before any UI wiring.
2. **Sort integration** in `tui/model.go` — add `SortByAttention`, flip default,
add the `sortRepos` case delegating to `attention.Less`.
3. **Status-column glyph** in `tui/model.go` row builder + `tui/styles.go` colors.
4. **Summary rollup** in `tui/view.go` `renderStats()`.
5. **Docs** — flip spec/plan Status to Accepted; update README (features +
keyboard table note that default sort is Attention).

Steps 2–4 all depend on step 1. Steps 3 and 4 are independent of each other
(could be parallel) but both depend on 2 being merged-in mentally (shared file
`model.go` for 2 and 3 → keep sequential to avoid edit conflicts).

## API surface of `internal/attention`

```go
type Tier int // Clean < Watch < Action (ordinal)
func (t Tier) String() string // "CLEAN" | "WATCH" | "ACTION"
func (t Tier) Glyph() string // "✓" | "●" | "▲"

func Classify(s model.RepoStatus, now time.Time) Tier
func SubScore(s model.RepoStatus, now time.Time) int // within-tier ordering
func Stale(s model.RepoStatus, now time.Time) bool // IsDirty && age>7d

// Less reports whether repo a should sort before repo b (tier, then subscore desc,
// then name asc). Used by tui sortRepos.
func Less(a, b model.Repo, now time.Time) bool

type Summary struct{ ToPush, Behind, Dirty, Clean int }
func Summarize(repos []model.Repo, now time.Time) Summary
```

`StaleThreshold = 7 * 24 * time.Hour` exported const (eases the future config knob
and the test boundary).

## Risks & mitigations

| Risk | Mitigation |
| :--- | :--- |
| Changing the default sort surprises existing users | It's a refinement of Dirty-first, not a reorder of unrelated data; legacy sorts stay on `s`/`1`–`4`; README note. |
| `now` via hidden `time.Now()` makes stale tests flaky | `now` is an injected param throughout; only the TUI call site passes `time.Now()`. |
| Sub-score integer overflow on huge ahead/behind | Counts capped (`min(.,99)`) per spec before weighting. |
| Glyph width breaks the 8-wide `Status` column alignment | Use single-rune glyphs already used elsewhere (`▲▼●✓`); verify by manual run. |
| Summary double-counts diverged repos | Intentional per spec (per-signal, not a partition); documented in header semantics. |
| Scan-error repos sorting/classification | Covered: ScanError → ACTION tier + sub-score bump; explicit test. |

## Verification checkpoints

- **After step 1:** `go test ./internal/attention/... -cover` ≥ 90%; package
builds with no `tui` import (verify `go list -deps`).
- **After step 2:** `go build ./...`; manual run shows ACTION repos on top at
launch; `1`–`4` still switch sorts.
- **After step 3:** manual run shows tier glyph in `Status` column, aligned.
- **After step 4:** manual run shows `▲ N to push · ▼ N behind · ● N dirty ·
✓ N clean` updating on filter/`r`/`F`.
- **Before PR:** `go test ./...` + `golangci-lint run` clean; spec success
criteria 1–7 each checked off.

## Out of scope (deferred)

- Config keys for weights / stale threshold (constants in v1).
- CLI/`--json`/CI exit-code consumption of `attention` (separate feature; the
package is designed to enable it later).
- Stale visual marker and diverged glyph beyond the spec's proposed defaults.
92 changes: 92 additions & 0 deletions docs/specs/attention-summary-scoring-tasks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# Tasks: Attention Summary + Scoring

Status: **Accepted**
Spec: `docs/specs/attention-summary-scoring.md`
Plan: `docs/specs/attention-summary-scoring-plan.md`

Tasks are dependency-ordered. Each is a single focused session, touches ≤5 files,
and has explicit acceptance + verification.

---

- [ ] **T1: `attention` package — Tier classification (TDD)**
- Build: `internal/attention/attention.go` with `Tier` (Clean<Watch<Action),
`String()`, `Glyph()`, `Classify(model.RepoStatus, now) Tier`, and
`Stale(model.RepoStatus, now) bool` + `StaleThreshold` const.
- Write `attention_test.go` first (failing): each tier; boundaries
(Ahead 0→1, Behind 0→1); dirty combinations; ScanError→Action;
diverged (Ahead>0 && Behind>0)→Action; Stale 7-day boundary with injected now.
- Acceptance: tier rules match spec table exactly; scan error → ACTION;
stale never changes tier.
- Verify: `go test ./internal/attention/... -cover` passes, ≥90%.
- Files: `internal/attention/attention.go`, `internal/attention/attention_test.go`.

- [ ] **T2: `attention` package — SubScore, Less, Summarize (TDD)**
- Build: `SubScore(status, now) int` (caps + weights per spec:
1000·ahead, 100·behind, 10·dirtyCount, 5·scanErr, 1·stale),
`Less(a, b model.Repo, now) bool` (tier, then subScore desc, then name asc),
`Summary{ToPush,Behind,Dirty,Clean}` + `Summarize([]model.Repo, now) Summary`.
- Tests first (failing): unpushed outranks behind outranks dirty; stale nudge
breaks a tie; caps prevent band overflow (ahead=200 stays in unpushed band);
`Less` total-order sanity on a mixed slice; `Summarize` on mixed slice incl.
diverged overlap and empty slice.
- Acceptance: ordering priority unpushed→behind→dirty→stale holds; Summarize
counts are per-signal (diverged repo counted in both ToPush and Behind).
- Verify: `go test ./internal/attention/... -cover` ≥90%; `go list -deps
./internal/attention` shows **no** `internal/tui` import.
- Files: `internal/attention/attention.go`, `internal/attention/attention_test.go`.

- [ ] **T3: Default sort = Attention**
- Build: add `SortByAttention` to the `SortMode` iota; init `sortMode` to it;
add `case SortByAttention` in `sortRepos()` using `attention.Less(.., time.Now())`;
include Attention in the `s` cycle; keep `1`–`4` mappings unchanged.
- Acceptance: on launch ACTION repos sort to top; `s` cycles through Attention +
the 4 legacy modes; `1`–`4` unchanged.
- Verify: `go build ./...`; `go run ./cmd/git-scope` in a multi-repo dir →
ACTION repos on top; press `1`–`4` and `s` to confirm legacy sorts still work.
- Files: `internal/tui/model.go` (+ `internal/tui/update.go` if `s`/help text).

- [ ] **T4: Status-column tier glyph**
- Build: render `Classify(...).Glyph()` in the `Status` column of the table row
builder; add tier colors in `styles.go` (Action/Watch/Clean).
- Acceptance: each row's `Status` cell shows `▲`/`●`/`✓` matching its tier,
column alignment intact (8-wide).
- Verify: `go build ./...`; `go run ./cmd/git-scope` → glyphs present, aligned,
colored; a known-dirty repo shows `●`, an ahead/behind repo shows `▲`.
- Files: `internal/tui/model.go`, `internal/tui/styles.go`.

- [ ] **T5: Summary rollup in header**
- Build: extend `renderStats()` to render
`▲ N to push · ▼ N behind · ● N dirty · ✓ N clean` from
`attention.Summarize(m.filteredRepos, time.Now())`; reuse existing badge styles.
- Acceptance: counts reflect the **filtered** set and update on filter (`f`),
rescan (`r`), and bulk fetch (`F`).
- Verify: `go build ./...`; `go run ./cmd/git-scope` → header shows rollup;
toggle `f` and observe counts change; `F` updates behind count.
- Files: `internal/tui/view.go`.

- [ ] **T6: Docs + final gate**
- Build: flip spec/plan/tasks Status → Accepted; update README features list and
note in the keyboard table that the **default sort is Attention** (and `s`
includes it).
- Acceptance: spec success criteria 1–7 all checked; README accurate.
- Verify: `go test ./...` clean; `golangci-lint run` clean; `go list -deps
./internal/attention | grep -q internal/tui` returns nothing (no TUI dep);
re-read spec §Success Criteria and tick each.
- Files: `README.md`, the three `docs/specs/attention-*` files.

---

## Sequencing notes

- T1 → T2 are the pure core; merge-able and reviewable before any TUI change.
- T3 and T4 both edit `model.go` → do T3 then T4, not in parallel.
- T5 is independent of T3/T4 (only `view.go`) but logically follows so the demo
shows glyph + header together.
- T6 closes the spec gate.

## Definition of done (whole feature)

All of spec §Success Criteria (1–7) pass; `go test ./...` and `golangci-lint run`
clean; default launch sorts by attention; header rollup + per-repo glyph render
correctly; `internal/attention` carries no TUI dependency and is ≥90% covered.
Loading
Loading