Skip to content

πŸ—οΈ ZSHAND ArchitectureΒΆ

ZSHAND Architecture Diagram
5-layer architecture β€” User Space β†’ Startup Pipeline β†’ Core Modules β†’ Extensions β†’ Post-Init

🎯 Read this first if you're new to the codebase. This document explains how the framework is structured, how it boots, how bundles and overrides work, and where your config plugs in.

TL;DR

Entry: zshrc.zsh β†’ shared_functions + startup β†’ core (02β†’90) β†’ hooks β†’ user post.d.
Speed: Use a full bundle (ZSHAND_AUTO_FULL=1 or zfull) for <50 ms cold start.
Override: Same basename in ~/.config/zshand/rc.d/ (or hooks/functions/widgets) replaces the framework file.

🧠 Design principles

  • ⚑ Performance first β€” target <50 ms cold start, <10 ms warm
  • 🧩 Modular β€” every directory is independently compilable
  • πŸ”„ Override-friendly β€” users replace any framework file by dropping a file in config
  • πŸ›‘οΈ Fail-safe β€” safe mode and graceful degradation at every layer
  • πŸ“ Convention-driven β€” numbered prefixes enforce load order; no magic, no plugins
Config & boot Config Β· Startup Β· Safe Mode
Build & perf Build System Β· Performance Β· Auto-Full
Reference Core Β· Hooks Β· Header Standard
Index Documentation Index Β· Home

In this doc: How it's structured β†’ Directory structure β†’ Boot sequence β†’ zshrc entry point β†’ Bundles β†’ Overrides β†’ Key subsystems β†’ Hooks β†’ Env vars.


How it's structured and works togetherΒΆ

The diagram above shows the five layers: User space (your config dir), Startup pipeline (shared_functions + startup scripts), Core modules (variables β†’ path β†’ engine β†’ … β†’ functions), Extensions (hooks, then functions and widgets), and Post-init (your post.d scripts). One entry point β€” zshrc.zsh β€” runs them in a fixed order so behavior is predictable and overridable.

Why it’s built this way:

  • Single entry point β€” Everything goes through zshrc.zsh. No plugin manager, no magic; you can trace exactly what runs and when.
  • Layers β€” Primitives (shared_functions) load first; then startup (TOML, safe mode, health); then core (config and engine); then extensions (hooks, functions, widgets). Your config is merged in at defined points (rc.d with core, separate dirs for hooks/functions/widgets), so framework updates never overwrite you.
  • Bundles β€” Instead of sourcing dozens of files every time, the build system compiles directories (or the whole stack) into bytecode. One read, one parse: cold start drops from hundreds of ms to tens. The β€œhow” (which strategy: full bundle, per-dir, or individual files) is chosen at boot.
  • Overrides β€” Same basename in your config replaces the framework file; different name adds. You get full control without forking.

The image summarizes this: your config and the framework meet at clear boundaries (init.d, rc.d, post.d, and the parallel dirs for functions/widgets/hooks). The sections below fill in directory layout, boot sequence, bundle types, and override rules so you can find things and change behavior.


πŸ“ Directory structureΒΆ

The repo is a flat set of directories under one root. Each directory has a single responsibility and is compiled to its own bundle (or included in the full bundle). Entry point: zshrc.zsh (sourced by ~/.zshrc). Bootstrap only: setup.zsh (first-time install and OS setup).

At a glanceΒΆ

Directory / file Role When / how it loads User override?
zshrc.zsh Entry point Every interactive shell No (sourced by your .zshrc)
setup.zsh First-time install Manual / installer only No
shared_functions/ Primitives (stderr, timing, load) Before core; same pass as startup No (framework-only)
startup/ TOML, safe mode, health, dev check Before core; same pass as shared_functions No
core/ Variables, PATH, engine, options, aliases, widgets, hooks loader, plugins, functions After startup; 02β†’90 in order; merged with user rc.d/ Yes β€” same basename in rc.d/ replaces
functions/ User-facing shell utilities After core; merged with user functions/ Yes β€” same basename replaces
widgets/ ZLE keybinding widgets After core; merged with user widgets/ Yes β€” same basename replaces
hooks/ Tool integrations (mise, atuin, docker, …) After core; merged with user hooks/ Yes β€” same basename replaces
bin/ CLI executables on $PATH PATH built from here (and user bin/) Yes β€” user bin/ in config
build/ Compile, profile, load logic Used at runtime only when not using a full bundle No
private_functions/ Internal helpers Loaded by core/engine when needed No
lib/ Rebuild logic, support code Used by build/ and engine No
config/ Framework config (e.g. profile exceptions) Read by build/profile scripts No
installers/ OS-specific install scripts Run by setup or manually No
tests/ ShellSpec specs just test No
docs/ This documentation β€” No
samples/ Example user configs Reference only No

Where to find or change whatΒΆ

