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.9 KB · 153 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, 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