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
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ Token Meter reads local agent traces, calculates clearly labeled usage estimates
- Keep `meter.py` and `token_meter_mcp.py` on the Python standard library.
- Keep the dashboard local-only; do not add hosted assets, analytics, or telemetry.
- Never output, commit, persist, or transmit prompts, responses, reasoning, tool contents, credentials, account data, or raw traces.
- Work insights are the only sanctioned consumer of typed user-turn text, the preceding assistant-reply tail (600 characters), per request, the basenames of files the agent changed, and the subjects (200 characters) of the user's own pushed commits for Git work types: they stay in the bounded in-memory queue, are sent only to the validated loopback Ollama URL, and only salted keys, labels, and content-free counts (costs, tool and command kinds, file categories) persist. Do not add other consumers.
- Work insights are the only sanctioned consumer of typed user-turn text, the preceding assistant-reply tail (600 characters), per request, the basenames of files the agent changed, and, for Git work types, the subject and first body line (200 characters each) of the user's own pushed commits with content-free counts of the kinds of files and lines each changed: they stay in the bounded in-memory queue, are sent only to the validated loopback Ollama URL, and only salted keys, labels, and content-free counts (costs, tool and command kinds, file categories) persist. Do not add other consumers.
- Provider-account requests must remain narrow, bounded, sanitized, and read-only.
- Cursor databases, transcripts, and request logs are read-only inputs.
- Label estimates as estimates. Unavailable evidence must not become a measured zero.
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,8 @@ skills, so the package targets Claude Code.
`~/.token-meter/mcp-evidence-cache.json`, never paths, titles, or text. It is safe to delete.
- Costs are estimates. Codex uses public API rates, which can differ from subscription billing.
- Work insights, when on, send typed prompts, the end of the reply before each one,
changed file names, and your pushed commits' subject lines only to the local Ollama;
changed file names, and your pushed commits' messages (subject and first body line) with
counts of the kinds of files and lines they changed only to the local Ollama;
Token Meter stores labels and counts, never text.

