#795·fff

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

Author: jms830Created Aug 17, 2026Updated Sep 1, 2026
Labelstriaged

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):

javascript
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):

javascript
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 [<relpath>#<TAG>] 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).