Development¶
Everything here runs through Nix, which is the supported toolchain. The
supported release target is SBCL on x86_64-linux; see
Runtime Support for the capability and portability
boundaries.
Commands¶
nix develop # devShell: SBCL, coreutils, jq, libxml2, paredit-cli
nix flake check # tests + formatting + docs, the same gate CI uses
nix run .#test # the self-test suite through run-tests.lisp
nix run . -- --help # the packaged CLI
nix build .#docs # this site, with mkdocs --strict
nix fmt # format Nix sources (treefmt)
Running direnv allow loads the devShell automatically.
Running the suite¶
The full suite is checks.default, which runs the packaged CLI against the
cl-weave/test system:
For a focused run during development, filter by a path substring:
run-tests.lisp at the repository root is the implementation-level entry
point. Every nerima-lisp package exposes the same file, so tooling never has to
know how a given package loads its suite:
It loads cl-weave/test and calls run-all directly rather than going through
asdf:test-system, because the suite itself performs test-op on
cl-weave/test to exercise the ASDF integration's failure path, and ASDF
rejects that operation appearing twice in one plan.
asdf:test-system "cl-weave" remains supported for downstream consumers.
What nix flake check covers¶
nix flake check evaluates each checks.* attribute as its own derivation, in
parallel and with build caching. Granularity belongs here rather than in extra
CI jobs.
| Check | What it proves |
|---|---|
default |
The whole self-test suite passes through the packaged CLI. |
formatting |
Every tracked Nix file is formatted (treefmt + nixfmt). |
docs |
This site builds under mkdocs --strict, so no link is broken and no page is missing from the nav. |
filtered-smoke |
A filtered run fails rather than passes when it matches no tests. |
*-artifact, cli-json-results |
Each reporter emits an artifact matching its frozen schema. |
coverage-artifact |
The coverage run produces a report and clears its gates. |
paredit-lint |
Every S-expression source parses structurally. |
Add a check here rather than a job in ci.yml. A CI job is justified only for
work the Nix sandbox cannot do: network access, a real PTY, artifact upload, or
a different runner OS.
Coverage¶
nix run . -- run cl-weave/test \
--coverage \
--coverage-output cl-weave.coverage \
--coverage-report-directory cl-weave-coverage-report/ \
--coverage-system cl-weave
See Reporters and CI for the reporter formats and the coverage gate options.
Benchmarks¶
benchmarks/ holds the micro-benchmark harness for the runner hot paths. See
Benchmarking for the benchmark and measure helpers and
the timing statistics they report.
Structural edits¶
The devShell provides
paredit-cli for structural
S-expression edits, and its use is mandatory: any structural change to
src/*.lisp or t/*.lisp -- renaming a scoped symbol, moving a definition,
extracting or inlining a form, reshaping a binding list, a conditional, or a
call -- goes through paredit, never through hand-editing parentheses. The
paredit-lint flake check parses every source file, so an unbalanced form
fails nix flake check rather than surfacing as a confusing compile error.
paredit inspect duplicates and paredit inspect unused-definitions are
worth running over src/*.lisp (add t/*.lisp to the latter to rule out
symbols a test file references but no src/ file does) before merging a
change that touches more than one file: they catch copy-pasted definitions
and internal helpers nothing calls anymore. Neither tool understands a
symbol's package export status or usage in a downstream consumer, so treat
their output as candidates, not a worklist -- check src/package.lisp's
:export list and grep the whole tree before removing or merging anything
they flag.
paredit-lint's platform-guard detection is text-level, not
macroexpansion-aware: it recognizes a feature-dispatch pair such as
#+sbcl/#-sbcl or #+sb-thread/#-sb-thread only when both reader
conditionals are literally present at the call site (see the
#+sb-thread/#-sb-thread split in run-concurrent-test-cases,
src/runner-concurrency.lisp, and the #+sbcl blocks in
src/platform-sbcl.lisp). Do not hide one half of such a pair behind a macro
argument or a helper function -- even where a define-conditional-lock-macro
style macro could parameterize it -- because the linter would then see only
one branch and cannot verify the other implementation still has a path
through the code.
Source organization¶
Three conventions keep individual files from growing into an unreadable mix of constants, control flow, and callback plumbing as the codebase grows:
*-data.lispcompanion files. When a file'sdefvar/defparameter/defconstant/plaindefstructforms outgrow a handful of lines, move them to a same-named*-data.lispfile loaded immediately before it incl-weave.asd(runner-control-data.lispbeforerunner-control.lisp,mocks-data.lispbeforemocks.lisp, and so on for thecli-metadata-*andcli-options-data/reporter-schema-datapairs). Apply this only where it genuinely separates unrelated concerns -- a single smalldefstructtightly coupled to the one function that uses it, or a file with no top-level data forms at all, is better left alone.define-X-macroconsolidation. When the same defmacro or defun shape recurs with only a name and a couple of symbols differing (seedefine-conditional-lock-macroinplatform-sbcl.lisp, ordefine-runner-option-parser/define-passthrough-option-parserincli-options.lisp), extract a macro-generating macro rather than hand-copy the pattern again. Verify a macro-generating macro at the value it actually produces, not just that it compiles: asublis/nested-backquote mistake can macroexpand cleanly to code that never substitutes the caller's argument, and that only shows up in a runtime check that exercises the generated macro's actual effect./kcontinuation-passing helpers. A function namedname/ktakes its continuation as an explicit trailing argument or arguments instead of returning a value, and calls one of them rather than returning normally -- for examplecall-with-snapshot-comparison/kinsrc/snapshots.lisp, which takeson-matchandon-mismatchand funcalls whichever branch the comparison resolves to, orrun-test-attempt/kinsrc/runner-attempts.lispandcall-with-platform-timeout/kinsrc/platform-protocol.lisp, which take a singlecontinue. Prefer the pairedon-X/on-Ynaming (mirroringon-match/on-mismatch) when a helper has a small fixed number of outcomes each needing different handling at the call site, and a singlecontinuewhen there is one success path and failure propagates as a condition instead. The/ksuffix is the signal to a reader that the function does not return a useful value on its own -- calling it and ignoring the result is a bug, not a style choice.
Contributing¶
Keep a pull request focused on one problem, add or update tests for behavior changes, and state the commands you ran and any validation that could not run.
Public API, CLI, reporter or policy changes must update the machine-readable
metadata and the affected pages in the same pull request; the contract tests in
t/cli-metadata-*.lisp enforce most of that coupling. Discuss substantial API,
reporter, metadata or runtime changes in an issue before implementing them, and
read the project scope and
support policy first.
Do not report security vulnerabilities in a public issue. Use private GitHub security advisories.
The org-wide contributor guide and code of conduct live in the nerima-lisp/.github repository, and the review and merge expectations for this project are in Governance.