Docs · Architecture

Module Map

checked against the actual require graph (2026-08-18; re-extracted 2026-08-26)

Arrows below are EXTRACTED from (:require …) forms across src/shiftlefter/**/*.clj — 316 require edges at the 2026-08-30 re-extraction (distinct shiftlefter.* → shiftlefter.* pairs, require forms only; freshness-guarded by the test suite, which re-runs the extraction and fails when this stamp disagrees with the code) — not drawn from memory. Raw edge list: regenerate with the one-liner in § Method. Re-stamped the same day (+2 over the 314 the basis change measured): stepengine.exec and stepengine.exec.step-loop now require shiftlefter.step for the shared error-classes literal — the guard’s first live catch.

The intended shape (the laws as practiced)

diagram diagram

Laws checked: (1) etaoin contained to the webdriver layer; (2) svo never reaches up into stepengine/runner — EXCEPT engine-owned vocabulary/grammar definitions; (3) gherkin is a leaf; (4) stepengine never reaches up into runner — passes unrestated since the config extraction (2026-08-26): the config-accessor exception dissolved when runner.config moved to the neutral shiftlefter.config address, so the one formerly-excepted edge now points down into substrate. The dotted SVO→CONFIG arrow is the language layer consuming ONLY the cluster’s shiftlefter.text did-you-mean substrate (svo/{validate,bindings_lint} → text); no svo namespace requires shiftlefter.config itself — the svo/validate → shiftlefter.config row the earlier token grep produced was a docstring mention, not a require, and the require-only edge list does not carry it (the freshness guard pins that row as its live anti-flap fixture).

Verdict

Sane. No dependency cycles (the Clojure loader would refuse them), the hub shape is as intended (invocation surfaces fan into runner; runner orchestrates the engine; the engine consumes the language layer), gherkin is a clean leaf (law 3 passes with zero violations).

Method

The raw edge list, source-ns -> target-ns, from the repo root:

clojure -M -e "(require 'shiftlefter.arch-doc.freshness)
  (run! (fn [[a b]] (println a \"->\" b))
        (shiftlefter.arch-doc.freshness/require-edges \"src/shiftlefter\"))"

The extraction (src/shiftlefter/arch_doc/freshness.clj) reads each file’s ns form as data and walks its (:require …) specs (bare symbols, [lib :as x] vectors, prefix lists), keeping shiftlefter.* targets — so a docstring or comment that names a namespace is not an edge. The same code runs inside the test suite (shiftlefter.arch-doc.freshness-test), which compares the live edge count to the bold stamp in the header above and fails naming the remedy: update the stamp (count and date), re-run the extraction, regenerate this page. The earlier method — a grep -o "shiftlefter\.[a-z0-9.-]*" over each file — counted qualified names in prose and each file’s own ns line too; its 525 rows (2026-08-26) against 314 real edges (2026-08-30) is that noise, not a relocation, and it is retired so a re-stamp cannot quote it again.

Cluster-level aggregate: awk the first path segment of each side, count distinct arrows. Re-run after any relocation to verify the arrows moved.

How this page stays true

This page is a mechanical projection of a live-maintained internal source, regenerated — never hand-edited — by the derivation pipeline and drift-guarded by the test suite: a hand edit here fails a test, and so does a page that no longer matches its source.