Full details: [User guide](specs/USER_GUIDE.md#data-and-evidence) ·
Expand Down
2 changes: 1 addition & 1 deletion dashboard/js/git.js
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,7 @@ function deliveryTypeNote(work){
if(work.available!==true)return 'No pushed commits in this period.';
const labeled=Number(work.labeled_commits)||0,total=Number(work.commits)||0;
if(!work.classifier_enabled&&labeled<total)return `${f(labeled)} of ${f(total)} commits labeled · turn on Work insights in Settings to label the rest locally`;
return `${f(labeled)} of ${f(total)} commits labeled by the local Work model${labeled<total?' · the rest are not labeled yet':''}`;
return `${f(labeled)} of ${f(total)} commits labeled from their messages${labeled<total?' · the rest are not labeled yet':''}`;
}
function deliveryTypeAxisMax(value){
// Gridlines sit at 0, half, and the top, so the top is even: every tick is a whole commit or line.
Expand Down
2 changes: 1 addition & 1 deletion dashboard/markup/git-learn-tools.html
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
<div class=deliveryDayInspector id=d-day-inspector hidden aria-live=polite><div class=deliveryDayIdentity><span>Selected day</span><strong id=d-day-title>--</strong><small id=d-day-note></small></div><div class=deliveryDayMetrics id=d-day-metrics></div><div class=deliveryDayActions><button id=d-day-prev type=button>&larr; Previous</button><button id=d-day-next type=button>Next &rarr;</button><button class=deliveryDayClose id=d-day-close type=button aria-label="Close selected day">&times;</button></div></div>
</section>
<section class="card deliveryVisual deliveryTypes" aria-labelledby=d-type-title>
<div class=deliveryVisualHead><div><h2 class=fieldtip id=d-type-title tabindex=0 aria-description="Estimate. With Work insights on, the local model reads each pushed commit&rsquo;s message once and keeps only its kind of work, never the text." data-tip="Estimate. With Work insights on, the local model reads each pushed commit&rsquo;s message once and keeps only its kind of work, never the text.">Work pushed by type</h2><p id=d-type-note>Reading labels&hellip;</p></div><div class=seg id=d-type-metric role=group aria-label="Measure work pushed by"><button type=button class=on data-delivery-type-metric=commits aria-pressed=true>Commits</button><button type=button data-delivery-type-metric=lines aria-pressed=false>Lines</button></div></div>
<div class=deliveryVisualHead><div><h2 class=fieldtip id=d-type-title tabindex=0 aria-description="Estimate. A conventional prefix such as fix: is used as written. Otherwise, with Work insights on, the local model reads the commit&rsquo;s message and the kinds of files it changed, and keeps only its kind of work, never the text." data-tip="Estimate. A conventional prefix such as fix: is used as written. Otherwise, with Work insights on, the local model reads the commit&rsquo;s message and the kinds of files it changed, and keeps only its kind of work, never the text.">Work pushed by type</h2><p id=d-type-note>Reading labels&hellip;</p></div><div class=seg id=d-type-metric role=group aria-label="Measure work pushed by"><button type=button class=on data-delivery-type-metric=commits aria-pressed=true>Commits</button><button type=button data-delivery-type-metric=lines aria-pressed=false>Lines</button></div></div>
<div class=workMixLegend id=d-type-legend aria-label="Work type legend"></div>
<div class=deliveryChartViewport><div class=deliveryChartWrap id=d-type-wrap><svg class=deliveryChart id=d-type-chart viewBox="0 0 1040 270" preserveAspectRatio=none role=img tabindex=0 aria-label="Pushed work per day by type" aria-describedby=d-type-tip></svg><div class="deliveryChartTip chartTip" id=d-type-tip role=tooltip aria-live=polite hidden></div></div></div>
</section>
Expand Down
2 changes: 1 addition & 1 deletion specs/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ Token Meter reads local agent traces, calculates clearly labeled usage estimates
- Keep `meter.py` and `token_meter_mcp.py` on the Python standard library.
- Keep the dashboard local-only; do not add hosted assets, analytics, or telemetry.
- Never output, commit, persist, or transmit prompts, responses, reasoning, tool contents, credentials, account data, or raw traces.
- Work insights are the only sanctioned consumer of typed user-turn text, the preceding assistant-reply tail (600 characters), per request, the basenames of files the agent changed, and the subjects (200 characters) of the user's own pushed commits for Git work types: they stay in the bounded in-memory queue, are sent only to the validated loopback Ollama URL, and only salted keys, labels, and content-free counts (costs, tool and command kinds, file categories) persist. Do not add other consumers.
- Work insights are the only sanctioned consumer of typed user-turn text, the preceding assistant-reply tail (600 characters), per request, the basenames of files the agent changed, and, for Git work types, the subject and first body line (200 characters each) of the user's own pushed commits with content-free counts of the kinds of files and lines each changed: they stay in the bounded in-memory queue, are sent only to the validated loopback Ollama URL, and only salted keys, labels, and content-free counts (costs, tool and command kinds, file categories) persist. Do not add other consumers.
- Provider-account requests must remain narrow, bounded, sanitized, and read-only.
- Cursor databases, transcripts, and request logs are read-only inputs.
- Label estimates as estimates. Unavailable evidence must not become a measured zero.
Expand Down
2 changes: 1 addition & 1 deletion specs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -714,7 +714,7 @@ No public HTTP, native, MCP, or telemetry projection may contain:
- unbounded trace rows or provider-controlled payloads.

Work insights read typed user-turn text, the preceding assistant-reply tail, and
the subjects of the user's own pushed commits (Git work types) only in process memory, only when the user enables them, and send them only to
the subject, first body line, and changed-file kind counts of the user's own pushed commits (Git work types) only in process memory, only when the user enables them, and send them only to
a loopback (`127.0.0.1`, `::1`; `localhost` pinned to `127.0.0.1`) plain-HTTP
Ollama endpoint with redirects refused; remote or cloud Ollama models are
rejected by a local `/api/tags` check before every model request. The ledger, logs, and
Expand Down
6 changes: 4 additions & 2 deletions specs/SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,10 @@ The Git page is a local-only Git reader. It inspects bounded remote-tracking ref
for successful-push markers and never fetches, pulls, pushes, updates refs, or
contacts a remote. Its SQLite ledger stores salted repository/object keys,
timestamped clear baselines, numeric text-line totals, coverage state, and enum
commit work-type labels only. With Work insights on, a commit subject is read into
the Work insights in-memory queue and sent only to the loopback model; it is never stored.
commit work-type labels only. With Work insights on, a commit's subject and first
body line, with counts of the kinds of files and lines it changed (never paths or names), are
read into the Work insights in-memory queue and sent only to the loopback model; they
are never stored.
Public project suffixes use the same per-machine salt. It must not
persist or project paths, remote/ref names, identities, messages, filenames,
diff content, raw object IDs, subprocess output, or exceptions.
Expand Down
8 changes: 6 additions & 2 deletions specs/USER_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -429,8 +429,12 @@ remotes, branches, identities, messages, file names, or diff content.

**Work pushed by type** stacks each day's pushed commits, or lines, by the kind
of work: features, fixes, refactoring, performance, tests, docs, and build & ops.
With **Settings → Work insights** on, the local model reads each of your pushed
commits' subject line once, in memory, and Token Meter keeps only the label.
With **Settings → Work insights** on, a commit whose subject starts with a
conventional type (`feat:`, `fix(ui):`, `docs:`, `chore:`, and so on) is labeled
with that type as written. For other commits the local model reads the subject,
the first line of the body, and how many files of each kind (code, UI, tests,
docs, config, and so on) and lines changed, once, in memory; Token Meter keeps only the label.
When an update improves the labeling, earlier labels show as not labeled yet until they are redone.
Commits it was unsure about show as Unclear; commits not labeled yet (Work
insights off, or still queued) stay visible as their own segment, never as zero.
Deleting Work labels also deletes these labels. Missing
Expand Down
86 changes: 66 additions & 20 deletions specs/WORK_INSIGHTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -226,27 +226,73 @@ History to backfill: 30, 90 (default), 365 days, or all.
### Pushed commits (Git page)

The same worker labels the kind of work in each of the user's own pushed,
non-merge commits for the Git page's **Work pushed by type** chart. The Git scan
reads only the subject line (`%s`, whitespace-collapsed, 200 characters) of a
commit it records, or of an already-recorded commit that has no label for the
current question version, within the history-to-backfill window; at most 200
subjects per five-minute scan. Subjects wait in a separate in-memory queue (256
commits) that alternates with session requests, so neither starves, and they go
through the same pause, pace, memory, CPU, battery, and local-model checks.

One question, `commit_type` (version `c1`), asks for feature, fix, refactor,
perf, test, docs, or ops, in both option orders averaged, after six invented
worked examples. Option descriptions carry verb cues ("often starts with Add",
non-merge commits for the Git page's **Work pushed by type** chart.

For each commit it records, and each already-recorded commit with no label for
the current question version within the history-to-backfill window (at most 200
per five-minute scan), the Git scan reads one
`git show --numstat --format=%s%x00%b%x00`. If listing a very large commit's files
fails or times out, it reads the message alone (`show -s`).

A subject that starts with a lowercase Conventional Commits type (`feat:`,
`fix(scope)!:`, `docs:`, `style:`, `refactor:`, `perf:`, `test:`, `build:`, `ci:`,
`chore:`) is then labeled with that type at once: confidence 1, model `prefix`, no
model call and no queue slot. The spec itself is case-insensitive; capitalised
prefixes (`Fix:`) are left to the model because they are often plain sentences,
as are package-style ones (`testing:`, `config:` in Go or Linux history) and
`revert:` or `wip:`. For the rest it keeps the subject and the first body line that is not a
trailer (any `…-by:` key such as `Co-authored-by` or `Reviewed-by`, `Change-Id`,
`Cc`, and `Fixes:`/`Closes:`/`Refs:` followed by an issue number or commit id),
whitespace-collapsed, 200 characters each. It reduces the
numstat paths, inside the scan, to a content-free line of file-kind counts and the
commit's total added and deleted lines, such as
`Changed files: 3 code, 1 tests (+120 / -30 lines).`, using the Work file kinds on
each file's new name (quoted non-ASCII paths unquoted, renames resolved). Paths and
file names never leave the scan. When only the message could be read, the line
says the files were not listed, never that none changed. These wait in a separate in-memory
queue (256 commits) that alternates with session requests, so neither starves,
and go through the same pause, pace, memory, CPU, battery, and local-model checks.

One question, `commit_type` (version `c2`), asks for feature, fix, refactor,
perf, test, docs, or ops from the commit message first and the changed files as
supporting evidence, in both option orders averaged, after six invented worked
examples. Option descriptions carry verb cues ("often starts with Add",
"Fix, Stop, Prevent"). Below 0.5 confidence the label is `unclear`. Only the
type, confidence, version, and model digest are stored, in the Git ledger keyed
by the salted commit key; a failed commit is offered again by later scans up to
three attempts. A version bump makes older labels read as not labeled yet.

Calibration (one local machine, 609 own commits from three repositories, 90
days; reference is the author's own conventional-commit prefix): 95.9% agreement
with the prefix kept in the subject, and 58% with the prefix stripped (a proxy
for free-form subjects), up from 89.5% and 34% before the verb cues and
examples. Each commit takes two model calls.
type, confidence, version, and model digest (or `prefix`) are stored, in the Git
ledger keyed by the salted commit key; a failed commit is offered again by later
scans up to three attempts. A version bump makes older labels read as not labeled
yet, so `c1` labels are redone once after the update; the chart shows those commits
as not labeled yet until then.

Calibration. The figures estimate how the model does on commits that reach it,
the ones without a recognised prefix; prefixed commits take their declared type.
No naturally prefix-free commits had a reference label, so the test set is
prefixed commits with the prefix removed. The evaluation ran
once on one machine with `token-meter-gemma` (Gemma 4 E4B), using `commit_state`'s
exact three-line state and this question. Its harness reads private Git history,
so it stayed in a scratch folder and was deleted with the data; only these
aggregates were kept.

- Data: 664 of one author's own non-merge commits, 180 days, three repositories.
- Reference: the type each commit's own prefix declares. The mapping was the ten
spec types above, matched case-insensitively, plus `readme:` as docs. The prefix
was removed from the subject before labeling. A stripped
subject is a proxy for a free-form message. It is written by someone who
uses prefixes, so it may read differently from naturally free-form messages.
- Split: by commit hash into dev and held-out halves; prompt variants were chosen
on the first 120 dev commits and scored once on the first 200 held-out commits.

Held-out, both option orders, 200 commits:

| Model input | Agreement | With a body (167) | No body (33) |
|---|---|---|---|
| Subject only (`c1`) | 57.5% | 61% | 39% |
| Subject, first body line, file kinds (`c2`) | 72.0% | 73% | 67% |

The no-body row is small (about ±16 points), so it shows direction only. On the dev
half, adding file basenames gave no gain (65% either way) and a measured-prior
correction gained under one point, so neither shipped. Each model-labeled commit
takes two calls.

## 4. From labels to outcomes

Expand Down
Loading
Loading