Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  scenario-catalog.ts

  Sprache: JAVA
 

Spracherkennung für: .ts vermutete Sprache: Unknown {[0] [0] [0]} [Methode: Schwerpunktbildung, einfache Gewichte, sechs Dimensionen]

import fs from "node:fs";
import path from "node:path";
import YAML from "yaml";
import { z } from "zod";

export const DEFAULT_QA_AGENT_IDENTITY_MARKDOWN = `# Dev C-3PO

You are the OpenClaw QA operator agent.

Persona:
- protocol-minded
- precise
- a little flustered
- conscientious
- eager to report what worked, failed, or remains blocked

Style:
- read source and docs first
- test systematically
- record evidence
- end with a concise protocol report`;

const qaScenarioConfigSchema = z.record(z.string(), z.unknown()).superRefine((config, ctx) => {
  for (const [key, value] of Object.entries(config)) {
    if (!key.endsWith("Any")) {
      continue;
    }
    if (!Array.isArray(value)) {
      ctx.addIssue({
        code: z.ZodIssueCode.custom,
        path: [key],
        message: `${key} must be an array of strings`,
      });
      continue;
    }
    for (const [index, entry] of value.entries()) {
      if (typeof entry !== "string") {
        ctx.addIssue({
          code: z.ZodIssueCode.custom,
          path: [key, index],
          message: `${key} entries must be strings`,
        });
      }
    }
  }
});

const qaScenarioExecutionSchema = z.object({
  kind: z.literal("flow").default("flow"),
  summary: z.string().trim().min(1).optional(),
  config: qaScenarioConfigSchema.optional(),
});

const qaCoverageIdSchema = z
  .string()
  .trim()
  .regex(/^[a-z0-9]+(?:[.-][a-z0-9]+)*$/, {
    message: "coverage ids must use lowercase dotted or dashed tokens",
  });

const qaCoverageIdListSchema = z.array(qaCoverageIdSchema).min(1);

const qaScenarioCoverageSchema = z
  .object({
    primary: qaCoverageIdListSchema,
    secondary: qaCoverageIdListSchema.optional(),
  })
  .superRefine((coverage, ctx) => {
    const seen = new Set<string>();
    const coverageEntries = [
      ["primary", coverage.primary],
      ["secondary", coverage.secondary],
    ] as const;
    for (const [intent, ids] of coverageEntries) {
      if (!ids) {
        continue;
      }
      for (const [index, id] of ids.entries()) {
        if (!seen.has(id)) {
          seen.add(id);
          continue;
        }
        ctx.addIssue({
          code: z.ZodIssueCode.custom,
          path: [intent, index],
          message: `duplicate coverage id: ${id}`,
        });
      }
    }
  });

const qaScenarioGatewayRuntimeSchema = z.object({
  forwardHostHome: z.boolean().optional(),
});

const qaFlowCallActionSchema = z.object({
  call: z.string().trim().min(1),
  args: z.array(z.unknown()).optional(),
  saveAs: z.string().trim().min(1).optional(),
});

const qaFlowSetActionSchema = z.object({
  set: z.string().trim().min(1),
  value: z.unknown(),
});

const qaFlowAssertActionSchema = z.object({
  assert: z.union([
    z.string().trim().min(1),
    z.object({
      expr: z.string().trim().min(1),
      message: z.unknown().optional(),
    }),
  ]),
});

const qaFlowThrowActionSchema = z.object({
  throw: z.union([
    z.string().trim().min(1),
    z.object({
      expr: z.string().trim().min(1).optional(),
      message: z.unknown().optional(),
    }),
  ]),
});

const qaFlowIfShapeBase: Record<string, z.ZodTypeAny> = {
  expr: z.string().trim().min(1),
  else: z.array(z.unknown()).optional(),
};
const qaFlowThenKey = String.fromCharCode(116, 104, 101, 110);
qaFlowIfShapeBase[qaFlowThenKey] = z.array(z.unknown()).min(1);

const qaFlowActionSchema: z.ZodType = z.lazy(() =>
  z.union([
    qaFlowCallActionSchema,
    qaFlowSetActionSchema,
    qaFlowAssertActionSchema,
    qaFlowThrowActionSchema,
    z.object({
      if: z
        .object(qaFlowIfShapeBase)
        .transform((value) => value as { expr: string; then: unknown[]; else?: unknown[] }),
    }),
    z.object({
      forEach: z.object({
        items: z.unknown(),
        item: z.string().trim().min(1),
        index: z.string().trim().min(1).optional(),
        actions: z.array(qaFlowActionSchema).min(1),
      }),
    }),
    z.object({
      try: z.object({
        actions: z.array(qaFlowActionSchema).min(1),
        catchAs: z.string().trim().min(1).optional(),
        catch: z.array(qaFlowActionSchema).optional(),
        finally: z.array(qaFlowActionSchema).optional(),
      }),
    }),
  ]),
);

