feat: implement context file loading system for agent prompts

- Introduced a new utility function `loadContextFiles` to load project-specific context files from the `.automaker/context/` directory, enhancing agent prompts with project rules and guidelines.
- Updated `AgentService` and `AutoModeService` to utilize the new context loading functionality, combining context prompts with existing system prompts for improved agent performance.
- Added comprehensive documentation on the context files system, including usage examples and metadata structure, to facilitate better understanding and implementation.
- Removed redundant context loading logic from `AutoModeService`, streamlining the codebase.

These changes aim to improve the agent's contextual awareness and adherence to project-specific conventions.
This commit is contained in:
Test User
2025-12-22 00:09:57 -05:00
parent e2718b37e3
commit 398f7b8fdd
5 changed files with 462 additions and 81 deletions

View File

@@ -0,0 +1,238 @@
/**
* Context Loader - Loads project context files for agent prompts
*
* Provides a shared utility to load context files from .automaker/context/
* and format them as system prompt content. Used by both auto-mode-service
* and agent-service to ensure all agents are aware of project context.
*
* Context files contain project-specific rules, conventions, and guidelines
* that agents must follow when working on the project.
*/
import path from 'path';
import fs from 'fs/promises';
/**
* Metadata structure for context files
* Stored in {projectPath}/.automaker/context/context-metadata.json
*/
export interface ContextMetadata {
files: Record<string, { description: string }>;
}
/**
* Individual context file with metadata
*/
export interface ContextFileInfo {
name: string;
path: string;
content: string;
description?: string;
}
/**
* Result of loading context files
*/
export interface ContextFilesResult {
files: ContextFileInfo[];
formattedPrompt: string;
}
/**
* Options for loading context files
*/
export interface LoadContextFilesOptions {
/** Project path to load context from */
projectPath: string;
/** Optional custom secure fs module (for dependency injection) */
fsModule?: {
access: (path: string) => Promise<void>;
readdir: (path: string) => Promise<string[]>;
readFile: (path: string, encoding: string) => Promise<string>;
};
}
/**
* Get the context directory path for a project
*/
function getContextDir(projectPath: string): string {
return path.join(projectPath, '.automaker', 'context');
}
/**
* Load context metadata from the metadata file
*/
async function loadContextMetadata(
contextDir: string,
fsModule: typeof fs
): Promise<ContextMetadata> {
const metadataPath = path.join(contextDir, 'context-metadata.json');
try {
const content = await fsModule.readFile(metadataPath, 'utf-8');
return JSON.parse(content);
} catch {
// Metadata file doesn't exist yet - that's fine
return { files: {} };
}
}
/**
* Format a single context file entry for the prompt
*/
function formatContextFileEntry(file: ContextFileInfo): string {
const header = `## ${file.name}`;
const pathInfo = `**Path:** \`${file.path}\``;
let descriptionInfo = '';
if (file.description) {
descriptionInfo = `\n**Purpose:** ${file.description}`;
}
return `${header}\n${pathInfo}${descriptionInfo}\n\n${file.content}`;
}
/**
* Build the formatted system prompt from context files
*/
function buildContextPrompt(files: ContextFileInfo[]): string {
if (files.length === 0) {
return '';
}
const formattedFiles = files.map(formatContextFileEntry);
return `# Project Context Files
The following context files provide project-specific rules, conventions, and guidelines.
Each file serves a specific purpose - use the description to understand when to reference it.
If you need more details about a context file, you can read the full file at the path provided.
**IMPORTANT**: You MUST follow the rules and conventions specified in these files.
- Follow ALL commands exactly as shown (e.g., if the project uses \`pnpm\`, NEVER use \`npm\` or \`npx\`)
- Follow ALL coding conventions, commit message formats, and architectural patterns specified
- Reference these rules before running ANY shell commands or making commits
---
${formattedFiles.join('\n\n---\n\n')}
---
**REMINDER**: Before taking any action, verify you are following the conventions specified above.
`;
}
/**
* Load context files from a project's .automaker/context/ directory
*
* This function loads all .md and .txt files from the context directory,
* along with their metadata (descriptions), and formats them into a
* system prompt that can be prepended to agent prompts.
*
* @param options - Configuration options
* @returns Promise resolving to context files and formatted prompt
*
* @example
* ```typescript
* const { formattedPrompt, files } = await loadContextFiles({
* projectPath: '/path/to/project'
* });
*
* // Use as system prompt
* const executeOptions = {
* prompt: userPrompt,
* systemPrompt: formattedPrompt,
* };
* ```
*/
export async function loadContextFiles(
options: LoadContextFilesOptions
): Promise<ContextFilesResult> {
const { projectPath, fsModule = fs } = options;
const contextDir = path.resolve(getContextDir(projectPath));
try {
// Check if directory exists
await fsModule.access(contextDir);
// Read directory contents
const allFiles = await fsModule.readdir(contextDir);
// Filter for text-based context files (case-insensitive for cross-platform)
const textFiles = allFiles.filter((f) => {
const lower = f.toLowerCase();
return (lower.endsWith('.md') || lower.endsWith('.txt')) && f !== 'context-metadata.json';
});
if (textFiles.length === 0) {
return { files: [], formattedPrompt: '' };
}
// Load metadata for descriptions
const metadata = await loadContextMetadata(contextDir, fsModule as typeof fs);
// Load each file with its content and metadata
const files: ContextFileInfo[] = [];
for (const fileName of textFiles) {
const filePath = path.join(contextDir, fileName);
try {
const content = await fsModule.readFile(filePath, 'utf-8');
files.push({
name: fileName,
path: filePath,
content,
description: metadata.files[fileName]?.description,
});
} catch (error) {
console.warn(`[ContextLoader] Failed to read context file ${fileName}:`, error);
}
}
const formattedPrompt = buildContextPrompt(files);
console.log(
`[ContextLoader] Loaded ${files.length} context file(s): ${files.map((f) => f.name).join(', ')}`
);
return { files, formattedPrompt };
} catch {
// Context directory doesn't exist or is inaccessible - this is fine
return { files: [], formattedPrompt: '' };
}
}
/**
* Get a summary of available context files (names and descriptions only)
* Useful for informing the agent about what context is available without
* loading full content.
*/
export async function getContextFilesSummary(
options: LoadContextFilesOptions
): Promise<Array<{ name: string; path: string; description?: string }>> {
const { projectPath, fsModule = fs } = options;
const contextDir = path.resolve(getContextDir(projectPath));
try {
await fsModule.access(contextDir);
const allFiles = await fsModule.readdir(contextDir);
const textFiles = allFiles.filter((f) => {
const lower = f.toLowerCase();
return (lower.endsWith('.md') || lower.endsWith('.txt')) && f !== 'context-metadata.json';
});
if (textFiles.length === 0) {
return [];
}
const metadata = await loadContextMetadata(contextDir, fsModule as typeof fs);
return textFiles.map((fileName) => ({
name: fileName,
path: path.join(contextDir, fileName),
description: metadata.files[fileName]?.description,
}));
} catch {
return [];
}
}

View File

@@ -37,21 +37,20 @@ export {
} from './prompt-builder.js';
// Logger
export {
createLogger,
getLogLevel,
setLogLevel,
LogLevel,
} from './logger.js';
export { createLogger, getLogLevel, setLogLevel, LogLevel } from './logger.js';
// File system utilities
export {
mkdirSafe,
existsSafe,
} from './fs-utils.js';
export { mkdirSafe, existsSafe } from './fs-utils.js';
// Path utilities
export { normalizePath, pathsEqual } from './path-utils.js';
// Context file loading
export {
normalizePath,
pathsEqual,
} from './path-utils.js';
loadContextFiles,
getContextFilesSummary,
type ContextMetadata,
type ContextFileInfo,
type ContextFilesResult,
type LoadContextFilesOptions,
} from './context-loader.js';