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/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