mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-02 08:33:36 +00:00
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:
@@ -7,7 +7,12 @@ import path from 'path';
|
|||||||
import * as secureFs from '../lib/secure-fs.js';
|
import * as secureFs from '../lib/secure-fs.js';
|
||||||
import type { EventEmitter } from '../lib/events.js';
|
import type { EventEmitter } from '../lib/events.js';
|
||||||
import type { ExecuteOptions } from '@automaker/types';
|
import type { ExecuteOptions } from '@automaker/types';
|
||||||
import { readImageAsBase64, buildPromptWithImages, isAbortError } from '@automaker/utils';
|
import {
|
||||||
|
readImageAsBase64,
|
||||||
|
buildPromptWithImages,
|
||||||
|
isAbortError,
|
||||||
|
loadContextFiles,
|
||||||
|
} from '@automaker/utils';
|
||||||
import { ProviderFactory } from '../providers/provider-factory.js';
|
import { ProviderFactory } from '../providers/provider-factory.js';
|
||||||
import { createChatOptions, validateWorkingDirectory } from '../lib/sdk-options.js';
|
import { createChatOptions, validateWorkingDirectory } from '../lib/sdk-options.js';
|
||||||
import { PathNotAllowedError } from '@automaker/platform';
|
import { PathNotAllowedError } from '@automaker/platform';
|
||||||
@@ -178,12 +183,27 @@ export class AgentService {
|
|||||||
await this.saveSession(sessionId, session.messages);
|
await this.saveSession(sessionId, session.messages);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// Determine the effective working directory for context loading
|
||||||
|
const effectiveWorkDir = workingDirectory || session.workingDirectory;
|
||||||
|
|
||||||
|
// Load project context files (CLAUDE.md, CODE_QUALITY.md, etc.)
|
||||||
|
const { formattedPrompt: contextFilesPrompt } = await loadContextFiles({
|
||||||
|
projectPath: effectiveWorkDir,
|
||||||
|
fsModule: secureFs as Parameters<typeof loadContextFiles>[0]['fsModule'],
|
||||||
|
});
|
||||||
|
|
||||||
|
// Build combined system prompt with base prompt and context files
|
||||||
|
const baseSystemPrompt = this.getSystemPrompt();
|
||||||
|
const combinedSystemPrompt = contextFilesPrompt
|
||||||
|
? `${contextFilesPrompt}\n\n${baseSystemPrompt}`
|
||||||
|
: baseSystemPrompt;
|
||||||
|
|
||||||
// Build SDK options using centralized configuration
|
// Build SDK options using centralized configuration
|
||||||
const sdkOptions = createChatOptions({
|
const sdkOptions = createChatOptions({
|
||||||
cwd: workingDirectory || session.workingDirectory,
|
cwd: effectiveWorkDir,
|
||||||
model: model,
|
model: model,
|
||||||
sessionModel: session.model,
|
sessionModel: session.model,
|
||||||
systemPrompt: this.getSystemPrompt(),
|
systemPrompt: combinedSystemPrompt,
|
||||||
abortController: session.abortController!,
|
abortController: session.abortController!,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -203,8 +223,8 @@ export class AgentService {
|
|||||||
const options: ExecuteOptions = {
|
const options: ExecuteOptions = {
|
||||||
prompt: '', // Will be set below based on images
|
prompt: '', // Will be set below based on images
|
||||||
model: effectiveModel,
|
model: effectiveModel,
|
||||||
cwd: workingDirectory || session.workingDirectory,
|
cwd: effectiveWorkDir,
|
||||||
systemPrompt: this.getSystemPrompt(),
|
systemPrompt: combinedSystemPrompt,
|
||||||
maxTurns: maxTurns,
|
maxTurns: maxTurns,
|
||||||
allowedTools: allowedTools,
|
allowedTools: allowedTools,
|
||||||
abortController: session.abortController!,
|
abortController: session.abortController!,
|
||||||
|
|||||||
@@ -11,10 +11,15 @@
|
|||||||
|
|
||||||
import { ProviderFactory } from '../providers/provider-factory.js';
|
import { ProviderFactory } from '../providers/provider-factory.js';
|
||||||
import type { ExecuteOptions, Feature } from '@automaker/types';
|
import type { ExecuteOptions, Feature } from '@automaker/types';
|
||||||
import { buildPromptWithImages, isAbortError, classifyError } from '@automaker/utils';
|
import {
|
||||||
|
buildPromptWithImages,
|
||||||
|
isAbortError,
|
||||||
|
classifyError,
|
||||||
|
loadContextFiles,
|
||||||
|
} from '@automaker/utils';
|
||||||
import { resolveModelString, DEFAULT_MODELS } from '@automaker/model-resolver';
|
import { resolveModelString, DEFAULT_MODELS } from '@automaker/model-resolver';
|
||||||
import { resolveDependencies, areDependenciesSatisfied } from '@automaker/dependency-resolver';
|
import { resolveDependencies, areDependenciesSatisfied } from '@automaker/dependency-resolver';
|
||||||
import { getFeatureDir, getAutomakerDir, getFeaturesDir, getContextDir } from '@automaker/platform';
|
import { getFeatureDir, getAutomakerDir, getFeaturesDir } from '@automaker/platform';
|
||||||
import { exec } from 'child_process';
|
import { exec } from 'child_process';
|
||||||
import { promisify } from 'util';
|
import { promisify } from 'util';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
@@ -549,7 +554,10 @@ export class AutoModeService {
|
|||||||
// Build the prompt - use continuation prompt if provided (for recovery after plan approval)
|
// Build the prompt - use continuation prompt if provided (for recovery after plan approval)
|
||||||
let prompt: string;
|
let prompt: string;
|
||||||
// Load project context files (CLAUDE.md, CODE_QUALITY.md, etc.) - passed as system prompt
|
// Load project context files (CLAUDE.md, CODE_QUALITY.md, etc.) - passed as system prompt
|
||||||
const contextFiles = await this.loadContextFiles(projectPath);
|
const { formattedPrompt: contextFilesPrompt } = await loadContextFiles({
|
||||||
|
projectPath,
|
||||||
|
fsModule: secureFs as Parameters<typeof loadContextFiles>[0]['fsModule'],
|
||||||
|
});
|
||||||
|
|
||||||
if (options?.continuationPrompt) {
|
if (options?.continuationPrompt) {
|
||||||
// Continuation prompt is used when recovering from a plan approval
|
// Continuation prompt is used when recovering from a plan approval
|
||||||
@@ -595,7 +603,7 @@ export class AutoModeService {
|
|||||||
projectPath,
|
projectPath,
|
||||||
planningMode: feature.planningMode,
|
planningMode: feature.planningMode,
|
||||||
requirePlanApproval: feature.requirePlanApproval,
|
requirePlanApproval: feature.requirePlanApproval,
|
||||||
systemPrompt: contextFiles || undefined,
|
systemPrompt: contextFilesPrompt || undefined,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -736,7 +744,10 @@ export class AutoModeService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Load project context files (CLAUDE.md, CODE_QUALITY.md, etc.) - passed as system prompt
|
// Load project context files (CLAUDE.md, CODE_QUALITY.md, etc.) - passed as system prompt
|
||||||
const contextFiles = await this.loadContextFiles(projectPath);
|
const { formattedPrompt: contextFilesPrompt } = await loadContextFiles({
|
||||||
|
projectPath,
|
||||||
|
fsModule: secureFs as Parameters<typeof loadContextFiles>[0]['fsModule'],
|
||||||
|
});
|
||||||
|
|
||||||
// Build complete prompt with feature info, previous context, and follow-up instructions
|
// Build complete prompt with feature info, previous context, and follow-up instructions
|
||||||
let fullPrompt = `## Follow-up on Feature Implementation
|
let fullPrompt = `## Follow-up on Feature Implementation
|
||||||
@@ -864,7 +875,7 @@ Address the follow-up instructions above. Review the previous work and make the
|
|||||||
projectPath,
|
projectPath,
|
||||||
planningMode: 'skip', // Follow-ups don't require approval
|
planningMode: 'skip', // Follow-ups don't require approval
|
||||||
previousContent: previousContext || undefined,
|
previousContent: previousContext || undefined,
|
||||||
systemPrompt: contextFiles || undefined,
|
systemPrompt: contextFilesPrompt || undefined,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -1044,63 +1055,6 @@ Address the follow-up instructions above. Review the previous work and make the
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Load context files from .automaker/context/ directory
|
|
||||||
* These are user-defined context files (CLAUDE.md, CODE_QUALITY.md, etc.)
|
|
||||||
* that provide project-specific rules and guidelines for the agent.
|
|
||||||
*/
|
|
||||||
private async loadContextFiles(projectPath: string): Promise<string> {
|
|
||||||
// Use path.resolve for cross-platform absolute path handling
|
|
||||||
const contextDir = path.resolve(getContextDir(projectPath));
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Check if directory exists first
|
|
||||||
await secureFs.access(contextDir);
|
|
||||||
|
|
||||||
const files = await secureFs.readdir(contextDir);
|
|
||||||
// Filter for text-based context files (case-insensitive for Windows)
|
|
||||||
const textFiles = files.filter((f) => {
|
|
||||||
const lower = f.toLowerCase();
|
|
||||||
return lower.endsWith('.md') || lower.endsWith('.txt');
|
|
||||||
});
|
|
||||||
|
|
||||||
if (textFiles.length === 0) return '';
|
|
||||||
|
|
||||||
const contents: string[] = [];
|
|
||||||
for (const file of textFiles) {
|
|
||||||
// Use path.join for cross-platform path construction
|
|
||||||
const filePath = path.join(contextDir, file);
|
|
||||||
const content = (await secureFs.readFile(filePath, 'utf-8')) as string;
|
|
||||||
contents.push(`## ${file}\n\n${content}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`[AutoMode] Loaded ${textFiles.length} context file(s): ${textFiles.join(', ')}`);
|
|
||||||
|
|
||||||
return `# ⚠️ CRITICAL: Project Context Files - READ AND FOLLOW STRICTLY
|
|
||||||
|
|
||||||
**IMPORTANT**: The following context files contain MANDATORY project-specific rules and conventions. You MUST:
|
|
||||||
1. Read these rules carefully before taking any action
|
|
||||||
2. Follow ALL commands exactly as shown (e.g., if the project uses \`pnpm\`, NEVER use \`npm\` or \`npx\`)
|
|
||||||
3. Follow ALL coding conventions, commit message formats, and architectural patterns specified
|
|
||||||
4. Reference these rules before running ANY shell commands or making commits
|
|
||||||
|
|
||||||
Failure to follow these rules will result in broken builds, failed CI, and rejected commits.
|
|
||||||
|
|
||||||
${contents.join('\n\n---\n\n')}
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**REMINDER**: Before running any command, verify you are using the correct package manager and following the conventions above.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
`;
|
|
||||||
} catch {
|
|
||||||
// Context directory doesn't exist or is empty - this is fine
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Analyze project to gather context
|
* Analyze project to gather context
|
||||||
*/
|
*/
|
||||||
|
|||||||
170
docs/context-files-pattern.md
Normal file
170
docs/context-files-pattern.md
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
# Context Files System
|
||||||
|
|
||||||
|
This document describes how context files work in Automaker and how to use them in agent prompts.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Context files are user-defined documents stored in `.automaker/context/` that provide project-specific rules, conventions, and guidelines for AI agents. They are automatically loaded and prepended to agent prompts.
|
||||||
|
|
||||||
|
## Directory Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
{projectPath}/.automaker/context/
|
||||||
|
├── CLAUDE.md # Project rules and conventions
|
||||||
|
├── CODE_QUALITY.md # Code quality guidelines
|
||||||
|
├── context-metadata.json # File descriptions
|
||||||
|
└── ... (any .md or .txt files)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Metadata
|
||||||
|
|
||||||
|
File descriptions are stored in `context-metadata.json`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"files": {
|
||||||
|
"CLAUDE.md": {
|
||||||
|
"description": "Project-specific rules including package manager, commit conventions, and architectural patterns"
|
||||||
|
},
|
||||||
|
"CODE_QUALITY.md": {
|
||||||
|
"description": "Code quality standards, testing requirements, and linting rules"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Shared Utility
|
||||||
|
|
||||||
|
The `loadContextFiles` function from `@automaker/utils` provides a unified way to load context files:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { loadContextFiles } from '@automaker/utils';
|
||||||
|
|
||||||
|
// Load context files from a project
|
||||||
|
const { formattedPrompt, files } = await loadContextFiles({
|
||||||
|
projectPath: '/path/to/project',
|
||||||
|
// Optional: inject custom fs module for secure operations
|
||||||
|
fsModule: secureFs,
|
||||||
|
});
|
||||||
|
|
||||||
|
// formattedPrompt contains the formatted system prompt
|
||||||
|
// files contains metadata about each loaded file
|
||||||
|
```
|
||||||
|
|
||||||
|
### Return Value
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface ContextFilesResult {
|
||||||
|
files: ContextFileInfo[]; // Individual file info
|
||||||
|
formattedPrompt: string; // Formatted prompt ready to use
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ContextFileInfo {
|
||||||
|
name: string; // File name (e.g., "CLAUDE.md")
|
||||||
|
path: string; // Full path to file
|
||||||
|
content: string; // File contents
|
||||||
|
description?: string; // From metadata (explains when/why to use)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage in Services
|
||||||
|
|
||||||
|
### Auto-Mode Service (Feature Execution)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { loadContextFiles } from '@automaker/utils';
|
||||||
|
import * as secureFs from '../lib/secure-fs.js';
|
||||||
|
|
||||||
|
// In executeFeature() or followUpFeature()
|
||||||
|
const { formattedPrompt: contextFilesPrompt } = await loadContextFiles({
|
||||||
|
projectPath,
|
||||||
|
fsModule: secureFs as Parameters<typeof loadContextFiles>[0]['fsModule'],
|
||||||
|
});
|
||||||
|
|
||||||
|
// Pass as system prompt
|
||||||
|
await this.runAgent(workDir, featureId, prompt, abortController, projectPath, imagePaths, model, {
|
||||||
|
projectPath,
|
||||||
|
systemPrompt: contextFilesPrompt || undefined,
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Agent Service (Chat Sessions)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { loadContextFiles } from '@automaker/utils';
|
||||||
|
import * as secureFs from '../lib/secure-fs.js';
|
||||||
|
|
||||||
|
// In sendMessage()
|
||||||
|
const { formattedPrompt: contextFilesPrompt } = await loadContextFiles({
|
||||||
|
projectPath: effectiveWorkDir,
|
||||||
|
fsModule: secureFs as Parameters<typeof loadContextFiles>[0]['fsModule'],
|
||||||
|
});
|
||||||
|
|
||||||
|
// Combine with base system prompt
|
||||||
|
const combinedSystemPrompt = contextFilesPrompt
|
||||||
|
? `${contextFilesPrompt}\n\n${baseSystemPrompt}`
|
||||||
|
: baseSystemPrompt;
|
||||||
|
```
|
||||||
|
|
||||||
|
## Formatted Prompt Structure
|
||||||
|
|
||||||
|
The formatted prompt includes:
|
||||||
|
|
||||||
|
1. **Header** - Emphasizes that these are project-specific rules
|
||||||
|
2. **File Entries** - Each file with:
|
||||||
|
- File name
|
||||||
|
- Full path (for agents to read more if needed)
|
||||||
|
- Purpose/description (from metadata)
|
||||||
|
- Full file content
|
||||||
|
3. **Reminder** - Reinforces that agents must follow the conventions
|
||||||
|
|
||||||
|
Example output:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
# 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
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## CLAUDE.md
|
||||||
|
|
||||||
|
**Path:** `/path/to/project/.automaker/context/CLAUDE.md`
|
||||||
|
**Purpose:** Project-specific rules including package manager, commit conventions, and architectural patterns
|
||||||
|
|
||||||
|
[File content here]
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## CODE_QUALITY.md
|
||||||
|
|
||||||
|
**Path:** `/path/to/project/.automaker/context/CODE_QUALITY.md`
|
||||||
|
**Purpose:** Code quality standards, testing requirements, and linting rules
|
||||||
|
|
||||||
|
[File content here]
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**REMINDER**: Before taking any action, verify you are following the conventions specified above.
|
||||||
|
```
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
1. **Add descriptions** - Always add descriptions to `context-metadata.json` so agents understand when to reference each file
|
||||||
|
2. **Be specific** - Context files should contain concrete rules, not general guidelines
|
||||||
|
3. **Include examples** - Show correct command usage, commit formats, etc.
|
||||||
|
4. **Keep focused** - Each file should have a single purpose
|
||||||
|
|
||||||
|
## File Locations
|
||||||
|
|
||||||
|
- **Shared Utility**: `libs/utils/src/context-loader.ts`
|
||||||
|
- **Auto-Mode Service**: `apps/server/src/services/auto-mode-service.ts`
|
||||||
|
- **Agent Service**: `apps/server/src/services/agent-service.ts`
|
||||||
238
libs/utils/src/context-loader.ts
Normal file
238
libs/utils/src/context-loader.ts
Normal 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 [];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -37,21 +37,20 @@ export {
|
|||||||
} from './prompt-builder.js';
|
} from './prompt-builder.js';
|
||||||
|
|
||||||
// Logger
|
// Logger
|
||||||
export {
|
export { createLogger, getLogLevel, setLogLevel, LogLevel } from './logger.js';
|
||||||
createLogger,
|
|
||||||
getLogLevel,
|
|
||||||
setLogLevel,
|
|
||||||
LogLevel,
|
|
||||||
} from './logger.js';
|
|
||||||
|
|
||||||
// File system utilities
|
// File system utilities
|
||||||
export {
|
export { mkdirSafe, existsSafe } from './fs-utils.js';
|
||||||
mkdirSafe,
|
|
||||||
existsSafe,
|
|
||||||
} from './fs-utils.js';
|
|
||||||
|
|
||||||
// Path utilities
|
// Path utilities
|
||||||
|
export { normalizePath, pathsEqual } from './path-utils.js';
|
||||||
|
|
||||||
|
// Context file loading
|
||||||
export {
|
export {
|
||||||
normalizePath,
|
loadContextFiles,
|
||||||
pathsEqual,
|
getContextFilesSummary,
|
||||||
} from './path-utils.js';
|
type ContextMetadata,
|
||||||
|
type ContextFileInfo,
|
||||||
|
type ContextFilesResult,
|
||||||
|
type LoadContextFilesOptions,
|
||||||
|
} from './context-loader.js';
|
||||||
|
|||||||
Reference in New Issue
Block a user