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/workspace/file-times.ts4 * Description: FileTimeRegistry — read-before-write and external-modification detection (TOOL_PROTOCOL §1.4).5 *6 * Author: Simon-Pierre Boucher7 * Contact: contact@spboucher.ai8 */910import { createHash } from "node:crypto";11import { statSync } from "node:fs";12import * as path from "node:path";1314/** Freshness of a file relative to what the agent last saw. */15export type FileFreshness = "unread" | "clean" | "externally-modified";1617/** Session-scoped record of a file as last read or written by the agent. */18export interface FileStamp {19 /** Absolute, resolved path. */20 path: string;21 mtimeMs: number;22 size: number;23 /** Of content as read/written. */24 sha256: string;25 /** Event timestamp. */26 at: number;27}2829/**30 * Session-scoped record of every file the agent has read or written.31 * `write`/`edit` enforce: existing files must have been read this session32 * before modification, and must not have changed externally since33 * (Absolute Rule #5). Rebuilt on resume by replaying FileRead/FileModified.34 */35export interface FileTimeRegistry {36 /** Called by read/write/edit on success. */37 stamp(path: string, content: string): void;38 get(path: string): FileStamp | undefined;39 /** "unread" | "clean" | "externally-modified" */40 check(path: string, currentContent: string): FileFreshness;41}4243function sha256Of(content: string): string {44 return createHash("sha256").update(content, "utf8").digest("hex");45}4647/** In-memory FileTimeRegistry — one instance per session. */48export class InMemoryFileTimeRegistry implements FileTimeRegistry {49 private readonly stamps = new Map<string, FileStamp>();5051 stamp(p: string, content: string): void {52 const abs = path.resolve(p);53 const at = Date.now();54 let mtimeMs = at;55 try {56 mtimeMs = statSync(abs).mtimeMs;57 } catch {58 // File vanished between operation and stamp — keep the event timestamp.59 }60 this.stamps.set(abs, {61 path: abs,62 mtimeMs,63 size: Buffer.byteLength(content, "utf8"),64 sha256: sha256Of(content),65 at,66 });67 }6869 get(p: string): FileStamp | undefined {70 return this.stamps.get(path.resolve(p));71 }7273 check(p: string, currentContent: string): FileFreshness {74 const stamp = this.stamps.get(path.resolve(p));75 if (stamp === undefined) return "unread";76 return sha256Of(currentContent) === stamp.sha256 ? "clean" : "externally-modified";77 }78}79