From 73ff1137cf5392d05493901a476e4720600bb111 Mon Sep 17 00:00:00 2001 From: Adam Clemens Date: Fri, 28 Aug 2026 11:11:29 +0100 Subject: [PATCH 1/2] Fix stale CLI help text and add a rule to keep it current pyflow --help / bare `pyflow` claimed "Stage 0 skeleton -- no simulation functionality yet" long after Stage 4 landed real numerics and a live-stepping golden demo, and never mentioned --config or how to run a golden demo anywhere the top-level help would show it. Rewrote the top-level description/epilog to describe current capability (phrased so it doesn't need editing every stage exit) and added a run_parser epilog example. Locked in with two new tests (test_main.py, test_cli.py) that assert the stale claim is gone and --config/examples/golden-demos are present, and a dated rule in src/pyflow/CLAUDE.md requiring this text be revisited whenever a subcommand, flag, or golden demo changes. Blast Radius: the two new tests brought the live count to 605, so roadmap.md's and repository-manifest.md's own restated test-count claims needed the same update make check-status exists to catch. --- docs/planning/roadmap.md | 18 +++++++++++--- docs/planning/status.md | 2 +- docs/repository-manifest.md | 4 ++- src/pyflow/CLAUDE.md | 27 ++++++++++++++++++++ src/pyflow/__main__.py | 46 ++++++++++++++++++++++++++++++++--- tests/integration/test_cli.py | 17 +++++++++++++ tests/unit/test_main.py | 18 ++++++++++++++ 7 files changed, 124 insertions(+), 8 deletions(-) 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/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..c2cf8d8 100644 --- a/src/pyflow/CLAUDE.md +++ b/src/pyflow/CLAUDE.md @@ -57,6 +57,33 @@ 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" long after Stage 4 landed +real numerics, PISO, and a live-stepping golden demo, and 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( From f93f206cc5bfec69f98833bfd0db71a15bacdbe3 Mon Sep 17 00:00:00 2001 From: Adam Clemens Date: Fri, 28 Aug 2026 11:26:56 +0100 Subject: [PATCH 2/2] Fix stale README status, add a rule to keep it current, correct a bad timeframe claim README.md's Project Status and Current Phase sections still said PyFlow "has completed Stage 3... and is about to begin Stage 4" and pointed a reader at Stage 2's Field Display demo as "the most recent" -- stale since Stage 4 closed (PR #38), caught while auditing the CLI help text for the same kind of staleness. Updated both sections to reflect Stage 4 being complete and Stage 5 not yet started, and repointed the "try the most recent demonstration" command at the Passive Scalar Transport demo. Added a rule in docs/practices.md: README's Project Status/Current Phase sections must be updated in the same change a stage opens or closes, same standing as the existing Quick Start rule, and stale docs found during unrelated work should be fixed on the spot rather than only flagged. Also corrects an inaccurate claim made in this same fix's own prior commit (src/pyflow/CLAUDE.md): the CLI help text was described as having gone stale "long after" Stage 4 landed, when in fact Stage 4 landed within about a day and the closing PR merged under an hour before that claim was written. Reworded to state the actual timeline. --- README.md | 45 ++++++++++++++++++++++++++++++-------------- docs/practices.md | 26 +++++++++++++++++++++++++ src/pyflow/CLAUDE.md | 12 ++++++++---- 3 files changed, 65 insertions(+), 18 deletions(-) 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/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/src/pyflow/CLAUDE.md b/src/pyflow/CLAUDE.md index c2cf8d8..e1d9506 100644 --- a/src/pyflow/CLAUDE.md +++ b/src/pyflow/CLAUDE.md @@ -61,10 +61,14 @@ paying for itself. `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" long after Stage 4 landed -real numerics, PISO, and a live-stepping golden demo, and neither -`--config` nor how to run a golden demo was mentioned anywhere the -bare-invocation or top-level `--help` output would show them -- +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,