Skip to content

feat(stats): --since, --json, and --graph for tsk stats#4

Merged
Sanjays2402 merged 6 commits into
mainfrom
feat-stats
May 10, 2026
Merged

feat(stats): --since, --json, and --graph for tsk stats#4
Sanjays2402 merged 6 commits into
mainfrom
feat-stats

Conversation

@Sanjays2402

Copy link
Copy Markdown
Owner

Summary

Three additions to tsk stats:

  • --since <dur> restricts completion-derived metrics (Done, Completion, Streak, TopTags) to a window. Whole-store counts (Total, Undone, Overdue, Today) are unaffected.
  • --graph appends a 30-day completion sparkline below the human summary.
  • --json emits a stable JSON document for scripts and dashboards.

--graph and --json are independent. With both set, --json wins (graph suppressed) so callers always get exactly one machine-readable doc.

--json schema (stable contract)

{
  "total": int, "done": int, "undone": int,
  "overdue": int, "today": int,
  "completion": float64, "streak": int,
  "since_seconds": int,
  "top_tags":           [{"tag": string, "count": int}, ...],
  "completion_history": [{"date": "YYYY-MM-DD", "count": int}, ...]
}

completion_history is always populated (30 oldest-first buckets) regardless of whether --graph was passed, so the schema is stable.

Sparkline notes

  • Alphabet: ▁▂▃▄▅▆▇█ plus space for zero. Ceiling-division mapping so any nonzero count gets at least the smallest visible block.
  • Plain runes only — no ANSI — so it works under NO_COLOR.
  • Window is always 30 days, intentionally independent of --since, so the visualization stays comparable across filters.
  • Format: 30d completions: ▁▂▁▃▄▂▁ ... █ (oldest left, today right).

--since notes

Accepts friendly suffixes 7d, 2w, 1m, 1y plus bare Go durations (72h, 1h30m). Months = 30 days, years = 365 days for "completions in the last N" semantics.

A local parseDurationLocal helper lives in stats.go to keep this PR independent of any sibling refactor; it can be promoted to internal/util later.

Scope

Only three files touched: internal/commands/stats.go, internal/commands/commands_test.go, README.md. No new external deps.

Tests

Five new cases covering the new surface:

  • TestStatsSinceFiltersDoneAndStreak — 5 backdated tasks; asserts whole-store counts unchanged, --since 7d narrows Done to 3 and streak to 2.
  • TestStatsSinceRejectsBogus — exit code 2 for invalid duration.
  • TestStatsGraphRendersSparkline — exactly 30 runes, all in the alphabet, today bucket non-empty when there's a same-day completion.
  • TestStatsJSONStableSchema — parses output, validates required keys + types, since_seconds matches 30d, top_tags sorted desc by count, completion_history is 30 oldest-first buckets.
  • TestStatsJSONWithoutSincesince_seconds is 0 when the flag is unset.

Plus a writeRawTasks helper that writes .tsk.md directly so tests can stamp arbitrary Completed timestamps (the CLI done command stamps time.Now(), which can't be backdated).

go vet ./..., gofmt -l ., go test ./... -count=1 — all clean.

Diff size

~574 net additions (well-tested side; ~240 of those are test code; ~290 stats code; ~43 README). Slightly over the soft ~500-line target because the test surface is what was asked for: 4 substantive cases plus a helper, exercising every new flag end-to-end.

Restrict completion-derived metrics (Done, Completion, Streak, TopTags)
to tasks completed within the given window. Whole-store counts (Total,
Undone, Overdue, Today) are unaffected.

Accepts friendly suffixes (7d, 2w, 1m, 1y) and bare Go durations (72h,
1h30m). A local parseDurationLocal helper keeps this PR independent of
any sibling refactor PR; it can be promoted to internal/util later.

Output prints one extra line ('since: <dur> ago') near the top when set.
Emit a stable JSON document via encoding/json.MarshalIndent. Schema:
total, done, undone, overdue, today, completion, streak, since_seconds,
top_tags ({tag, count}), completion_history ({date, count}).

completion_history is always populated as a 30-bucket trailing window
(oldest-first) so the schema is stable regardless of any visualization
flag. The window is independent of --since on purpose: callers want
'completions in the last 30 days' even when their stats are scoped to
a different window.

JSON output is machine-only — no human prelude, no trailing prose.
Append a single-line sparkline below the human summary using the
standard 9-rune alphabet (space + 8 increasing block heights). Each
bucket is one day, oldest on the left, today on the right. Plain runes
only \u2014 no ANSI escapes \u2014 so it works under NO_COLOR.

The sparkline is always the trailing 30 days, intentionally independent
of --since, so the visualization stays comparable across windows. The
JSON contract already exposed completion_history in the previous commit;
this commit just adds the rendering and the --graph flag.

--graph and --json are independent. When both are set, --json wins
(graph is suppressed) so callers get exactly one machine-readable doc.
Document the three new stats flags (--since, --graph, --json) under a
new 'Stats and history' subsection in Usage. Spells out the JSON schema
verbatim so scripts and dashboards have a written contract, calls out
that --graph + --json is JSON-only, and notes that completion_history
is always 30 buckets independent of --since.
Six new tests:

- TestStatsSinceFiltersDoneAndStreak: 5 tasks with backdated completed
  timestamps (today/-1/-3/-10/-45 days) plus one open. Asserts whole-store
  total/undone unchanged, --since 7d narrows done to 3 and streak to 2.
- TestStatsSinceRejectsBogus: 'banana' returns ExitCode 2.
- TestStatsGraphRendersSparkline: counts the runes (must be exactly 30),
  validates each is in the sparkline alphabet, and asserts the right
  edge (today) is non-empty when there's a same-day completion.
- TestStatsJSONStableSchema: parses the JSON, asserts every required
  key is present and correctly typed, since_seconds matches 30d, top_tags
  is sorted desc by count, completion_history has 30 oldest-first
  buckets.
- TestStatsJSONWithoutSince: when --since is unset, since_seconds is 0.

Adds a writeRawTasks helper that writes the raw .tsk.md so tests can
control the Completed timestamps the CLI 'done' command would otherwise
stamp at time.Now().
@Sanjays2402 Sanjays2402 merged commit a7f27de into main May 10, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant