Skip to content

feat(filter): reduce observed agent confusion in git diff/status/log - #320

Merged
mpecan merged 1 commit into
mainfrom
feat/git-filter-confusion-fixes
Apr 8, 2026
Merged

feat(filter): reduce observed agent confusion in git diff/status/log#320
mpecan merged 1 commit into
mainfrom
feat/git-filter-confusion-fixes

Conversation

@mpecan

@mpecan mpecan commented Apr 8, 2026

Copy link
Copy Markdown
Owner

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 — added passthrough_args so the model can get -p/--patch/--no-stat/-U<n>/--name-only/--name-status/--numstat/--shortstat/--raw content out of the forced --stat override. The previous behaviour caused 51-call retry bursts (model trying every workaround it knew, including git --no-pager diff).
  • git/statusrun override now uses --porcelain=v1 -b -uall --find-renames so untracked files in newly-created directories appear individually (instead of ?? new_dir/) and renames render as R old -> new (instead of D old + ?? new looking 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 an on_empty hint 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, run git 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)

Filter Biggest burst Burst pattern
git/diff 51 calls in 3m41s model tries every output-format variant (-p, --no-stat, git --no-pager diff, --no-index /dev/null <file>)
git/status observed cycling on missing files in new dirs (per maintainer feedback) ?? new_dir/ collapses path of just-created file
git/log 44 calls / 22 calls in single bursts model passes --all, --diff-filter=A, --source, --follow, pathspecs; gets empty back; can't tell why

Across the dataset, git/diff calls contained 86 explicit --no-stat, 127 -p/--patch, and 27 --no-pager workaround 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 passed
  • cargo run -p tokf -- verify git/status --scope stdlib → 9/9 passed (incl. 2 new regression tests for -uall and --find-renames)
  • cargo run -p tokf -- verify git/log --scope stdlib → 2/2 passed (empty test now asserts the on_empty hint)
  • cargo run -p tokf -- verify --scope stdlib134/134 passed
  • cargo test --workspace1965 passed, 0 failed, 166 ignored
  • cargo clippy --workspace --all-targets -- -D warnings → clean
  • cargo fmt --check → clean
  • bash scripts/generate-readme.sh → README up to date with new docs rows

Files

Filters

  • crates/tokf-cli/filters/git/diff.toml — added passthrough_args
  • crates/tokf-cli/filters/git/status.toml — new run override + new replace rules
  • crates/tokf-cli/filters/git/log.toml — added passthrough_args + on_empty hint

Tests

  • 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 (asserts on_empty hint)
  • crates/tokf-cli/src/config/types.rstest_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.rs

Docs

  • docs/getting-started.md — stdlib filter table entries for git/diff, git/status, git/log
  • README.md — regenerated

Follow-up (separate work)

🤖 Generated with Claude Code

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]>
@repository-butler

Copy link
Copy Markdown
Contributor

Filter Verification Report

Changed Filters

Filter Status Tests Passed Failed
git/diff 4 4 0
git/log 2 2 0
git/status 9 9 0

All Filters Summary

✅ 134/134 test cases passed across 49 filters


Generated by tokf verify

@mpecan
mpecan merged commit 6992712 into main Apr 8, 2026
5 checks passed
@mpecan
mpecan deleted the feat/git-filter-confusion-fixes branch April 8, 2026 15:28
@repository-butler repository-butler Bot mentioned this pull request Apr 8, 2026
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]>
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>
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