AIQUAA QA Core

Núcleo de QA reutilizável para servidores MCP, fornecendo análise de requisitos, rastreabilidade, cobertura, planejamento de mudanças, correções seguras e integrações com GitHub.

Documentação

@aiquaa/mcp-qa-core

Núcleo reutilizável de arquitetura hexagonal compartilhado pelos servidores MCP de QA da AIQUAA (aiquaa-playwright-mcp-server, aiquaa-api-quality-mcp-server, aiquaa-performance-mcp-server e futuros MCPs de Hurl/Pact/k6/Selenium/Appium/REST Assured).

Ele centraliza as partes de um servidor MCP de QA da AIQUAA que não têm relação com uma ferramenta de teste específica: normalização de requisitos, rastreabilidade, agregação de cobertura, planejamento de mudanças, geração de patches, políticas de segurança e adaptadores para GitHub, AIQUAA, CodeGraph e Engram. Ele nunca importa Playwright, Postman, Newman, JMeter, Hurl, k6, Selenium ou Appium.

Propósito e limites

O núcleo faz: normalizar requisitos e critérios de aceitação; modelar regras de negócio; construir e consultar um grafo de rastreabilidade requisito→evidência; agregar cobertura de avaliadores específicos de ferramentas; planejar mudanças (criar/estender/modificar/manter/descontinuar/excluir/bloquear) sob regras de segurança; gerar/validar/aplicar diffs unificados; verificar segredos e aplicar políticas de caminho/gravação/execução/host; comunicar-se com GitHub, AIQUAA, CodeGraph e Engram por meio de portas; e moldar respostas de ferramentas MCP de forma consistente.

O núcleo não faz: gerar especificações Playwright ou Gherkin, construir coleções Postman, executar Newman ou JMeter, abrir um navegador, interpretar localizadores, calcular percentis JTL ou gerar XML JMX. Isso permanece no MCP consumidor, conectado por meio do contrato QaToolPlugin.

Consulte ARCHITECTURE.md para a divisão em camadas e MIGRATION.md para o inventário do que saiu de aiquaa-playwright-mcp-server e por quê.

Instalação

npm install @aiquaa/mcp-qa-core zod

Node.js 20+ é necessário. O pacote é somente ESM, tree-shakeable, sem efeitos colaterais na importação e inclui exports de subcaminho para que você nunca precise acessar 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";

Início 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

Todo MCP consumidor implementa QaToolPlugin<TInput, TAnalysis, TArtifact> — a costura que mantém a lógica específica de ferramenta fora do 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

Consulte MIGRATION.md para um exemplo completo no formato Playwright.

Cobertura

O núcleo nunca decide o que "coberto" significa para uma ferramenta específica — ele agrega os avaliadores que você fornece:

import { CoverageEngine } from "@aiquaa/mcp-qa-core/coverage";

const engine = new CoverageEngine([myToolSpecificEvaluator]);
const report = await engine.evaluate(myContext);
// report.summary.percentage, report.items[]

Rastreabilidade

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

Planejamento de mudanças e patches

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 recusa artefatos duplicados, sobrescritas cegas (modify/delete sem hasReadExistingContent: true), caminhos fora do escopo, exclusões injustificadas e mudanças sem evidência de requisito/regra de negócio — ele retorna "block" em vez disso.

Repositório e pull requests

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.
});

Segurança

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 e HostPolicy aplicam o mesmo padrão "explícito e limitado" a gravações no repositório, execução de processos externos e chamadas HTTP de saída, respectivamente.

Utilitários de teste

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

O harness conecta InMemoryFileSystemAdapter, InMemoryRepositoryAdapter, InMemoryPullRequestAdapter, InMemoryAiquaaAdapter, InMemoryProjectMemoryAdapter e TestLoggerAdapter a um QaCore real — sem rede, sem disco, sem processo externo.

Respostas 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 }

Configuração

import { loadConfigFromEnvironment, validateConfig } from "@aiquaa/mcp-qa-core";

const config = loadConfigFromEnvironment(process.env);
const validated = validateConfig(config); // Result<QaCoreConfig, ConfigurationError>

Variáveis de ambiente reconhecidas: 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. O código de domínio e de aplicação nunca lê process.env diretamente — apenas loadConfigFromEnvironment o faz, e somente quando você o chama.

Desenvolvimento

npm install
npm run check   # typecheck + lint + test:coverage + build

Consulte CONTRIBUTING.md para o fluxo de trabalho completo e SECURITY.md para o processo de relato de vulnerabilidades.

Licença

MIT — consulte LICENSE.