mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-15 18:01:22 +00:00
feat: hierarchical context file loading for monorepos
- Walk up parent directories to load all AGENT.md/CLAUDE.md files - Load global context from ~/.pi/agent/AGENT.md or CLAUDE.md - Load order: global → top-most parent → ... → cwd - Prefer AGENT.md over CLAUDE.md in each directory - Each context file injected as separate message - Updated README with detailed documentation
This commit is contained in:
parent
812f2f43cd
commit
dca3e1cc60
2 changed files with 138 additions and 44 deletions
|
|
@ -120,15 +120,55 @@ Paste multiple lines of text (e.g., code snippets, logs) and they'll be automati
|
|||
|
||||
## Project Context Files
|
||||
|
||||
Place an `AGENT.md` or `CLAUDE.md` file in your project root to provide context to the AI. The contents will be automatically included at the start of new sessions (not when continuing/resuming sessions).
|
||||
The agent automatically loads context from `AGENT.md` or `CLAUDE.md` files at the start of new sessions (not when continuing/resuming). These files are loaded in hierarchical order to support both global preferences and monorepo structures.
|
||||
|
||||
This is useful for:
|
||||
### File Locations
|
||||
|
||||
Context files are loaded in this order:
|
||||
|
||||
1. **Global context**: `~/.pi/agent/AGENT.md` or `CLAUDE.md`
|
||||
- Applies to all your coding sessions
|
||||
- Great for personal coding preferences and workflows
|
||||
|
||||
2. **Parent directories** (top-most first down to current directory)
|
||||
- Walks up from current directory to filesystem root
|
||||
- Each directory can have its own `AGENT.md` or `CLAUDE.md`
|
||||
- Perfect for monorepos with shared context at higher levels
|
||||
|
||||
3. **Current directory**: Your project's `AGENT.md` or `CLAUDE.md`
|
||||
- Most specific context, loaded last
|
||||
- Overwrites or extends parent/global context
|
||||
|
||||
**File preference**: In each directory, `AGENT.md` is preferred over `CLAUDE.md` if both exist.
|
||||
|
||||
### What to Include
|
||||
|
||||
Context files are useful for:
|
||||
- Project-specific instructions and guidelines
|
||||
- Common bash commands and workflows
|
||||
- Architecture documentation
|
||||
- Coding conventions and style guides
|
||||
- Dependencies and setup information
|
||||
- Testing instructions
|
||||
- Repository etiquette (branch naming, merge vs. rebase, etc.)
|
||||
|
||||
The file is injected as a user message at the beginning of each new session, ensuring the AI has project context without modifying the system prompt.
|
||||
### Example
|
||||
|
||||
```markdown
|
||||
# Common Commands
|
||||
- npm run build: Build the project
|
||||
- npm test: Run tests
|
||||
|
||||
# Code Style
|
||||
- Use TypeScript strict mode
|
||||
- Prefer async/await over promises
|
||||
|
||||
# Workflow
|
||||
- Always run tests before committing
|
||||
- Update CHANGELOG.md for user-facing changes
|
||||
```
|
||||
|
||||
Each file is injected as a separate user message at the beginning of new sessions, ensuring the AI has full project context without modifying the system prompt.
|
||||
|
||||
## Image Support
|
||||
|
||||
|
|
@ -142,26 +182,6 @@ Supported formats: `.jpg`, `.jpeg`, `.png`, `.gif`, `.webp`
|
|||
|
||||
The image will be automatically encoded and sent with your message. JPEG and PNG are supported across all vision models. Other formats may only be supported by some models.
|
||||
|
||||
## Available Tools
|
||||
|
||||
The agent has access to four core tools for working with your codebase:
|
||||
|
||||
### read
|
||||
|
||||
Read file contents. Supports text files and images (jpg, png, gif, webp). Images are sent as attachments. For text files, defaults to first 2000 lines. Use offset/limit parameters for large files. Lines longer than 2000 characters are truncated.
|
||||
|
||||
### write
|
||||
|
||||
Write content to a file. Creates the file if it doesn't exist, overwrites if it does. Automatically creates parent directories.
|
||||
|
||||
### edit
|
||||
|
||||
Edit a file by replacing exact text. The oldText must match exactly (including whitespace). Use this for precise, surgical edits. Returns an error if the text appears multiple times or isn't found.
|
||||
|
||||
### bash
|
||||
|
||||
Execute a bash command in the current working directory. Returns stdout and stderr. Commands run with a 30 second timeout.
|
||||
|
||||
## Session Management
|
||||
|
||||
Sessions are automatically saved in `~/.pi/agent/sessions/` organized by working directory. Each session is stored as a JSONL file with a unique timestamp-based ID.
|
||||
|
|
@ -268,6 +288,26 @@ pi -c "What did we discuss?"
|
|||
pi --provider openai --model gpt-4o "Help me refactor this code"
|
||||
```
|
||||
|
||||
## Available Tools
|
||||
|
||||
The agent has access to four core tools for working with your codebase:
|
||||
|
||||
### read
|
||||
|
||||
Read file contents. Supports text files and images (jpg, png, gif, webp). Images are sent as attachments. For text files, defaults to first 2000 lines. Use offset/limit parameters for large files. Lines longer than 2000 characters are truncated.
|
||||
|
||||
### write
|
||||
|
||||
Write content to a file. Creates the file if it doesn't exist, overwrites if it does. Automatically creates parent directories.
|
||||
|
||||
### edit
|
||||
|
||||
Edit a file by replacing exact text. The oldText must match exactly (including whitespace). Use this for precise, surgical edits. Returns an error if the text appears multiple times or isn't found.
|
||||
|
||||
### bash
|
||||
|
||||
Execute a bash command in the current working directory. Returns stdout and stderr. Commands run with a 30 second timeout.
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@ import { getModel, type KnownProvider } from "@mariozechner/pi-ai";
|
|||
import { ProcessTerminal, TUI } from "@mariozechner/pi-tui";
|
||||
import chalk from "chalk";
|
||||
import { existsSync, readFileSync } from "fs";
|
||||
import { dirname, join } from "path";
|
||||
import { homedir } from "os";
|
||||
import { dirname, join, resolve } from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
import { SessionManager } from "./session-manager.js";
|
||||
import { codingTools } from "./tools/index.js";
|
||||
|
|
@ -149,22 +150,72 @@ Guidelines:
|
|||
Current directory: ${process.cwd()}`;
|
||||
|
||||
/**
|
||||
* Look for AGENT.md or CLAUDE.md in the current directory and return its contents
|
||||
* Look for AGENT.md or CLAUDE.md in a directory (prefers AGENT.md)
|
||||
*/
|
||||
function loadProjectContext(): string | null {
|
||||
function loadContextFileFromDir(dir: string): { path: string; content: string } | null {
|
||||
const candidates = ["AGENT.md", "CLAUDE.md"];
|
||||
for (const filename of candidates) {
|
||||
if (existsSync(filename)) {
|
||||
const filePath = join(dir, filename);
|
||||
if (existsSync(filePath)) {
|
||||
try {
|
||||
return readFileSync(filename, "utf-8");
|
||||
return {
|
||||
path: filePath,
|
||||
content: readFileSync(filePath, "utf-8"),
|
||||
};
|
||||
} catch (error) {
|
||||
console.error(chalk.yellow(`Warning: Could not read ${filename}: ${error}`));
|
||||
console.error(chalk.yellow(`Warning: Could not read ${filePath}: ${error}`));
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load all project context files in order:
|
||||
* 1. Global: ~/.pi/agent/AGENT.md or CLAUDE.md
|
||||
* 2. Parent directories (top-most first) down to cwd
|
||||
* Each returns {path, content} for separate messages
|
||||
*/
|
||||
function loadProjectContextFiles(): Array<{ path: string; content: string }> {
|
||||
const contextFiles: Array<{ path: string; content: string }> = [];
|
||||
|
||||
// 1. Load global context from ~/.pi/agent/
|
||||
const homeDir = homedir();
|
||||
const globalContextDir = resolve(process.env.CODING_AGENT_DIR || join(homeDir, ".pi/agent/"));
|
||||
const globalContext = loadContextFileFromDir(globalContextDir);
|
||||
if (globalContext) {
|
||||
contextFiles.push(globalContext);
|
||||
}
|
||||
|
||||
// 2. Walk up from cwd to root, collecting all context files
|
||||
const cwd = process.cwd();
|
||||
const ancestorContextFiles: Array<{ path: string; content: string }> = [];
|
||||
|
||||
let currentDir = cwd;
|
||||
const root = resolve("/");
|
||||
|
||||
while (true) {
|
||||
const contextFile = loadContextFileFromDir(currentDir);
|
||||
if (contextFile) {
|
||||
// Add to beginning so we get top-most parent first
|
||||
ancestorContextFiles.unshift(contextFile);
|
||||
}
|
||||
|
||||
// Stop if we've reached root
|
||||
if (currentDir === root) break;
|
||||
|
||||
// Move up one directory
|
||||
const parentDir = resolve(currentDir, "..");
|
||||
if (parentDir === currentDir) break; // Safety check
|
||||
currentDir = parentDir;
|
||||
}
|
||||
|
||||
// Add ancestor files in order (top-most → cwd)
|
||||
contextFiles.push(...ancestorContextFiles);
|
||||
|
||||
return contextFiles;
|
||||
}
|
||||
|
||||
async function selectSession(sessionManager: SessionManager): Promise<string | null> {
|
||||
return new Promise((resolve) => {
|
||||
const ui = new TUI(new ProcessTerminal());
|
||||
|
|
@ -428,23 +479,26 @@ export async function main(args: string[]) {
|
|||
// Note: Session will be started lazily after first user+assistant message exchange
|
||||
// (unless continuing/resuming, in which case it's already initialized)
|
||||
|
||||
// Inject project context (AGENT.md/CLAUDE.md) if not continuing/resuming
|
||||
// Inject project context files (AGENT.md/CLAUDE.md) if not continuing/resuming
|
||||
if (!parsed.continue && !parsed.resume) {
|
||||
const projectContext = loadProjectContext();
|
||||
if (projectContext) {
|
||||
// Queue the context as a message that will be injected at the start
|
||||
await agent.queueMessage({
|
||||
role: "user",
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `[Project Context from ${existsSync("AGENT.md") ? "AGENT.md" : "CLAUDE.md"}]\n\n${projectContext}`,
|
||||
},
|
||||
],
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
const contextFiles = loadProjectContextFiles();
|
||||
if (contextFiles.length > 0) {
|
||||
// Queue each context file as a separate message
|
||||
for (const { path: filePath, content } of contextFiles) {
|
||||
await agent.queueMessage({
|
||||
role: "user",
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `[Project Context from ${filePath}]\n\n${content}`,
|
||||
},
|
||||
],
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
}
|
||||
if (shouldPrintMessages) {
|
||||
console.log(chalk.dim(`Loaded project context from ${existsSync("AGENT.md") ? "AGENT.md" : "CLAUDE.md"}`));
|
||||
const fileList = contextFiles.map((f) => f.path).join(", ");
|
||||
console.log(chalk.dim(`Loaded project context from: ${fileList}`));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue