From d0572d0758e9c313f1ed1c893e85f5fa473f90db Mon Sep 17 00:00:00 2001 From: seekskyworld Date: Sun, 13 Sep 2026 14:10:03 +0800 Subject: [PATCH] refactor(git-read): use shared search seams Move bounded search output/process implementations into extensions/shared so file-search and git-read depend on an owner-neutral module. Add an import-boundary regression so shared cannot depend on feature extensions. --- extensions/file-search/index.ts | 10 +++++++-- extensions/git-read/index.ts | 2 +- extensions/git-read/src/process.ts | 6 ++--- .../src/output.ts => shared/search-output.ts} | 2 +- .../process.ts => shared/search-process.ts} | 5 ++++- tests/extensions/file-search/index.spec.ts | 4 ++-- .../extensions/shared/import-boundary.test.ts | 22 +++++++++++++++++++ 7 files changed, 41 insertions(+), 10 deletions(-) rename extensions/{file-search/src/output.ts => shared/search-output.ts} (97%) rename extensions/{file-search/src/process.ts => shared/search-process.ts} (99%) create mode 100644 tests/extensions/shared/import-boundary.test.ts diff --git a/extensions/file-search/index.ts b/extensions/file-search/index.ts index 9b86ee96..b070f315 100644 --- a/extensions/file-search/index.ts +++ b/extensions/file-search/index.ts @@ -42,7 +42,10 @@ import { type PlatformTarget, type ResolvedBinary, } from "./src/binaries.ts"; -import { formatCapturedOutput, type CapturedOutput } from "./src/output.ts"; +import { + formatCapturedOutput, + type CapturedOutput, +} from "../shared/search-output.ts"; import { FD_PARAMETER_DESCRIPTIONS, FD_PROMPT_GUIDELINES, @@ -53,7 +56,10 @@ import { RG_PROMPT_SNIPPET, RG_TOOL_DESCRIPTION, } from "./src/prompt.ts"; -import { discardCapturedOutput, executeSearchProcess } from "./src/process.ts"; +import { + discardCapturedOutput, + executeSearchProcess, +} from "../shared/search-process.ts"; import { OPENPI_TOOL_SURFACE, patchOwnedTools, diff --git a/extensions/git-read/index.ts b/extensions/git-read/index.ts index fe02239e..58c27c14 100644 --- a/extensions/git-read/index.ts +++ b/extensions/git-read/index.ts @@ -20,7 +20,7 @@ import type { import { Text } from "@earendil-works/pi-tui"; import { Cause, Effect, Exit } from "effect"; import { Type } from "typebox"; -import { formatCapturedOutput } from "../file-search/src/output.ts"; +import { formatCapturedOutput } from "../shared/search-output.ts"; import { sanitizeTerminalText } from "../shared/terminal-text.ts"; import { OPENPI_TOOL_SURFACE, diff --git a/extensions/git-read/src/process.ts b/extensions/git-read/src/process.ts index ae0c5960..2782e139 100644 --- a/extensions/git-read/src/process.ts +++ b/extensions/git-read/src/process.ts @@ -1,7 +1,7 @@ /** * Bounded git process execution for the read-only git tools. * - * Reuses the file-search capture discipline: a preview is retained in memory + * Reuses the shared bounded capture discipline: a preview is retained in memory * under pi's standard truncation limits while the complete output (up to the * 10 MiB capture cap) streams to a temporary file, and the process group is * terminated when the cap is exceeded. @@ -9,11 +9,11 @@ import * as NodeServices from "@effect/platform-node/NodeServices"; import { Data, Effect } from "effect"; -import type { CapturedOutput } from "../../file-search/src/output.ts"; +import type { CapturedOutput } from "../../shared/search-output.ts"; import { discardCapturedOutput, executeSearchProcess, -} from "../../file-search/src/process.ts"; +} from "../../shared/search-process.ts"; import { GIT_TIMEOUT_MS } from "./args.ts"; export const GIT_CAPTURE_MAX_BYTES = 10 * 1024 * 1024; diff --git a/extensions/file-search/src/output.ts b/extensions/shared/search-output.ts similarity index 97% rename from extensions/file-search/src/output.ts rename to extensions/shared/search-output.ts index e33c2769..c6a58b5a 100644 --- a/extensions/file-search/src/output.ts +++ b/extensions/shared/search-output.ts @@ -1,5 +1,5 @@ /** - * Shared output shaping for the fd and rg tools: standard pi truncation + * Shared output shaping for bounded search and git-read tools: standard pi truncation * (2000 lines / 50KB) with complete output persisted to a temp file up to the * documented 10 MiB capture limit. */ diff --git a/extensions/file-search/src/process.ts b/extensions/shared/search-process.ts similarity index 99% rename from extensions/file-search/src/process.ts rename to extensions/shared/search-process.ts index f7674645..c3079f68 100644 --- a/extensions/file-search/src/process.ts +++ b/extensions/shared/search-process.ts @@ -8,7 +8,10 @@ import { truncateHead, } from "@earendil-works/pi-coding-agent"; import { Data, Effect, FileSystem } from "effect"; -import { COMPLETE_OUTPUT_MAX_BYTES, type CapturedOutput } from "./output.ts"; +import { + COMPLETE_OUTPUT_MAX_BYTES, + type CapturedOutput, +} from "./search-output.ts"; const STDERR_MAX_BYTES = 64 * 1024; diff --git a/tests/extensions/file-search/index.spec.ts b/tests/extensions/file-search/index.spec.ts index 7a8e4352..a46e0c32 100644 --- a/tests/extensions/file-search/index.spec.ts +++ b/tests/extensions/file-search/index.spec.ts @@ -30,8 +30,8 @@ import { COMPLETE_OUTPUT_MAX_BYTES, formatCapturedOutput, formatOutput, -} from "../../../extensions/file-search/src/output.ts"; -import { executeSearchProcess } from "../../../extensions/file-search/src/process.ts"; +} from "../../../extensions/shared/search-output.ts"; +import { executeSearchProcess } from "../../../extensions/shared/search-process.ts"; import { expandedPreview, installNotifications, diff --git a/tests/extensions/shared/import-boundary.test.ts b/tests/extensions/shared/import-boundary.test.ts new file mode 100644 index 00000000..8c919858 --- /dev/null +++ b/tests/extensions/shared/import-boundary.test.ts @@ -0,0 +1,22 @@ +import assert from "node:assert/strict"; +import { readdir, readFile } from "node:fs/promises"; +import { join } from "node:path"; +import test from "node:test"; + +const SHARED_DIR = join(import.meta.dirname, "../../../extensions/shared"); +const FEATURE_IMPORT = + /from\s+["'](?:\.\.\/(?!shared\/)[A-Za-z0-9._-]+|.*extensions\/(?!shared\/))/u; + +test("shared modules do not import feature extension implementations", async () => { + const files = (await readdir(SHARED_DIR)).filter((name) => + name.endsWith(".ts"), + ); + assert.ok(files.includes("search-output.ts")); + assert.ok(files.includes("search-process.ts")); + const offenders: string[] = []; + for (const name of files) { + const source = await readFile(join(SHARED_DIR, name), "utf8"); + if (FEATURE_IMPORT.test(source)) offenders.push(name); + } + assert.deepEqual(offenders, []); +});