diff --git a/README.md b/README.md index 5d4849a..233e0c0 100644 --- a/README.md +++ b/README.md @@ -8,8 +8,8 @@ **Current Version:** 0.0.1 — no release has been made. -PyFlow has completed **Stage 3 (Numerical Engine)** and is about to -begin Stage 4 (First Numerical Methods). Stage 0 built the engineering +PyFlow has completed **Stage 4 (First Numerical Methods)** and has not +yet begun Stage 5 (First Fluid Solver). Stage 0 built the engineering foundations; Stage 1 added the first real engine code -- a `CoordinateSystem`, a `Mesh` with a structured Cartesian implementation, and a mesh visualiser you can zoom and pan; Stage 2 added `Field` and @@ -18,11 +18,18 @@ them visible; Stage 3 added the six `adr/ADR-003-modular-numerical- strategies.md` interfaces (advection, diffusion, time integration, pressure-velocity coupling, linear solver, boundary condition) and the configuration/assembly mechanism that resolves a configured name to a -real instance. There is still no *physics*: the interfaces exist and -assemble, but nothing under `src/` computes anything real yet -- that is -Stage 4. See `docs/planning/roadmap.md` for the per-task status and each -stage's exit audit, and `docs/implementation/golden-demos.md` for what -each stage's demonstration proves. +real instance, with every interface still resolving only to a trivial, +non-physical reference implementation; Stage 4 gave each of those six +interfaces its first real, physically meaningful implementation +(`FirstOrderUpwindAdvection`, `CentralDifferenceDiffusion`, `RK4Integrator`, +`ConjugateGradientSolver`, `PISO`, `Dirichlet`/`Neumann`/periodic boundary +conditions) and, with them, PyFlow's first live-stepping simulation -- +see the Passive Scalar Transport demo below. Stage 5 is what solves +incompressible flow for real (a coupled velocity/pressure system); +today's numerical schemes are individually real but not yet assembled +into that solve. See `docs/planning/roadmap.md` for the per-task status +and each stage's exit audit, and `docs/implementation/golden-demos.md` +for what each stage's demonstration proves. The project's primary objective is to build a reusable fluid simulation engine while documenting every significant engineering decision along the way. @@ -74,7 +81,7 @@ make install # creates .venv, installs dependencies, installs the git pre-comm Then: ```bash -make demo # opens the render window -- press Escape/Enter or close the window to exit (no simulation yet) +make demo # opens the render window with the built-in default config -- press Escape/Enter or close the window to exit (no simulation configured by default; see "Current Phase" below for one that steps live) make test # runs the test suite, with a coverage report make lint # formats and lints code and docs (see the Makefile's own comment for exactly what runs) make ci # the full sequence CI runs on every push and pull request -- lint, typecheck, test, and the documentation/graph/inventory/manifest checks; see CLAUDE.md for what each one covers @@ -117,9 +124,10 @@ need to find it. ## Current Phase -Stage 4 — First Numerical Methods. +Stage 5 — First Fluid Solver -- not yet started (Stage 4 closed +2026-08-28, PR #38). -Stages 0 through 3 are complete, each closed against its own written +Stages 0 through 4 are complete, each closed against its own written completion criteria (`docs/planning/roadmap.md`): - Stage 0 — planning system, capability map, repository structure, @@ -140,14 +148,23 @@ completion criteria (`docs/planning/roadmap.md`): interface resolves only to a trivial, non-physical reference implementation, an explicit exception recorded against that stage's own completion criteria. +- Stage 4 — each of Stage 3's six interfaces gets its first real, + physically meaningful implementation (`FirstOrderUpwindAdvection`, + `CentralDifferenceDiffusion`, `RK4Integrator`, `ConjugateGradientSolver`, + `PISO`, and Dirichlet/Neumann/periodic boundary conditions), plus the + simulation-stepping mechanism that drives a live `pyflow run` + (`engine/simulation.py`), demonstrated in the Passive Scalar Transport + golden demo. Individually real numerics, not yet the coupled + velocity/pressure solve -- that's Stage 5. -Stage 4 gives each of Stage 3's interfaces its first real, physically -meaningful implementation. +Stage 5 will solve incompressible flow: a coupled velocity/pressure +system built from Stage 4's now-real numerical schemes. -Try the most recent demonstration: +Try the most recent demonstration -- a scalar blob advected and +diffused across a periodic domain, stepped live: ```bash -uv run python -m pyflow run --config examples/golden-demos/field_display.yaml +uv run python -m pyflow run --config examples/golden-demos/passive_scalar_transport.yaml ``` --- diff --git a/docs/planning/roadmap.md b/docs/planning/roadmap.md index 0759b84..bf7a8ad 100644 --- a/docs/planning/roadmap.md +++ b/docs/planning/roadmap.md @@ -189,7 +189,7 @@ This paragraph previously said `make install` and `make test` were still expected to fail, pending `uv.lock` and a test suite (B2/C1) -- stale since 2026-08-16 and corrected 2026-08-19. Both now succeed: `uv.lock` is committed (B2) and `make test` runs the suite with coverage -(C1a/C1b): **603 tests at 99% as of 2026-08-28**, having been 64 when +(C1a/C1b): **605 tests at 99% as of 2026-08-28**, having been 64 when this paragraph was rewritten on 2026-08-19, 202 earlier the same day, 212 after TASK-014, 226 after TASK-015, 250 after TASK-016, 287 after TASK-017, 297 after TASK-039, 315 after the Stage 2 exit audit and 337 @@ -371,8 +371,20 @@ The rest of the climb to 508 that same day is suite -- growing from 23 to 35 tests as that tool itself grew, a live demonstration that this count moves for reasons having nothing to do with the fluid solver and everything to do with why it needs checking -rather than re-reading. **53 of those 603 are Gherkin scenarios -rather than pytest functions** +rather than re-reading. The same is true again, 2026-08-28: 603 to 605 +from a CLI help-message accuracy fix, no Stage 4/5 task work involved -- +`pyflow --help`'s top-level text still described the CLI as a "Stage 0 +skeleton -- no simulation functionality yet" and never mentioned +`--config` or how to run a golden demo, caught by a user auditing the +CLI directly. Fixed in `src/pyflow/__main__.py`'s `description`/`epilog` +(phrased by capability now, not roadmap stage number, so it survives a +stage exit unedited), with two new tests holding it there -- +`test_top_level_help_describes_current_capabilities` +(`tests/unit/test_main.py`) and its subprocess-boundary mirror in +`tests/integration/test_cli.py` -- and a dated rule in +`src/pyflow/CLAUDE.md` requiring this text be revisited whenever a +subcommand, flag, or golden demo changes. **53 of those 605 are Gherkin +scenarios rather than pytest functions** (`adr/ADR-007-executable-acceptance-criteria.md`; up from fourteen with `field_display.feature` gaining scenarios and `numerics_assembly.feature` joining, TASK-021; to 24 with TASK-040's own diff --git a/docs/planning/status.md b/docs/planning/status.md index 6e8143d..e706d12 100644 --- a/docs/planning/status.md +++ b/docs/planning/status.md @@ -40,7 +40,7 @@ pie showData ## Live repository facts - **45** `CLAUDE.md` files -- **603** tests collected +- **605** tests collected - **53** Gherkin scenarios (`tests/features/*.feature`) ## Stages diff --git a/docs/practices.md b/docs/practices.md index 30d6b7d..1d13f96 100644 --- a/docs/practices.md +++ b/docs/practices.md @@ -1115,6 +1115,32 @@ where a command already explains itself when run (e.g. `make clean` stating what it can't remove and why), point at running it rather than restating its output in the README. +**The same obligation extends to `README.md`'s Project Status and +Current Phase sections** (added 2026-08-28, maintainer's instruction, +after Stage 4's own closing PR (#38) merged and neither section was +updated in the same change -- both still described the project as +mid-Stage-3/about to begin Stage 4, and pointed a new reader at Stage +2's own demo as "the most recent" one to try, discovered when a user +asked for an unrelated CLI fix and read the README while auditing what +else might be stale). These sections exist for the audience the Quick +Start rule above already names -- specifically, "relevant information a +potential user would want to see": whoever lands on this README to find +out what PyFlow can currently do, not only how to install it. Whenever a +stage opens or closes -- Completion Criteria met, exit audit written, +`roadmap.md`'s own status line updated -- update `README.md`'s Project +Status paragraph, Current Phase section, and its "try the most recent +demonstration" pointer in the same change; this is a Blast Radius +consequence of closing a stage, not a separate documentation pass to +remember later. Stale status here is worse than stale Quick Start +instructions: it doesn't just inconvenience a contributor who already +knows what the project does, it actively misinforms a first-time reader +about what they are looking at. **Found stale, fix it -- don't only +report it**: if a session doing unrelated work notices README.md (or any +other document) stating something the repository no longer does, correct +it in the same change rather than leaving a note for later, the same +standard `docs/CLAUDE.md`'s Validation section already sets for a +principle violation. + The Definition of Done for documentation lives in `docs/documentation-guidelines.md` and is not restated elsewhere. diff --git a/docs/repository-manifest.md b/docs/repository-manifest.md index 736d789..afbd8dc 100644 --- a/docs/repository-manifest.md +++ b/docs/repository-manifest.md @@ -585,7 +585,9 @@ stage boundary, not only when something here is being edited. `tests/` with `unit/`, `integration/`, `golden/`, `performance/`. -🟨 — 56 test modules, **603 tests, 99% coverage** (2026-08-28; 35 of +🟨 — 56 test modules, **605 tests, 99% coverage** (2026-08-28; two of +those from a same-day CLI help-message accuracy fix in existing modules +`test_main.py`/`test_cli.py`, no new module; 35 of these being `test_generate_status_report.py` itself; the 47th module, `test_simulation.py` (TASK-040), was the first Gherkin feature file bound outside `tests/golden/` -- not a golden demo, so it lives here per diff --git a/src/pyflow/CLAUDE.md b/src/pyflow/CLAUDE.md index 8114cc7..e1d9506 100644 --- a/src/pyflow/CLAUDE.md +++ b/src/pyflow/CLAUDE.md @@ -57,6 +57,37 @@ machinery, not a phenomenon. Keep that line; the moment a discretisation lands in `physics/` or a phenomenon in `numerics/`, the distinction stops paying for itself. +**The CLI's own help text (`__main__.py`'s top-level `description`/ +`epilog`, plus `run_parser`'s own `epilog`) must be kept current with +what PyFlow can actually do** (rule added 2026-08-28, prompted by a user +noticing it wasn't: the top-level `description` still read "Stage 0 +skeleton -- no simulation functionality yet" through the entirety of +Stage 4 (TASK-023 through TASK-030, 2026-08-27..28) landing real +numerics, PISO, and a live-stepping golden demo -- stale from the first +of those, not from some distant point; PyFlow moves fast enough that a +day of silence is enough for this text to go wrong, so "recently" is not +a reason to skip re-reading it. Neither `--config` nor how to run a +golden demo was mentioned anywhere the bare-invocation or top-level +`--help` output would show them -- +argparse only surfaces a subcommand's own flags under that subcommand's +own `--help`, so `run_parser`'s `--config` help text was never enough on +its own). Concretely: whenever a subcommand or flag is added, removed, +or renamed, or a new golden demo lands under `examples/golden-demos/`, +re-read `__main__.py`'s `description`/`epilog` text in the same change +and update it if it no longer matches -- this is the Blast Radius rule +applied to the CLI's own self-description, not a separate obligation. +Phrase the top-level `description` in terms of what the CLI can *do*, +never by roadmap stage number, so a stage exit that changes nothing +about the CLI itself never forces an edit here. **This is enforced by a +test, not only remembered**: `tests/unit/test_main.py`'s +`test_top_level_help_describes_current_capabilities` (mirrored in +`tests/integration/test_cli.py` across the real subprocess boundary) +asserts concrete current content (`--config`, `examples/golden-demos`) +is present and the stale `"Stage 0"` claim is not, rather than only the +structural markers (`usage:`, `-h, --help`) the original C1a test +already checked -- so a forgotten or reverted update fails `make test` +instead of depending on a reviewer noticing. + **`__main__.py`'s second subcommand, `pyflow generate-config [--output PATH]` (TASK-039, added 2026-08-21)**, does not orchestrate multiple subpackages the way `bootstrap()` does -- it is a thin argparse wrapper diff --git a/src/pyflow/__main__.py b/src/pyflow/__main__.py index aefaf1d..6b34859 100644 --- a/src/pyflow/__main__.py +++ b/src/pyflow/__main__.py @@ -13,6 +13,17 @@ scaffold to stdout, or writes it to `--output PATH` if given -- so a config author starts from something `load_config` already accepts rather than hand-typing section and field names from memory. + +The top-level parser's own `description`/`epilog` (below) is the CLI's +self-description, printed both by bare invocation and by `--help`. +**It must be kept current with what the CLI can actually do** -- see +`src/pyflow/CLAUDE.md`'s dated rule, added 2026-08-28 after this text +spent well past Stage 0 still claiming "no simulation functionality +yet" and never mentioning `--config` or how to run a golden demo at +all (argparse does not surface a subcommand's own flags at the +top level, so `run_parser`'s `--config` help text alone was never +enough). `description` is phrased by capability, not by roadmap stage +number, so it does not need editing every stage exit. """ import argparse @@ -37,9 +48,32 @@ def main(argv: list[str] | None = None) -> None: """ parser = argparse.ArgumentParser( prog="pyflow", - description="PyFlow: a modular, field-centric computational fluid " - "dynamics engine. Stage 0 skeleton -- no simulation " - "functionality yet.", + description=( + "PyFlow: a modular, field-centric computational fluid dynamics\n" + "engine. Configure a mesh, boundary conditions, and numerical\n" + "scheme in YAML, and PyFlow will assemble, run, and visualise\n" + "the simulation." + ), + epilog=( + "examples:\n" + " pyflow run\n" + " Run with the built-in default configuration.\n" + " pyflow run --config path/to/config.yaml\n" + " Run with your own configuration file.\n" + " pyflow run --config examples/golden-demos/.yaml\n" + " Run one of the golden demos shipped under " + "examples/golden-demos/\n" + " (see docs/implementation/golden-demos.md for what each " + "one shows).\n" + " pyflow generate-config --output config.yaml\n" + " Write a valid starting configuration file, ready to " + "edit.\n" + "\n" + "Run 'pyflow --help' for a command's own options -- " + "e.g. 'pyflow run --help'\n" + "for --config, --max-frames, and --backend." + ), + formatter_class=argparse.RawDescriptionHelpFormatter, ) subparsers = parser.add_subparsers(dest="command") @@ -47,6 +81,12 @@ def main(argv: list[str] | None = None) -> None: "run", help="Bootstrap the engine: load configuration, initialise logging, " "open the rendering window, and run until it's closed.", + epilog=( + "example:\n" + " pyflow run --config examples/golden-demos/.yaml " + "--backend offscreen --max-frames 100\n" + ), + formatter_class=argparse.RawDescriptionHelpFormatter, ) run_parser.add_argument( "--config", diff --git a/tests/integration/test_cli.py b/tests/integration/test_cli.py index 882b7ca..ecc69b5 100644 --- a/tests/integration/test_cli.py +++ b/tests/integration/test_cli.py @@ -27,6 +27,23 @@ def test_entry_point_prints_version_and_help() -> None: assert "-h, --help" in result.stdout +def test_entry_point_help_mentions_config_flag_and_golden_demos() -> None: + """Real packaged-entry-point companion to + `tests/unit/test_main.py::test_top_level_help_describes_current_capabilities` + -- the same content, verified crossing the real subprocess boundary. + """ + result = subprocess.run( + [sys.executable, "-m", "pyflow", "--help"], + capture_output=True, + text=True, + check=False, + ) + + assert result.returncode == 0 + assert "--config" in result.stdout + assert "examples/golden-demos" in result.stdout + + def test_generate_config_prints_valid_yaml_to_stdout() -> None: """`pyflow generate-config` with no arguments (TASK-039): a real subprocess, per this project's CLI-testing convention, checking diff --git a/tests/unit/test_main.py b/tests/unit/test_main.py index 8b7b6cd..dd90b8f 100644 --- a/tests/unit/test_main.py +++ b/tests/unit/test_main.py @@ -28,6 +28,24 @@ def test_no_args_prints_version_and_help(capsys: pytest.CaptureFixture[str]) -> assert "-h, --help" in captured.out +def test_top_level_help_describes_current_capabilities( + capsys: pytest.CaptureFixture[str], +) -> None: + """The top-level help text must reflect what PyFlow can actually do + (`src/pyflow/CLAUDE.md`'s "help text must stay current" rule) -- it + previously described the CLI as a "Stage 0 skeleton -- no simulation + functionality yet" long after real numerics and golden demos landed, + and never mentioned `--config` or how to run one at all, since + `--config` lives on the `run` subcommand's own help and argparse + does not surface a subcommand's flags in the top-level listing. + """ + main([]) + captured = capsys.readouterr() + assert "Stage 0" not in captured.out + assert "--config" in captured.out + assert "examples/golden-demos" in captured.out + + def test_run_dispatches_to_bootstrap_with_parsed_args() -> None: with patch("pyflow.__main__.bootstrap") as mock_bootstrap: main(