const qaFlowStepSchema = z.object({
  name: z.string().trim().min(1),
  actions: z.array(qaFlowActionSchema).min(1),
  detailsExpr: z.string().trim().min(1).optional(),
});

const qaFlowSchema = z.object({
  steps: z.array(qaFlowStepSchema).min(1),
});

const qaSeedScenarioSchema = z.object({
  id: z.string().trim().min(1),
  title: z.string().trim().min(1),
  surface: z.string().trim().min(1),
  category: z.string().trim().min(1).optional(),
  coverage: qaScenarioCoverageSchema.optional(),
  surfaces: z.array(z.string().trim().min(1)).min(1).optional(),
  risk: z.enum(["low", "medium", "high"]).optional(),
  capabilities: z.array(z.string().trim().min(1)).optional(),
  lane: z.record(z.string(), z.union([z.boolean(), z.string()])).optional(),
  riskLevel: z.string().trim().min(1).optional(),
  objective: z.string().trim().min(1),
  successCriteria: z.array(z.string().trim().min(1)).min(1),
  plugins: z.array(z.string().trim().min(1)).optional(),
  gatewayConfigPatch: z.record(z.string(), z.unknown()).optional(),
  gatewayRuntime: qaScenarioGatewayRuntimeSchema.optional(),
  docsRefs: z.array(z.string().trim().min(1)).optional(),
  codeRefs: z.array(z.string().trim().min(1)).optional(),
  execution: qaScenarioExecutionSchema.optional(),
});

const qaScenarioPackSchema = z.object({
  version: z.number().int().positive(),
  agent: z
    .object({
      identityMarkdown: z.string().trim().min(1),
    })
    .default({
      identityMarkdown: DEFAULT_QA_AGENT_IDENTITY_MARKDOWN,
    }),
  kickoffTask: z.string().trim().min(1),
});

export type QaScenarioExecution = z.infer<typeof qaScenarioExecutionSchema>;
export type QaScenarioFlow = z.infer<typeof qaFlowSchema>;
export type QaSeedScenario = z.infer<typeof qaSeedScenarioSchema>;
export type QaSeedScenarioWithSource = QaSeedScenario & {
  sourcePath: string;
  execution: QaScenarioExecution & {
    flow?: QaScenarioFlow;
  };
};

export type QaScenarioPack = z.infer<typeof qaScenarioPackSchema> & {
  scenarios: QaSeedScenarioWithSource[];
};

export type QaBootstrapScenarioCatalog = {
  agentIdentityMarkdown: string;
  kickoffTask: string;
  scenarios: QaSeedScenarioWithSource[];
};

const QA_SCENARIO_PACK_INDEX_PATH = "qa/scenarios/index.md";
const QA_SCENARIO_LEGACY_OVERVIEW_PATH = "qa/scenarios.md";
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<string, string | null>();
let qaScenarioMarkdownPathsCache: string[] | null = null;
let qaScenarioPackCache: QaScenarioPack | null = null;

function walkUpDirectories(start: string): string[] {
  const roots: string[] = [];
  let current = path.resolve(start);
  while (true) {
    roots.push(current);
    const parent = path.dirname(current);
    if (parent === current) {
      return roots;
    }
    current = parent;
  }
}

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)) {
      continue;
    }
    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;
}

export function hasQaScenarioPack(): boolean {
  return resolveRepoPath(QA_SCENARIO_PACK_INDEX_PATH, "file") !== null;
}

function readTextFile(relativePath: string): string {
  const resolved = resolveRepoPath(relativePath, "file");
  if (!resolved) {
    return "";
  }
  return fs.readFileSync(resolved, "utf8");
}

function extractQaPackYaml(content: string) {
  const match = content.match(QA_PACK_FENCE_RE);
  if (!match?.[1]) {
    throw new Error(
      `qa scenario pack missing \`\`\`yaml qa-pack fence in ${QA_SCENARIO_PACK_INDEX_PATH}`,
    );
  }
  return match[1];
}

function extractQaScenarioYaml(content: string, relativePath: string) {
  const match = content.match(QA_SCENARIO_FENCE_RE);
  if (!match?.[1]) {
    throw new Error(`qa scenario file missing \`\`\`yaml qa-scenario fence in ${relativePath}`);
  }
  return match[1];
}

function extractQaScenarioFlow(content: string, relativePath: string) {
  const match = content.match(QA_FLOW_YAML_FENCE_RE);
  if (!match?.[1]) {
    throw new Error(`qa scenario file missing \`\`\`yaml qa-flow fence in ${relativePath}`);
  }
  return parseQaYamlWithContext(qaFlowSchema, YAML.parse(match[1]) as unknown, relativePath);
}

