feat(filter): reduce observed agent confusion in git diff/status/log - #320
Merged
Conversation
Three related fixes informed by analysing ~54k tokf events from local
tracking.db. Each addresses a distinct retry-burst pattern where the
model thrashes through flag variations trying to "escape" the filter.
git/diff
- Forced --stat had no escape hatch, so models repeatedly tried
--no-stat, -p, --no-pager, etc. (240+ workaround flags across the
dataset; biggest single burst: 51 calls in 3m41s).
- Added passthrough_args for the output-format flags the model already
reaches for: -p/--patch, --no-stat, -U<n>, --name-only/--name-status,
--numstat, --shortstat, --raw.
git/status
- Porcelain default (-unormal) collapsed untracked directories to
"?? newdir/", so models couldn't see files they had just created and
would loop. Run override now uses
`git status --porcelain=v1 -b -uall --find-renames` so every untracked
file appears individually and renames render as "R old -> new" instead
of D + ?? on separate lines.
- Branch-line replace rules now always communicate upstream sync state
([synced], [ahead N], [behind N], (no upstream)). Previously the
upstream was stripped when in sync, leaving the model unable to tell
whether commits had been pushed.
git/log
- Empty output was indistinguishable from filter-induced suppression —
models would burn 10–40 calls cycling through --all, --diff-filter=A,
--follow, --grep, --source, '-- pathspec' variants trying to escape a
non-existent filter. Added an `on_empty` hint that points at the most
likely causes (untracked pathspec, missing --all, missing --follow).
- Added passthrough for output-format flags incompatible with
--oneline -n 20: --name-only, --name-status, --shortstat, --dirstat,
-L (line-history). Other flags (--all, --follow, --diff-filter,
--grep, -S, -G, --author, --since) compose fine with the override.
Tests
- New regression cases:
git/status_test/untracked_in_new_dir.toml (-uall)
git/status_test/rename_detected.toml (--find-renames)
- Updated existing fixtures for the new branch-line markers:
git/status_test/{clean,normal,local_only_branch}.toml
- Updated git/log_test/empty.toml to assert the on_empty hint.
- test_deserialize_git_{diff,status,log} unit tests in
crates/tokf-cli/src/config/types.rs assert the new fields,
passthrough_args entries, and prefix-matching behaviour for flags
like -U3, --patch-with-stat, -L1,10:src/main.rs.
Verified
- cargo run -p tokf -- verify --scope stdlib -> 134/134 passed
- cargo test --workspace -> 1965 passed, 0 failed
- cargo clippy --workspace --all-targets -- -D warnings -> clean
- cargo fmt --check -> clean
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Contributor
Filter Verification ReportChanged Filters
All Filters Summary✅ 134/134 test cases passed across 49 filters Generated by |
This was referenced Apr 8, 2026
Merged
mpecan
added a commit
that referenced
this pull request
Apr 12, 2026
Adds a new `tokf doctor` post-hoc analysis subcommand that scans the
local `tracking.db` and reports per-filter health signals:
- retry-burst detection (same exact command run >=N times within
a window — the issue's main signal, manually applied in #320)
- workaround-flag frequency cross-referenced against each filter's
declared `passthrough_args`, surfacing flags the agent reaches
for that the filter doesn't already handle
- empty-output-then-retry pattern (the `git log` ambiguity case)
- filters with negative token savings (filtered output > raw)
- composite per-filter health score 0-100 with capped penalties
so no single signal can dominate
Closes #321 (Phase 1 only — runtime LRU surfacing and interactive
fixes are explicitly deferred to follow-up issues).
Schema migration:
- new `events.project` column populated from `tokf::history::current_project()`
so `tokf doctor` defaults to the current repo (matches the
convention `tokf history` already uses)
- `idx_events_command_timestamp` and `idx_events_filter_timestamp`
indexes for the burst-detection query path
Architecture:
- `crates/tokf-cli/src/doctor/mod.rs` — orchestration + health-score
- `crates/tokf-cli/src/doctor/queries/` — pure analysis functions
over slim `EventRow` slices fetched once from the DB
- `crates/tokf-cli/src/doctor/render/` — human (TTY-aware) + JSON
- `crates/tokf-cli/src/doctor/noise.rs` — temp-dir / test-fixture
exclusion + `command_shape()` redaction helper
- `crates/tokf-cli/src/doctor_cmd.rs` — clap entry point
- `crates/tokf-cli/src/commands.rs` — `DoctorArgs` (extracted to
keep `main.rs` under the 700-line hard limit) + `SortByCli`
Tests:
- 65 unit tests in `tokf::doctor::*` covering burst session split,
arg-varying exploration negative case, empty-retry windowing,
legacy-zero-raw exclusion, score monotonicity, sort orderings,
JSON round-trip, render snapshots
- 7 end-to-end tests in `tests/cli_doctor.rs` seeding `tracking.db`
via `TOKF_DB_PATH`, asserting on stdout/exit codes
- 4 new migration tests in `tracking/tests_project.rs`
Docs:
- `docs/diagnostics.md` gains a `## tokf doctor` section with an
example output and per-metric explanation; README regenerated.
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
8 tasks
mpecan
added a commit
that referenced
this pull request
Apr 12, 2026
## Summary Adds a new `tokf doctor` post-hoc analysis subcommand that scans the local `tracking.db` and reports per-filter health signals. Phase 1 of #321 — runtime surfacing (Phase 2) and interactive fixes (Phase 3) are explicitly deferred. Closes #321 ### What it detects - **Retry bursts** — same exact command run ≥N times within W seconds (default: ≥5 within 60s). The main signal from the #320 investigation. - **Workaround-flag frequency** — flags like `--no-stat`, `-p`, `--name-only` that appear often but aren't in the filter's `passthrough_args`. Cross-referenced against the filter discovery system. - **Empty-output retries** — filtered output looks empty, followed by the same command again within the window. Signal that the filter should use `on_empty`. - **Negative token savings** — filters where the average filtered output is _larger_ than the raw command output. - **Per-filter health score** (0–100) — composite of all four signals with capped penalties so no single dimension dominates. - **Median arg uniqueness** — per-filter ratio distinguishing confusion (low) from exploration (high). ### Schema changes - `events` table gains a `project TEXT NOT NULL DEFAULT ''` column, populated from `tokf::history::current_project()` (same function `tokf history` uses). `tokf doctor` defaults to the current project scope; `--all` to see globally. - New indexes: `idx_events_command_timestamp` and `idx_events_filter_timestamp` for burst-detection query performance. ### Architecture ``` doctor/ ├── mod.rs — DoctorReport, run(), score_filter(), median_uniqueness() ├── queries/mod.rs — pure analysis functions over &[EventRow] ├── render/mod.rs — human (TTY-aware) + JSON output └── noise.rs — temp-dir/test-fixture exclusion + command_shape() doctor_cmd.rs — clap entry point commands.rs — DoctorArgs + SortByCli (extracted to keep main.rs under 700 lines) ``` ### CLI flags | Flag | Default | Description | |---|---|---| | `--json` | off | Machine-readable JSON output | | `--burst-threshold N` | 5 | Min identical commands to count as a burst | | `--window SECS` | 60 | Time window for burst detection | | `--filter NAME` | all | Scope report to one filter (`git/diff` or `git diff` both work) | | `--project PATH` | cwd project | Scope to specific project | | `--all` | off | Show events from all projects | | `--include-noise` | off | Don't filter out temp-dir/test-fixture events | | `--sort {health,bursts,tokens}` | health | Sort order for the per-filter table | | `--no-color` | off | Disable ANSI colour codes | ## Test plan - [x] `cargo fmt -- --check` clean - [x] `cargo clippy --workspace --all-targets -- -D warnings` clean - [x] `cargo test -p tokf` — **1500+ passed**, 0 failed - [x] 68 unit tests in `tokf::doctor::*` covering burst detection, workaround flags, empty retries, negative savings, score monotonicity, sort orderings, rendering, median uniqueness, noise filtering - [x] 9 end-to-end tests in `tests/cli_doctor.rs`: empty DB, burst detection, JSON mode, `--all`, `--include-noise`, `--filter` scoping, `--filter` slash-form normalization, default project scoping, `--help` sanity - [x] 4 migration tests in `tracking/tests_project.rs` - [x] Multi-agent review (4 parallel): Acceptance Criteria (16/16 PASS after fix), Code Quality (CLEAN/MINOR), Architecture (CLEAN), Test Coverage (CLEAN) - [x] Smoke-tested against real `tracking.db` — correctly identifies git/diff with 67 burst sessions, 4056 events, health score 20 ## Commits 1. `feat(cli): tokf doctor — detect filters causing agent confusion` — main implementation 2. `fix(cli): add median arg-uniqueness metric to tokf doctor` — addresses MAJOR review finding 3. `test(cli): add integration tests for --filter normalization and --project default` — addresses MINOR review findings 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]>
mpecan
pushed a commit
that referenced
this pull request
Apr 13, 2026
🤖 I have created a release *beep* *boop* --- <details><summary>e2e-tests: 0.1.28</summary> ### Dependencies </details> <details><summary>tokf-common: 0.2.40</summary> ## [0.2.40](tokf-common-v0.2.39...tokf-common-v0.2.40) (2026-04-13) ### Features * **cli:** tokf doctor — detect filters causing agent confusion ([#329](#329)) ([694c66c](694c66c)) * **filter:** generic [tree] transform for path-list outputs ([#325](#325)) ([322e133](322e133)) </details> <details><summary>tokf-filter: 0.2.40</summary> ## [0.2.40](tokf-filter-v0.2.39...tokf-filter-v0.2.40) (2026-04-13) ### Features * **filter:** generic [tree] transform for path-list outputs ([#325](#325)) ([322e133](322e133)) ### Dependencies * The following workspace dependencies were updated * dependencies * tokf-common bumped from 0.2.39 to 0.2.40 </details> <details><summary>tokf-hook-types: 0.2.40</summary> ## [0.2.40](tokf-hook-types-v0.2.39...tokf-hook-types-v0.2.40) (2026-04-13) ### Features * **hook:** forward permission decision reasons to AI tools ([#317](#317)) ([cf6b5d7](cf6b5d7)) </details> <details><summary>tokf-server: 0.2.40</summary> ## [0.2.40](tokf-server-v0.2.39...tokf-server-v0.2.40) (2026-04-13) ### Miscellaneous * **tokf-server:** Synchronize workspace versions ### Dependencies * The following workspace dependencies were updated * dependencies * tokf-common bumped from 0.2.39 to 0.2.40 * tokf-filter bumped from 0.2.39 to 0.2.40 </details> <details><summary>catalog-types: 0.2.40</summary> ## [0.2.40](catalog-types-v0.2.39...catalog-types-v0.2.40) (2026-04-13) ### Miscellaneous * **catalog-types:** Synchronize workspace versions </details> <details><summary>tokf: 0.2.40</summary> ## [0.2.40](tokf-v0.2.39...tokf-v0.2.40) (2026-04-13) ### Features * **cli:** tokf doctor — detect filters causing agent confusion ([#329](#329)) ([694c66c](694c66c)) * **filter:** generic [tree] transform for path-list outputs ([#325](#325)) ([322e133](322e133)) * **filter:** reduce observed agent confusion in git diff/status/log ([#320](#320)) ([6992712](6992712)) * **hook:** forward permission decision reasons to AI tools ([#317](#317)) ([cf6b5d7](cf6b5d7)) ### Bug Fixes * **rewrite:** skip filtering when output is redirected to a file ([#323](#323)) ([dd66681](dd66681)) ### Dependencies * The following workspace dependencies were updated * dependencies * tokf-common bumped from 0.2.39 to 0.2.40 * tokf-filter bumped from 0.2.39 to 0.2.40 * tokf-hook-types bumped from 0.2.39 to 0.2.40 </details> --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). Co-authored-by: repository-butler[bot] <166800726+repository-butler[bot]@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Three related fixes informed by analysing ~54k tokf events from local
tracking.db. Each addresses a distinct retry-burst pattern where an agent thrashes through flag variations trying to "escape" a filter.git/diff— addedpassthrough_argsso the model can get-p/--patch/--no-stat/-U<n>/--name-only/--name-status/--numstat/--shortstat/--rawcontent out of the forced--statoverride. The previous behaviour caused 51-call retry bursts (model trying every workaround it knew, includinggit --no-pager diff).git/status—runoverride now uses--porcelain=v1 -b -uall --find-renamesso untracked files in newly-created directories appear individually (instead of?? new_dir/) and renames render asR old -> new(instead ofD old+?? newlooking unrelated). Branch-line replace rules now always communicate upstream sync state:[synced],[ahead N],[behind N],(no upstream)— previously the upstream was stripped when in sync, so the model couldn't tell whether commits had been pushed.git/log— empty output now emits anon_emptyhint pointing at the most likely causes (untracked pathspec, missing--all, missing--follow) instead of nothing. Previously the model would burn 10–40 calls cycling through--all,--diff-filter=A,--follow,--grep,--source,'-- pathspec'variants trying to escape a non-existent filter — when the actual answer was "the file is untracked, rungit ls-files". Also added passthrough for output-format flags incompatible with--oneline -n 20:--name-only,--name-status,--shortstat,--dirstat,-L.Evidence (from
tracking.db, 2026-02-18 → 2026-04-08)git/diff-p,--no-stat,git --no-pager diff,--no-index /dev/null <file>)git/status?? new_dir/collapses path of just-created filegit/log--all,--diff-filter=A,--source,--follow, pathspecs; gets empty back; can't tell whyAcross the dataset,
git/diffcalls contained 86 explicit--no-stat, 127-p/--patch, and 27--no-pagerworkaround flags — clear "I am trying to escape your filter" signals from agents.Test plan
cargo run -p tokf -- verify git/diff --scope stdlib→ 4/4 passedcargo run -p tokf -- verify git/status --scope stdlib→ 9/9 passed (incl. 2 new regression tests for-ualland--find-renames)cargo run -p tokf -- verify git/log --scope stdlib→ 2/2 passed (empty test now asserts theon_emptyhint)cargo run -p tokf -- verify --scope stdlib→ 134/134 passedcargo test --workspace→ 1965 passed, 0 failed, 166 ignoredcargo clippy --workspace --all-targets -- -D warnings→ cleancargo fmt --check→ cleanbash scripts/generate-readme.sh→ README up to date with new docs rowsFiles
Filters
crates/tokf-cli/filters/git/diff.toml— addedpassthrough_argscrates/tokf-cli/filters/git/status.toml— newrunoverride + newreplacerulescrates/tokf-cli/filters/git/log.toml— addedpassthrough_args+on_emptyhintTests
crates/tokf-cli/filters/git/status_test/untracked_in_new_dir.toml(new)crates/tokf-cli/filters/git/status_test/rename_detected.toml(new)crates/tokf-cli/filters/git/status_test/{clean,normal,local_only_branch}.toml(updated for new branch markers)crates/tokf-cli/filters/git/log_test/empty.toml(assertson_emptyhint)crates/tokf-cli/src/config/types.rs—test_deserialize_git_{diff,status,log}updated to assert new fields, passthrough entries, and prefix-matching behaviour for-U3,--patch-with-stat,-L1,10:src/main.rsDocs
docs/getting-started.md— stdlib filter table entries forgit/diff,git/status,git/logREADME.md— regeneratedFollow-up (separate work)
[tree]filter transform for path-list outputs (would further reduce tokens forgit/statusandgit/diff --name-onlyonce landed). Filed but not implemented in this PR.🤖 Generated with Claude Code