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%
2.5 KB · 64 lines typescript
Raw Blame History
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