diff --git a/extensions/qa-lab/src/scenario-catalog.ts b/extensions/qa-lab/src/scenario-catalog.ts index 496ad55f96e..6e8b200a192 100644 --- a/extensions/qa-lab/src/scenario-catalog.ts +++ b/extensions/qa-lab/src/scenario-catalog.ts @@ -230,6 +230,9 @@ const QA_SCENARIO_DIR_PATH = "qa/scenarios"; const QA_PACK_FENCE_RE = /```ya?ml qa-pack\r?\n([\s\S]*?)\r?\n```/i; const QA_SCENARIO_FENCE_RE = /```ya?ml qa-scenario\r?\n([\s\S]*?)\r?\n```/i; const QA_FLOW_YAML_FENCE_RE = /```ya?ml qa-flow\r?\n([\s\S]*?)\r?\n```/i; +const repoPathCache = new Map(); +let qaScenarioMarkdownPathsCache: string[] | null = null; +let qaScenarioPackCache: QaScenarioPack | null = null; function walkUpDirectories(start: string): string[] { const roots: string[] = []; @@ -245,6 +248,10 @@ function walkUpDirectories(start: string): string[] { } function resolveRepoPath(relativePath: string, kind: "file" | "directory" = "file"): string | null { + const cacheKey = `${kind}:${relativePath}`; + if (repoPathCache.has(cacheKey)) { + return repoPathCache.get(cacheKey) ?? null; + } for (const dir of walkUpDirectories(import.meta.dirname)) { const candidate = path.join(dir, relativePath); if (!fs.existsSync(candidate)) { @@ -252,9 +259,11 @@ function resolveRepoPath(relativePath: string, kind: "file" | "directory" = "fil } const stat = fs.statSync(candidate); if ((kind === "file" && stat.isFile()) || (kind === "directory" && stat.isDirectory())) { + repoPathCache.set(cacheKey, candidate); return candidate; } } + repoPathCache.set(cacheKey, null); return null; } @@ -320,17 +329,21 @@ export function readQaScenarioPackMarkdown(): string { } export function readQaScenarioPack(): QaScenarioPack { + if (qaScenarioPackCache) { + return qaScenarioPackCache; + } const packMarkdown = readTextFile(QA_SCENARIO_PACK_INDEX_PATH).trim(); if (!packMarkdown) { // The QA scenario pack is optional in npm distributions. Return an empty // pack so completion cache updates and other consumers don't crash when // the qa/scenarios/ directory is not shipped with the package. - return { + qaScenarioPackCache = { version: 1, agent: { identityMarkdown: DEFAULT_QA_AGENT_IDENTITY_MARKDOWN }, kickoffTask: "QA scenarios not available in this distribution.", scenarios: [], }; + return qaScenarioPackCache; } const parsedPack = parseQaYamlWithContext( qaScenarioPackSchema, @@ -368,18 +381,26 @@ export function readQaScenarioPack(): QaScenarioPack { } seenScenarioIds.add(scenario.id); } - return { + qaScenarioPackCache = { ...parsedPack, scenarios, }; + return qaScenarioPackCache; } export function listQaScenarioMarkdownPaths(): string[] { + if (qaScenarioMarkdownPathsCache) { + return qaScenarioMarkdownPathsCache; + } const resolved = resolveRepoPath(QA_SCENARIO_DIR_PATH, "directory"); if (!resolved) { return []; } - return listQaScenarioMarkdownPathsInDirectory(resolved, QA_SCENARIO_DIR_PATH).toSorted(); + qaScenarioMarkdownPathsCache = listQaScenarioMarkdownPathsInDirectory( + resolved, + QA_SCENARIO_DIR_PATH, + ).toSorted(); + return qaScenarioMarkdownPathsCache; } function listQaScenarioMarkdownPathsInDirectory(