@robinson_ai_systems/free-agent-mcp
Version:
Free Agent MCP - Portable, workspace-agnostic code generation using FREE models (Ollama)
1,475 lines (1,442 loc) • 655 kB
JavaScript
#!/usr/bin/env node
import { fileURLToPath } from 'url';
import { dirname } from 'path';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
var __defProp = Object.defineProperty;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
}) : x)(function(x) {
if (typeof require !== "undefined") return require.apply(this, arguments);
throw Error('Dynamic require of "' + x + '" is not supported');
});
var __esm = (fn, res) => function __init() {
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
};
var __commonJS = (cb, mod) => function __require2() {
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
};
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
// src/shared/shared-llm/ollama-client.ts
async function pingOllama(timeoutMs = 1e3) {
const urls = [
`${BASE}/api/tags`,
`${BASE.replace("localhost", "127.0.0.1")}/api/tags`
];
for (const url of urls) {
try {
const r = await fetch(url, {
method: "GET",
signal: AbortSignal.timeout(timeoutMs),
headers: { "Accept": "application/json" }
});
if (r.ok) {
return true;
}
} catch (error) {
console.error(`[pingOllama] Failed to ping ${url}: ${error?.message || error}`);
}
}
return false;
}
async function ollamaGenerate(opts) {
const { model, prompt, format, timeoutMs = 12e4, retries = 2 } = opts;
console.error(`[sharedGenerate] Starting generation with model: ${model}, timeout: ${timeoutMs}ms`);
let lastErr;
for (let i = 0; i <= retries; i++) {
try {
console.error(`[sharedGenerate] Attempt ${i + 1}/${retries + 1}`);
const body = { model, prompt, stream: false };
if (format === "json") {
body.format = "json";
}
console.error(`[sharedGenerate] Sending fetch to ${BASE}/api/generate`);
const r = await fetch(`${BASE}/api/generate`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body),
signal: AbortSignal.timeout(timeoutMs)
});
console.error(`[sharedGenerate] Fetch completed with status: ${r.status}`);
if (!r.ok) throw new Error(`HTTP ${r.status}`);
console.error("[sharedGenerate] Parsing JSON response...");
const json = await r.json();
console.error("[sharedGenerate] JSON parsed successfully");
return json.response || "";
} catch (e) {
console.error(`[sharedGenerate] Error on attempt ${i + 1}:`, e);
lastErr = e;
if (i < retries) {
console.error(`[sharedGenerate] Retrying in ${500 * (i + 1)}ms...`);
await sleep(500 * (i + 1));
}
}
}
throw new Error(`Ollama generate failed after ${retries + 1} attempt(s): ${lastErr?.message || lastErr}`);
}
async function warmModels(models) {
for (const model of models) {
try {
await ollamaGenerate({ model, prompt: "test", timeoutMs: 1e4, retries: 0 });
} catch {
}
}
}
var BASE, sleep;
var init_ollama_client = __esm({
"src/shared/shared-llm/ollama-client.ts"() {
"use strict";
BASE = (process.env.OLLAMA_BASE_URL || "http://localhost:11434").replace(/\/+$/, "");
sleep = (ms) => new Promise((r) => setTimeout(r, ms));
}
});
// src/pipeline/types.ts
function calculateWeightedScore(scores, weights = DEFAULT_PIPELINE_CONFIG.weights) {
const w = weights;
return scores.compilation * w.compilation + scores.tests_functional * w.tests_functional + scores.tests_edge * w.tests_edge + scores.types * w.types + scores.style * w.style + scores.security * w.security + (scores.conventions || 0) * (w.conventions || 0);
}
function meetsAcceptanceCriteria(verdict, config = DEFAULT_PIPELINE_CONFIG) {
const score = calculateWeightedScore(verdict.scores, config.weights);
if (score < (config.acceptThreshold ?? DEFAULT_PIPELINE_CONFIG.acceptThreshold)) {
return false;
}
if (verdict.scores.compilation === 0) return false;
if (verdict.scores.security === 0) return false;
return true;
}
var DEFAULT_PIPELINE_CONFIG;
var init_types = __esm({
"src/pipeline/types.ts"() {
"use strict";
DEFAULT_PIPELINE_CONFIG = {
maxAttempts: 5,
acceptThreshold: 0.9,
weights: {
compilation: 0.15,
tests_functional: 0.25,
tests_edge: 0.15,
types: 0.1,
security: 0.1,
style: 0.05,
conventions: 0.2
},
allowedLibraries: [
// Node.js built-ins
"fs",
"path",
"util",
"crypto",
"stream",
"events",
"buffer",
// Common safe libraries
"lodash",
"axios",
"express",
"react",
"vue",
"next",
// Testing
"jest",
"@jest/globals",
"vitest",
"mocha",
"chai",
// TypeScript
"typescript",
"@types/*"
],
minCoverage: 80,
testTimeout: 5e3,
globalTimeout: 3e4,
memoryLimit: 512,
spec: "",
provider: "ollama",
model: "qwen2.5-coder:7b"
};
}
});
// src/utils/repo-tools.ts
import * as fs from "fs";
import * as path from "path";
import { execSync } from "child_process";
async function runRepoTools(root, files) {
const result = {
passed: true,
lintErrors: [],
typeErrors: [],
boundaryErrors: [],
customRuleErrors: [],
allErrors: []
};
const lintResult = await runESLint(root, files);
result.lintErrors = lintResult.errors;
if (lintResult.errors.length > 0) result.passed = false;
const typeResult = await runTypeScript(root, files);
result.typeErrors = typeResult.errors;
if (typeResult.errors.length > 0) result.passed = false;
const boundaryResult = await checkBoundaries(root, files);
result.boundaryErrors = boundaryResult.errors;
if (boundaryResult.errors.length > 0) result.passed = false;
const customResult = await runCustomRules(root, files);
result.customRuleErrors = customResult.errors;
if (customResult.errors.length > 0) result.passed = false;
result.allErrors = [
...result.lintErrors,
...result.typeErrors,
...result.boundaryErrors,
...result.customRuleErrors
];
return result;
}
async function runESLint(root, files) {
const errors = [];
const eslintPath = path.join(root, "node_modules", ".bin", "eslint");
if (!fs.existsSync(eslintPath) && !fs.existsSync(eslintPath + ".cmd")) {
return { errors: [] };
}
try {
const fileArgs = files.map((f) => `"${f}"`).join(" ");
const cmd = process.platform === "win32" ? `"${eslintPath}.cmd" --format json ${fileArgs}` : `"${eslintPath}" --format json ${fileArgs}`;
const output = execSync(cmd, {
cwd: root,
encoding: "utf-8",
stdio: ["pipe", "pipe", "pipe"]
});
const results = JSON.parse(output);
for (const result of results) {
for (const message of result.messages) {
errors.push(`${result.filePath}:${message.line}:${message.column} - ${message.message} (${message.ruleId})`);
}
}
} catch (error) {
if (error.stdout) {
try {
const results = JSON.parse(error.stdout);
for (const result of results) {
for (const message of result.messages) {
errors.push(`${result.filePath}:${message.line}:${message.column} - ${message.message} (${message.ruleId})`);
}
}
} catch (parseError) {
errors.push(`ESLint error: ${error.message}`);
}
}
}
return { errors: errors.slice(0, 10) };
}
async function runTypeScript(root, files) {
const errors = [];
const tscPath = path.join(root, "node_modules", ".bin", "tsc");
if (!fs.existsSync(tscPath) && !fs.existsSync(tscPath + ".cmd")) {
return { errors: [] };
}
try {
const cmd = process.platform === "win32" ? `"${tscPath}.cmd" --noEmit --pretty false` : `"${tscPath}" --noEmit --pretty false`;
execSync(cmd, {
cwd: root,
encoding: "utf-8",
stdio: ["pipe", "pipe", "pipe"]
});
} catch (error) {
if (error.stderr || error.stdout) {
const output = error.stderr || error.stdout;
const lines = output.split("\n");
for (const line of lines) {
const match = line.match(/^(.+?)\((\d+),(\d+)\):\s*error\s+TS\d+:\s*(.+)$/);
if (match) {
const [, file, line2, col, message] = match;
errors.push(`${file}:${line2}:${col} - ${message}`);
}
}
}
}
return { errors: errors.slice(0, 10) };
}
async function checkBoundaries(root, files) {
const errors = [];
const eslintConfigPath = findESLintConfig(root);
if (!eslintConfigPath) {
return { errors: [] };
}
try {
const configContent = fs.readFileSync(eslintConfigPath, "utf-8");
if (!configContent.includes("boundaries")) {
return { errors: [] };
}
const boundaryRules = parseBoundaryRules(configContent);
for (const file of files) {
const violations = checkFileBoundaries(root, file, boundaryRules);
errors.push(...violations);
}
} catch (error) {
}
return { errors: errors.slice(0, 10) };
}
function findESLintConfig(root) {
const configFiles = [
".eslintrc.js",
".eslintrc.cjs",
".eslintrc.json",
".eslintrc",
"eslint.config.js"
];
for (const file of configFiles) {
const fullPath = path.join(root, file);
if (fs.existsSync(fullPath)) {
return fullPath;
}
}
return null;
}
function parseBoundaryRules(configContent) {
const rules = [];
const ruleMatches = configContent.matchAll(/"from":\s*\[([^\]]+)\][^}]*"allow":\s*\[([^\]]+)\]/g);
for (const match of ruleMatches) {
const from = match[1].split(",").map((s) => s.trim().replace(/['"]/g, ""));
const allow = match[2].split(",").map((s) => s.trim().replace(/['"]/g, ""));
rules.push({ from, allow });
}
return rules;
}
function checkFileBoundaries(root, file, rules) {
const errors = [];
try {
const content = fs.readFileSync(path.join(root, file), "utf-8");
const imports = extractImports(content);
const fileLayer = determineLayer(file);
if (!fileLayer) return errors;
const rule = rules.find((r) => r.from.includes(fileLayer));
if (!rule) return errors;
for (const imp of imports) {
const importLayer = determineLayer(imp);
if (!importLayer) continue;
if (!rule.allow.includes(importLayer)) {
errors.push(`${file}: Cannot import from ${importLayer} (${imp}). ${fileLayer} can only import from: ${rule.allow.join(", ")}`);
}
}
} catch (error) {
}
return errors;
}
function extractImports(content) {
const imports = [];
const importMatches = content.matchAll(/import\s+.*?\s+from\s+['"]([^'"]+)['"]/g);
for (const match of importMatches) {
imports.push(match[1]);
}
const requireMatches = content.matchAll(/require\(['"]([^'"]+)['"]\)/g);
for (const match of requireMatches) {
imports.push(match[1]);
}
return imports;
}
function determineLayer(filePath) {
if (filePath.includes("/features/") || filePath.includes("\\features\\")) return "feature";
if (filePath.includes("/domain/") || filePath.includes("\\domain\\")) return "domain";
if (filePath.includes("/infra/") || filePath.includes("\\infra\\")) return "infra";
if (filePath.includes("/utils/") || filePath.includes("\\utils\\")) return "utils";
if (filePath.includes("/lib/") || filePath.includes("\\lib\\")) return "lib";
if (filePath.includes("/core/") || filePath.includes("\\core\\")) return "core";
return null;
}
async function runCustomRules(root, files) {
const errors = [];
for (const file of files) {
try {
const content = fs.readFileSync(path.join(root, file), "utf-8");
const snakeCaseMatches = content.matchAll(/\b(const|let|var)\s+([a-z]+_[a-z_]+)\s*=/g);
for (const match of snakeCaseMatches) {
const varName = match[2];
if (!/^[A-Z_]+$/.test(varName)) {
errors.push(`${file}: Use camelCase instead of snake_case for variable '${varName}'`);
}
}
const anyTypeMatches = content.matchAll(/export\s+(function|const|class|interface|type)\s+[^:]+:\s*any/g);
for (const match of anyTypeMatches) {
errors.push(`${file}: Do not use 'any' type in public exports`);
}
if (!file.includes(".test.") && !file.includes(".spec.")) {
const consoleMatches = content.matchAll(/console\.log\(/g);
for (const match of consoleMatches) {
errors.push(`${file}: Remove console.log() from production code`);
}
}
} catch (error) {
}
}
return { errors: errors.slice(0, 10) };
}
var init_repo_tools = __esm({
"src/utils/repo-tools.ts"() {
"use strict";
}
});
// src/utils/edit-constraints.ts
import * as path2 from "path";
async function checkEditConstraints(root, files, constraints) {
const violations = [];
for (const file of files) {
const isAllowed = constraints.allowedPaths.some(
(allowed) => file.path.startsWith(allowed) || matchesGlob(file.path, allowed)
);
if (!isAllowed) {
const isReadOnly = constraints.readOnlyPaths.some(
(readonly) => file.path.startsWith(readonly) || matchesGlob(file.path, readonly)
);
if (isReadOnly) {
violations.push({
file: file.path,
violation: "File is read-only and cannot be modified",
severity: "error"
});
continue;
}
}
if (!constraints.allowPublicRenames) {
const renameViolations = await checkPublicRenames(root, file);
violations.push(...renameViolations);
}
}
return violations;
}
function matchesGlob(filePath, pattern) {
const regexPattern = pattern.replace(/\*\*/g, ".*").replace(/\*/g, "[^/]*").replace(/\./g, "\\.");
const regex = new RegExp(`^${regexPattern}$`);
return regex.test(filePath);
}
async function checkPublicRenames(root, file) {
const violations = [];
try {
const newSymbols = extractSymbolsFromContent(file.content, file.path);
const fs22 = await import("fs");
const fullPath = path2.join(root, file.path);
if (!fs22.existsSync(fullPath)) {
return violations;
}
const originalContent = fs22.readFileSync(fullPath, "utf-8");
const originalSymbols = extractSymbolsFromContent(originalContent, file.path);
const originalPublic = originalSymbols.filter((s) => s.isPublic);
const newPublic = newSymbols.filter((s) => s.isPublic);
const originalNames = new Set(originalPublic.map((s) => s.name));
const newNames = new Set(newPublic.map((s) => s.name));
for (const name of originalNames) {
if (!newNames.has(name)) {
violations.push({
file: file.path,
violation: `Public symbol '${name}' was removed or renamed. This is a breaking change.`,
severity: "error"
});
}
}
} catch (error) {
}
return violations;
}
function extractSymbolsFromContent(content, filePath) {
const symbols = [];
const lines = content.split("\n");
const patterns = [
// export function/const/class/interface/type
{ regex: /export\s+(async\s+)?function\s+([a-zA-Z_$][a-zA-Z0-9_$]*)/g, type: "function", isPublic: true },
{ regex: /export\s+const\s+([a-zA-Z_$][a-zA-Z0-9_$]*)/g, type: "const", isPublic: true },
{ regex: /export\s+class\s+([a-zA-Z_$][a-zA-Z0-9_$]*)/g, type: "class", isPublic: true },
{ regex: /export\s+interface\s+([a-zA-Z_$][a-zA-Z0-9_$]*)/g, type: "interface", isPublic: true },
{ regex: /export\s+type\s+([a-zA-Z_$][a-zA-Z0-9_$]*)/g, type: "type", isPublic: true },
{ regex: /export\s+enum\s+([a-zA-Z_$][a-zA-Z0-9_$]*)/g, type: "enum", isPublic: true }
];
for (const pattern of patterns) {
let match;
while ((match = pattern.regex.exec(content)) !== null) {
const name = match[2] || match[1];
if (!name) continue;
const lineNumber = content.substring(0, match.index).split("\n").length;
symbols.push({
name,
type: pattern.type,
file: filePath,
line: lineNumber,
isPublic: pattern.isPublic
});
}
}
return symbols;
}
function inferAllowedPaths(spec, root) {
const allowedPaths = [];
const pathMatches = spec.matchAll(/(?:^|\s)([a-zA-Z0-9_\-/]+\.[a-zA-Z]+)/g);
for (const match of pathMatches) {
allowedPaths.push(match[1]);
}
const dirMatches = spec.matchAll(/(?:^|\s)([a-zA-Z0-9_\-/]+\/)/g);
for (const match of dirMatches) {
allowedPaths.push(match[1] + "**/*");
}
if (allowedPaths.length === 0) {
allowedPaths.push("src/**/*");
}
return allowedPaths;
}
function getDefaultReadOnlyPaths() {
return [
"node_modules/**/*",
"dist/**/*",
"build/**/*",
".next/**/*",
"coverage/**/*",
"__generated__/**/*",
"src/gen/**/*",
"src/generated/**/*",
"prisma/client/**/*"
];
}
function calculateDiffSize(originalContent, newContent) {
const originalLines = originalContent.split("\n");
const newLines = newContent.split("\n");
let changes = 0;
const maxLines = Math.max(originalLines.length, newLines.length);
for (let i = 0; i < maxLines; i++) {
const originalLine = originalLines[i] || "";
const newLine = newLines[i] || "";
if (originalLine !== newLine) {
changes++;
}
}
return changes;
}
function isMinimalDiff(originalContent, newContent) {
const diffSize = calculateDiffSize(originalContent, newContent);
const totalLines = originalContent.split("\n").length;
if (totalLines === 0) return true;
const changePercentage = diffSize / totalLines * 100;
return changePercentage < 50;
}
async function enforceMinimalDiffs(root, files) {
const violations = [];
const fs22 = await import("fs");
for (const file of files) {
const fullPath = path2.join(root, file.path);
if (!fs22.existsSync(fullPath)) {
continue;
}
const originalContent = fs22.readFileSync(fullPath, "utf-8");
if (!isMinimalDiff(originalContent, file.content)) {
const diffSize = calculateDiffSize(originalContent, file.content);
const totalLines = originalContent.split("\n").length;
const changePercentage = (diffSize / totalLines * 100).toFixed(1);
violations.push({
file: file.path,
violation: `Diff is too large (${changePercentage}% of file changed). Prefer minimal, targeted changes.`,
severity: "warning"
});
}
}
return violations;
}
var init_edit_constraints = __esm({
"src/utils/edit-constraints.ts"() {
"use strict";
}
});
// src/utils/convention-tests.ts
function generateConventionTests(files, brief) {
const tests = [];
tests.push(generateImportPathTest(files, brief));
tests.push(generateNamingConventionTest(files, brief));
tests.push(generateNoAnyTest(files));
tests.push(generateLayeringTest(files, brief));
tests.push(generateFileNamingTest(files, brief));
return tests;
}
function generateImportPathTest(files, brief) {
const code = `
describe('Import Path Conventions', () => {
it('should use correct import paths', () => {
const files = ${JSON.stringify(files.map((f) => ({ path: f.path, content: f.content })), null, 2)};
for (const file of files) {
const imports = extractImports(file.content);
for (const imp of imports) {
// Check for relative imports going up too many levels
const upLevels = (imp.match(/\\.\\./g) || []).length;
expect(upLevels).toBeLessThanOrEqual(2);
// Check for absolute imports from src
if (imp.startsWith('src/')) {
fail(\`File \${file.path} uses absolute import '\${imp}'. Use relative imports or path aliases.\`);
}
}
}
});
});
`;
return {
name: "import-paths",
description: "Validates import path conventions",
code
};
}
function generateNamingConventionTest(files, brief) {
const code = `
describe('Naming Conventions', () => {
it('should follow repo naming conventions', () => {
const files = ${JSON.stringify(files.map((f) => ({ path: f.path, content: f.content })), null, 2)};
const namingRules = ${JSON.stringify(brief.naming, null, 2)};
for (const file of files) {
// Check variable names (camelCase)
const varMatches = file.content.matchAll(/(?:const|let|var)\\s+([a-zA-Z_$][a-zA-Z0-9_$]*)/g);
for (const match of varMatches) {
const name = match[1];
// Skip UPPER_SNAKE_CASE constants
if (name === name.toUpperCase()) continue;
// Check camelCase
if (!/^[a-z][a-zA-Z0-9]*$/.test(name)) {
fail(\`Variable '\${name}' in \${file.path} should be camelCase\`);
}
}
// Check type names (PascalCase)
const typeMatches = file.content.matchAll(/(?:interface|type|class|enum)\\s+([A-Z][a-zA-Z0-9]*)/g);
for (const match of typeMatches) {
const name = match[1];
// Check PascalCase
if (!/^[A-Z][a-zA-Z0-9]*$/.test(name)) {
fail(\`Type '\${name}' in \${file.path} should be PascalCase\`);
}
}
}
});
});
`;
return {
name: "naming-conventions",
description: "Validates naming conventions (camelCase, PascalCase)",
code
};
}
function generateNoAnyTest(files) {
const code = `
describe('Type Safety', () => {
it('should not use any in public exports', () => {
const files = ${JSON.stringify(files.map((f) => ({ path: f.path, content: f.content })), null, 2)};
for (const file of files) {
// Find export statements
const exportMatches = file.content.matchAll(/export\\s+(?:function|const|class|interface|type)\\s+[^{;]+/g);
for (const match of exportMatches) {
const exportStatement = match[0];
// Check for 'any' type
if (exportStatement.includes(': any') || exportStatement.includes('<any>')) {
fail(\`Public export in \${file.path} uses 'any' type: \${exportStatement}\`);
}
}
}
});
});
`;
return {
name: "no-any-in-exports",
description: "Ensures no any type in public exports",
code
};
}
function generateLayeringTest(files, brief) {
const code = `
describe('Layering Rules', () => {
it('should respect layer boundaries', () => {
const files = ${JSON.stringify(files.map((f) => ({ path: f.path, content: f.content })), null, 2)};
const layers = ${JSON.stringify(brief.layering?.layers || [], null, 2)};
// Skip if no layers defined
if (!layers || layers.length === 0) {
return;
}
for (const file of files) {
// Determine file's layer
const fileLayer = layers.find(layer => file.path.includes(layer.name));
if (!fileLayer) continue;
// Extract imports
const imports = extractImports(file.content);
for (const imp of imports) {
// Skip external imports
if (!imp.startsWith('.') && !imp.startsWith('/')) continue;
// Check if import violates layer rules
const importedLayer = layers.find(layer => imp.includes(layer.name));
if (!importedLayer) continue;
if (!fileLayer.canImport.includes(importedLayer.name)) {
fail(\`File \${file.path} in layer '\${fileLayer.name}' cannot import from layer '\${importedLayer.name}'\`);
}
}
}
});
});
`;
return {
name: "layering-rules",
description: "Validates layer boundary rules",
code
};
}
function generateFileNamingTest(files, brief) {
const code = `
describe('File Naming', () => {
it('should follow file naming conventions', () => {
const files = ${JSON.stringify(files.map((f) => f.path), null, 2)};
const fileNamingPattern = ${JSON.stringify(brief.naming.files)};
for (const filePath of files) {
const fileName = filePath.split('/').pop() || '';
const baseName = fileName.replace(/\\.(ts|tsx|js|jsx)$/, '');
// Check kebab-case for files
if (fileNamingPattern === 'kebab-case') {
if (!/^[a-z][a-z0-9]*(-[a-z0-9]+)*$/.test(baseName)) {
fail(\`File '\${fileName}' should be kebab-case\`);
}
}
// Check camelCase for files
if (fileNamingPattern === 'camelCase') {
if (!/^[a-z][a-zA-Z0-9]*$/.test(baseName)) {
fail(\`File '\${fileName}' should be camelCase\`);
}
}
// Check PascalCase for files
if (fileNamingPattern === 'PascalCase') {
if (!/^[A-Z][a-zA-Z0-9]*$/.test(baseName)) {
fail(\`File '\${fileName}' should be PascalCase\`);
}
}
}
});
});
`;
return {
name: "file-naming",
description: "Validates file naming conventions",
code
};
}
function generateConventionTestFile(files, brief) {
const tests = generateConventionTests(files, brief);
const testFile = `/**
* Convention Tests - Auto-generated
*
* These tests ensure generated code follows repository conventions.
*/
import { describe, it, expect } from '@jest/globals';
// Helper function used by multiple tests
function extractImports(content: string): string[] {
const imports: string[] = [];
const importRegex = /import\\s+.*?from\\s+['"]([^'"]+)['"]/g;
let match;
while ((match = importRegex.exec(content)) !== null) {
imports.push(match[1]);
}
return imports;
}
${tests.map((t) => t.code).join("\n\n")}
`;
return testFile;
}
var init_convention_tests = __esm({
"src/utils/convention-tests.ts"() {
"use strict";
}
});
// src/utils/symbol-indexer.ts
import * as fs2 from "fs";
import * as path3 from "path";
async function buildSymbolIndex(root, options = {}) {
const {
exts = [".ts", ".tsx", ".js", ".jsx"],
maxFiles = 2e3,
maxPerFileIds = 200,
exclude = ["node_modules", "dist", "build", ".next", "coverage"]
} = options;
const index = {
symbols: [],
byFile: /* @__PURE__ */ new Map(),
byName: /* @__PURE__ */ new Map(),
byType: /* @__PURE__ */ new Map()
};
const files = await findFiles(root, exts, exclude, maxFiles);
for (const file of files) {
try {
const content = fs2.readFileSync(file, "utf-8");
const symbols = extractSymbols(content, file, maxPerFileIds);
index.symbols.push(...symbols);
index.byFile.set(file, symbols);
for (const symbol of symbols) {
if (!index.byName.has(symbol.name)) {
index.byName.set(symbol.name, []);
}
index.byName.get(symbol.name).push(symbol);
if (!index.byType.has(symbol.type)) {
index.byType.set(symbol.type, []);
}
index.byType.get(symbol.type).push(symbol);
}
} catch (error) {
console.warn(`Failed to index ${file}:`, error);
}
}
return index;
}
async function findFiles(dir, exts, exclude, maxFiles) {
const files = [];
function walk(currentDir) {
if (files.length >= maxFiles) return;
const entries = fs2.readdirSync(currentDir, { withFileTypes: true });
for (const entry of entries) {
if (files.length >= maxFiles) break;
const fullPath = path3.join(currentDir, entry.name);
const relativePath = path3.relative(dir, fullPath);
if (exclude.some((ex) => relativePath.startsWith(ex))) {
continue;
}
if (entry.isDirectory()) {
walk(fullPath);
} else if (entry.isFile()) {
const ext = path3.extname(entry.name);
if (exts.includes(ext)) {
files.push(fullPath);
}
}
}
}
walk(dir);
return files;
}
function extractSymbols(content, file, maxIds) {
const symbols = [];
const lines = content.split("\n");
const patterns = [
// export function/const/class/interface/type
{ regex: /export\s+(async\s+)?function\s+([a-zA-Z_$][a-zA-Z0-9_$]*)/g, type: "function", isPublic: true },
{ regex: /export\s+const\s+([a-zA-Z_$][a-zA-Z0-9_$]*)/g, type: "const", isPublic: true },
{ regex: /export\s+class\s+([a-zA-Z_$][a-zA-Z0-9_$]*)/g, type: "class", isPublic: true },
{ regex: /export\s+interface\s+([a-zA-Z_$][a-zA-Z0-9_$]*)/g, type: "interface", isPublic: true },
{ regex: /export\s+type\s+([a-zA-Z_$][a-zA-Z0-9_$]*)/g, type: "type", isPublic: true },
{ regex: /export\s+enum\s+([a-zA-Z_$][a-zA-Z0-9_$]*)/g, type: "enum", isPublic: true },
// non-export function/const/class/interface/type
{ regex: /^(?!export)\s*(async\s+)?function\s+([a-zA-Z_$][a-zA-Z0-9_$]*)/gm, type: "function", isPublic: false },
{ regex: /^(?!export)\s*const\s+([a-zA-Z_$][a-zA-Z0-9_$]*)/gm, type: "const", isPublic: false },
{ regex: /^(?!export)\s*class\s+([a-zA-Z_$][a-zA-Z0-9_$]*)/gm, type: "class", isPublic: false },
{ regex: /^(?!export)\s*interface\s+([a-zA-Z_$][a-zA-Z0-9_$]*)/gm, type: "interface", isPublic: false },
{ regex: /^(?!export)\s*type\s+([a-zA-Z_$][a-zA-Z0-9_$]*)/gm, type: "type", isPublic: false },
{ regex: /^(?!export)\s*enum\s+([a-zA-Z_$][a-zA-Z0-9_$]*)/gm, type: "enum", isPublic: false }
];
for (const pattern of patterns) {
let match;
while ((match = pattern.regex.exec(content)) !== null) {
if (symbols.length >= maxIds) break;
const name = match[2] || match[1];
if (!name) continue;
const lineNumber = content.substring(0, match.index).split("\n").length;
symbols.push({
name,
type: pattern.type,
file,
line: lineNumber,
isPublic: pattern.isPublic
});
}
}
return symbols;
}
function topFrequencyIdentifiers(index, options = {}) {
const { minLen = 3, byDir = [], limit = 50 } = options;
const frequency = /* @__PURE__ */ new Map();
for (const symbol of index.symbols) {
if (byDir.length > 0 && !byDir.some((dir) => symbol.file.includes(dir))) {
continue;
}
if (symbol.name.length < minLen) {
continue;
}
frequency.set(symbol.name, (frequency.get(symbol.name) || 0) + 1);
}
const sorted = Array.from(frequency.entries()).sort((a, b) => b[1] - a[1]).slice(0, limit).map(([name]) => name);
return sorted;
}
function publicExports(index, patterns) {
const exports = [];
for (const symbol of index.symbols) {
if (!symbol.isPublic) continue;
const matches = patterns.some((pattern) => {
const regex = new RegExp(pattern.replace(/\*/g, ".*"));
return regex.test(symbol.file);
});
if (matches) {
exports.push(`${symbol.type} ${symbol.name} (${symbol.file}:${symbol.line})`);
}
}
return exports;
}
function collectNamingExamples(index) {
const examples = [];
const types = ["function", "class", "interface", "type", "const", "enum"];
for (const type of types) {
const symbols = index.byType.get(type) || [];
const publicSymbols = symbols.filter((s) => s.isPublic).slice(0, 5);
for (const symbol of publicSymbols) {
examples.push(`${type}: ${symbol.name}`);
}
}
return examples;
}
function inferNamingConvention(index) {
const variables = index.byType.get("const") || [];
const types = [...index.byType.get("class") || [], ...index.byType.get("interface") || []];
let camelCaseCount = 0;
let snakeCaseCount = 0;
for (const v of variables.slice(0, 100)) {
if (/^[a-z][a-zA-Z0-9]*$/.test(v.name)) camelCaseCount++;
if (/^[a-z][a-z0-9_]*$/.test(v.name)) snakeCaseCount++;
}
const variableStyle = camelCaseCount > snakeCaseCount ? "camelCase" : "snake_case";
let pascalCaseCount = 0;
for (const t of types.slice(0, 100)) {
if (/^[A-Z][a-zA-Z0-9]*$/.test(t.name)) pascalCaseCount++;
}
const typeStyle = pascalCaseCount > types.length / 2 ? "PascalCase" : "unknown";
const constants = variables.filter((v) => /^[A-Z_][A-Z0-9_]*$/.test(v.name));
const constantStyle = constants.length > 10 ? "UPPER_SNAKE_CASE" : "unknown";
const files = Array.from(new Set(index.symbols.map((s) => path3.basename(s.file))));
let kebabCaseCount = 0;
let camelCaseFileCount = 0;
for (const file of files.slice(0, 100)) {
const name = file.replace(/\.(ts|tsx|js|jsx)$/, "");
if (/^[a-z][a-z0-9-]*$/.test(name)) kebabCaseCount++;
if (/^[a-z][a-zA-Z0-9]*$/.test(name)) camelCaseFileCount++;
}
const fileStyle = kebabCaseCount > camelCaseFileCount ? "kebab-case" : "camelCase";
return {
variables: variableStyle,
types: typeStyle,
constants: constantStyle,
files: fileStyle
};
}
var init_symbol_indexer = __esm({
"src/utils/symbol-indexer.ts"() {
"use strict";
}
});
// src/utils/schema-codegen.ts
import * as fs3 from "fs";
import * as path4 from "path";
async function detectSchemas(root) {
const schemas = [];
const openapiSchema = await detectOpenAPI(root);
if (openapiSchema) schemas.push(openapiSchema);
const graphqlSchema = await detectGraphQL(root);
if (graphqlSchema) schemas.push(graphqlSchema);
const prismaSchema = await detectPrisma(root);
if (prismaSchema) schemas.push(prismaSchema);
const migrationsSchema = await detectMigrations(root);
if (migrationsSchema) schemas.push(migrationsSchema);
return schemas;
}
async function detectOpenAPI(root) {
const openapiPaths = [
"openapi.json",
"openapi.yaml",
"openapi.yml",
"swagger.json",
"api-spec.json",
"docs/openapi.json"
];
for (const p of openapiPaths) {
const fullPath = path4.join(root, p);
if (fs3.existsSync(fullPath)) {
try {
const content = fs3.readFileSync(fullPath, "utf-8");
const spec = p.endsWith(".json") ? JSON.parse(content) : null;
if (!spec) continue;
const entities = [];
const types = [];
const enums = [];
if (spec.components?.schemas) {
for (const [name, schema] of Object.entries(spec.components.schemas)) {
types.push(name);
if (schema.enum) {
enums.push(name);
} else {
entities.push(name);
}
}
}
return {
source: "openapi",
path: p,
entities,
types,
enums
};
} catch (error) {
}
}
}
return null;
}
async function detectGraphQL(root) {
const graphqlPaths = [
"schema.graphql",
"schema.gql",
"src/schema.graphql",
"graphql/schema.graphql"
];
for (const p of graphqlPaths) {
const fullPath = path4.join(root, p);
if (fs3.existsSync(fullPath)) {
try {
const content = fs3.readFileSync(fullPath, "utf-8");
const entities = [];
const types = [];
const enums = [];
const typeMatches = content.matchAll(/type\s+([A-Z][a-zA-Z0-9]*)/g);
for (const match of typeMatches) {
const name = match[1];
if (name !== "Query" && name !== "Mutation" && name !== "Subscription") {
entities.push(name);
types.push(name);
}
}
const enumMatches = content.matchAll(/enum\s+([A-Z][a-zA-Z0-9]*)/g);
for (const match of enumMatches) {
enums.push(match[1]);
types.push(match[1]);
}
const interfaceMatches = content.matchAll(/interface\s+([A-Z][a-zA-Z0-9]*)/g);
for (const match of interfaceMatches) {
types.push(match[1]);
}
return {
source: "graphql",
path: p,
entities,
types,
enums
};
} catch (error) {
}
}
}
return null;
}
async function detectPrisma(root) {
const prismaPath = path4.join(root, "prisma", "schema.prisma");
if (fs3.existsSync(prismaPath)) {
try {
const content = fs3.readFileSync(prismaPath, "utf-8");
const entities = [];
const types = [];
const enums = [];
const modelMatches = content.matchAll(/model\s+([A-Z][a-zA-Z0-9]*)/g);
for (const match of modelMatches) {
entities.push(match[1]);
types.push(match[1]);
}
const enumMatches = content.matchAll(/enum\s+([A-Z][a-zA-Z0-9]*)/g);
for (const match of enumMatches) {
enums.push(match[1]);
types.push(match[1]);
}
return {
source: "prisma",
path: "prisma/schema.prisma",
entities,
types,
enums
};
} catch (error) {
}
}
return null;
}
async function detectMigrations(root) {
const migrationsDirs = [
"migrations",
"db/migrations",
"prisma/migrations",
"database/migrations"
];
for (const dir of migrationsDirs) {
const fullPath = path4.join(root, dir);
if (fs3.existsSync(fullPath)) {
try {
const files = fs3.readdirSync(fullPath);
const sqlFiles = files.filter((f) => f.endsWith(".sql"));
if (sqlFiles.length === 0) continue;
const entities = [];
const types = [];
for (const file of sqlFiles.slice(0, 10)) {
const content = fs3.readFileSync(path4.join(fullPath, file), "utf-8");
const tableMatches = content.matchAll(/CREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?([a-zA-Z_][a-zA-Z0-9_]*)/gi);
for (const match of tableMatches) {
const tableName = match[1];
const entityName = tableName.split("_").map(
(part) => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase()
).join("");
if (!entities.includes(entityName)) {
entities.push(entityName);
types.push(entityName);
}
}
}
return {
source: "migrations",
path: dir,
entities,
types,
enums: []
};
} catch (error) {
}
}
}
return null;
}
var init_schema_codegen = __esm({
"src/utils/schema-codegen.ts"() {
"use strict";
}
});
// src/utils/project-brief.ts
var project_brief_exports = {};
__export(project_brief_exports, {
formatBriefForPrompt: () => formatBriefForPrompt,
makeProjectBrief: () => makeProjectBrief
});
import * as fs4 from "fs";
import * as path5 from "path";
async function makeProjectBrief(root) {
const brief = {
language: "unknown",
versions: {},
style: {},
layering: { type: "unknown" },
testing: { framework: "unknown", testPattern: "**/*.test.*" },
schema: { sources: [] },
glossary: { entities: [], enums: [], constants: [], commonAbbreviations: {} },
naming: { variables: "camelCase", types: "PascalCase", constants: "UPPER_SNAKE_CASE", files: "kebab-case", examples: [] },
apis: { publicExports: [] },
doNotTouch: []
};
await detectLanguageAndVersions(root, brief);
await detectStyleRules(root, brief);
await detectLayering(root, brief);
await detectTesting(root, brief);
await detectSchemasForBrief(root, brief);
await detectDoNotTouch(root, brief);
await buildGlossaryAndNaming(root, brief);
return brief;
}
async function detectLanguageAndVersions(root, brief) {
const pkgPath = path5.join(root, "package.json");
if (fs4.existsSync(pkgPath)) {
try {
const pkg = JSON.parse(fs4.readFileSync(pkgPath, "utf-8"));
brief.language = pkg.type === "module" ? "esm" : "commonjs";
brief.versions.node = pkg.engines?.node;
if (pkg.devDependencies?.typescript || pkg.dependencies?.typescript) {
brief.language = "typescript";
const tsconfigPath = path5.join(root, "tsconfig.json");
if (fs4.existsSync(tsconfigPath)) {
const tsconfig = JSON.parse(fs4.readFileSync(tsconfigPath, "utf-8"));
brief.versions.typescript = pkg.devDependencies?.typescript || pkg.dependencies?.typescript;
brief.style.tsconfig = tsconfig.compilerOptions;
}
}
} catch (error) {
console.warn("Failed to parse package.json:", error);
}
}
const pyprojectPath = path5.join(root, "pyproject.toml");
if (fs4.existsSync(pyprojectPath)) {
brief.language = "python";
}
const gomodPath = path5.join(root, "go.mod");
if (fs4.existsSync(gomodPath)) {
brief.language = "go";
try {
const gomod = fs4.readFileSync(gomodPath, "utf-8");
const match = gomod.match(/^go\s+(\d+\.\d+)/m);
if (match) {
brief.versions.go = match[1];
}
} catch (error) {
console.warn("Failed to parse go.mod:", error);
}
}
}
async function detectStyleRules(root, brief) {
const editorconfigPath = path5.join(root, ".editorconfig");
if (fs4.existsSync(editorconfigPath)) {
brief.style.editorconfig = fs4.readFileSync(editorconfigPath, "utf-8");
}
const prettierPaths = [".prettierrc", ".prettierrc.json", ".prettierrc.js", "prettier.config.js"];
for (const p of prettierPaths) {
const fullPath = path5.join(root, p);
if (fs4.existsSync(fullPath)) {
try {
if (p.endsWith(".json") || p === ".prettierrc") {
brief.style.prettier = JSON.parse(fs4.readFileSync(fullPath, "utf-8"));
}
} catch (error) {
console.warn(`Failed to parse ${p}:`, error);
}
break;
}
}
const eslintPaths = [".eslintrc", ".eslintrc.json", ".eslintrc.js", "eslint.config.js"];
for (const p of eslintPaths) {
const fullPath = path5.join(root, p);
if (fs4.existsSync(fullPath)) {
try {
if (p.endsWith(".json") || p === ".eslintrc") {
brief.style.eslint = JSON.parse(fs4.readFileSync(fullPath, "utf-8"));
}
} catch (error) {
console.warn(`Failed to parse ${p}:`, error);
}
break;
}
}
}
async function detectLayering(root, brief) {
const packagesDir = path5.join(root, "packages");
const appsDir = path5.join(root, "apps");
if (fs4.existsSync(packagesDir) || fs4.existsSync(appsDir)) {
brief.layering.type = "monorepo";
brief.layering.packages = [];
if (fs4.existsSync(packagesDir)) {
const packages = fs4.readdirSync(packagesDir).filter((p) => {
const stat = fs4.statSync(path5.join(packagesDir, p));
return stat.isDirectory();
});
brief.layering.packages.push(...packages.map((p) => `packages/${p}`));
}
if (fs4.existsSync(appsDir)) {
const apps = fs4.readdirSync(appsDir).filter((p) => {
const stat = fs4.statSync(path5.join(appsDir, p));
return stat.isDirectory();
});
brief.layering.packages.push(...apps.map((p) => `apps/${p}`));
}
} else {
brief.layering.type = "single";
}
const srcDir = path5.join(root, "src");
if (fs4.existsSync(srcDir)) {
const layers = [];
const commonLayers = ["features", "domain", "infra", "utils", "lib", "core"];
for (const layer of commonLayers) {
const layerPath = path5.join(srcDir, layer);
if (fs4.existsSync(layerPath)) {
layers.push({
name: layer,
pattern: `src/${layer}/**/*`,
canImport: inferCanImport(layer)
});
}
}
if (layers.length > 0) {
brief.layering.layers = layers;
}
}
if (brief.style.eslint?.settings?.["boundaries/elements"]) {
brief.layering.boundaries = "eslint-plugin-boundaries";
}
}
function inferCanImport(layer) {
const rules = {
features: ["domain", "infra", "utils", "lib", "core"],
domain: ["domain", "infra", "utils", "lib", "core"],
infra: ["infra", "utils", "lib", "core"],
utils: ["utils", "lib", "core"],
lib: ["lib", "core"],
core: ["core"]
};
return rules[layer] || [];
}
async function detectTesting(root, brief) {
const pkgPath = path5.join(root, "package.json");
if (fs4.existsSync(pkgPath)) {
try {
const pkg = JSON.parse(fs4.readFileSync(pkgPath, "utf-8"));
if (pkg.devDependencies?.jest || pkg.dependencies?.jest) {
brief.testing.framework = "jest";
brief.testing.testPattern = "**/*.test.{ts,tsx,js,jsx}";
} else if (pkg.devDependencies?.vitest || pkg.dependencies?.vitest) {
brief.testing.framework = "vitest";
brief.testing.testPattern = "**/*.test.{ts,tsx,js,jsx}";
} else if (pkg.devDependencies?.mocha || pkg.dependencies?.mocha) {
brief.testing.framework = "mocha";
brief.testing.testPattern = "**/*.test.{ts,js}";
}
} catch (error) {
console.warn("Failed to detect testing framework:", error);
}
}
}
async function detectSchemasForBrief(root, brief) {
const schemas = await detectSchemas(root);
for (const schema of schemas) {
brief.schema.sources.push({
type: schema.source,
path: schema.path,
entities: schema.entities
});
if (!brief.schema.generatedTypes) {
brief.schema.generatedTypes = [];
}
brief.schema.generatedTypes.push(...schema.types);
}
}
async function detectDoNotTouch(root, brief) {
const commonGenerated = [
"node_modules",
"dist",
"build",
".next",
".nuxt",
"out",
"coverage",
".turbo",
"src/gen",
"src/generated",
"prisma/client",
"__generated__"
];
for (const dir of commonGenerated) {
const fullPath = path5.join(root, dir);
if (fs4.existsSync(fullPath)) {
brief.doNotTouch.push(dir);
}
}
}
function formatBriefForPrompt(brief) {
const sections = [];
sections.push(`# Project Brief
`);
sections.push(`## Language & Versions`);
sections.push(`- Language: ${brief.language}`);
if (brief.versions.node) sections.push(`- Node: ${brief.versions.node}`);
if (brief.versions.typescript) sections.push(`- TypeScript: ${brief.versions.typescript}`);
if (brief.versions.python) sections.push(`- Python: ${brief.versions.python}`);
if (brief.versions.go) sections.push(`- Go: ${brief.versions.go}`);
sections.push("");
sections.push(`## Style Rules`);
if (brief.style.prettier) {
sections.push(`- Prettier: ${JSON.stringify(brief.style.prettier, null, 2).slice(0, 200)}...`);
}
if (brief.style.eslint?.rules) {
const topRules = Object.keys(brief.style.eslint.rules).slice(0, 10);
sections.push(`- ESLint rules: ${topRules.join(", ")}`);
}
if (brief.style.tsconfig) {
sections.push(`- TypeScript: strict=${brief.style.tsconfig.strict}, target=${brief.style.tsconfig.target}`);
}
sections.push("");
sections.push(`## Layering & Boundaries`);
sections.push(`- Type: ${brief.layering.type}`);
if (brief.layering.layers) {
for (const layer of brief.layering.layers) {
sections.push(`- ${layer.name}: can import ${layer.canImport.join(", ")}`);
}
}
sections.push("");
sections.push(`## Testing`);
sections.push(`- Framework: ${brief.testing.framework}`);
sections.push(`- Pattern: ${brief.testing.testPattern}`);
sections.push("");
sections.push(`## Naming Conventions`);
sections.push(`- Variables: ${brief.naming.variables}`);
sections.push(`- Types: ${brief.naming.types}`);
sections.push(`- Constants: ${brief.naming.constants}`);
sections.push(`- Files: ${brief.naming.files}`);
sections.push("");
if (brief.schema.generatedTypes && brief.schema.generatedTypes.length > 0) {
sections.push(`## Schema Types (Use These!)`);
sections.push(`- ${brief.schema.generatedTypes.slice(0, 20).join(", ")}`);
sections.push("");
}
if (brief.doNotTouch.length > 0) {
sections.push(`## Do Not Touch`);
sections.push(`- ${brief.doNotTouch.join(", ")}`);
sections.push("");
}
return sections.join("\n");
}
async function buildGlossaryAndNaming(root, brief) {
try {
const index = await buildSymbolIndex(root, {
exts: [".ts", ".tsx", ".js", ".jsx"],
maxFiles: 2e3,
maxPerFileIds: 200,
exclude: ["node_modules", "dist", "build", ".next", "coverage", "__generated__"]
});
const topIds = topFrequencyIdentifiers(index, {
minLen: 3,
byDir: ["/domain", "/models", "/entities", "/types"],
limit: 50
});
brief.glossary.entities = topIds.filter((id) => /^[A-Z]/.test(id) && !/^[A-Z_]+$/.test(id));
brief.glossary.enums = topIds.filter((id) => /^[A-Z][a-z]+[A-Z]/.test(id));
brief.glossary.constants = topIds.filter((id) => /^[A-Z_]+$/.test(id));
brief.apis.publicExports = publicExports(index, [
"src/**/index.ts",
"src/**/api/**/*.ts",
"src/lib/**/*.ts"
]).slice(0, 20);
brief.naming.examples = collectNamingExamples(index);
const conventions = inferNamingConvention(index);
brief.naming.variables = conventions.variables;
brief.naming.types = conventions.types;
brief.naming.constants = conventions.constants;
brief.naming.files = conventions.files;
} catch (error) {
console.warn("Failed to build glossary and naming:", error);
}
}
var init_project_brief = __esm({
"src/utils/project-brief.ts"() {
"use strict";
init_symbol_indexer();
init_schema_codegen();
}
});
// src/utils/dependency-cache.ts
import * as fs5 from "fs";
import * as path6 from "path";
import * as os from "os";
import { execSync as execSync2 } from "child_process";
import * as crypto from "crypto";
function ensureCacheDir() {
if (!fs5.existsSync(CACHE_DIR)) {
fs5.mkdirSync(CACHE_DIR, { recursive: true });
}
}
function generateCacheKey(packageJson) {
const deps = {
dependencies: packageJson.dependencies || {},
devDependencies: packageJson.devDependencies || {}
};
const hash = crypto.createHash("sha256");
hash.update(JSON.stringify(deps));
return hash.digest("hex").substring(0, 16);
}
function getCachedNodeModulesPath(cacheKey) {
return path6.join(CACHE_DIR, `node_modules-${cacheKey}`);
}
function installAndCacheDependencies(sandboxDir, packageJson) {
ensureCacheDir();
const cacheKey = generateCacheKey(packageJson);
const cachedPath = getCachedNodeModulesPath(cacheKey);
if (fs5.existsSync(cachedPath)) {
console.log(`Using cached dependencies (key: ${cacheKey})`);
linkCachedDependencies(sandboxDir, cachedPath);
return;
}
console.log(`Installing dependencies (will cache with key: ${cacheKey})...`);
try {
execSync2("npm install --silent --n