commit 447f298ac526cce4995cf171c9313d8a3b80a49a Author: Hassan Abedi Date: Thu May 21 12:28:09 2026 +0200 The base commit diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..e5be459 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,22 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_style = space +indent_size = 4 +insert_final_newline = true +trim_trailing_whitespace = true + +[*.rs] +max_line_length = 100 + +[*.md] +max_line_length = 150 +trim_trailing_whitespace = false + +[*.{sh,nix,flake}] +indent_size = 2 + +[*.{yaml,yml,json}] +indent_size = 2 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..afc5177 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,38 @@ +* text=auto eol=lf + +*.go text +*.mod text +*.sum text +*.md text +*.rst text +*.json text +*.yaml text +*.yml text +*.toml text +*.sh text eol=lf +*.html text +*.css text +*.js text +*.svg text +*.xml text + +*.png filter=lfs diff=lfs merge=lfs -text +*.jpg filter=lfs diff=lfs merge=lfs -text +*.jpeg filter=lfs diff=lfs merge=lfs -text +*.gif filter=lfs diff=lfs merge=lfs -text +*.ico filter=lfs diff=lfs merge=lfs -text +*.mp4 filter=lfs diff=lfs merge=lfs -text +*.mov filter=lfs diff=lfs merge=lfs -text +*.zip filter=lfs diff=lfs merge=lfs -text +*.tar filter=lfs diff=lfs merge=lfs -text +*.gz filter=lfs diff=lfs merge=lfs -text +*.tgz filter=lfs diff=lfs merge=lfs -text +*.ttf filter=lfs diff=lfs merge=lfs -text +*.woff filter=lfs diff=lfs merge=lfs -text +*.woff2 filter=lfs diff=lfs merge=lfs -text +*.exe filter=lfs diff=lfs merge=lfs -text +*.dll filter=lfs diff=lfs merge=lfs -text +*.so filter=lfs diff=lfs merge=lfs -text +*.out filter=lfs diff=lfs merge=lfs -text +*.a filter=lfs diff=lfs merge=lfs -text +*.o filter=lfs diff=lfs merge=lfs -text diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d0bfb94 --- /dev/null +++ b/.gitignore @@ -0,0 +1,86 @@ +# Python specific +__pycache__/ +*.py[cod] +*$py.class + +# Virtual environments +.env/ +env/ +.venv/ +venv/ + +# Packaging and distribution files +.Python +build/ +dist/ +*.egg-info/ +*.egg +MANIFEST + +# Dependency directories +develop-eggs/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +.installed.cfg + +# Test and coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ +.pytest_cache/ + +# IDE specific files and directories +.idea/ +*.iml +.vscode/ + +# Jupyter Notebook files +.ipynb_checkpoints + +# Temporary files created by editors and the system and folders to ignore +*.swp +*~ +*.bak +*.tmp +temp/ +tmp/ + +# Database files (SQLite, DuckDB, etc.) +*.duckdb +*.db +*.wal +*.sqlite + +# Dependency lock files (uncomment to ignore) +poetry.lock + +# Rust specific +/target/ +.cargo-ok +cobertura.xml +tarpaulin-report.html + +# Comment out the next line if you want to checkin your lock file for Cargo +Cargo.lock + +# Misc +*.proptest-regressions +.DS_Store +.benchmarks +.env +.claude/ +.codex +.agents/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..60a3d84 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,44 @@ +default_stages: [ pre-commit ] +fail_fast: false +exclude: '^\.idea/' + +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 + hooks: + - id: trailing-whitespace + args: [ --markdown-linebreak-ext=md ] + - id: end-of-file-fixer + - id: mixed-line-ending + - id: check-merge-conflict + - id: check-added-large-files + - id: detect-private-key + - id: check-yaml + - id: check-toml + - id: check-json + + - repo: local + hooks: + - id: nix-fmt-check + name: Check Nix Formatting + entry: make fmt-check + language: system + pass_filenames: false + files: \.nix$ + stages: [ pre-commit ] + + - id: nix-lint + name: Lint Nix Files + entry: make lint + language: system + pass_filenames: false + files: \.nix$ + stages: [ pre-commit ] + + - id: nix-flake-check + name: Run Nix Flake Checks + entry: make check + language: system + pass_filenames: false + files: \.(nix|lock)$ + stages: [ pre-push ] diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..205e1e6 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,183 @@ +# AGENTS.md + +This file provides guidance to coding agents collaborating on this repository. + +## Mission + +`nix-playground` is a personal learning playground for Nix and flakes. +The goal is not production software but clear, runnable, progressively more advanced examples plus prose notes that explain them. + +Priorities, in order: + +1. Correctness: examples must actually evaluate and build. +2. Clarity: each example teaches one concept; names, comments, and directory structure should make that concept obvious. +3. Minimality: prefer the shortest flake or expression that demonstrates the idea. +4. Accuracy of notes: prose under `notes/` must not describe behavior the examples do not demonstrate. +5. Reproducibility: every flake commits its `flake.lock`; nothing depends on ambient state. + +## Core Rules + +- Use English for code, comments, and prose. +- Keep each numbered example self-contained: its own `flake.nix`, own `flake.lock`, no cross-example imports. +- Prefer small, focused changes over broad rewrites across examples. +- Add comments only when they clarify non-obvious Nix behavior (laziness, `rec`, string vs. path, `with` scoping, etc.). +- Do not describe Nix features in notes or comments as if they were implemented in an example unless the example actually uses them. +- When an example grows beyond one concept, split it into a new numbered directory rather than expanding the existing one. + +Quick examples: + +- Good: add `03-multi-system/` that demonstrates `forAllSystems` in isolation. +- Good: add a `checks` output to an existing flake with a one-line comment explaining what `nix flake check` will do with it. +- Bad: combine overlays, NixOS modules, and home-manager into one "comprehensive" example. +- Bad: edit `notes/` to describe an approach no example in the repo uses. + +## Writing Style + +- Use Oxford commas in inline lists: "a, b, and c" not "a, b, c". +- Do not use em dashes. Restructure the sentence, or use a colon or semicolon instead. +- Avoid colorful adjectives and adverbs. Write "dev shell" not "lightweight dev shell", "overlay" not "flexible overlay". +- Use noun phrases for checklist items, not imperative verbs. Write "input pinning" not "pin inputs". +- Headings in Markdown files must be in title case: "Build from Source" not "Build from source". Minor words (a, an, the, and, but, or, for, in, on, + at, to, by, of) stay lowercase unless they are the first word. + +## Repository Layout + +- `NN-/`: self-contained numbered examples. Each matching top-level directory is a flake root. +- `notes/NNN-*.md`: prose companions numbered to match reading order. + - Lower note numbers cover shared foundations such as the glossary, the Nix language, and flakes. + - Later note numbers may cover specific example tracks or cross-example guides. +- `Makefile`: discovery-based helpers that run formatting, linting, and `nix flake check` across all examples. +- `AGENTS.md`: this file. +- `.pre-commit-config.yaml`, `.editorconfig`, `.gitattributes`, `.gitignore`: repository hygiene. +- `pyproject.toml`: Python environment metadata used only to install `pre-commit`. + +New examples follow `NN-/` where `NN` is a two-digit ordinal. + +Do not assume the directory list or note list in this file is exhaustive. The repository is expected to grow over time, and agents should discover the +current layout from the filesystem when needed. + +Example ordering should still feel like a readable path from simpler to more involved, but the repository may branch into themed subtracks, for example +Nix and flake outputs, Haskell with Nix, or future ecosystem-specific tracks. Keep each example focused on one concept even when the broader sequence +branches. + +## Example Layout Constraints + +- Each example owns exactly one `flake.nix` at its root and commits its `flake.lock`. +- Examples do not import each other. Copy and adapt if a pattern needs to be shown twice. +- An example may depend only on flakes it declares in its own `inputs`. +- Prefer `nixpkgs` pinned to `nixos-unstable` for consistency across examples unless the example's point is pinning strategy. +- Keep the `outputs` attrset flat enough that `nix flake show` reads as a single screen. +- If an example exposes `checks..*`, those checks must pass under `nix flake check`. + +## Nix and Flake Conventions + +- Target Nix with `experimental-features = nix-command flakes` enabled (already the case on this machine). +- Prefer `pkgs.mkShell` for dev shells; reach for `mkShellNoCC` only when explaining the distinction. +- Use `nixpkgs.lib.genAttrs` or `flake-utils.lib.eachDefaultSystem` for multi-system outputs; pick one per example and say which in a comment. +- Use `follows` to unify transitive `nixpkgs` inputs when pulling in ecosystem flakes. +- Prefer `inherit` over repetition in attrsets. +- Avoid top-level `with` statements; keep `with` narrowly scoped to package lists. +- Format every `.nix` file with `nixfmt` (RFC 166 style) before committing. + +## Required Validation + +Run these checks for any non-trivial change: + +1. `make fmt-check` +2. `make lint` +3. `make check` + +These map to `nixfmt --check`, `statix check` plus `deadnix`, and `nix flake check` across every numbered example. + +For notes-only changes, `make fmt-check` and a manual read-through suffice. + +## First Contribution Flow + +Use this sequence for your first change: + +1. Read the relevant `notes/` file and the nearest existing example. +2. Add the smallest possible flake or expression demonstrating the new concept. +3. Add a short header comment in the new `flake.nix` stating what the example teaches. +4. Run `nix flake check` inside the new example directory. +5. Run `make fmt-check` and `make lint` from the repository root. +6. Add or update the matching entry in `notes/` if the concept is not yet covered there. + +Example scopes that are good first tasks: + +- Add `02-package/` with a trivial `stdenv.mkDerivation` and one-line install phase. +- Add a `checks` output to `01-devshell/` that asserts a tool is on `$PATH`. +- Add a short section to `notes/003-flakes.md` referencing a newly added example. +- Convert an existing example from a hand-rolled `forAllSystems` to `flake-utils`, or vice versa, with a comment explaining the tradeoff. + +When the repository contains multiple themed tracks, "the nearest existing example" means the nearest example in the relevant track, not necessarily the +numerically closest directory overall. + +## Testing Expectations + +- This repository has no runtime test suite; "tests" are `nix flake check` outcomes and successful builds of each example's default output. +- Any example that exposes non-trivial behavior (a derivation, a module) should expose a `checks..*` attribute that `nix flake check` + exercises. +- Do not merge changes that regress `make check`. + +## Change Design Checklist + +Before coding: + +1. Identify which existing example or notes file the change belongs to, or whether it needs a new `NN-/`. +2. Confirm the change teaches one concept, not several. +3. Confirm `nixpkgs` input choice is consistent with surrounding examples. + +Before submitting: + +1. Verify `make fmt-check`, `make lint`, and `make check` pass. +2. Verify every modified flake's `flake.lock` is committed. +3. Verify `notes/` accurately reflects what the examples now demonstrate. + +## Review Guidelines (P0/P1 Focus) + +Review output should be concise and only include critical issues. + +- `P0`: must-fix defects (a flake fails to evaluate, an example documents the wrong mechanism, notes contradict the code). +- `P1`: high-priority defects (eval warnings, missing `flake.lock`, unpinned or inconsistent inputs, misleading comment). + +Do not include: + +- style-only nitpicks, +- praise or summary of what is already good, +- exhaustive restatement of the patch. + +Use this review format: + +1. `Severity` (`P0`/`P1`) +2. `File:line` +3. `Issue` +4. `Why it matters` +5. `Minimal fix direction` + +## Practical Notes for Agents + +- Prefer targeted edits over broad mechanical rewrites across examples. +- If two examples disagree on a convention, prefer the newer one and update the older example in a dedicated commit. +- When uncertain whether a concept deserves its own example, start by expanding the notes; promote to an example once the idea stabilizes. +- Keep presentational prose in `notes/`. Keep runnable material in numbered directories. Do not cross the streams. +- Prefer layout guidance based on naming patterns and discovery, not hard-coded counts of examples or notes. If you need the current tree, inspect it. +- Keep user-facing naming consistent with the repository name: `nix-playground`. The directory spelling `nix-playgraound` is intentional and should + not be "fixed". + +## Commit and PR Hygiene + +- Keep commits scoped to one logical change: one example, one notes update, one convention shift. +- Commit `flake.lock` in the same commit that introduces or updates the `flake.nix` it belongs to. +- PR descriptions should include: + 1. what concept the change teaches or clarifies, + 2. which example directories or notes files are affected, + 3. any new `inputs` added and why, + 4. output of `make check` (pass/fail). + +Suggested PR checklist: + +- [ ] `make fmt-check` passes +- [ ] `make lint` passes +- [ ] `make check` passes +- [ ] `flake.lock` committed for every new or updated `flake.nix` +- [ ] Notes updated where the change introduces or changes a concept diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..2bde144 --- /dev/null +++ b/Makefile @@ -0,0 +1,133 @@ +# Variables +# Every numbered directory at the repository root is treated as a self-contained flake example. +EXAMPLES := $(sort $(wildcard [0-9][0-9]-*)) + +# Tools are invoked via `nix run` so nothing needs to be pre-installed beyond Nix itself. +# Override any of these if you have the tools on $PATH and want faster startup. +NIX ?= nix +NIXFMT ?= $(NIX) run nixpkgs\#nixfmt -- +STATIX ?= $(NIX) run nixpkgs\#statix -- +DEADNIX ?= $(NIX) run nixpkgs\#deadnix -- + +# Selector for single-example targets (dev, show, update-one). +EXAMPLE ?= + +# Default target +.DEFAULT_GOAL := help + +.PHONY: help +help: ## Show help messages for all available targets + @grep -E '^[a-zA-Z_-]+:.*## .*$$' Makefile | \ + awk 'BEGIN {FS = ":.*## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' + +.PHONY: examples +examples: ## List discovered example directories + @echo "Examples:" + @for e in $(EXAMPLES); do echo " - $$e"; done + +.PHONY: fmt +fmt: ## Format every .nix file with nixfmt (RFC 166 style) + @echo "Formatting .nix files..." + @find . -type f -name '*.nix' -not -path './.git/*' -print0 | xargs -0 -r $(NIXFMT) + +.PHONY: fmt-check +fmt-check: ## Check formatting of every .nix file (non-zero exit on drift) + @echo "Checking .nix formatting..." + @find . -type f -name '*.nix' -not -path './.git/*' -print0 | xargs -0 -r $(NIXFMT) --check + +.PHONY: lint +lint: ## Run statix and deadnix against every example + @echo "Running statix..." + @$(STATIX) check . + @echo "Running deadnix..." + @$(DEADNIX) --fail . + +.PHONY: fix-lint +fix-lint: ## Apply statix and deadnix autofixes where possible + @echo "Applying statix fixes..." + @$(STATIX) fix . + @echo "Applying deadnix fixes..." + @$(DEADNIX) --edit . + +.PHONY: check +check: ## Run `nix flake check` in every example directory + @if [ -z "$(EXAMPLES)" ]; then echo "No examples found."; exit 0; fi + @set -e; for e in $(EXAMPLES); do \ + echo ">>> nix flake check $$e"; \ + (cd "$$e" && $(NIX) flake check); \ + done + +.PHONY: build +build: ## Run `nix build` on the default package of every example (skips those without one) + @if [ -z "$(EXAMPLES)" ]; then echo "No examples found."; exit 0; fi + @set -e; for e in $(EXAMPLES); do \ + if $(NIX) eval --no-warn-dirty "$$e#packages.$$($(NIX) eval --impure --raw --expr 'builtins.currentSystem').default" --apply 'x: true' >/dev/null 2>&1; then \ + echo ">>> nix build $$e"; \ + (cd "$$e" && $(NIX) build --no-link); \ + else \ + echo "--- $$e has no default package, skipping"; \ + fi; \ + done + +.PHONY: show +show: ## Print `nix flake show` for every example + @if [ -z "$(EXAMPLES)" ]; then echo "No examples found."; exit 0; fi + @for e in $(EXAMPLES); do \ + echo ">>> nix flake show $$e"; \ + (cd "$$e" && $(NIX) flake show) || true; \ + done + +.PHONY: update +update: ## Run `nix flake update` in every example (updates all flake.lock files) + @if [ -z "$(EXAMPLES)" ]; then echo "No examples found."; exit 0; fi + @set -e; for e in $(EXAMPLES); do \ + echo ">>> nix flake update $$e"; \ + (cd "$$e" && $(NIX) flake update); \ + done + +.PHONY: update-one +update-one: ## Run `nix flake update` in EXAMPLE= + @if [ -z "$(EXAMPLE)" ]; then echo "Usage: make update-one EXAMPLE="; exit 1; fi + @(cd "$(EXAMPLE)" && $(NIX) flake update) + +.PHONY: dev +dev: ## Enter `nix develop` in EXAMPLE= + @if [ -z "$(EXAMPLE)" ]; then echo "Usage: make dev EXAMPLE="; exit 1; fi + @(cd "$(EXAMPLE)" && $(NIX) develop) + +.PHONY: metadata +metadata: ## Print `nix flake metadata` for every example + @for e in $(EXAMPLES); do \ + echo ">>> nix flake metadata $$e"; \ + (cd "$$e" && $(NIX) flake metadata) || true; \ + done + +.PHONY: clean +clean: ## Remove `result` symlinks produced by `nix build` + @echo "Removing result symlinks..." + @find . -maxdepth 3 -type l \( -name 'result' -o -name 'result-*' \) -print -delete + +.PHONY: gc +gc: ## Run `nix store gc` to garbage collect unreferenced store paths + @echo "Running nix store gc..." + @$(NIX) store gc + +.PHONY: setup-hooks +setup-hooks: ## Install Git hooks (pre-commit) + @echo "Setting up Git hooks..." + @if ! command -v pre-commit >/dev/null 2>&1; then \ + echo "pre-commit not found. Install it via 'pip install pre-commit' or enter a shell that provides it."; \ + exit 1; \ + fi + @pre-commit install --hook-type pre-commit + @pre-commit install --hook-type pre-push + @pre-commit install-hooks + +.PHONY: test-hooks +test-hooks: ## Run pre-commit hooks across all files + @echo "Running pre-commit hooks on all files..." + @pre-commit run --all-files --show-diff-on-failure + +.PHONY: all +all: fmt-check lint check ## Run format check, lint, and flake checks + @echo "All checks passed." diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..db2050b --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,11 @@ +[project] +name = "storage-engine-playground" +version = "0.1.0" +description = "The Python environment for the `storage-engine-playground` project" + +requires-python = ">=3.10,<4.0" + +dependencies = [ + "pre-commit (>=4.2.0,<5.0.0)", + "icecream (>=2.1.4,<3.0.0)", +]