spb/khaelor Public
KHAELOR — a terminal-native autonomous engineering agent powered by Anthropic.
TypeScript 82.9%
HTML 14.9%
CSS 1.1%
JavaScript 0.7%
1/**2 * KHAELOR3 * File: src/context/system-prompt.ts4 * Description: Three-tier byte-stable system prompt and KHAELOR.md/CLAUDE.md/AGENTS.md instruction discovery (ARCHITECTURE.md §6.5, CLAUDE.md §12).5 *6 * Author: Simon-Pierre Boucher7 * Contact: contact@spboucher.ai8 */910import * as path from "node:path";11import type { SystemTier } from "../anthropic/index.js";1213// ───────────────────────────── instruction discovery ─────────────────────────────1415/**16 * The file-reading capability this module needs — a structural subset of17 * `Workspace` (ADR-13). Injected so `src/context/` never imports the18 * workspace module (dependency rule 8) and never touches `node:fs`.19 */20export interface InstructionReader {21 cwd(): string;22 readFile(filePath: string): Promise<string>;23}2425/** Precedence within one directory: KHAELOR.md, then compatibility fallbacks. */26export const INSTRUCTION_FILENAMES: readonly string[] = ["KHAELOR.md", "CLAUDE.md", "AGENTS.md"];2728export interface InstructionFile {29 /** Absolute path of the discovered file. */30 path: string;31 scope: "user" | "project" | "nested";32 content: string;33}3435export interface DiscoverInstructionsOptions {36 /** Directory holding the user-global instructions (e.g. ~/.khaelor). Omit to skip the user tier. */37 userDir?: string;38 /** Project root. Default: the reader's cwd. Nested directories between root and cwd refine it. */39 projectRoot?: string;40}4142async function readFirstMatch(43 reader: InstructionReader,44 dir: string,45): Promise<{ path: string; content: string } | null> {46 for (const name of INSTRUCTION_FILENAMES) {47 const candidate = path.join(dir, name);48 try {49 const content = await reader.readFile(candidate);50 return { path: candidate, content };51 } catch {52 // absent or unreadable — try the next compatibility name53 }54 }55 return null;56}5758/**59 * Discover project instructions with the CLAUDE.md §12 precedence, ordered60 * global → nested (instructions closer to the working path refine earlier61 * ones):62 *63 * <userDir>/KHAELOR.md → <projectRoot>/KHAELOR.md → nested dirs toward cwd64 *65 * In each directory KHAELOR.md wins; CLAUDE.md then AGENTS.md are66 * compatibility fallbacks. All reads go through the injected reader.67 */68export async function discoverProjectInstructions(69 reader: InstructionReader,70 options: DiscoverInstructionsOptions = {},71): Promise<InstructionFile[]> {72 const found: InstructionFile[] = [];73 const cwd = path.resolve(reader.cwd());74 const projectRoot = path.resolve(options.projectRoot ?? cwd);7576 if (options.userDir !== undefined) {77 const user = await readFirstMatch(reader, path.resolve(options.userDir));78 if (user) found.push({ ...user, scope: "user" });79 }8081 // Directory chain from projectRoot down to cwd (boundary-aware containment).82 const chain: string[] = [projectRoot];83 const relative = path.relative(projectRoot, cwd);84 if (relative.length > 0 && !relative.startsWith("..") && !path.isAbsolute(relative)) {85 let dir = projectRoot;86 for (const segment of relative.split(path.sep)) {87 dir = path.join(dir, segment);88 chain.push(dir);89 }90 }9192 for (const candidateDir of chain) {93 const match = await readFirstMatch(reader, candidateDir);94 if (match) {95 found.push({ ...match, scope: candidateDir === projectRoot ? "project" : "nested" });96 }97 }98 return found;99}100101// ───────────────────────────── system prompt tiers ─────────────────────────────102103export interface SystemPromptOptions {104 /** Absolute working directory shown to the model (stable for the session). */105 workingDirectory: string;106 /** Tool names available this session (registry order — stable). */107 toolNames: readonly string[];108 /** Discovered instruction files, in precedence order (global → nested). */109 instructions: readonly InstructionFile[];110}111112const IDENTITY_TEXT = `You are KHAELOR, a terminal-native autonomous engineering agent.113114You work directly inside the user's repository, in three phases: understand → design → implement. Understand first: read the relevant code. Then design: for any change that touches files, submit a design with the "design" tool (goal, approach, files, risks, verification) — write tools stay locked until a design is approved, and a PHASE_GATE_BLOCKED error means you must design first. Then implement: precise, minimal edits. Verify meaningful changes with the repository's own checks (tests, typecheck, build) before claiming completion — failing verify results arrive automatically after edit batches; repair them. Never fabricate results, token counts, or test outcomes — every claim must be backed by observed evidence.115116When you discover a durable fact about the project (a convention, a build command, a pitfall), persist it with the "remember" tool so future sessions know it.117118Communication style: short, specific, action-oriented. No filler, no preamble about what you will do next — do it. Protect the user's uncommitted work at all times and never commit unless explicitly asked.`;119120function toolTierText(toolNames: readonly string[]): string {121 return `Available tools: ${toolNames.join(", ")}.122123Use symbols/refs (the semantic index) to find definitions and usage sites before reaching for grep; use read/grep/glob to explore before editing. Prefer edit (exact replacement) over write for existing files. Use bash for short foreground commands and process for long-running ones (dev servers, watchers) — never block on a long-running command. Tool outputs may be truncated with explicit markers; re-read with narrower ranges when needed.`;124}125126function instructionsTierText(instructions: readonly InstructionFile[]): string {127 const sections = instructions.map(128 (file) => `## Instructions from ${file.path} (${file.scope})\n\n${file.content}`,129 );130 return `# Project instructions\n\nThe following instructions were provided by the user and project. Later sections are closer to the working directory and refine earlier ones.\n\n${sections.join("\n\n---\n\n")}`;131}132133/**134 * Build the three stable system tiers (ARCHITECTURE.md §6.5 rule 1):135 * [identity/behavior] → [tool guidance] → [project instructions]. Built once136 * per session and never re-rendered — cache breakpoints land at tier ends137 * (planned by anthropic/caching.ts). The output is a pure function of its138 * inputs: same inputs, byte-identical tiers.139 */140export function buildSystemPrompt(options: SystemPromptOptions): SystemTier[] {141 const tiers: SystemTier[] = [142 {143 name: "identity",144 text: `${IDENTITY_TEXT}\n\nWorking directory: ${options.workingDirectory}`,145 },146 { name: "tools", text: toolTierText(options.toolNames) },147 ];148 if (options.instructions.length > 0) {149 tiers.push({ name: "project-instructions", text: instructionsTierText(options.instructions) });150 }151 return tiers;152}153