@noanswer/context-compose
Version:
Orchestrate complex AI interactions with Context Compose. A powerful CLI and server for building, validating, and managing context for large language models using the Model Context Protocol (MCP).
161 lines • 6.23 kB
JavaScript
import { readFile } from 'node:fs/promises';
import * as fs from 'node:fs/promises';
import { join } from 'node:path';
import { parse as parseYaml } from 'yaml';
import { z } from 'zod';
import { ValidateContextToolSchema } from '../../schemas/validate-context.js';
import { fileExists } from '../utils/index.js';
const RequiredFieldsSchema = z
.object({
version: z.union([z.string(), z.number()]),
kind: z.string(),
name: z.string(),
description: z.string(),
prompt: z.string(),
'enhanced-prompt': z.string(),
})
.passthrough();
async function validateComponentFile(filePath, projectRoot) {
const errors = [];
if (!(await fileExists(filePath))) {
return [{ filePath, message: 'File does not exist.' }];
}
try {
const content = await readFile(filePath, 'utf8');
const yaml = parseYaml(content);
const result = RequiredFieldsSchema.safeParse(yaml);
if (!result.success) {
const missing = result.error.issues.map(i => i.path.join('.')).join(', ');
errors.push({
filePath,
message: `Missing or invalid required fields: ${missing}`,
});
}
// Check if this is a context file and validate references
const fileName = filePath.split('/').pop() || '';
if (fileName.endsWith('-context.yaml') ||
fileName.endsWith('-context.yml')) {
const referenceErrors = await validateContextReferences(filePath, yaml, projectRoot);
errors.push(...referenceErrors);
}
}
catch (error) {
errors.push({
filePath,
message: `Failed to parse YAML file: ${error instanceof Error ? error.message : String(error)}`,
});
}
return errors;
}
async function validateContextReferences(contextFilePath, contextYaml, projectRoot) {
const errors = [];
const contextDir = join(projectRoot, '.contextcompose');
if (!contextYaml.context || typeof contextYaml.context !== 'object') {
return errors;
}
const sections = ['personas', 'rules', 'mcps', 'actions'];
for (const section of sections) {
const sectionData = contextYaml.context[section];
if (Array.isArray(sectionData)) {
for (const referencedFile of sectionData) {
if (typeof referencedFile === 'string') {
const referencedFilePath = join(contextDir, referencedFile);
if (!(await fileExists(referencedFilePath))) {
errors.push({
filePath: contextFilePath,
message: `Referenced file does not exist: ${referencedFile}`,
});
}
}
}
}
}
return errors;
}
async function executeValidateContext(args) {
const { projectRoot } = args;
const errors = [];
const validatedFiles = [];
try {
const rootPath = join(projectRoot, '.contextcompose');
if (!(await fileExists(rootPath))) {
return {
success: false,
message: 'Validation failed: .contextcompose directory not found in project root.',
validatedFiles: 0,
errors: [{ filePath: rootPath, message: 'Directory not found' }],
};
}
const assetTypes = ['actions', 'mcps', 'personas', 'rules', 'contexts'];
for (const assetType of assetTypes) {
const assetDir = join(rootPath, assetType);
if (await fileExists(assetDir)) {
const files = await fs.readdir(assetDir);
for (const file of files) {
if (file.endsWith('.yaml') || file.endsWith('.yml')) {
const filePath = join(assetDir, file);
validatedFiles.push(filePath);
const fileErrors = await validateComponentFile(filePath, projectRoot);
errors.push(...fileErrors);
}
}
}
}
// Also validate context files in the root of .contextcompose
const contextFiles = await fs.readdir(rootPath);
for (const file of contextFiles) {
if ((file.endsWith('-context.yaml') || file.endsWith('-context.yml')) &&
(file.endsWith('.yaml') || file.endsWith('.yml'))) {
const filePath = join(rootPath, file);
validatedFiles.push(filePath);
const fileErrors = await validateComponentFile(filePath, projectRoot);
errors.push(...fileErrors);
}
}
if (errors.length > 0) {
return {
success: false,
message: `Validation failed with ${errors.length} errors.`,
validatedFiles: validatedFiles.length,
errors,
};
}
return {
success: true,
message: `Successfully validated ${validatedFiles.length} files.`,
validatedFiles: validatedFiles.length,
errors: [],
};
}
catch (error) {
return {
success: false,
message: error instanceof Error ? error.message : 'An unknown error occurred.',
validatedFiles: 0,
errors: [
{
filePath: '',
message: error instanceof Error
? error.message
: 'An unknown error occurred.',
},
],
};
}
}
export async function executeValidateContextTool(args) {
const validationResult = ValidateContextToolSchema.safeParse(args);
if (!validationResult.success) {
const errorMessages = validationResult.error.errors
.map(e => `${e.path.join('.')}: ${e.message}`)
.join(', ');
return {
success: false,
message: `Invalid input: ${errorMessages}`,
validatedFiles: 0,
errors: [],
};
}
return executeValidateContext(validationResult.data);
}
//# sourceMappingURL=validate-context.js.map