Skip to content

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

Description

@jms830

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

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

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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions