feat: add MCP server, A2A protocol, auto-combo engine & VS Code extension

Introduce full AI orchestration ecosystem:
- MCP Server with 16 tools, scoped auth, and audit logging
- A2A v0.3 server with JSON-RPC 2.0, SSE streaming, and task manager
- Auto-Combo engine with 6-factor scoring and self-healing
- VS Code extension with smart dispatch and budget tracking
- Harden CI pipeline: add static checks, remove continue-on-error
- Add translator schema validation tests
- Update .gitignore and CHANGELOG for release checklist
This commit is contained in:
diegosouzapw
2026-03-04 18:45:02 -03:00
parent 5ecef5c90c
commit bddec84f4e
26 changed files with 1311 additions and 51 deletions

177
scripts/check-cycles.mjs Normal file
View File

@@ -0,0 +1,177 @@
#!/usr/bin/env node
import fs from "node:fs";
import path from "node:path";
const cwd = process.cwd();
const defaultRoots = ["src/shared/components", "src/lib/db", "open-sse/translator"];
const roots = process.argv.slice(2).length > 0 ? process.argv.slice(2) : defaultRoots;
const sourceExtensions = [".ts", ".tsx", ".js", ".mjs", ".jsx", ".mts", ".cts"];
function toPosix(filePath) {
return filePath.split(path.sep).join("/");
}
function listSourceFiles(rootDir) {
const absRoot = path.resolve(cwd, rootDir);
if (!fs.existsSync(absRoot)) {
return [];
}
const stack = [absRoot];
const files = [];
while (stack.length > 0) {
const current = stack.pop();
const entries = fs.readdirSync(current, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path.join(current, entry.name);
if (entry.isDirectory()) {
stack.push(fullPath);
continue;
}
if (sourceExtensions.includes(path.extname(entry.name))) {
files.push(path.resolve(fullPath));
}
}
}
return files;
}
function resolveRelativeImport(fromFile, specifier) {
const base = path.resolve(path.dirname(fromFile), specifier);
const ext = path.extname(base);
if (ext && fs.existsSync(base) && fs.statSync(base).isFile()) {
return path.resolve(base);
}
for (const extension of sourceExtensions) {
const candidate = `${base}${extension}`;
if (fs.existsSync(candidate) && fs.statSync(candidate).isFile()) {
return path.resolve(candidate);
}
}
for (const extension of sourceExtensions) {
const candidate = path.join(base, `index${extension}`);
if (fs.existsSync(candidate) && fs.statSync(candidate).isFile()) {
return path.resolve(candidate);
}
}
return null;
}
function extractImportSpecifiers(fileContents) {
const specs = [];
const regex = /\b(?:import|export)\s+(?:[^"'`]*?\sfrom\s*)?["'`]([^"'`]+)["'`]/g;
let match = regex.exec(fileContents);
while (match) {
specs.push(match[1]);
match = regex.exec(fileContents);
}
return specs;
}
function buildGraph(files) {
const fileSet = new Set(files);
const graph = new Map();
for (const filePath of files) {
const code = fs.readFileSync(filePath, "utf8");
const dependencies = new Set();
const importSpecifiers = extractImportSpecifiers(code);
for (const specifier of importSpecifiers) {
if (!specifier.startsWith(".")) continue;
const resolved = resolveRelativeImport(filePath, specifier);
if (!resolved) continue;
if (!fileSet.has(resolved)) continue;
dependencies.add(resolved);
}
graph.set(filePath, dependencies);
}
return graph;
}
function stronglyConnectedComponents(graph) {
const indexMap = new Map();
const lowLinkMap = new Map();
const onStack = new Set();
const stack = [];
const components = [];
let indexCounter = 0;
function strongConnect(node) {
indexMap.set(node, indexCounter);
lowLinkMap.set(node, indexCounter);
indexCounter += 1;
stack.push(node);
onStack.add(node);
for (const neighbor of graph.get(node) || []) {
if (!indexMap.has(neighbor)) {
strongConnect(neighbor);
lowLinkMap.set(node, Math.min(lowLinkMap.get(node), lowLinkMap.get(neighbor)));
} else if (onStack.has(neighbor)) {
lowLinkMap.set(node, Math.min(lowLinkMap.get(node), indexMap.get(neighbor)));
}
}
if (lowLinkMap.get(node) === indexMap.get(node)) {
const component = [];
while (stack.length > 0) {
const candidate = stack.pop();
onStack.delete(candidate);
component.push(candidate);
if (candidate === node) break;
}
components.push(component);
}
}
for (const node of graph.keys()) {
if (!indexMap.has(node)) {
strongConnect(node);
}
}
return components;
}
function isSelfCycle(component, graph) {
if (component.length !== 1) return false;
const [file] = component;
return (graph.get(file) || new Set()).has(file);
}
const files = roots.flatMap((root) => listSourceFiles(root));
const graph = buildGraph(files);
const components = stronglyConnectedComponents(graph);
const cycles = components.filter(
(component) => component.length > 1 || isSelfCycle(component, graph)
);
if (cycles.length === 0) {
console.log(
`[cycles] OK - no cycles detected across ${graph.size} files in: ${roots.join(", ")}`
);
process.exit(0);
}
console.error(`[cycles] FAIL - detected ${cycles.length} strongly connected component(s):`);
for (const component of cycles) {
const sorted = [...component].sort((a, b) => a.localeCompare(b));
console.error(`\n- SCC (${sorted.length} files)`);
for (const filePath of sorted) {
console.error(` - ${toPosix(path.relative(cwd, filePath))}`);
}
}
process.exit(1);

111
scripts/check-docs-sync.mjs Normal file
View File

@@ -0,0 +1,111 @@
#!/usr/bin/env node
import fs from "node:fs";
import path from "node:path";
const cwd = process.cwd();
const packageJsonPath = path.resolve(cwd, "package.json");
const openApiPath = path.resolve(cwd, "docs/openapi.yaml");
const changelogPath = path.resolve(cwd, "CHANGELOG.md");
function readText(filePath) {
if (!fs.existsSync(filePath)) {
throw new Error(`File not found: ${path.relative(cwd, filePath)}`);
}
return fs.readFileSync(filePath, "utf8");
}
function extractOpenApiVersion(content) {
const lines = content.split(/\r?\n/);
let inInfoBlock = false;
for (const line of lines) {
const trimmed = line.trim();
if (!inInfoBlock) {
if (trimmed === "info:") {
inInfoBlock = true;
}
continue;
}
if (line.length > 0 && !line.startsWith(" ")) {
break;
}
const match = line.match(/^\s{2}version:\s*["']?([^"'\s]+)["']?\s*$/);
if (match) {
return match[1];
}
}
return null;
}
function extractChangelogSections(content) {
const headings = [...content.matchAll(/^##\s+\[([^\]]+)\](?:\s+—\s+.*)?$/gm)];
return headings.map((match) => match[1]);
}
function isSemver(value) {
return /^\d+\.\d+\.\d+$/.test(value);
}
let hasFailure = false;
function fail(message) {
hasFailure = true;
console.error(`[docs-sync] FAIL - ${message}`);
}
try {
const packageJson = JSON.parse(readText(packageJsonPath));
const packageVersion = packageJson.version;
if (!isSemver(packageVersion)) {
fail(`package.json version is not valid semver: "${packageVersion}"`);
} else {
console.log(`[docs-sync] package.json version: ${packageVersion}`);
}
const openApiVersion = extractOpenApiVersion(readText(openApiPath));
if (!openApiVersion) {
fail("could not extract docs/openapi.yaml info.version");
} else if (openApiVersion !== packageVersion) {
fail(`OpenAPI version (${openApiVersion}) differs from package.json (${packageVersion})`);
} else {
console.log(`[docs-sync] openapi.yaml info.version matches: ${openApiVersion}`);
}
const changelogSections = extractChangelogSections(readText(changelogPath));
if (changelogSections.length === 0) {
fail("CHANGELOG.md has no version sections");
} else {
if (changelogSections[0] !== "Unreleased") {
fail('CHANGELOG.md first section must be "## [Unreleased]"');
} else {
console.log("[docs-sync] changelog has top Unreleased section");
}
const semverSections = changelogSections.filter((section) => isSemver(section));
if (semverSections.length === 0) {
fail("CHANGELOG.md has no semver release section");
} else if (semverSections[0] !== packageVersion) {
fail(
`Latest changelog release (${semverSections[0]}) differs from package.json (${packageVersion})`
);
} else {
console.log(
`[docs-sync] latest changelog release matches package version: ${packageVersion}`
);
}
}
} catch (error) {
fail(error instanceof Error ? error.message : String(error));
}
if (hasFailure) {
process.exit(1);
}
console.log("[docs-sync] PASS - documentation version sync is consistent.");

View File

@@ -0,0 +1,61 @@
#!/usr/bin/env node
import fs from "node:fs";
import path from "node:path";
const ROOT = process.cwd();
const API_ROOT = path.join(ROOT, "src", "app", "api");
const FILE_NAME = "route.ts";
const REQUEST_JSON_REGEX = /request\.json\s*\(/;
const VALIDATE_BODY_REGEX = /\bvalidateBody\s*\(/;
/**
* Walk directory recursively and collect route files.
* @param {string} dir
* @returns {string[]}
*/
function collectRouteFiles(dir) {
const entries = fs.readdirSync(dir, { withFileTypes: true });
const files = [];
for (const entry of entries) {
const fullPath = path.join(dir, entry.name);
if (entry.isDirectory()) {
files.push(...collectRouteFiles(fullPath));
continue;
}
if (entry.isFile() && entry.name === FILE_NAME) {
files.push(fullPath);
}
}
return files;
}
if (!fs.existsSync(API_ROOT)) {
console.error(`[t06:route-validation] FAIL - API root not found: ${API_ROOT}`);
process.exit(1);
}
const routeFiles = collectRouteFiles(API_ROOT).sort();
const missingValidation = [];
for (const fullPath of routeFiles) {
const source = fs.readFileSync(fullPath, "utf8");
if (!REQUEST_JSON_REGEX.test(source)) continue;
if (!VALIDATE_BODY_REGEX.test(source)) {
missingValidation.push(path.relative(ROOT, fullPath));
}
}
if (missingValidation.length > 0) {
console.error("[t06:route-validation] FAIL - routes with request.json() without validateBody():");
for (const file of missingValidation) {
console.error(` - ${file}`);
}
process.exit(1);
}
console.log(
`[t06:route-validation] PASS - ${routeFiles.length} route files scanned, all request.json() usages are validated.`
);

View File

@@ -0,0 +1,56 @@
#!/usr/bin/env node
import fs from "node:fs";
import path from "node:path";
const cwd = process.cwd();
/**
* T11 Phase-A budget:
* keep explicit `any` at zero in files already hardened.
*/
const budget = [
{ file: "src/app/api/settings/proxy/route.ts", maxAny: 0 },
{ file: "src/app/api/settings/proxy/test/route.ts", maxAny: 0 },
{ file: "src/shared/components/OAuthModal.tsx", maxAny: 0 },
{ file: "open-sse/translator/index.ts", maxAny: 0 },
{ file: "open-sse/translator/registry.ts", maxAny: 0 },
// Freeze legacy hot spots to avoid any-regression while strict migration continues.
{ file: "src/lib/db/apiKeys.ts", maxAny: 0 },
{ file: "src/lib/db/providers.ts", maxAny: 0 },
{ file: "src/lib/db/settings.ts", maxAny: 0 },
{ file: "open-sse/config/providerRegistry.ts", maxAny: 0 },
{ file: "open-sse/config/providerModels.ts", maxAny: 0 },
{ file: "open-sse/mcp-server/server.ts", maxAny: 0 },
];
const anyRegex = /\bany\b/g;
let hasFailure = false;
for (const item of budget) {
const absolutePath = path.resolve(cwd, item.file);
if (!fs.existsSync(absolutePath)) {
console.error(`[t11:any-budget] FAIL - file not found: ${item.file}`);
hasFailure = true;
continue;
}
const content = fs.readFileSync(absolutePath, "utf8");
const matches = content.match(anyRegex);
const count = matches ? matches.length : 0;
const status = count <= item.maxAny ? "OK" : "FAIL";
if (status === "FAIL") {
hasFailure = true;
}
console.log(
`[t11:any-budget] ${status} - ${item.file} (explicit any: ${count}, budget: ${item.maxAny})`
);
}
if (hasFailure) {
process.exit(1);
}
console.log("[t11:any-budget] PASS - explicit any budget respected.");

View File

@@ -2,6 +2,7 @@
import { spawn } from "node:child_process";
import { setTimeout as delay } from "node:timers/promises";
import { sanitizeColorEnv } from "./runtime-env.mjs";
const port = process.env.DASHBOARD_PORT || process.env.PORT || "20128";
const baseUrl = process.env.OMNIROUTE_BASE_URL || `http://localhost:${port}`;
@@ -33,11 +34,12 @@ async function waitForServerReady() {
async function main() {
let serverProcess = null;
let startedHere = false;
const testEnv = sanitizeColorEnv(process.env);
if (!(await isServerReady())) {
serverProcess = spawn(process.execPath, ["scripts/run-next-playwright.mjs", "dev"], {
stdio: "inherit",
env: process.env,
env: testEnv,
});
startedHere = true;
await waitForServerReady();
@@ -48,7 +50,7 @@ async function main() {
["./node_modules/vitest/vitest.mjs", "run", "tests/e2e/ecosystem.test.ts"],
{
stdio: "inherit",
env: process.env,
env: testEnv,
}
);

View File

@@ -4,6 +4,7 @@ import { existsSync, renameSync } from "node:fs";
import { join } from "node:path";
import {
resolveRuntimePorts,
sanitizeColorEnv,
spawnWithForwardedSignals,
withRuntimePortEnv,
} from "./runtime-env.mjs";
@@ -54,6 +55,11 @@ process.on("uncaughtException", (error) => {
prepareAppDir();
const runtimePorts = resolveRuntimePorts();
const testServerEnv = {
...sanitizeColorEnv(process.env),
OMNIROUTE_DISABLE_TOKEN_HEALTHCHECK: process.env.OMNIROUTE_DISABLE_TOKEN_HEALTHCHECK || "1",
OMNIROUTE_HIDE_HEALTHCHECK_LOGS: process.env.OMNIROUTE_HIDE_HEALTHCHECK_LOGS || "1",
};
const args = [
"./node_modules/next/dist/bin/next",
mode,
@@ -66,5 +72,5 @@ if (mode === "dev") {
spawnWithForwardedSignals(process.execPath, args, {
stdio: "inherit",
env: withRuntimePortEnv(process.env, runtimePorts),
env: withRuntimePortEnv(testServerEnv, runtimePorts),
});

View File

@@ -0,0 +1,25 @@
#!/usr/bin/env node
import { spawn } from "node:child_process";
import { sanitizeColorEnv } from "./runtime-env.mjs";
const defaultArgs = ["test", "tests/e2e/*.spec.ts"];
const forwardedArgs = process.argv.slice(2);
const args = forwardedArgs.length > 0 ? forwardedArgs : defaultArgs;
const playwrightEnv = sanitizeColorEnv(process.env);
delete playwrightEnv.NO_COLOR;
delete playwrightEnv.FORCE_COLOR;
const child = spawn(process.execPath, ["./node_modules/playwright/cli.js", ...args], {
stdio: "inherit",
env: playwrightEnv,
});
child.on("exit", (code, signal) => {
if (signal) {
process.kill(process.pid, signal);
return;
}
process.exit(code ?? 0);
});

View File

@@ -25,6 +25,18 @@ export function withRuntimePortEnv(env, runtimePorts) {
};
}
export function sanitizeColorEnv(env = {}) {
const sanitized = { ...env };
// Node warns when both FORCE_COLOR and NO_COLOR are set.
// Prefer NO_COLOR in test tooling to avoid noisy process warnings.
if (typeof sanitized.FORCE_COLOR !== "undefined" && typeof sanitized.NO_COLOR !== "undefined") {
delete sanitized.FORCE_COLOR;
}
return sanitized;
}
export function spawnWithForwardedSignals(command, args, options = {}) {
const child = spawn(command, args, options);