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%
5.0 KB · 139 lines typescript
Raw Blame History
1/**2 * KHAELOR3 * File: src/tools/design.ts4 * Description: The design tool — submit a DesignArtifact through the phase gate to unlock implement (v2 design §1).5 *6 * Author: Simon-Pierre Boucher7 * Contact: contact@spboucher.ai8 */910import type { ToolDefinition } from "./registry.js";11import type { ToolContext, ToolDesignArtifact, ToolResult } from "./types.js";1213export interface DesignInput {14  goal: string;15  approach: string;16  files: string;17  risks?: string;18  verification: string;19}2021const DESCRIPTION =22  "Submit your design before implementing. KHAELOR works in three phases: understand → design → " +23  "implement. Write tools stay locked until a design is approved. Provide: goal (restate the need in " +24  "your own words), approach (the technical plan, 5-15 lines), files (one file path per line you plan " +25  'to modify), risks (one per line; prefix a line with "out of scope:" to declare a non-goal), and ' +26  "verification (how you will prove it works). Small designs are auto-approved; larger ones may need " +27  "user approval.";2829const OUT_OF_SCOPE_PREFIX = /^out[ -]of[ -]scope:\s*/i;3031function splitLines(value: string | undefined): string[] {32  if (value === undefined) return [];33  return value34    .split("\n")35    .map((line) => line.trim())36    .filter((line) => line.length > 0);37}3839/** Parse the flat tool input into the structured artifact (risks vs out-of-scope). */40export function parseDesignInput(input: DesignInput): ToolDesignArtifact {41  const riskLines = splitLines(input.risks);42  const risks: string[] = [];43  const outOfScope: string[] = [];44  for (const line of riskLines) {45    const match = OUT_OF_SCOPE_PREFIX.exec(line);46    if (match !== null) outOfScope.push(line.slice(match[0].length));47    else risks.push(line);48  }49  return {50    goal: input.goal.trim(),51    approach: input.approach.trim(),52    filesTouched: splitLines(input.files),53    risks,54    verification: input.verification.trim(),55    outOfScope,56  };57}5859async function executeDesign(input: DesignInput, ctx: ToolContext): Promise<ToolResult> {60  const startedAt = Date.now();61  const artifact = parseDesignInput(input);6263  if (ctx.phases === undefined || ctx.phases.mode === "off") {64    return {65      content:66        "Design recorded (phase gates are off in this session — implementation was already unlocked).",67      metadata: { title: "Design recorded (gates off)", durationMs: Date.now() - startedAt },68    };69  }7071  if (artifact.filesTouched.length === 0) {72    return {73      content:74        'The "files" parameter must list at least one file you plan to modify (one path per line). ' +75        "If the task changes no files, say so in your answer instead of designing.",76      isError: true,77      metadata: { title: "Design rejected · no files", durationMs: Date.now() - startedAt },78    };79  }8081  const decision = await ctx.phases.submitDesign(artifact);82  const fileCount = artifact.filesTouched.length;8384  switch (decision.status) {85    case "approved":86      return {87        content:88          `Design approved (${fileCount} file${fileCount === 1 ? "" : "s"}). ` +89          "You are now in the implement phase — write, edit, and full bash are unlocked. " +90          "Follow your design; verify as planned before claiming completion.",91        metadata: {92          title: `Design approved · ${fileCount} file${fileCount === 1 ? "" : "s"}`,93          durationMs: Date.now() - startedAt,94          extra: { artifactId: decision.artifactId },95        },96      };97    case "rejected":98      return {99        content:100          `Design rejected: ${decision.reason ?? "no reason given"}. ` +101          "Revise the design based on this feedback and submit again, or ask the user for direction.",102        isError: true,103        metadata: { title: "Design rejected", durationMs: Date.now() - startedAt },104      };105    case "pending":106      return {107        content:108          `Design submitted but not yet approved: ${decision.reason ?? "awaiting approval"}. ` +109          "Tell the user what you plan to do and wait for their approval before implementing.",110        metadata: { title: "Design pending approval", durationMs: Date.now() - startedAt },111      };112  }113}114115/** Build the design tool (phase gates, v2 §1). */116export function createDesignTool(): ToolDefinition<DesignInput> {117  return {118    name: "design",119    description: DESCRIPTION,120    inputSchema: {121      type: "object",122      properties: {123        goal: { type: "string", description: "The need, restated in your own words." },124        approach: { type: "string", description: "Technical approach, 5-15 lines." },125        files: { type: "string", description: "Files you plan to modify, one path per line." },126        risks: {127          type: "string",128          description:129            'Identified risks, one per line. Prefix with "out of scope:" for explicit non-goals.',130        },131        verification: { type: "string", description: "How you will prove the change works." },132      },133      required: ["goal", "approach", "files", "verification"],134    },135    capability: "file.read",136    execute: executeDesign,137  };138}139