Skip to content

core/src/paths

The workspace path helpers (#59) — THE one place that walks the filesystem for roots.

Before this module, seven call sites each computed the repo root by counting .. segments off their own import.meta.url — a shape that breaks silently when a file moves (dev.ts carried 4×".."). An anchor test (no-duplicate-paths.test.ts) now refuses an eighth copy: fileURLToPath and inline .enso path assembly outside this module fail the suite by name.

⚠ REALPATH FIRST, then walk — load-bearing, observed live (PR #58 receipts): bun does NOT realpath import.meta.url through the .enso/print-harness/ symlink mirror, so a walk from the un-resolved module path starts inside the mirror. The mirror’s entries are symlinks back into the real tree, so resolving first lands the walk in the real checkout; walking first would search the wrong ancestry.

⚠ These helpers run under node/bun only (node:fs). They are barrel-exported and the browser bundle tolerates that because nothing browser-side imports them — probed 2026-09-04: bun build index.html --target=browser tree-shakes the module out.

Defined in: core/src/paths.ts:33

Thrown when no ancestor of the start path is a workspace root.

  • Error

new RepoRootNotFoundError(startPath): RepoRootNotFoundError

Defined in: core/src/paths.ts:34

string

RepoRootNotFoundError

Error.constructor


ensoAgentDir(repoRoot): string

Defined in: core/src/paths.ts:133

pi’s scratch agent dir — settings, extensions, auth. Shared by the TUI launcher and the web server.

string

string


ensoConfigPath(repoRoot): string

Defined in: core/src/paths.ts:231

The committed config file’s path under a repository root (#59): enso.config.json, beside package.json.

string

string


ensoLogsDir(repoRoot): string

Defined in: core/src/paths.ts:194

Where the harness’s log lives (#132): .enso/logs/enso-YYYY-MM-DD.jsonl, ONE file per day that every process appends to (dated, never renamed, so a second writer is safe — see web/server/logging.ts).

Under .enso/ so bun never autoloads from it and the write gate already knows it (guard.ts).

string

string


ensoProjectsPath(repoRoot): string

Defined in: core/src/paths.ts:204

The projects registered from a page (#395): .enso/projects.json. Server STATE, not config — the roots they may sit under are config (enso.config.json), committed with the install.

string

string


ensoSecretsPath(repoRoot): string

Defined in: core/src/paths.ts:163

The harness’s secrets file (#89): .enso/secrets.env, NOT .env at the root.

bun auto-loads .env* from the cwd into EVERY bun process — including a bun -e the model runs from the repo root — so a root .env reaches a tool child no matter what the parent scrubbed (verified with env -i).

.enso/ is git-ignored, write-protected (ENSO_PERSISTENCE_WRITE_DIRECTORIES) and read-denied on BOTH tool routes by the /.enso/ fragment in DEFAULT_GUARD_POLICY: the reader gate for every tool carrying a path, and the shell deny (#204) for a literal path word in bash/powershell. ⚠ The shell leg is a bar, not a boundary — shell-secret-paths.ts states what a runtime-assembled name still gets past.

⚠ THE core/src! QUALIFIER IS LOAD-BEARING, and the obvious simplification does not work (#188). guard.ts is not imported here, so TypeScript’s own link resolution — which TypeDoc prefers by default — has nothing in scope, and a bare {@link DEFAULT_GUARD_POLICY} is an unresolved link. The documented root-scoped form {@link !DEFAULT_GUARD_POLICY} does not help either: with several entry points, the PROJECT’s children are the modules, not their symbols, so resolution from the root finds nothing. Tested, all three spellings. The module source is therefore named, which couples this link to the entry-point layout in typedoc.json — and that coupling breaks LOUDLY, because an unresolved link is a fatal warning in the api-docs gate.

string

string


fileUrlToPath(fileUrl): string

Defined in: core/src/paths.ts:97

Decode a file:// URL to the filesystem path it names.

The ONLY fileURLToPath call site in the repository (the anchor test enforces it). Two callers with different reasons share it: findRepoRoot accepts import.meta.url, and the harness guard decodes a file:// path a tool was handed — pi’s own tools decode one before touching the filesystem, so the guard must judge the decoded path (#53).

string

string


findRepoRoot(startPathOrFileUrl): string

Defined in: core/src/paths.ts:114

The repository root: the nearest ancestor whose package.json declares workspaces.

Accepts a filesystem path OR a file:// URL, so callers pass import.meta.url directly and never touch fileURLToPath themselves (the anchor test enforces that). The start path is realpath-resolved BEFORE walking — see the module header for why.

⚠ This finds the root of the checkout the given FILE lives in — for a module inside this repo that is always the Enso checkout, never the session cwd. That distinction is what config.ts relies on; do not “fix” this to consult process.cwd().

string

string


harnessPackageDir(repoRoot): string

Defined in: core/src/paths.ts:213

The harness package — the directory enso.ts hands to pi’s -e.

string

string


isFileUrl(candidate): boolean

Defined in: core/src/paths.ts:83

True when the string is a file:// URL rather than a filesystem path.

string

boolean


printHarnessDir(repoRoot): string

Defined in: core/src/paths.ts:181

Where the print variant of the harness package is materialized (#20).

string

string


realpathOrSelf(path): string

Defined in: core/src/paths.ts:48

realpathSync, or the input unchanged when the path (or a segment of it) does not exist.

string

string


rootDotenvPath(repoRoot): string

Defined in: core/src/paths.ts:172

The root .env — where secrets must NOT live (#89); reported as misplaced when they do.

string

string


webPackageDir(repoRoot): string

Defined in: core/src/paths.ts:222

The web package — the cwd both dev surfaces run in.

string

string