A path is constructed exactly once. Everywhere else references the constructed value. This is the strict form of DRY for paths. Paths drift the easiest because they're string literals that look harmless until two of them diverge and you spend an hour finding which copy is the source of truth.
- Within a package: every script imports its own
scripts/paths.mts. Nopath.join('build', mode, …)outside that module.paths.mtsis per-package (likepackage.json). Every package that has ascripts/dir has its own. - Across packages: package B imports package A's
paths.mtsvia the workspaceexportsfield. Neverpath.join(PKG, '..', '<sibling>', 'build', …). - Sub-packages inherit: a sub-package's
paths.mtsexport * from '<rel>/paths.mts'from the nearest ancestor and adds local overrides below the re-export. Don't re-deriveREPO_ROOT/CONFIG_DIR/NODE_MODULES_CACHE_DIR(enforced by.claude/hooks/fleet/paths-mts-inherit-guard/). - Not just build paths:
paths.mtsis for every path the package constructs (config files (socket-wheelhouse.json), lockfiles, cache dirs, manifest files). The fleet ships a startertemplate/scripts/paths.mtsthat exports the common constants +loadSocketWheelhouseConfig(). - Workflows / Dockerfiles / shell can't
importTS. Construct once, reference by output /ENV/ variable.
Build outputs live at <package-root>/build/<mode>/<platform-arch>/out/Final/<artifact>, where mode ∈ {dev, prod} and platform-arch is the Node-style <process.platform>-<process.arch> (e.g. darwin-arm64, linux-x64). socket-btm is the worked example; ultrathink follows it; smaller TS-only repos that don't fork by platform may use 'any' as the platform-arch sentinel but keep the same nesting.
Each package's scripts/paths.mts exports at minimum:
PACKAGE_ROOT: absolute path to the package directoryBUILD_ROOT:<PACKAGE_ROOT>/buildgetBuildPaths(mode, platformArch): returns at leastoutputFinalDir+outputFinalFileoroutputFinalBinary
| Level | Surface | What it catches |
|---|---|---|
| Edit-time | .claude/hooks/fleet/path-guard/ |
Build-path construction outside paths.mts |
| Edit-time | .claude/hooks/fleet/paths-mts-inherit-guard/ |
Sub-package paths.mts that doesn't inherit from the nearest ancestor |
| Commit-time | scripts/fleet/check/paths-are-canonical.mts (run by pnpm check) |
Whole-repo path-hygiene scan |
| Audit + fix | /guarding-paths skill |
Interactive cleanup |
- Recomputing a sibling's build dir. Import from the sibling's
paths.mtsinstead. - Hard-coding
build/dev/orbuild/prod/. UsegetBuildPaths(mode, ...)so a future--mode=stagingdoesn't require N edits. - Constructing the same
~/.socket/...cache dir in 3 places. Either it belongs inscripts/paths.mtsor in@socketsecurity/lib'spaths/module if it's truly cross-package.
When in doubt: find the canonical owner and import from it.