Stack-specific rules loaded by dh:code-reviewer when CLI entrypoints are detected (argparse, click, typer, commander.js, or similar argument parsing libraries).
Read Review principles before applying these checks; it defines authority, applicability, evidence, and blocking criteria. Establish the command's supported platforms and human/machine output contracts before prescribing an exact flag or exit code.
Exit Codes
- Verify exit status distinguishes success from failure under the documented command contract, including partial-success or best-effort modes where supported.
- Report success status after a required operation failed when callers cannot detect that failure; distinguish an overall failure from a documented recoverable warning.
- Common conventions:
1for general errors,2for usage/argument errors,3+for application-specific codes documented in--help - Verify fatal paths reach a non-zero exit through the framework or explicit exit handling; printing to stderr alone is not a failure signal for a caller that checks exit status.
# WRONG: exits 0 even on error
def main():
try:
run()
except Exception as e:
print(f"Error: {e}", file=sys.stderr)
# implicit exit 0
# RIGHT: non-zero on error
def main():
try:
run()
except Exception as e:
print(f"Error: {e}", file=sys.stderr)
sys.exit(1)
Help and Version Flags
- Check that supported help/version interfaces are discoverable and usable by their intended callers; use
--helpand--versionas conventions for new human-facing commands. - Verify help describes the actual options and examples, and version output agrees with the installed/distributed package's version source.
- Help text must be consistent with actual behavior — stale help text is a blocking finding
- Supported help and version requests must report success when they complete successfully.
stdin / stdout / stderr Separation
- Progress indicators, status messages, and diagnostic output go to
stderr - Data output (results to be piped or redirected) goes to
stdout - Mixing diagnostic messages into
stdoutbreaks pipe usage — this is a blocking finding for tools that produce structured output - Error messages go to
stderrwith a non-zero exit code
Non-Interactive Operation
- For supported CI/script use, provide explicit inputs for interactive choices or a clear non-interactive failure; identify any prompt that makes the documented automation path unusable.
- Interactive prompts that block in non-TTY environments (piped input, CI) are a blocking finding
- Detect non-TTY input with
sys.stdin.isatty()or equivalent. Do not interpret an unavailable prompt as consent to a consequential operation.
Signal Handling
- Check interruption behavior for the supported platform/signals: required cleanup, an observable interrupted result, and preservation of valid durable state. Use conventional signal exit codes where that platform and command contract apply.
- For long-running commands, trace termination through resource ownership and partial operations; report abandoned resources or corrupt writes rather than requiring a particular handler API.
- Temporary resources need cleanup on relevant normal/error/interruption paths; a registered callback alone does not prove those paths are covered.
Argument Validation
- Validate all arguments before beginning any work — do not fail halfway through a destructive operation due to a missing flag
- Report actionable validation errors; aggregate independent errors when that helps the caller without executing partial work.
- Check path preconditions before consequential work and handle failures at the operation boundary; a preflight existence/permission check does not guarantee the later operation succeeds.
ANSI Color Codes
- ANSI escape codes must not be emitted when
NO_COLORenvironment variable is set (any non-empty value) - Keep piped/redirected output free of unintended ANSI sequences by default; respect documented explicit color controls and the machine-output contract.
- Check TTY with
sys.stdout.isatty()or equivalent before colorizing output
Dry Run for Destructive Operations
- For commands that delete or overwrite durable data, verify how the caller can inspect and authorize the intended effects before execution. Report missing safeguards with the unintended effect they permit.
- Where the command promises
--dry-run, verify it previews the relevant effects without performing them and clearly identifies affected resources. Do not require a specific flag name when the supported interface supplies equivalent protection.
Anti-Patterns
# Missing preview in a command whose contract requires --dry-run.
@app.command()
def delete_records(pattern: str):
records = find_records(pattern)
for r in records:
r.delete()
print(f"Deleted {len(records)} records")
# RIGHT: dry-run support
@app.command()
def delete_records(pattern: str, dry_run: bool = typer.Option(False, "--dry-run")):
records = find_records(pattern)
if dry_run:
for r in records:
print(f"Would delete: {r.id}")
print(f"Would delete {len(records)} records (dry run)")
return
for r in records:
r.delete()
print(f"Deleted {len(records)} records")
# WRONG: ANSI always on
print(f"\033[32mSuccess\033[0m")
# RIGHT: conditional color
import os
import sys
USE_COLOR = sys.stdout.isatty() and not os.environ.get("NO_COLOR")
success = "\033[32mSuccess\033[0m" if USE_COLOR else "Success"
print(success)