You want to… In repo In config
Change PATH core/04_path.zsh path.txt, bin/, or rc.d/04_*.zsh
Change aliases core/14_aliases.zsh aliases.zsh or rc.d/14_aliases.zsh
Add a shell function functions/*.zsh ~/.config/zshand/functions/
Add a keybinding widget widgets/*.zsh ~/.config/zshand/widgets/
Add a tool hook (mise, docker, …) hooks/*.zsh ~/.config/zshand/hooks/
Run code before core β€” init.d/*.zsh
Run code between core modules β€” rc.d/NN_name.zsh (same numbering as core)
Run code after everything β€” post.d/*.zsh
Rebuild / compile build/*.zsh, core/06_engine.zsh β€”

Full config layout: Config.

Tree by roleΒΆ

Entry and bootstrap

zshand/
β”œβ”€β”€ zshrc.zsh          Entry point (sourced by ~/.zshrc)
└── setup.zsh          First-time bootstrap & OS setup (installers use this)

Primitives and startup (load before core)

shared_functions/     Low-level utilities β€” stderr, timing, bytecode load, clipboard
β”‚   β”œβ”€β”€ 01_stderr_error.zsh
β”‚   β”œβ”€β”€ 06_time_millis.zsh
β”‚   β”œβ”€β”€ 11_source_with_zwc.zsh
β”‚   β”œβ”€β”€ 15_load_file_timed.zsh
β”‚   └── ...
startup/               Early pipeline β€” dev check, TOML, safe mode, health
β”‚   β”œβ”€β”€ 05_az_dev_mode_check.zsh
β”‚   β”œβ”€β”€ 21_az_health_validate_tools.zsh
β”‚   β”œβ”€β”€ 24_az_safe_mode.zsh
β”‚   └── ...

Core pipeline (02β†’90; user rc.d/ merged by basename)

core/                  Framework core β€” one module per concern
β”‚   β”œβ”€β”€ 02_vars.zsh        Variables & identity (first in core)
β”‚   β”œβ”€β”€ 04_path.zsh       PATH construction
β”‚   β”œβ”€β”€ 06_engine.zsh     Build engine (zprime, zr, zfull), zrun, completion
β”‚   β”œβ”€β”€ 08_audit.zsh      Integrity & telemetry
β”‚   β”œβ”€β”€ 10_options.zsh    Zsh options (setopt)
β”‚   β”œβ”€β”€ 12_completions.zsh Completion system
β”‚   β”œβ”€β”€ 14_aliases.zsh    Shell aliases
β”‚   β”œβ”€β”€ 16_widgets.zsh    ZLE widget registration
β”‚   β”œβ”€β”€ 18_hooks.zsh      Hook classification & loader
β”‚   β”œβ”€β”€ 20_plugins.zsh    Plugin management
β”‚   └── 90_functions.zsh  Function autoloading

Extensions (user-facing; merged with config dirs)

functions/             Shell utilities (add_path, health, ztop, …)
widgets/               ZLE widgets (paste, AI commit, git branch, …)
hooks/                 Tool hooks (mise, atuin, docker, zoxide, …)
bin/                   CLI tools on PATH (zr, zfull, aicontext, …)

Build and internal

build/                 Compilation, profiling, load strategy
β”‚   β”œβ”€β”€ compile_full_bundle.zsh
β”‚   β”œβ”€β”€ compile_profile_bundle.zsh
β”‚   β”œβ”€β”€ profile_bundle_report.zsh
β”‚   └── ...
private_functions/     Internal helpers (not for user config)
lib/                    Support (e.g. lib/rebuild/ for incremental compile)
config/                 Framework config (e.g. profile-exceptions.conf)

Project and docs

installers/            OS-specific system installers
tests/                 ShellSpec test suites
docs/                  Documentation (this file)
samples/               Example user config templates

πŸ“ Numbering conventionΒΆ

Numbered directories (core/, shared_functions/, startup/, hooks/) use 2-digit prefixes so load order is deterministic. Numbers are per-directory (e.g. core/02_vars is first in core; hooks/01_mise is first in hooks).

Range Typical use Examples
01–09 Foundation / bootstrap 02_vars.zsh, 01_mise.zsh (hooks)
10–19 Core config / options 10_options.zsh, 14_aliases.zsh
20–29 Plugins & integrations 20_plugins.zsh, 20_docker.zsh
30–49 Mid-priority tools 30_zoxide.zsh
50–79 Optional / less critical 50_gitid.zsh
80–89 Cleanup / maintenance 80_archclean.zsh
90–99 Late-stage / finalization 90_functions.zsh

Even numbers = framework. Odd numbers = reserved for user insertion (e.g. ~/.config/zshand/rc.d/03_my_vars.zsh loads between 02_vars and 04_path). See Override system.


πŸš€ Boot SequenceΒΆ

zshrc.zsh is the single entry point. It decides how to load (bundle vs individual files), then runs a fixed pipeline. Non-interactive shells (scripts, cron) exit early; interactive shells run the full sequence.

🎯 zshrc.zsh entry point¢

What it is: The one file your ~/.zshrc sources. Typically your .zshrc contains only:

# ~/.zshrc
[[ -f /path/to/zshand/zshrc.zsh ]] && source /path/to/zshand/zshrc.zsh

What it does:

Responsibility Brief
Gate Exits immediately for non-interactive shells (scripts, cron, scp) so the framework never runs there.
Bootstrap Sets ZSHAND, ZSHAND_CACHE_DIR, ZSHAND_CONFIG_DIR and related paths so every script can find the repo and your config.
Strategy Chooses loading mode: full bundle, per-directory bundles, or individual files (see Loading strategies).
Pipeline Runs the 10-step sequence below: P10k, safe mode, health, core (with your rc.d merged), hooks, staleness check, your post.d, cleanup.
No plugin manager There is no second entry point. Everything is traceable from this file.

Where it lives: At the framework root, next to setup.zsh. The installer or cloner places the repo and points your .zshrc at zshrc.zsh.

Pipeline (high level)ΒΆ

Step What runs Why it matters
0 Non-interactive check Scripts and cron skip the framework entirely.
1 Path & cache init ZSHAND, ZSHAND_CACHE_DIR, etc. so everything else can find files.
2 Loading strategy Chooses full bundle, hybrid (per-dir bundles), or individual files. See Loading strategies below.
3 P10k instant prompt Prompt appears immediately; rest of init can take time without blocking the UI.
4 Safe mode check If init failed 3+ times recently (or ZSHAND_SAFE_MODE=1), drop into recovery shell and stop.
5 Health validation Require git, zsh, etc.; write to failure tracker if something is missing.
6 Core (02β†’90) Variables, PATH, engine, audit, options, completions, aliases, widgets, hooks loader, plugins, functions. User rc.d/ is merged in by basename.
7 Hooks Critical hooks sync; lazy hooks can be deferred if ZSHAND_LAZY_LOAD=1.
8 Staleness & recompile If bundles are older than source, set flag; optional auto zprime --quiet in dev.
9 User post.d/ Your scripts run last; ideal for one-off tweaks.
10 Debug summary & cleanup Unset _az_* startup functions, print timing if ZSHAND_DEBUG=1.
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                    zshrc.zsh Entry Point                        β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  0. 🚫 Non-interactive check (early exit for scripts/cron)     β”‚
β”‚  1. πŸ“ Path & cache initialization                             β”‚
β”‚  2. πŸ”€ Loading strategy selection (see below)                  β”‚
β”‚  3. 🎨 P10k instant prompt (zero-latency visual)               β”‚
β”‚  4. πŸ›‘οΈ Safe mode check (emergency recovery)                    β”‚
β”‚  5. βœ… Health validation (system dependencies)                  β”‚
β”‚  6. βš™οΈ Core (02β†’90) + user rc.d/ merged                        β”‚
β”‚  7. πŸͺ¨ Hooks (critical sync, then lazy/deferred)                β”‚
β”‚  8. πŸ” Staleness check & optional auto-recompile               β”‚
β”‚  9. πŸ‘€ User post.d/ scripts                                    β”‚
β”‚ 10. πŸ“Š Debug summary & cleanup                                 β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

πŸ”€ Loading strategiesΒΆ

The framework picks the fastest available strategy that matches your environment (e.g. dev mode forces individual files). In production, the full bundle is fastest.

Priority Strategy When it's used Typical speed
1 Profile bundle ZSHAND_PROFILE_BUNDLE=1 ~100 ms (instrumented)
2 Full bundle (fast path) ZSHAND_AUTO_FULL=1 and bundle exists <50 ms
3 Full bundle (first run) ZSHAND_AUTO_FULL=1 but no bundle yet ~200 ms once, then fast
4 Monolithic ZSHAND_USE_MONOLITHIC=1 (framework only) ~60 ms
5 Hybrid Default: per-directory .compiled/*.zwc ~80 ms
6 Individual files ZSHAND_DEV_MODE=1 or fallback ~200 ms (best for debugging)

Which mode am I in?

Run echo $ZSHAND_DEV_MODE $ZSHAND_AUTO_FULL. In production with auto-full, one cached file is sourced from $ZSHAND_CACHE_DIR β€” no build code loaded, no per-dir reads.


πŸ“¦ Bundle SystemΒΆ

Why bundles? Sourcing dozens of .zsh files costs time; one pre-compiled file (or one per directory) loads in a single read and parse. The build system concatenates sources (with user overrides merged in), then zcompiles to .zwc bytecode. Result: 3–5Γ— faster load per directory, and with a full bundle the whole framework + your config loads in one shot.

Bundle typesΒΆ

Bundle Where it lives Contains Built by
Full $ZSHAND_CACHE_DIR/zshand-full.zsh + .zwc Framework + user (init.d, rc.d, post.d, functions, widgets, hooks, aliases) zfull / compile_full_bundle.zsh
Profile Same dir, zshand-profile.* Full bundle + timing instrumentation ZSHAND_PROFILE_BUNDLE=1 + compile
Per-directory <dir>/.compiled/<dir>-all.zsh + .zwc One directory’s files only zprime / compile_single_directory.zsh

How compilation worksΒΆ

flowchart LR
  subgraph inputs[" "]
    A[Source files\nframework + user]
  end
  subgraph merge[" "]
    B[Merge by basename\nuser replaces framework]
  end
  subgraph build[" "]
    C[Concatenate\nload order]
    D[one .zsh]
    E[zcompile]
    F[.zwc bytecode]
  end
  A --> B --> C --> D --> E --> F

User files in rc.d/, functions/, widgets/, hooks/ that share a basename with a framework file are replaced in the bundle; unique names are added. See Override system.

CommandsΒΆ

Command What it does
zr / zprime Rebuild all per-directory bundles (and optionally full). Reload shell.
zprime --quiet Same, no output (e.g. background).
zfull / zprime --full Build full bundle (framework + user) in cache.
zr-dir <dir> / zprime-dir <dir> Rebuild one directory only.
zrecompile user Rebuild only user-related parts of the full bundle (faster after config-only changes).

Stale bundles

If you edit .zsh files and don’t rebuild, the old bytecode is still loaded. You’ll see a β€œstale” warning at startup. Run zr or zfull (and exec zsh) to refresh.


πŸ”„ Override SystemΒΆ

You can replace or extend the framework without forking. Same basename in your config = replaces the framework file. Different name = adds (both load). Framework updates never overwrite your config.

Example: To replace the framework’s aliases entirely, add ~/.config/zshand/rc.d/14_aliases.zsh. The loader will use your file instead of $ZSHAND/core/14_aliases.zsh. To add aliases on top, use ~/.config/zshand/aliases.zsh (loaded after core).

Resolution ruleΒΆ

Framework:  $ZSHAND/core/14_aliases.zsh
User:       $ZSHAND_CONFIG_DIR/rc.d/14_aliases.zsh   ← wins (same basename)

Same rule applies in functions/, widgets/, hooks/: same basename β†’ replace; new name β†’ add. Full layout: Config.

User config directoryΒΆ

~/.config/zshand/                   ($ZSHAND_CONFIG_DIR)
β”œβ”€β”€ env.zsh           🌍 Environment variables (auto-exported)
β”œβ”€β”€ secrets.zsh       πŸ”’ API keys and tokens (chmod 600)
β”œβ”€β”€ aliases.zsh       πŸ“ Custom aliases (loaded after framework)
β”œβ”€β”€ path.txt          πŸ›€οΈ Additional PATH entries (one per line)
β”œβ”€β”€ config.toml       βš™οΈ Framework behavior (dev_mode, quiet, perf_budget, etc.)
β”œβ”€β”€ bin/              πŸ“Ÿ Personal scripts (on $PATH)
β”œβ”€β”€ functions/        πŸ› οΈ Custom functions (override or extend)
β”œβ”€β”€ widgets/          ⌨️ Custom ZLE widgets
β”œβ”€β”€ hooks/            πŸͺ¨ Custom tool hooks (e.g. 60_my_tool.zsh)
β”œβ”€β”€ rc.d/             βš™οΈ Core overrides (same numbering as core/, e.g. 14_aliases.zsh)
β”œβ”€β”€ init.d/           πŸš€ Before framework (early scripts)
└── post.d/           🏁 After everything (final tweaks)

User file load orderΒΆ

Order What loads Notes
1 init.d/*.zsh Before any framework core.
2 env.zsh, secrets.zsh Environment and secrets.
3 path.txt, bin/ PATH.
4 Core (02β†’90) Merged with rc.d/: user file same basename replaces framework.
5 Framework then user hooks/, functions/, widgets/ Each dir: framework first, then user (user can override by basename).
6 aliases.zsh Loaded after framework aliases; can override.
7 post.d/*.zsh Last; your final tweaks.

βš™οΈ Key SubsystemsΒΆ

πŸ”§ Engine (core/06_engine.zsh)ΒΆ

The engine is the largest core module and the one that makes β€œrebuild and reload” work. It defines:

  • zprime / zr β€” Compile directories (or full bundle), optionally reload shell
  • zrun β€” Run commands with optional telemetry logging
  • _z_system_init β€” Post-init: Atuin, terminal title, security checks
  • Incremental rebuild β€” Only recompile dirs that changed (or depend on changed dirs)
  • Completion generation β€” Atomic completion for external CLIs

If you’re asking β€œwhere does zr come from?” or β€œwho builds the bundle?” β€” it’s here. See Build System for the full pipeline.

🧱 Shared functions (shared_functions/)¢

Lowest-level primitives, loaded in the same pass as startup/ (alphabetically). No core/ or user code runs before these. Used by startup scripts, init.d, post.d, and rc.d.

Function Purpose
stderr_error / stderr_warn Colored stderr output
echo_info / echo_ok Status messages
time_millis Millisecond timing
assert_*_exists Guard assertions
source_with_zwc Prefer .zwc when present
load_file_timed Source with timing and perf budget
_zrun Telemetry logging (used by zrun)
clipboard_copy / clipboard_paste Clipboard (Wayland/X11/macOS)

Shared Functions has the full list.

πŸ›‘οΈ Safe mode (startup/24_az_safe_mode.zsh)ΒΆ

If the shell fails to init 3+ times in a short window (or you set ZSHAND_SAFE_MODE=1), the next start drops into safe mode: minimal PATH, no framework, recovery menu. Lets you fix a broken config without losing shell access. See Safe Mode.

πŸ“Š Profiling pipelineΒΆ

ZSHAND_PROFILE_BUNDLE=1 exec zsh   β†’  instrumented bundle built & run
         ↓
profile-timing.log                 β†’  raw TYPE|NAME|START|END
         ↓
profile_bundle_report.zsh          β†’  graded report (A–F, bar charts)

Use when tracking down slow startup. Performance and Profile Report Guide have details.


πŸͺ¨ Hook classificationΒΆ

Hooks (tool integrations) are classified in core/18_hooks.zsh so the loader can run some at once and defer others:

Type When they load Use for Examples
Critical Synchronous, before first prompt Things the prompt or early commands need 01_mise, 02_atuin, 05_ssh-agent
Lazy After prompt, or after 1s delay if ZSHAND_LAZY_LOAD=1 Tools that can wait 20_docker, 30_zoxide, 50_gitid

With ZSHAND_LAZY_LOAD=1, non-critical hooks run in a background subshell after 1 second, so startup stays fast. Add your own in ~/.config/zshand/hooks/ (e.g. 60_my_tool.zsh). Full list and ordering: Hooks.


πŸ§ͺ Testing architectureΒΆ

Tests live under tests/ and spec/ (ShellSpec). Specs mirror the source tree: e.g. functions/dcopy.zsh β†’ spec/functions/dcopy_spec.sh. Run just test (or shellspec). CI uses Kcov for coverage. See Testing.


🌍 Environment variables¢

These control where the framework looks for files, whether to use bundles, and how noisy or strict to be. Set before the framework loads (e.g. in ~/.zshenv for ZSHAND_AUTO_FULL).

Framework controlsΒΆ

Variable Type Default Purpose
ZSHAND string auto-detected Framework root directory
ZSHAND_CONFIG_DIR string $XDG_CONFIG_HOME/zshand User config directory
ZSHAND_CACHE_DIR string $XDG_CACHE_HOME/zshand Cache & compiled bundles
ZSHAND_LOG_DIR string $HOME/logs/zshand Log file directory
ZSHAND_DEV_MODE bool 0 Enable development mode
ZSHAND_AUTO_FULL bool 0 Auto-compile full bundle
ZSHAND_DEBUG bool 0 Verbose debug output
ZSHAND_PROFILE_BUNDLE bool 0 Enable profiling
ZSHAND_LAZY_LOAD bool 0 Defer non-critical hooks
ZSHAND_SAFE_MODE bool 0 Emergency recovery mode
ZSHAND_AUDIT_MODE bool 0 Full audit (recompile + test)
ZSHAND_PERF_BUDGET int 100 Max ms per file before warning

πŸ”‘ XDG ComplianceΒΆ

The framework follows the XDG Base Directory Specification:

XDG Variable ZSHAND Maps To Default
XDG_CONFIG_HOME ZSHAND_CONFIG_DIR ~/.config/zshand
XDG_CACHE_HOME ZSHAND_CACHE_DIR ~/.cache/zshand
XDG_DATA_HOME β€” (not currently used)

πŸ“ Dependency graphΒΆ

zshrc.zsh
  β”œβ”€β”€ shared_functions/*     (first β€” primitives; no core yet)
  β”œβ”€β”€ startup/*              (same pass as shared_functions, alphabetical merge)
  β”œβ”€β”€ [if not bundle] build/detect_dev_mode.zsh
  β”œβ”€β”€ core/02_vars.zsh       (bootstrap; no other core deps)
  β”‚     β†’ 04_path β†’ 06_engine β†’ 08_audit β†’ 10_options β†’ … β†’ 90_functions
  β”œβ”€β”€ hooks/*                (after core; order from 18_hooks.zsh)
  β”œβ”€β”€ P10k theme
  └── post.d/*               (last)

No circular deps

shared_functions/ and startup/ load before any core/. Within core/, files run in numeric order only; a file must not depend on a higher number in the same directory.