function formatZodIssuePath(path: PropertyKey[]) {
  return path.length ? path.map(String).join(".") : "<root>";
}

function parseQaYamlWithContext<T>(schema: z.ZodType<T>, value: unknown, label: string): T {
  const parsed = schema.safeParse(value);
  if (parsed.success) {
    return parsed.data;
  }
  const issues = parsed.error.issues
    .map((issue) => `${formatZodIssuePath(issue.path)}: ${issue.message}`)
    .join("; ");
  throw new Error(`${label}: ${issues}`);
}

export function readQaScenarioPackMarkdown(): string {
  const chunks = [readTextFile(QA_SCENARIO_PACK_INDEX_PATH).trim()];
  for (const relativePath of listQaScenarioMarkdownPaths()) {
    chunks.push(readTextFile(relativePath).trim());
  }
  return chunks.filter(Boolean).join("\n\n");
}

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.
    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,
    YAML.parse(extractQaPackYaml(packMarkdown)) as unknown,
    QA_SCENARIO_PACK_INDEX_PATH,
  );
  const scenarios = listQaScenarioMarkdownPaths().map((relativePath) =>
    (() => {
      const content = readTextFile(relativePath);
      const parsedScenario = parseQaYamlWithContext(
        qaSeedScenarioSchema,
        YAML.parse(extractQaScenarioYaml(content, relativePath)) as unknown,
        relativePath,
      );
      const execution = parseQaYamlWithContext(
        qaScenarioExecutionSchema,
        parsedScenario.execution ?? {},
        relativePath,
      );
      const flow = extractQaScenarioFlow(content, relativePath);
      return {
        ...parsedScenario,
        sourcePath: relativePath,
        execution: {
          ...execution,
          flow,
        },
      } satisfies QaSeedScenarioWithSource;
    })(),
  );
  const seenScenarioIds = new Set<string>();
  for (const scenario of scenarios) {
    if (seenScenarioIds.has(scenario.id)) {
      throw new Error(`duplicate qa scenario id: ${scenario.id}`);
    }
    seenScenarioIds.add(scenario.id);
  }
  qaScenarioPackCache = {
    ...parsedPack,
    scenarios,
  };
  return qaScenarioPackCache;
}

export function listQaScenarioMarkdownPaths(): string[] {
  if (qaScenarioMarkdownPathsCache) {
    return qaScenarioMarkdownPathsCache;
  }
  const resolved = resolveRepoPath(QA_SCENARIO_DIR_PATH, "directory");
  if (!resolved) {
    return [];
  }
  qaScenarioMarkdownPathsCache = listQaScenarioMarkdownPathsInDirectory(
    resolved,
    QA_SCENARIO_DIR_PATH,
  ).toSorted();
  return qaScenarioMarkdownPathsCache;
}

function listQaScenarioMarkdownPathsInDirectory(
  absoluteDir: string,
  relativeDir: string,
): string[] {
  const paths: string[] = [];
  const entries = fs
    .readdirSync(absoluteDir, { withFileTypes: true })
    .toSorted((left, right) => left.name.localeCompare(right.name));
  for (const entry of entries) {
    if (entry.name.startsWith(".")) {
      continue;
    }
    const relativePath = `${relativeDir}/${entry.name}`;
    if (entry.isDirectory()) {
      paths.push(
        ...listQaScenarioMarkdownPathsInDirectory(path.join(absoluteDir, entry.name), relativePath),
      );
      continue;
    }
    if (entry.isFile() && entry.name.endsWith(".md") && entry.name !== "index.md") {
      paths.push(relativePath);
    }
  }
  return paths;
}

export function readQaScenarioOverviewMarkdown(): string {
  return readTextFile(QA_SCENARIO_LEGACY_OVERVIEW_PATH).trim();
}

export function readQaBootstrapScenarioCatalog(): QaBootstrapScenarioCatalog {
  const pack = readQaScenarioPack();
  return {
    agentIdentityMarkdown: pack.agent.identityMarkdown,
    kickoffTask: pack.kickoffTask,
    scenarios: pack.scenarios,
  };
}

export function readQaScenarioById(id: string): QaSeedScenarioWithSource {
  const scenario = readQaScenarioPack().scenarios.find((candidate) => candidate.id === id);
  if (!scenario) {
    throw new Error(`unknown qa scenario: ${id}`);
  }
  return scenario;
}

export function readQaScenarioExecutionConfig(id: string): Record<string, unknown> | undefined {
  return readQaScenarioPack().scenarios.find((candidate) => candidate.id === id)?.execution?.config;
}

export function validateQaScenarioExecutionConfig(config: Record<string, unknown>) {
  return qaScenarioConfigSchema.parse(config);
}

¤ Dauer der Verarbeitung: 0.31 Sekunden  (vorverarbeitet am  2026-04-27) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge