dmtrKovalenko / dmtrKovalenko/fff

pi-fff: emit hashline [path#TAG] headers so ffgrep output is directly editable in omp

Open
#795 2 comments 0 reactions 0 assignees View on GitHub
triaged
Dominant language
Rust
Stars
10.7k
Forks
446
Avg merge
1d 21h
Merged PRs (30d)
39

Description

### Summary

`ffgrep` / `fffind` output carries no hashline `[path#TAG]` header, so in oh-my-pi (omp) every fff hit needs a follow-up native `read` before `edit` can anchor. That follow-up round trip spends most of the token saving fff just earned.

The fix needs **no session access** — omp's hashline tag is derived from file bytes alone. `pi-fff` can mint it locally.

### Why this is cheap

Tag recipe (`@oh-my-pi/hashline/src/format.ts:108-121`):

```js
const norm = text.replace(/[ \t\r]+(?=\n|$)/g, "");
const tag = (Bun.hash.xxHash32(norm, 0) & 0xffff).toString(16).padStart(4, "0").toUpperCase();
```

Apply gate consults **no** snapshot store (`@oh-my-pi/hashline/src/patcher.ts:688,728`):

```js
const liveMatches = expected !== undefined && computeFileHash(normalized) === expected;
if (expected === undefined || liveMatches) { /* applyEdits */ }
```

Verified empirically 2026-08-17 on omp 0.10.5-era build: hand-computed a tag (`DD92`) in a REPL for a file the agent had **never `read`**, issued `[path#DD92] PUT 2.=2:` and the edit applied, returning a fresh tag. So emitting `[relpath#TAG]` plus `N: text` rows from the bytes fff already has in hand makes fff output directly editable.

### Proposed change

When the host is omp (or unconditionally, behind a flag), prefix each file's match group in `ffgrep` output with `[#]` and format rows as `N: text`, matching omp's native `grep`/`read` shape (`src/tools/grep.ts:1466` mints the same header).

### Caveat worth encoding

If `edit.enforceSeenLines` is `true` (default `false`, `settings-schema.ts:3239-3241`), the stricter guard additionally wants seen-line provenance via `recordSeenLinesFromBody` (`src/edit/file-snapshot-store.ts:141`). That lives on `AgentSession.fileSnapshotStore`, and `ExtensionContext` exposes `sessionManager`, `modelRegistry`, `cwd`, `ui`, `model`, `models` — never the session (`extensibility/extensions/runner.ts:1045-1081`). So an extension **cannot** register provenance.

Fallback for that configuration only: register under the builtin name (override mode) and delegate to the native implementation via `ctx.invokeTool`, which is wired only when the extension tool name shadows a builtin (`extensibility/extensions/types.ts:497-509`, `wrapper.ts:69-86`). Note override mode is currently affected by #792.

### Prior art

Searched both `dmtrKovalenko/fff` and `can1357/oh-my-pi` — 0 hits for the tag gap. Related but distinct: #737 / PR #741 (dynamic `${toolNames.grep}` in `promptGuidelines`), #792 / PR #793 (tools registered before startup mode known).

Contributor guide

No contributing guide indexed for this repository

Research direction

Start by tracing the ffgrep and fffind output paths, then compare their format with src/tools/grep.ts and the tag recipe in @oh-my-pi/hashline/src/format.ts. Verify the generated [relpath#TAG] headers and N: text rows against omp editing, while preserving the documented enforceSeenLines limitation and override-mode caveat.

Written by the indexing model from the issue text.

Assessment

Tech stack
bun, rust
Domain
cli, tooling
Issue type
Feature
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
55/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.