Skip to content

Releases: woksin/workstats

Release v1.11.0

Choose a tag to compare

@github-actions github-actions released this 01 Oct 11:17
05667ba

Summary

workstats can now produce consultant timesheets: suggested hours per day and engagement, rounded the way a timesheet is, with the evidence behind each entry, a ledger for manual hours and locked periods, and CSV exports for Toggl, Harvest and Clockify. Work is attributed to branches, issues and features, and new commands cover the effort behind a branch or pull request, insights, a weekly digest, a prompt-line status, goals, a calendar heatmap and merging several machines.

Added

  • workstats timesheet: suggested hours per day and engagement with evidence, rounding (nearest, up, down, balanced), minimum entry, drop threshold and daily cap; every hour attributed once, reconciled with the report's human time, in table, JSON, CSV, Markdown and HTML. See Timesheet.
  • Engagements in the config: client work matched by project, remote, path, branch pattern or issue prefix, with rate, currency and billable flag; also usable as --group-by engagement.
  • A timesheet ledger: timesheet add, rm, set, unset, entries for manual hours and overrides, and lock, unlock, locks to freeze a submitted period and report drift instead of silently changing it.
  • timesheet --export toggl|harvest|clockify|generic for importing into time trackers.
  • Branch, issue and feature attribution from the branch fields Claude, Codex and Copilot record and from local Git refs, with --group-by branch|issue|feature and configurable issue-key patterns. See Branches and pull requests.
  • workstats branch and workstats pr: the human time, agent time, tokens, list value and commits behind one branch or pull request, with PR-ready Markdown.
  • workstats insights (focus, weekday-by-hour patterns, leverage, models) and workstats digest (this week against the last, with top repositories and features).
  • workstats now: a fast one-line status for a shell prompt or status bar, with templates and background refresh.
  • Goals: weekly and daily hour targets and list-value caps per subscription, shown in reports, the digest and now.
  • A calendar heatmap of human time per day: workstats calendar, the HTML report, Markdown with --daily, and the explorer (c).
  • workstats export and workstats merge (or --import) to combine machines without double counting; bundles carry no prompts, commit subjects, titles or absolute paths. See Merging machines.
  • Opt-in descriptions: --describe commits (subject lines of your own commits), --describe sessions (tool-generated titles, never prompts) and timesheet --summarize-with CMD with --digest to preview what is sent.
  • --daily per-day figures in JSON, and workstats record --branch NAME.

Changed

  • The transcript cache moves to parser version 5 and is rebuilt on first use, now holding branch names and pull-request numbers.
  • New config blocks branches, issues, engagements, timesheet, goals, insights and now are validated when used, naming any bad key.

Fixed

  • Report rows that tie on every figure now come out in the same order on every run.

Release v1.10.0

Choose a tag to compare

@github-actions github-actions released this 30 Sep 21:48
346d221

Summary

Git counting now sees work it used to miss, and reports gain weekly periods, period comparison, and Markdown and HTML output. Everyday flags can live in the config file, and the documentation moved into focused guides under docs/.

Added

  • Weekly periods: --period week, --group-by week and --week YYYY-Www|current|last group and filter a report by ISO 8601 week.
  • --compare previous (or a named YYYY-MM, YYYY-Www or YYYY window) shows the headline figures beside the previous period's, with changes labelled as estimates. Each side is exactly the report that window prints alone.
  • --format markdown and --format html for reports and workstats allocate. The HTML is a single self-contained file with no scripts or external references. Both formats escape every value, stop GitHub mentions and issue links, and replace your home directory with ~.
  • A defaults block in the config file for everyday flags (dir, depth, format, providers, group_by, gap_cap, human_idle, review_credit). A flag always wins, and the output names the defaults that applied.
  • --author can be given more than once, and the config file accepts authors, so several Git identities count as you.
  • model_rates in the config file overrides or adds per-model list rates for workstats allocate, and allocate warns when the built-in rate table is more than 90 days old.
  • A warning when a tool's history files are read but yield no usable activity, which usually means the tool changed its history format.

Changed

  • The documentation moved from the README into guides under docs/: install, usage, configuration, how it works, allocate, privacy and development.
  • The transcript index is also keyed on the workstats version, so an upgrade never answers from entries an older parser wrote.
  • A config file with an invalid authors, defaults or model_rates value is refused with an error naming the key, instead of being ignored as a whole.

