Repository navigation
Address review feedback: launcher safety, exact render output, cli test errors, examples in --help - #60
Conversation
The print_command case passed --version and never ran the generated command. It now runs the command and asserts its output; --version stays as its own case. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- A name given to rm, update, info, build and docs is validated before it is used in a path: ../x no longer selects metadata, modules or launchers outside of cliffy's directories. homer and Loader refuse such names themselves. - A launcher is only cliffy's when it is a regular file, not a link, whose text is the launcher of exactly that CLI. A file that merely contains 'from cliffy.clis.' is no longer overwritten or deleted. - Launchers and modules are written to a temporary file and renamed into place. A new launcher is created exclusively; links are never followed. - cli rm removes the launcher, the module and then the metadata record. When the launcher is not cliffy's, nothing is removed and the CLI stays listed and runnable. cli rm-all exits with 1 when a CLI could not be removed. - The symlink test skips where symbolic links cannot be created. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
run_cli and Tester appended their file to a process-wide TEMP_FILES list and then deleted everything on it, so a run that finished removed the file a run on another thread was about to import. The generated code is now imported from a TemporaryDirectory that belongs to the call. TEMP_FILES and delete_temp_files are gone; their tests are replaced by tests of import_generated_code. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every change event started a reload thread of its own. A reload that had read the older manifest could finish last and load the older CLI over the newer one. Reloads now hold one lock and read the manifest only once it is their turn; a reload that waits behind one for a newer change is skipped. The CLI of --run-cli runs outside the lock. An event within a second of the last reload is still treated as a duplicate report of the same save, but only when the watched files are unchanged: a second save within that second used to be dropped. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
cli render and cli init --render printed through rich with overflow="fold". Lines longer than the terminal, or than 80 columns when redirected, were broken, so cli render x.yaml > x.py was not the module that runs. Redirected output is now the generated text character for character; on a terminal the text is highlighted and left to the terminal to wrap. The 'Rendered ...' line goes to stderr. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- A command that raises is reported as the command (or callback) it is in the manifest, with the exception and the line of its run block, instead of '<Result RuntimeError(...)>'. cli test --traceback adds the full traceback. - An assertion that raises something other than AssertionError, or is not valid Python, is reported with its line. Both printed a traceback through cliffy/cli.py and cliffy/tester.py. - A manifest whose generated code does not import, and a shell step whose program cannot be started, end the run with an error message instead of a traceback. The generated CLI records which scripts each function was generated from (CLI.blocks), which is what maps a line of generated code to a manifest line. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SOUL principle 7 promises examples in the help, but `examples` was only read by cli docs. Both backends now put an Examples section into the epilog of the CLI: one paragraph per example, `$ command # description`, after an epilog set in cli_options. The text is a string literal in the generated code like every other manifest string, and for Typer brackets are escaped so that rich markup does not drop `[home]` from a command line. A manifest without examples generates the same code as before. examples/hello.yaml and examples/click_hello.yaml carry examples and a test for them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A message that is valid JSON but not an object (an array, a string, a number) raised AttributeError in _handle_message and ended the server. It is answered with -32600 Invalid Request, as is a batch array (MCP has no batches since 2025-06-18) and an object without a method. params or arguments that are not objects get -32602. A response object is ignored. The server keeps serving. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
cli build does not check requires against the installed packages, so an entry such as --index-url=... or -r file reached pip's argument list as an option and changed where it installs from. A manifest or include with such an entry is rejected when it is read, build_cli rejects it for a loaded CLI, and the requirements follow -- in the pip call. Every subprocess call in cliffy/ is an argument list without a shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
uv publish --check-url skips files that are on PyPI already with the same content, so a release whose upload stopped half way can be finished by re-running the job. The build job and a test check that the wheel and the sdist carry cliffy/py.typed and the Typing :: Typed classifier, which they do. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A manifest can read environment variables and include files, and its header names the cliffy version, so 'the same manifest produces the same CLI on every machine, every time' was not true as written. The promise is now: the same manifest with the same includes, blueprint and environment variables, read by the same cliffy, gives the same CLI, and cliffy adds no input of its own. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Changelog and known issues for what changed on this branch; the guides for cli test output and --traceback, examples in --help, cli render to a file, launcher ownership and removal. The examples field description, the JSON schema and the generated reference pages follow. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configuration
You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Reviewer's GuideThis PR hardens CLI filesystem operations and concurrent execution, makes rendering and test failures deterministic and actionable, adds manifest examples to generated root help, blocks pip-option injection, improves release and MCP-example behavior, and updates the associated tests and documentation. Sequence diagram for safe CLI launcher loading and removalsequenceDiagram
participant User
participant CLI
participant Loader
participant Filesystem
User->>CLI: load or rm cli_name
CLI->>Loader: validate_cli_name(cli_name)
Loader->>Filesystem: script_state(script_path, cli_name)
alt foreign launcher
Filesystem-->>Loader: foreign
Loader-->>CLI: LoaderError
CLI-->>User: refuse and leave CLI loaded
else owned or missing launcher
alt load
CLI->>Loader: load_from_cli(cli)
Loader->>Filesystem: write_atomically
else remove
CLI->>Loader: unload_cli(cli_name)
Loader->>Filesystem: unlink launcher
Loader->>Filesystem: unlink module
CLI->>Filesystem: remove_metadata(cli_name)
end
CLI-->>User: success
end
Sequence diagram for manifest-based CLI test error reportingsequenceDiagram
participant User
participant CLI
participant Tester
participant GeneratedCLI
User->>CLI: test manifest
CLI->>Tester: Tester(manifest)
Tester->>Tester: import_generated_code
CLI->>Tester: invoke_test(command, assert_script)
Tester->>GeneratedCLI: invoke command
alt command raises
GeneratedCLI-->>Tester: Result with exception and exc_info
CLI->>Tester: describe_exception(command, result)
Tester-->>CLI: manifest command and run-block location
CLI-->>User: one-line actionable error
opt show_traceback
CLI-->>User: full traceback
end
else command succeeds
GeneratedCLI-->>Tester: Result
Tester->>Tester: execute assertion
CLI-->>User: test result
end
Flow diagram for manifest examples in generated CLI helpflowchart LR
Manifest[Manifest examples] --> Commander[Commander examples_epilog]
Commander --> Options[cli_options_with_examples]
Options --> Click[Click root help]
Options --> Typer[Typer root help]
Click --> Help[Examples section]
Typer --> Help
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
Summary
Addresses the Sourcery review comments left on #52, #53, #54 and #56. Each comment was checked against the top of the stack first: four were already fixed by later PRs, one was already in place and gained a check, and the rest are fixed here, each with a regression test written first. Replies on the original threads point here.
What changed
Loaded CLIs, launchers and removal (#54 comments)
..is rejected byrm,update,info,buildanddocs. Before,cli rm ../outsidedeleted files one level above cliffy's directories.loadandrmwith a message. Before, any file containingfrom cliffy.clis.was overwritten.Concurrency (#54 comments)
TEMP_FILESlist is gone.cli devreloads run one at a time and the last change wins. A second save within one second is no longer dropped.Output and errors (#52 comments on promises in
SOUL.md)cli render x.yaml > x.pyis byte for byte the generated module: no line folding, and the status line goes to stderr. Same forcli init --render.cli testreports a command that raises ascommand `x` raised Error: messagewith the run-block line, instead of a traceback through generated code.--tracebackshows the full one.examplesin an Examples section of the root--help, for both backends.SOUL.mdprinciple 1 now says which inputs a CLI depends on (the manifest, its includes and blueprint, the environment variables it reads, the cliffy version) instead of promising identical output unconditionally.Packaging, release, examples, tests (#53, #54, #56 comments)
--check-url, so a half-finished upload can be completed by re-running the job.cliffy/py.typedships in the wheel and sdist (the file was already there).requiresentry starting with-is a validation error and can no longer reach pip as an option; requirements follow--in the pip call.printcommand; the symlink test skips only where symlinks cannot be created.Behaviour changes a user would notice
cli render: no wrapping; the "Rendered" line is on stderr.cli test: one-line error reports instead of tracebacks; new--tracebackflag; a shell step whose program is missing ends the run with exit 1.cli rm/rm-allexit 1 and remove nothing when a launcher was edited or is not cliffy's.cli loadrefuses to replace an edited launcher.--helpof a generated CLI gains an Examples section when the manifest hasexamples. Output is unchanged for manifests without them.cli dev: a second save within a second triggers a reload.Tests that are not pure additions
test_delete_temp_filesand theTEMP_FILESassertions were removed together with the global and function they tested; tests of the per-run temporary directories replace them.test_unload_keeps_foreign_executablenow expects unload to refuse.How verified
Python 3.11 and 3.15:
ruff format --check,ruff check,mypy cliffy --strict,mypy tests,pytest -n auto(843 passed, 1 skipped: the opt-in native build), the same withGITHUB_ACTIONS=true,make docs-check,mkdocs build --strict.Left open
--helponly. Commands have noexamplesfield in the manifest; adding one is a schema decision.requiresentry that is not a valid requirement but does not look like an option is still rejected by pip rather than by the manifest.:rocket:in an example render as emoji.🤖 Generated with Claude Code
Summary by Sourcery
Harden CLI lifecycle and development workflows while making generated output, help examples, testing diagnostics, packaging, and example-server behavior more reliable.
New Features:
Bug Fixes:
Enhancements:
CI:
Deployment:
Documentation:
Tests: