AIQUAA QA Core
Núcleo de QA reutilizable para servidores MCP, que proporciona análisis de requisitos, trazabilidad, cobertura, planificación de cambios, parcheo seguro e integraciones con GitHub.
Documentación
@aiquaa/mcp-qa-core
Núcleo reutilizable de arquitectura hexagonal compartido por los servidores MCP de QA de AIQUAA (aiquaa-playwright-mcp-server, aiquaa-api-quality-mcp-server, aiquaa-performance-mcp-server, y futuros MCPs de Hurl/Pact/k6/Selenium/Appium/REST Assured).
Centraliza las partes de un servidor MCP de QA de AIQUAA que no tienen relación con una herramienta de prueba específica: normalización de requisitos, trazabilidad, agregación de cobertura, planificación de cambios, generación de parches, políticas de seguridad y adaptadores para GitHub, AIQUAA, CodeGraph y Engram. Nunca importa Playwright, Postman, Newman, JMeter, Hurl, k6, Selenium o Appium.
Propósito y límites
El núcleo hace: normalizar requisitos y criterios de aceptación; modelar reglas de negocio; construir y consultar un grafo de trazabilidad requisito→evidencia; agregar cobertura desde evaluadores específicos de herramientas; planificar cambios (crear/extender/modificar/mantener/desaprobar/eliminar/bloquear) bajo reglas de seguridad; generar/validar/aplicar diffs unificados; escanear secretos y aplicar políticas de ruta/escritura/ejecución/host; comunicarse con GitHub, AIQUAA, CodeGraph y Engram a través de puertos; y dar forma consistente a las respuestas de las herramientas MCP.
El núcleo no hace: generar especificaciones de Playwright o Gherkin, construir colecciones de Postman, ejecutar Newman o JMeter, abrir un navegador, interpretar localizadores, calcular percentiles JTL o generar XML JMX. Esas tareas permanecen en el MCP consumidor, conectadas a través del contrato QaToolPlugin.
Consulta ARCHITECTURE.md para el desglose de capas y MIGRATION.md para el inventario de lo que se movió fuera de aiquaa-playwright-mcp-server y por qué.
Instalación
npm install @aiquaa/mcp-qa-core zod
Se requiere Node.js 20+. El paquete es solo ESM, se puede reducir con tree-shaking, no tiene efectos secundarios al importarse y envía exportaciones de subruta para que nunca necesites acceder a dist/:
import { createQaCore } from "@aiquaa/mcp-qa-core";
import { CoverageEngine } from "@aiquaa/mcp-qa-core/coverage";
import { TraceabilityEngine } from "@aiquaa/mcp-qa-core/traceability";
import { ChangePlanner } from "@aiquaa/mcp-qa-core/changes";
import { generateUnifiedDiff, PatchValidator } from "@aiquaa/mcp-qa-core/patches";
import { LocalGitRepositoryAdapter } from "@aiquaa/mcp-qa-core/repository";
import { OctokitPullRequestAdapter, GitHubRepositoryAdapter } from "@aiquaa/mcp-qa-core/github";
import { HttpAiquaaAdapter } from "@aiquaa/mcp-qa-core/aiquaa";
import { CodeGraphCliAdapter } from "@aiquaa/mcp-qa-core/codegraph";
import { EngramCliAdapter } from "@aiquaa/mcp-qa-core/memory";
import { SecretScanner, PathPolicy } from "@aiquaa/mcp-qa-core/security";
import { createToolSuccess, toMcpStructuredContent } from "@aiquaa/mcp-qa-core/mcp";
import { createQaCoreTestHarness } from "@aiquaa/mcp-qa-core/testing";
Inicio rápido
import { createQaCore, createDefaultConfig } from "@aiquaa/mcp-qa-core";
const qaCore = createQaCore({ config: createDefaultConfig() });
const [requirement] = qaCore.requirements.normalize([
{ externalId: "REQ-42", title: "Login", description: "User can log in with email and password." },
]);
const context = qaCore.createContext({ operationId: "generate-123", dryRun: true });
Contrato de plugin
Cada MCP consumidor implementa QaToolPlugin<TInput, TAnalysis, TArtifact> — la costura que mantiene la lógica específica de la herramienta fuera del núcleo:
import type { QaToolPlugin, CoverageReport, ChangePlan } from "@aiquaa/mcp-qa-core";
export const examplePlugin: QaToolPlugin<ExampleInput, ExampleAnalysis, ExampleArtifact> = {
name: "example",
version: "1.0.0",
canHandle(input) {
return input.type === "example";
},
async analyze(input, context) {
return { source: input, findings: [] };
},
async evaluateCoverage(analysis, context): Promise<CoverageReport> {
return { items: [], summary: { total: 0, covered: 0, partiallyCovered: 0, uncovered: 0, blocked: 0, outdated: 0, percentage: 0 } };
},
async planChanges(analysis, coverage, context): Promise<ChangePlan> {
return { strategy: "keep", changes: [], assumptions: [], warnings: [], blockedReasons: [] };
},
async generateArtifacts() {
return [];
},
};
const result = await qaCore.runPlugin(examplePlugin, input);
// result.analysis, result.coverage, result.changePlan, result.artifacts
Consulta MIGRATION.md para un ejemplo completo con forma de Playwright.
Cobertura
El núcleo nunca decide qué significa "cubierto" para una herramienta específica — agrega los evaluadores que tú proporcionas:
import { CoverageEngine } from "@aiquaa/mcp-qa-core/coverage";
const engine = new CoverageEngine([myToolSpecificEvaluator]);
const report = await engine.evaluate(myContext);
// report.summary.percentage, report.items[]
Trazabilidad
import { TraceabilityEngine } from "@aiquaa/mcp-qa-core/traceability";
const engine = new TraceabilityEngine();
const graph = engine.build({ requirements, scenarios, artifacts, executionResults, evidence });
engine.findUncoveredCriteria(graph, allCriteria);
engine.findOrphanArtifacts(graph, allArtifacts);
engine.findBrokenLinks(graph, { requirement: new Set(requirementIds) });
Planificación de cambios y parches
import { ChangePlanner } from "@aiquaa/mcp-qa-core/changes";
import { PatchGenerator, PatchValidator, PatchApplier } from "@aiquaa/mcp-qa-core/patches";
const plan = new ChangePlanner().plan({
candidates: [{ targetPath: "tests/login.spec.ts", decision: "create", reason: "covers REQ-42", requirementIds: ["req-42"], businessRuleIds: [], risk: "low" }],
existingArtifactPaths: [],
requestedScope: ["tests"],
});
// plan.strategy, plan.changes[].decision ("create" | "block" | …), plan.blockedReasons
ChangePlanner rechaza artefactos duplicados, sobrescrituras ciegas (modify/delete sin hasReadExistingContent: true), rutas fuera de alcance, eliminaciones injustificadas y cambios sin evidencia de requisito/regla de negocio — devuelve "block" en su lugar.
Repositorio y solicitudes de extracción
import { GitHubRepositoryAdapter, OctokitPullRequestAdapter, runPullRequestFlow } from "@aiquaa/mcp-qa-core/github";
const pullRequests = new OctokitPullRequestAdapter({ octokit });
const result = await runPullRequestFlow(pullRequests, {
repository: { owner: "aiquaa", repo: "demo" },
baseBranch: "main",
branchName: "feat/login-coverage",
commitMessage: "test: add login coverage",
title: "Add login coverage",
body: "Generated by the QA MCP.",
files: changePlanFiles,
// dryRun and draft both default to true — nothing is written until you opt out.
});
Seguridad
import { SecretScanner, PathPolicy, RepositoryWritePolicy } from "@aiquaa/mcp-qa-core/security";
const scanner = new SecretScanner();
scanner.scanText(fileContent); // → SecretFinding[], never the full secret value
const pathPolicy = new PathPolicy({ allowedRoots: [projectRoot] });
pathPolicy.assertSafe(candidatePath); // throws UnsafePathError on traversal, .git, node_modules, or escape
RepositoryWritePolicy, ExecutionPolicy y HostPolicy aplican el mismo valor predeterminado "explícito y acotado" a escrituras en el repositorio, ejecución de procesos externos y llamadas HTTP salientes, respectivamente.
Utilidades de prueba
import { createQaCoreTestHarness } from "@aiquaa/mcp-qa-core/testing";
const harness = createQaCoreTestHarness({ files: { "src/controller.ts": "..." } });
const result = await harness.runPlugin(myPlugin, input);
expect(result.changePlan.strategy).toBe("extend");
expect(harness.pullRequests.commits).toHaveLength(0); // dryRun by default
El arnés conecta InMemoryFileSystemAdapter, InMemoryRepositoryAdapter, InMemoryPullRequestAdapter, InMemoryAiquaaAdapter, InMemoryProjectMemoryAdapter y TestLoggerAdapter a un QaCore real — sin red, sin disco, sin proceso externo.
Respuestas MCP
import { createToolSuccess, toMcpStructuredContent } from "@aiquaa/mcp-qa-core/mcp";
const response = createToolSuccess({ operationId: context.operationId, summary: "Generated 3 artifacts", data: artifacts });
return toMcpStructuredContent(response, "files"); // { content: [...], isError: false, structuredContent: artifacts }
Configuración
import { loadConfigFromEnvironment, validateConfig } from "@aiquaa/mcp-qa-core";
const config = loadConfigFromEnvironment(process.env);
const validated = validateConfig(config); // Result<QaCoreConfig, ConfigurationError>
Variables de entorno reconocidas: GITHUB_TOKEN, GITHUB_API_URL, AIQUAA_API_BASE_URL, AIQUAA_ACCESS_TOKEN, CODEGRAPH_BIN, CODEGRAPH_ALLOWED_ROOTS, ENGRAM_BIN, ENGRAM_PROJECT_PREFIX, QA_CORE_ALLOWED_ROOTS, QA_CORE_MAX_FILE_SIZE, QA_CORE_LOG_LEVEL, QA_CORE_DRY_RUN. El código de dominio y de aplicación nunca lee process.env directamente — solo loadConfigFromEnvironment lo hace, y únicamente cuando lo llamas.
Desarrollo
npm install
npm run check # typecheck + lint + test:coverage + build
Consulta CONTRIBUTING.md para el flujo de trabajo completo y SECURITY.md para el proceso de reporte de vulnerabilidades.
Licencia
MIT — consulta LICENSE.