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.
Workspace paths
Section titled “Workspace paths”RepoRootNotFoundError
Section titled “RepoRootNotFoundError”Defined in: core/src/paths.ts:33
Thrown when no ancestor of the start path is a workspace root.
Extends
Section titled “Extends”Error
Constructors
Section titled “Constructors”Constructor
Section titled “Constructor”new RepoRootNotFoundError(
startPath):RepoRootNotFoundError
Defined in: core/src/paths.ts:34
Parameters
Section titled “Parameters”startPath
Section titled “startPath”string
Returns
Section titled “Returns”Overrides
Section titled “Overrides”Error.constructor
ensoAgentDir()
Section titled “ensoAgentDir()”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.
Parameters
Section titled “Parameters”repoRoot
Section titled “repoRoot”string
Returns
Section titled “Returns”string
ensoConfigPath()
Section titled “ensoConfigPath()”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.
Parameters
Section titled “Parameters”repoRoot
Section titled “repoRoot”string
Returns
Section titled “Returns”string
ensoLogsDir()
Section titled “ensoLogsDir()”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).
Parameters
Section titled “Parameters”repoRoot
Section titled “repoRoot”string
Returns
Section titled “Returns”string
ensoProjectsPath()
Section titled “ensoProjectsPath()”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.
Parameters
Section titled “Parameters”repoRoot
Section titled “repoRoot”string
Returns
Section titled “Returns”string
ensoSecretsPath()
Section titled “ensoSecretsPath()”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.
Parameters
Section titled “Parameters”repoRoot
Section titled “repoRoot”string
Returns
Section titled “Returns”string
fileUrlToPath()
Section titled “fileUrlToPath()”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).
Parameters
Section titled “Parameters”fileUrl
Section titled “fileUrl”string
Returns
Section titled “Returns”string
findRepoRoot()
Section titled “findRepoRoot()”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().
Parameters
Section titled “Parameters”startPathOrFileUrl
Section titled “startPathOrFileUrl”string
Returns
Section titled “Returns”string
harnessPackageDir()
Section titled “harnessPackageDir()”harnessPackageDir(
repoRoot):string
Defined in: core/src/paths.ts:213
The harness package — the directory enso.ts hands to pi’s -e.
Parameters
Section titled “Parameters”repoRoot
Section titled “repoRoot”string
Returns
Section titled “Returns”string
isFileUrl()
Section titled “isFileUrl()”isFileUrl(
candidate):boolean
Defined in: core/src/paths.ts:83
True when the string is a file:// URL rather than a filesystem path.
Parameters
Section titled “Parameters”candidate
Section titled “candidate”string
Returns
Section titled “Returns”boolean
printHarnessDir()
Section titled “printHarnessDir()”printHarnessDir(
repoRoot):string
Defined in: core/src/paths.ts:181
Where the print variant of the harness package is materialized (#20).
Parameters
Section titled “Parameters”repoRoot
Section titled “repoRoot”string
Returns
Section titled “Returns”string
realpathOrSelf()
Section titled “realpathOrSelf()”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.
Parameters
Section titled “Parameters”string
Returns
Section titled “Returns”string
rootDotenvPath()
Section titled “rootDotenvPath()”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.
Parameters
Section titled “Parameters”repoRoot
Section titled “repoRoot”string
Returns
Section titled “Returns”string
webPackageDir()
Section titled “webPackageDir()”webPackageDir(
repoRoot):string
Defined in: core/src/paths.ts:222
The web package — the cwd both dev surfaces run in.
Parameters
Section titled “Parameters”repoRoot
Section titled “repoRoot”string
Returns
Section titled “Returns”string