Fixed

  • Commits on every local branch are counted, not only those reachable from the checked-out branch.
  • The report window is decided by author date, so rebased and amended commits are no longer dropped from the period they were written in. Reports for past periods also no longer diff every later commit.
  • workstats allocate names every window flag it accepts when no window is given.

Release v1.9.0

Choose a tag to compare

@github-actions github-actions released this 02 Sep 19:58
82362c1

Summary

--project was required, so the only question allocate could answer was what one project is owed. The prior question — where did the money actually go — had no answer, and working it out meant running the command once per project and adding up the results by hand.

Naming a project still asks for a claim. Omitting it now asks for a breakdown:

$ workstats allocate --sub claude=2@1866 --sub codex=3@1992 \
    --currency NOK --vat 25 --month 2026-08 --top 6

  ALLOCATION  every project
  2026-08 · 5 subscriptions · 12,135 kr billed incl. 25% tax · basis: output tokens

  PROJECTS
  project                              claude       openai        TOTAL       %        out    tokens
  ──────────────────────────────────────────────────────────────────────────────────────────────────
  Ada                                2,072 kr     3,048 kr     5,121 kr   42.2%    111.3M   38.28B
  Chronicle.Wolverine [f81e3bee]       102 kr     1,020 kr     1,121 kr    9.2%     18.5M    7.56B
  AI                                   295 kr       433 kr       728 kr    6.0%     15.8M    7.41B
  cratis                               385 kr       261 kr       646 kr    5.3%     16.1M    5.89B
  Screenplay                            83 kr       532 kr       616 kr    5.1%     10.6M    5.07B
  Strategy                              90 kr       491 kr       581 kr    4.8%     10.2M    5.55B
  (59 smaller projects)                                        3,322 kr   27.4%

One column per plan held, so a project leaning on a single vendor is visible at a glance — Screenplay draws six times as much from OpenAI as from Claude, cratis the other way.

Added

  • workstats allocate no longer requires --project. Omitting it reports every project's slice of the spend, ranked by amount, with one column per plan held.
  • --top bounds the list and states the remainder rather than dropping it; --top 0 shows every project.

Notes for review

  • The rows reconcile exactly to what was billed, and a test pins that — a breakdown that does not add up is not a breakdown of anything.
  • Ranking is by money, not tokens. A test covers the case where a project with less total output outranks one with more because it drew on the pool backed by more plans; that is the correct answer and an easy one to get wrong.
  • A project absent from a pool contributes zero to it rather than silently borrowing another project's share. Also tested.
  • Stating the remainder follows the rule the rest of the command already uses for pruned history and unmatched project names: never a silent zero.
  • 288 tests pass (2 new). Clippy, rustfmt, MSRV 1.88 and rustdoc clean locally.

Release v1.8.0

Choose a tag to compare

@github-actions github-actions released this 02 Sep 19:51
8b0bce1

Summary

allocate assumed one price, untaxed, in dollars. None of that holds for a buyer outside the US, and each gap pushed arithmetic back onto the person the command exists to spare.

$ workstats allocate -p Ada --sub claude=2@1866 --sub codex=3@1992 \
    --currency NOK --vat 25 --month 2026-08

  2026-08 · 5 subscriptions · 12,135 kr billed incl. 25% tax · basis: output tokens

  month     family   subs    plan/mo      project         pool    share        owed
  ─────────────────────────────────────────────────────────────────────────────────
  2026-08   claude      2   2,333 kr        65.5M       147.5M    44.4%    2,072 kr
  2026-08   openai      3   2,490 kr        45.8M       112.1M    40.8%    3,048 kr
  ─────────────────────────────────────────────────────────────────────────────────
  ATTRIBUTABLE                                                    42.2%    5,121 kr

Tax is money that left the account. Vendors advertise ex-tax prices, so a Norwegian buyer paying 25% MVA was under-reporting the pool by a fifth.

Two vendors rarely cost the same. Anthropic prices in dollars and lets a card convert them; OpenAI sets a local price. Previously the only way to express that was to run the two pools separately and add them by hand — which defeats the point of the command.

No exchange rate is applied. --currency labels and formats; it converts nothing. workstats makes no network calls outside workstats update, and a rate compiled into a binary goes stale without saying so — the worst failure mode for a number about to be invoiced. State what you were charged and the arithmetic stays yours.

