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/permissions/paths.ts4 * Description: Pure path helpers for capability classification — resolution, project-root scoping, home expansion.5 *6 * Author: Simon-Pierre Boucher7 * Contact: contact@spboucher.ai8 */910import * as path from "node:path";1112/**13 * Context every path-classifying function receives. The project root is14 * `workspace.cwd()` resolved (and symlink-canonicalized) at session start15 * (PERMISSION_MODEL.md §1). `resolvePath` is an injected canonicalizer16 * (realpath-style) supplied by the composition root — this module never17 * touches the filesystem itself (ARCHITECTURE.md §2.1: workspace is the18 * only fs module).19 */20export interface PathContext {21 /** Absolute, canonical project root. */22 projectRoot: string;23 /** Effective cwd for resolving relative paths (usually the project root). */24 cwd: string;25 /** Optional symlink canonicalizer applied AFTER lexical resolution. */26 resolvePath?: (absolutePath: string) => string;27 /** Home directory for `~` / `$HOME` expansion. */28 home?: string;29}3031/** Expand a leading `~` or `$HOME` in a single shell word. */32export function expandHomeWord(word: string, home: string): string {33 if (home.length === 0) return word;34 if (word === "~" || word === "$HOME") return home;35 if (word.startsWith("~/")) return home + word.slice(1);36 if (word.startsWith("$HOME/")) return home + word.slice(5);37 return word;38}3940/**41 * Fully resolve a path subject: home expansion, relative resolution against42 * the effective cwd, lexical `..` normalization, then the injected symlink43 * canonicalizer. Subjects are resolved BEFORE classification — a write to44 * `./x/../../etc/hosts` is outside the project (PERMISSION_MODEL.md §1).45 */46export function resolveSubjectPath(raw: string, ctx: PathContext): string {47 const expanded = ctx.home !== undefined ? expandHomeWord(raw, ctx.home) : raw;48 const absolute = path.resolve(ctx.cwd, expanded);49 return ctx.resolvePath ? ctx.resolvePath(absolute) : absolute;50}5152/** True when `resolved` is the project root or strictly under it. */53export function isUnderRoot(resolved: string, root: string): boolean {54 const normalizedRoot = path.normalize(root);55 if (resolved === normalizedRoot) return true;56 const prefix = normalizedRoot.endsWith(path.sep) ? normalizedRoot : normalizedRoot + path.sep;57 return resolved.startsWith(prefix);58}5960/** Collapse runs of whitespace — command subjects are matched collapsed (PERMISSION_MODEL.md §4.1). */61export function collapseWhitespace(text: string): string {62 return text.trim().replace(/\s+/g, " ");63}64