




















@@ -13,6 +13,16 @@ type QaCoverageScenarioSummary = {
1313risk: string;
1414};
151516+type QaScenarioSearchMatch = QaCoverageScenarioSummary & {
17+coverageIds: string[];
18+docsRefs: string[];
19+codeRefs: string[];
20+runtimeParityTier?: string;
21+requiredProviderMode?: string;
22+requiredProvider?: string;
23+requiredModel?: string;
24+};
25+1626type QaCoverageIntent = "primary" | "secondary";
17271828type QaCoverageScenarioReference = QaCoverageScenarioSummary & {
@@ -70,6 +80,85 @@ function summarizeScenario(scenario: QaSeedScenarioWithSource): QaCoverageScenar
7080};
7181}
728283+function normalizeSearchText(value: string) {
84+return value.toLowerCase();
85+}
86+87+function tokenizeScenarioSearchQuery(query: string) {
88+return query
89+.toLowerCase()
90+.split(/\s+/u)
91+.map((token) => token.trim())
92+.filter(Boolean);
93+}
94+95+function scenarioSearchText(scenario: QaSeedScenarioWithSource) {
96+const config = scenario.execution.config ?? {};
97+return normalizeSearchText(
98+[
99+scenario.id,
100+scenario.title,
101+scenario.sourcePath,
102+scenario.surface,
103+ ...(scenario.surfaces ?? []),
104+scenario.category ?? "",
105+scenario.runtimeParityTier ?? "",
106+scenario.risk ?? "",
107+scenario.riskLevel ?? "",
108+scenario.objective,
109+ ...scenario.successCriteria,
110+ ...(scenario.capabilities ?? []),
111+ ...(scenario.plugins ?? []),
112+ ...(scenario.docsRefs ?? []),
113+ ...(scenario.codeRefs ?? []),
114+ ...(scenario.coverage?.primary ?? []),
115+ ...(scenario.coverage?.secondary ?? []),
116+ ...Object.entries(config).flatMap(([key, value]) => [
117+key,
118+typeof value === "string" ? value : "",
119+]),
120+].join("\n"),
121+);
122+}
123+124+function stringifyConfigValue(value: unknown) {
125+return typeof value === "string" && value.trim() ? value.trim() : undefined;
126+}
127+128+function summarizeScenarioSearchMatch(scenario: QaSeedScenarioWithSource): QaScenarioSearchMatch {
129+const config = scenario.execution.config ?? {};
130+return {
131+ ...summarizeScenario(scenario),
132+coverageIds: [
133+ ...(scenario.coverage?.primary ?? []),
134+ ...(scenario.coverage?.secondary ?? []),
135+].toSorted((left, right) => left.localeCompare(right)),
136+docsRefs: [...(scenario.docsRefs ?? [])],
137+codeRefs: [...(scenario.codeRefs ?? [])],
138+runtimeParityTier: scenario.runtimeParityTier,
139+requiredProviderMode: stringifyConfigValue(config.requiredProviderMode),
140+requiredProvider: stringifyConfigValue(config.requiredProvider),
141+requiredModel: stringifyConfigValue(config.requiredModel),
142+};
143+}
144+145+export function findQaScenarioMatches(
146+scenarios: readonly QaSeedScenarioWithSource[],
147+query: string,
148+) {
149+const tokens = tokenizeScenarioSearchQuery(query);
150+if (tokens.length === 0) {
151+return [];
152+}
153+return scenarios
154+.filter((scenario) => {
155+const haystack = scenarioSearchText(scenario);
156+return tokens.every((token) => haystack.includes(token));
157+})
158+.map(summarizeScenarioSearchMatch)
159+.toSorted((left, right) => left.id.localeCompare(right.id));
160+}
161+73162function sortFeatures(features: readonly QaCoverageFeatureSummary[]) {
74163return features.toSorted((left, right) => left.id.localeCompare(right.id));
75164}
@@ -280,3 +369,52 @@ export function renderQaCoverageMarkdownReport(inventory: QaCoverageInventory):
280369281370return `${lines.join("\n").trimEnd()}\n`;
282371}
372+373+function formatOptionalScenarioMetadata(match: QaScenarioSearchMatch) {
374+const metadata = [
375+match.runtimeParityTier ? `runtimeParityTier=${match.runtimeParityTier}` : "",
376+match.requiredProviderMode ? `providerMode=${match.requiredProviderMode}` : "",
377+match.requiredProvider ? `provider=${match.requiredProvider}` : "",
378+match.requiredModel ? `model=${match.requiredModel}` : "",
379+].filter(Boolean);
380+return metadata.length > 0 ? metadata.join("; ") : "none";
381+}
382+383+export function renderQaScenarioMatchesMarkdownReport(params: {
384+query: string;
385+matches: readonly QaScenarioSearchMatch[];
386+}) {
387+const scenarioArgs = params.matches.map((match) => `--scenario ${match.id}`).join(" ");
388+const lines = [
389+"# QA Scenario Matches",
390+"",
391+`- Query: ${params.query}`,
392+`- Matches: ${params.matches.length}`,
393+];
394+395+if (scenarioArgs) {
396+lines.push(`- Suite command: \`pnpm openclaw qa suite ${scenarioArgs}\``);
397+}
398+lines.push("");
399+400+if (params.matches.length === 0) {
401+lines.push("No QA scenarios matched the query.", "");
402+return lines.join("\n");
403+}
404+405+for (const match of params.matches) {
406+lines.push(`- ${match.id}: ${match.title}`);
407+lines.push(` - source: ${match.sourcePath}`);
408+lines.push(` - surface: ${match.surfaces.join(", ")}`);
409+lines.push(` - coverage: ${match.coverageIds.join(", ") || "none"}`);
410+lines.push(` - live requirements: ${formatOptionalScenarioMetadata(match)}`);
411+if (match.codeRefs.length > 0) {
412+lines.push(` - code refs: ${match.codeRefs.join(", ")}`);
413+}
414+if (match.docsRefs.length > 0) {
415+lines.push(` - docs refs: ${match.docsRefs.join(", ")}`);
416+}
417+}
418+419+return `${lines.join("\n").trimEnd()}\n`;
420+}
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。