Added

  • --vat PERCENT adds the consumption tax applied at checkout, and the tax-inclusive unit price now appears on each row so a claim can be checked line by line against a bank statement.
  • --currency CODE states which currency --price is in and formats amounts to match — symbols where unambiguous, ISO code otherwise. No conversion is performed.
  • --sub PLAN=N@PRICE prices one vendor separately from the rest, for the common case of two vendors billing the same buyer different amounts.

Notes for review

  • A share is a usage ratio and cannot move when only a price does. A test pins that, so a pricing change can never quietly restate what the work was.
  • Plan { count, price } replaces the bare count in AllocationOptions; a vendor given no @PRICE falls back to the run's --price, which is also tested.
  • Two display bugs found while running real data: a literal $ in the cross-check that rendered $ 5,121 kr, and the ATTRIBUTABLE row sitting one column out once the plan-price column was added. Both fixed.
  • 286 tests pass (4 new). Clippy, rustfmt, MSRV 1.88 and rustdoc all clean locally.

Release v1.7.0

Choose a tag to compare

@github-actions github-actions released this 02 Sep 15:20
71ac162

Summary

Flat-rate plans are bought per vendor and spent across every project. workstats allocate answers what share of that spend one project accounts for — the question a reimbursement claim turns on.

It splits each vendor's pool separately and weighs it by how many plans that vendor holds, rather than pooling every token together. With 2 Claude and 4 Codex plans, 44% of the Claude pool and 41% of the OpenAI pool are worth different amounts; pooling the tokens would have reported a single misleading 43%.

$ workstats allocate -p Ada --sub claude=2 --sub codex=4 --month 2026-08

  ALLOCATION  Ada
  2026-08 · 6 subscriptions @ $200/mo · $1,200 billed · basis: output tokens

  month     family   subs      project         pool    share       owed
  ────────────────────────────────────────────────────────────────────
  2026-08   claude      2        65.5M       147.5M    44.4%       $178
  2026-08   openai      4        45.8M       112.1M    40.8%       $326
  ────────────────────────────────────────────────────────────────────
  ATTRIBUTABLE                                        42.0%       $504

Three failure modes share one shape — a silent zero that reads as "nothing to claim" when it means "nothing was seen" — and none of them are reported quietly:

  • Pruned history. A month whose transcripts are gone is not a month of no work. --gap-policy chooses explicitly between excluding it, claiming nothing for it, or imputing that vendor's mean share.
  • Clients billing on their own seat. A Copilot seat is not a Claude or ChatGPT plan even when it runs their models. Copilot forms its own pool instead of diluting one it was never billed to — which also stops a single stray Copilot session from making a month of missing vendor history look covered.
  • A misspelled project. Named, with the nearest real repository offered.

Every run prints all five bases side by side, so a share that depends on the metric is visible rather than chosen quietly. Human time is not the default and is marked estimated: it is inferred from prompt counts and session edges, so orchestration-heavy work books more apparent attention per real hour than one long session does.

Added

  • workstats allocate apportions flat-rate subscription spend to one project, splitting each vendor pool separately and weighting it by plans held.
  • Per-model breakdown behind every share, with models weighed by published list rate so a million Opus tokens and a million Haiku tokens are not equal claims on a plan.
  • --basis selects the measured quantity — output (default), value, wall, tokens or human — and every run cross-checks all five against each other.
  • --gap-policy makes the handling of pruned history an explicit choice rather than a silent zero.
  • Table, JSON and CSV output, composing with the existing --month, --since and --until window flags.

Notes for review

  • src/pricing.rs carries the published list rates, stamped with RATES_AS_OF so a stale table is visible in output rather than silent. They are used as relative weights and as a stated ceiling, never as an amount owed.
  • Allocation takes &[ReportRow] rather than a whole Report; it is a pure function of the grouped rows and is unit tested directly.
  • ReportRow gains #[derive(Default)] so tests can build rows without restating every field.
  • 282 tests pass (17 new). Two of them are regression tests for bugs found during development: the cross-check row for the chosen basis contradicting the headline it corroborates, and Copilot usage being counted into the vendor pool.

Release v1.6.1

Choose a tag to compare

@github-actions github-actions released this 31 Aug 10:32
4b92e4d

Summary

Apply the formatting emitted by the Rust 1.98 stable toolchain used by GitHub Actions.

Fixed

  • Make the rustfmt CI job pass under Rust 1.98 without changing behavior.

Release v1.6.0

Choose a tag to compare

@github-actions github-actions released this 31 Aug 10:24
04fea0f

Summary

Treat linked worktrees, clones of one remote, and explicitly aliased repositories as logical projects while preserving natural-repository identity where deduplication requires it.

