Find .opencode files in workspace search (#2049)

* fix(search): find .opencode workspace files

Workspace autocomplete skips hidden directories unless they are explicitly allowlisted. The missing .opencode entry made provider commands and workflows invisible to file search.

* refactor(search): centralize hidden directory policy
This commit is contained in:
Mohamed Boudra
2026-07-13 22:50:34 +02:00
committed by GitHub
parent 5e2a8b6633
commit b4ab0d9db6
4 changed files with 55 additions and 15 deletions

View File

@@ -892,6 +892,42 @@ test("returns typed relative suggestions within a requested directory", async ()
} }
}, 30000); }, 30000);
test("finds workspace files inside the OpenCode directory", async () => {
const cwd = mkdtempSync(path.join(tmpdir(), "paseo-opencode-suggestion-"));
const target = path.join(
cwd,
".opencode",
"command",
"workflow",
"00-kickoff",
"00-user-stories.md",
);
try {
mkdirSync(path.dirname(target), { recursive: true });
writeFileSync(target, "");
const result = await ctx.client.getDirectorySuggestions({
cwd,
query: "00-user-stories.md",
includeFiles: true,
includeDirectories: false,
limit: 20,
});
expect(result.error).toBeNull();
expect(result.directories).toEqual([]);
expect(result.entries).toEqual([
{
path: ".opencode/command/workflow/00-kickoff/00-user-stories.md",
kind: "file",
},
]);
} finally {
rmSync(cwd, { recursive: true, force: true });
}
}, 30000);
test("receives server_info on websocket connect", async () => { test("receives server_info on websocket connect", async () => {
const client = new DaemonClient({ const client = new DaemonClient({
url: `ws://127.0.0.1:${ctx.daemon.port}/ws`, url: `ws://127.0.0.1:${ctx.daemon.port}/ws`,

View File

@@ -174,7 +174,10 @@ import {
type AgentUpdatesService, type AgentUpdatesService,
} from "./session/agent-updates/agent-updates-service.js"; } from "./session/agent-updates/agent-updates-service.js";
import { expandTilde } from "../utils/path.js"; import { expandTilde } from "../utils/path.js";
import { searchDirectoryEntries } from "../utils/directory-suggestions.js"; import {
searchDirectoryEntries,
WORKSPACE_SEARCH_HIDDEN_DIRECTORIES,
} from "../utils/directory-suggestions.js";
import type { CheckoutDiffManager } from "./checkout-diff-manager.js"; import type { CheckoutDiffManager } from "./checkout-diff-manager.js";
import type { Resolvable } from "./speech/provider-resolver.js"; import type { Resolvable } from "./speech/provider-resolver.js";
import type { SpeechReadinessSnapshot } from "./speech/speech-runtime.js"; import type { SpeechReadinessSnapshot } from "./speech/speech-runtime.js";
@@ -224,15 +227,6 @@ import { CreateAgentLifecycleDispatch } from "./agent/create-agent-lifecycle-dis
// the entire session message if they encounter an unknown provider. // the entire session message if they encounter an unknown provider.
const LEGACY_PROVIDER_IDS = new Set(["claude", "codex", "opencode"]); const LEGACY_PROVIDER_IDS = new Set(["claude", "codex", "opencode"]);
const MIN_VERSION_ALL_PROVIDERS = "0.1.45"; const MIN_VERSION_ALL_PROVIDERS = "0.1.45";
const WORKSPACE_SEARCH_HIDDEN_DIRECTORIES = [
".agents",
".claude",
".codex",
".github",
".paseo",
".vscode",
];
function errorToFriendlyMessage(error: unknown): string { function errorToFriendlyMessage(error: unknown): string {
if (error instanceof Error) return error.message; if (error instanceof Error) return error.message;
if (typeof error === "string") return error; if (typeof error === "string") return error;

View File

@@ -11,12 +11,13 @@ import { tmpdir } from "node:os";
import path from "node:path"; import path from "node:path";
import { afterEach, beforeEach, describe, expect, it } from "vitest"; import { afterEach, beforeEach, describe, expect, it } from "vitest";
import { isPlatform } from "../test-utils/platform.js"; import { isPlatform } from "../test-utils/platform.js";
import { searchDirectoryEntries } from "./directory-suggestions.js"; import {
searchDirectoryEntries,
WORKSPACE_SEARCH_HIDDEN_DIRECTORIES,
} from "./directory-suggestions.js";
const isWindows = isPlatform("win32"); const isWindows = isPlatform("win32");
const filesystemRootDirectoryName = isWindows ? "Windows" : "usr"; const filesystemRootDirectoryName = isWindows ? "Windows" : "usr";
const workspaceHiddenDirectories = [".agents", ".claude", ".codex", ".github", ".paseo", ".vscode"];
async function searchAbsoluteDirectoryPaths(options: { async function searchAbsoluteDirectoryPaths(options: {
homeDir: string; homeDir: string;
query: string; query: string;
@@ -60,7 +61,7 @@ async function searchRelativeDirectoryEntries(options: {
matchMode: options.matchMode, matchMode: options.matchMode,
pathQueryPolicy: "slashes", pathQueryPolicy: "slashes",
blankQueryBehavior: "children", blankQueryBehavior: "children",
traversableHiddenDirectoryNames: workspaceHiddenDirectories, traversableHiddenDirectoryNames: WORKSPACE_SEARCH_HIDDEN_DIRECTORIES,
limit: options.limit, limit: options.limit,
maxDepth: options.maxDepth, maxDepth: options.maxDepth,
maxEntriesScanned: options.maxEntriesScanned, maxEntriesScanned: options.maxEntriesScanned,

View File

@@ -24,7 +24,7 @@ export interface SearchDirectoryEntriesOptions {
pathQueryPolicy?: PathQueryPolicy; pathQueryPolicy?: PathQueryPolicy;
rootAliases?: string[]; rootAliases?: string[];
blankQueryBehavior?: BlankQueryBehavior; blankQueryBehavior?: BlankQueryBehavior;
traversableHiddenDirectoryNames?: string[]; traversableHiddenDirectoryNames?: readonly string[];
limit?: number; limit?: number;
maxDepth?: number; maxDepth?: number;
maxEntriesScanned?: number; maxEntriesScanned?: number;
@@ -84,6 +84,15 @@ const NO_SEGMENT_INDEX = Number.MAX_SAFE_INTEGER;
const NO_MATCH_OFFSET = Number.MAX_SAFE_INTEGER; const NO_MATCH_OFFSET = Number.MAX_SAFE_INTEGER;
const NO_FUZZY_SCORE = Number.MAX_SAFE_INTEGER; const NO_FUZZY_SCORE = Number.MAX_SAFE_INTEGER;
const NO_MATCH_TIER = 5; const NO_MATCH_TIER = 5;
export const WORKSPACE_SEARCH_HIDDEN_DIRECTORIES = [
".agents",
".claude",
".codex",
".github",
".opencode",
".paseo",
".vscode",
] as const;
const IGNORED_DIRECTORY_NAMES = new Set([ const IGNORED_DIRECTORY_NAMES = new Set([
"node_modules", "node_modules",
"venv", "venv",