@juspay/neurolink
Version:
Universal AI Development Platform with working MCP integration, multi-provider support, voice (TTS/STT/realtime), and professional CLI. 58+ external MCP servers discoverable, multimodal file processing, RAG pipelines. Build, test, and deploy AI applicatio
109 lines (108 loc) • 5.59 kB
JavaScript
/**
* Research configuration validation and resolution.
*/
import { existsSync, statSync } from "node:fs";
import path from "node:path";
import { logger } from "../utils/logger.js";
import { AutoresearchError } from "./errors.js";
import { AUTORESEARCH_DEFAULTS } from "../types/index.js";
export function resolveConfig(partial) {
const config = {
repoPath: path.resolve(partial.repoPath),
programPath: partial.programPath ?? AUTORESEARCH_DEFAULTS.programPath,
mutablePaths: partial.mutablePaths,
immutablePaths: partial.immutablePaths ?? [],
resultsPath: partial.resultsPath ?? AUTORESEARCH_DEFAULTS.resultsPath,
statePath: partial.statePath ?? AUTORESEARCH_DEFAULTS.statePath,
runCommand: partial.runCommand,
logPath: partial.logPath ?? AUTORESEARCH_DEFAULTS.logPath,
metric: partial.metric,
memoryMetric: partial.memoryMetric,
timeoutMs: partial.timeoutMs ?? AUTORESEARCH_DEFAULTS.timeoutMs,
branchPrefix: partial.branchPrefix ?? AUTORESEARCH_DEFAULTS.branchPrefix,
provider: partial.provider,
model: partial.model,
maxExperiments: partial.maxExperiments,
thinkingLevel: partial.thinkingLevel ?? AUTORESEARCH_DEFAULTS.thinkingLevel,
};
return config;
}
export function validateConfig(config) {
// Verify repoPath exists and is a directory
if (!existsSync(config.repoPath) ||
!statSync(config.repoPath).isDirectory()) {
throw AutoresearchError.create("CONFIG_INVALID", `repoPath does not exist or is not a directory: ${config.repoPath}`);
}
// Verify it's a git repo
if (!existsSync(path.join(config.repoPath, ".git"))) {
throw AutoresearchError.create("REPO_NOT_FOUND", `repoPath is not a git repository: ${config.repoPath}`);
}
// Verify mutablePaths is non-empty
if (config.mutablePaths.length === 0) {
throw AutoresearchError.create("CONFIG_INVALID", "mutablePaths must contain at least one path");
}
// Verify mutable and immutable don't overlap (normalize paths first)
const normMutable = config.mutablePaths.map((p) => path.normalize(p).replace(/\/+$/, ""));
const normImmutable = config.immutablePaths.map((p) => path.normalize(p).replace(/\/+$/, ""));
const overlap = normMutable.filter((p) => normImmutable.includes(p));
if (overlap.length > 0) {
throw AutoresearchError.create("CONFIG_INVALID", `mutablePaths and immutablePaths overlap: ${overlap.join(", ")}`);
}
// Verify metric pattern — length limit and basic ReDoS safety
const MAX_PATTERN_LENGTH = 200;
if (config.metric.pattern.length > MAX_PATTERN_LENGTH) {
throw AutoresearchError.create("CONFIG_INVALID", `metric.pattern exceeds ${MAX_PATTERN_LENGTH} characters: ${config.metric.pattern.slice(0, 50)}...`);
}
// Reject patterns with obvious catastrophic backtracking (nested quantifiers)
if (/(\+|\*|\{)\s*(\+|\*|\{)/.test(config.metric.pattern) ||
/\(\?[^)]*\)\+\+/.test(config.metric.pattern)) {
throw AutoresearchError.create("CONFIG_INVALID", `metric.pattern contains nested quantifiers (potential ReDoS): ${config.metric.pattern}`);
}
// Verify metric pattern is valid regex with exactly one capture group
try {
const _regex = new RegExp(config.metric.pattern);
const match = new RegExp(config.metric.pattern + "|").exec("");
const groupCount = (match?.length ?? 1) - 1;
if (groupCount !== 1) {
throw AutoresearchError.create("CONFIG_INVALID", `metric.pattern must have exactly one capture group, found ${groupCount}: ${config.metric.pattern}`);
}
}
catch (error) {
if (error instanceof Error &&
"code" in error &&
typeof error.code === "string") {
throw error; // Rethrow structured AutoresearchError
}
throw AutoresearchError.create("CONFIG_INVALID", `metric.pattern is not a valid regex: ${config.metric.pattern}`);
}
// Validate memoryMetric.pattern with the same checks if present
if (config.memoryMetric) {
if (config.memoryMetric.pattern.length > MAX_PATTERN_LENGTH) {
throw AutoresearchError.create("CONFIG_INVALID", `memoryMetric.pattern exceeds ${MAX_PATTERN_LENGTH} characters`);
}
if (/(\+|\*|\{)\s*(\+|\*|\{)/.test(config.memoryMetric.pattern) ||
/\(\?[^)]*\)\+\+/.test(config.memoryMetric.pattern)) {
throw AutoresearchError.create("CONFIG_INVALID", `memoryMetric.pattern contains nested quantifiers (potential ReDoS)`);
}
try {
const _memRegex = new RegExp(config.memoryMetric.pattern);
const memMatch = new RegExp(config.memoryMetric.pattern + "|").exec("");
const memGroupCount = (memMatch?.length ?? 1) - 1;
if (memGroupCount !== 1) {
throw AutoresearchError.create("CONFIG_INVALID", `memoryMetric.pattern must have exactly one capture group, found ${memGroupCount}`);
}
}
catch (error) {
if (error instanceof Error &&
"code" in error &&
typeof error.code === "string") {
throw error; // Rethrow structured AutoresearchError
}
throw AutoresearchError.create("CONFIG_INVALID", `memoryMetric.pattern is not a valid regex: ${config.memoryMetric.pattern}`);
}
}
logger.info("[Autoresearch] Config validated", {
repoPath: config.repoPath,
mutablePaths: config.mutablePaths,
});
}