Added

  • Add configurable project_aliases for combining distinct repositories into one product.
  • Add --explain-repository-attribution / --explain-repos with privacy-safe table and JSON evidence.
  • Persist repository identity history so deleted foreground and delegated Pi worktrees remain attributable.

Changed

  • Group repository rows by normalized local Git identity instead of checkout folder name.
  • Keep commit and file deduplication scoped to natural repositories, including TUI navigation and search.
  • Make --repo-exact prefer final display labels, then fall back to checkout folder names when no label matches.

Fixed

  • Recover linked and orphaned worktree identities without contacting remotes.
  • Reject overlapping project aliases and ambiguous history instead of choosing silently.
  • Invalidate Pi cache entries when their parent-session attribution context changes.
  • Restrict attribution evidence and counters to checkouts that contribute inside the report window.
  • Normalize HTTPS, SSH, SCP-style, file, and Windows-path remote spellings consistently.

Release v1.5.0

Choose a tag to compare

@github-actions github-actions released this 25 Aug 10:47
ab24aca

Summary

Make the human-time estimate auditable without turning reports into an attendance claim. The calculation now has one versioned source of truth, an opt-in structural ledger for people and API consumers, and explicit safeguards around block overlap, precision, and sensitive metadata.

Release notes are generated from this description:

Added

  • Add --explain-human-time for a readable table ledger and structured JSON details covering scoped/effective signals, timestamp deduplication, block boundaries, credit, clipping, and the reconciled total.
  • Add stable signal-blocks-v1 algorithm, timezone, and boundary-basis metadata to JSON reports.
  • Add privacy and reconciliation regression coverage, including exact half-millisecond ties and equal-priority timestamp collisions.

Changed

  • Calculate human-time intervals, block explanations, and the summary total through one shared path.
  • Round the final total from exact integer microseconds with ties-to-even, exposing the unrounded block subtotal and rounding adjustment for auditability.
  • Reject --explain-human-time for CSV and the TUI, whose output shapes cannot represent the one-to-many ledger without ambiguity.

Fixed

  • Reject --review-credit values greater than --human-idle, preventing separately clustered work blocks from overlapping and counting time twice.

Privacy

  • Explanation output includes structural timestamps, signal kinds, providers, and repository labels only; it excludes prompt/response text, raw session IDs, cwd/root paths, model names, and commit hashes.

Verification

  • cargo test — 232 unit tests and 10 integration tests passed.
  • cargo clippy --all-targets -- -D warnings — passed.
  • cargo fmt --check — passed.
  • git diff --check — passed.
  • Two structured validation reviews completed; all findings were addressed before opening this PR.

Release v1.4.2

Choose a tag to compare

@github-actions github-actions released this 23 Aug 12:02
fa46f11

Fixed

  • Unfiltered reports now scan retained AI sessions\u2019 locally available Git checkouts outside --dir, so a repository filter no longer changes whether authored commits are found.

Release v1.4.1

Choose a tag to compare

@github-actions github-actions released this 19 Aug 19:10
7594863

Follow-up to #18, which added the pi adapter. Completes the source support.

The gap

Pi persists !command — a shell command typed at its own prompt — with the
role bashExecution. The adapter skipped that role, so a session spent driving
a build or a test run by hand reported no human involvement at all beyond
its session edges.

The model cannot produce one of these. It reaches the shell through the bash
tool, which is persisted as a tool result and is already treated as agent
activity. So bashExecution is unambiguous keyboard evidence — more direct
than the session edges that were carrying these sessions.

It is counted in a foreground session only: inside a subagent the command
was issued by the agent driving it, not typed by anyone. !!command, which
keeps output out of the model's context, counts the same — someone still typed
it.

Measured

A session holding one typed !npm run lint:

Binary prompts human work
v1.4.0 (shipped) 0 1800 s
this PR 1 1810 s

Real local history is unchanged (203 prompts either way), because the bash
tool — not ! — is what an agent-driven session uses. This affects people who
drive Pi from the keyboard.

Privacy

The command and its output are still never read. They reach the deserializer as
ignored fields, exactly like a response body, so nothing about them can appear
in a report or the cache. A test asserts that a token placed in a command and
in its output is absent from the serialized result.

Verification

  • cargo fmt --all -- --check, cargo clippy --all-targets --locked -D warnings, cargo test --locked — all clean
  • 228 unit + 9 integration tests pass, including 2 new tests covering the
    foreground and delegated cases