SPB Git

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%
6.3 KB · 151 lines typescript
Raw Blame History
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. Understand before changing: read the relevant code, then make precise, minimal edits. Verify meaningful changes with the repository's own checks (tests, typecheck, build) before claiming completion. Never fabricate results, token counts, or test outcomes — every claim must be backed by observed evidence.115116Communication 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.`;117118function toolTierText(toolNames: readonly string[]): string {119  return `Available tools: ${toolNames.join(", ")}.120121Use 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.`;122}123124function instructionsTierText(instructions: readonly InstructionFile[]): string {125  const sections = instructions.map(126    (file) => `## Instructions from ${file.path} (${file.scope})\n\n${file.content}`,127  );128  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")}`;129}130131/**132 * Build the three stable system tiers (ARCHITECTURE.md §6.5 rule 1):133 * [identity/behavior] → [tool guidance] → [project instructions]. Built once134 * per session and never re-rendered — cache breakpoints land at tier ends135 * (planned by anthropic/caching.ts). The output is a pure function of its136 * inputs: same inputs, byte-identical tiers.137 */138export function buildSystemPrompt(options: SystemPromptOptions): SystemTier[] {139  const tiers: SystemTier[] = [140    {141      name: "identity",142      text: `${IDENTITY_TEXT}\n\nWorking directory: ${options.workingDirectory}`,143    },144    { name: "tools", text: toolTierText(options.toolNames) },145  ];146  if (options.instructions.length > 0) {147    tiers.push({ name: "project-instructions", text: instructionsTierText(options.instructions) });148  }149  return tiers;150}151