Architecture¶
nshell follows a domain-driven, layered design. Each layer depends only on the layers beneath it:
src/
├── domain/ Pure shell logic: parsing, expansion, completion,
│ history, prompting, job-control — no I/O.
├── application/ Use cases: builtins, pipeline execution, job management.
├── infrastructure/ ACLs over the OS: syscalls, PTY, signals, terminal I/O,
│ persistence. SBCL-specific code is isolated here.
└── presentation/ The REPL, line editor (input-state reducer), rendering,
highlighting, autosuggestions, completion UI.
src/<DDD> is the composition route for the existing shell runtime. New
capabilities are vertical features under packages/, with the same DDD layer
names kept together inside each feature:
packages/
├── core/<name>/src/<DDD>/ Shared domain and architecture primitives.
└── feature/<name>/src/<DDD>/ A feature's domain, use cases, adapters, and UI.
The command-line feature is the first slice in this layout. Its pure option
policy, application contract, and help presentation live in
packages/feature/command-line/src/. src/main.lisp remains a small
composition root and consumes cl-cli directly, so the feature boundary does
not contain a compatibility adapter. Its established internal entry points
delegate to the feature boundary, so startup and test callers do not need to
know where the slice is stored. ASDF loads the package modules before the
shared src/<DDD> components, preserving the dependency direction while
making the vertical boundary explicit.
Assistant feature¶
The assistant is a vertical feature under
packages/feature/assistant/src/<DDD>/. Its application layer assembles
context and tracks settings and usage, while the infrastructure layer isolates
the model-sidecar boundary and audit state. Domain checks classify proposed
commands and redact sensitive payloads before the REPL approval gate allows a
proposal to execute.
Tests follow the same observable boundaries: t/unit/ checks feature policy
and contracts, t/integration/ checks the source topology, and t/e2e/
checks that the src/main route exposes the feature's user-facing behavior.
The REPL is structured as a continuation-passing / trampoline loop: each
keystroke runs a pure reducer over an immutable input-state, and rendering is
derived from that state. This keeps the interactive core deterministic and
unit-testable without a terminal. See
Core concepts for what that
buys in practice.
Confining SBCL-specific code to infrastructure/ is what makes the layers
above it portable in principle and testable in practice: a test replaces a
boundary with a value instead of arranging for the operating system to produce
one.
Pipeline orchestration keeps process waiting and termination policy in
infrastructure/acl/syscall-pipeline-wait.lisp, separate from pipe topology
and stage spawning. This makes timeout escalation, process-group ownership,
and pipefail status calculation independently reviewable while the public
pipeline entry points remain unchanged.
Toolkit foundation¶
nshell builds on the nerima-lisp Common Lisp toolkit family, each wired at the
layer where it fits the domain-driven design:
- cl-parser-kit — its
rule-based tokenizer and Pratt (operator-precedence) parser drive
$((...))arithmetic, which parses to an AST and then evaluates, adding**, bitwise& | ^ ~, shifts<< >>, and the ternary?:. - cl-dataflow-kit — renders
pipelines as validated computation graphs (
pipeline-graph) and models the job lifecycle as an analyzable state machine. - cl-boundary-kit — makes the REPL edge's OS effects (hostname, working directory, clock) explicit, swappable boundaries, so the prompt and command timing are deterministic under test.
- cl-cli — declaratively describes
the
nshellcommand line (--help/--version/-c/script dispatch). - cl-tty-kit — provides
Unicode-correct display-width, truncation, and padding; the ANSI/SGR escape
vocabulary used by rendering, prompt, and completion; and the
ioctl(TIOCGWINSZ)window-size query behindnshell.infrastructure.acl:get-terminal-size. Raw mode is deliberately not taken from the kit:cl-tty-kit:enable-raw-modeis a full cfmakeraw-style mode that also clearsISIGandOPOST, while nshell holds raw mode for the whole session — including while a foreground child runs — and needs the terminal driver to keep turning^C/^Zinto signals for that child and to keep mapping LF to CR-LF. See the commentary insrc/infrastructure/terminal/raw-mode.lisp. - cl-process-kit — backs timeout-guarded process launch, escalating SIGTERM to SIGKILL across a child's whole process group so a timed-out command substitution leaves no orphaned descendants.
- cl-prolog-kit — the logic engine behind the completion knowledge base.
Test suites¶
Two suites run under cl-weave, both exposed as Nix checks:
nshell/test— the primary regression suite, int/.nshell/weave— a focused suite exercising the completion engine's cl-prolog-kit knowledge base with property-based tests, fixtures, benchmarks, and direct Prolog queries (findall, negation-as-failure, foreign predicates) plus thecl-prolog-kit/weavequery bridge.
Cases that need a real PTY, stty, or external binaries cannot run in the Nix
sandbox and are covered by CI's separate integration job; see
Recipes.