diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d515a05f..2d26a0f95 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1097,6 +1097,11 @@ iteration` and data corruption under concurrent plan execution. All public The `export` command gains `--output-format` and the `import` command gains `--format` to select the output envelope format independently of the export/import file format. +- **CLI global format flag parsing** (#6879): Promotes global CLI options before + Typer parsing so `agents --format json` and `-f` shorthand work from + the documented position. Adds regression coverage for version, info, and + diagnostics commands. + - **Robot Framework TDD Listener Guards** (#5436): Added three guard conditions to the `tdd_expected_fail_listener` `end_test()` function to prevent blindly inverting ALL test failures to passes, which was masking infrastructure errors and causing flaky CI behavior. diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index af51c30bc..60cce2905 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -28,6 +28,7 @@ Below are some of the specific details of various contributions. * HAL 9000 has contributed the plan tree JSON/YAML command envelope fix (#9163): wrapped `agents plan tree --format json/yaml` output in the spec-required command envelope structure, added summary statistics, decision_ids mapping, child_plans list, and accurate timing measurement. * HAL 9000 contributed Structural Component Output Validation (PR #11161 / issue #8164): implemented `validate_plan_tree`, `validate_decision_dict`, `validate_structured_output`, and `validate_structured_component_output` validators that replace exact-character matching with structural schema checking for plan tree nodes, decision CLI dictionaries, and structured session output envelopes. * HAMZA KHYARI has contributed the ACMS execute-phase context assembler project-level hot_max_tokens fix (PR #11036 / issue #11035): added `_resolve_effective_budget()` method that reads each linked project's `settings.hot_max_tokens` and uses the maximum override value as the pipeline budget instead of the hardcoded global 16K default. +* HAL 9000 has contributed the CLI global format flag fix (#6879): promoted global options before subcommand parsing to allow `agents --format ...` syntax as documented. * This project was made possible thanks to considerable donation of time, money, and resources by CleverThis, Inc. * HAL 9000 has contributed automated bug fixes, CLI output formatting improvements, and ongoing maintenance as part of the CleverAgents automation system. * HAL 9000 has contributed the pr-review-pool-supervisor tracking prefix documentation fix (#7891): aligned all documentation references from the outdated `AUTO-REV-POOL` prefix to the correct `AUTO-REV-SUP` prefix used in production. diff --git a/features/cli_global_format_flag.feature b/features/cli_global_format_flag.feature index 1bb764c4e..3c719e9df 100644 --- a/features/cli_global_format_flag.feature +++ b/features/cli_global_format_flag.feature @@ -44,6 +44,25 @@ Feature: Global --format flag propagation to subcommands Then the global format command should succeed And the global format output should be valid JSON + # ----------------------------------------------------------------------- + # Regression: global --format accepted after the subcommand name + # ----------------------------------------------------------------------- + + Scenario: Global --format flag works after the info subcommand name + When I invoke the CLI with global args "info --format json" + Then the global format command should succeed + And the global format output should be valid JSON + + Scenario: Global --format flag works after the version subcommand name + When I invoke the CLI with global args "version --format json" + Then the global format command should succeed + And the global format output should be valid JSON + + Scenario: Global --format flag works after the diagnostics subcommand name + When I invoke the CLI with global args "diagnostics --format json" + Then the global format command should succeed + And the global format output should be valid JSON + # ----------------------------------------------------------------------- # Subtask 3: version, info, diagnostics read format from global ctx.obj # ----------------------------------------------------------------------- diff --git a/src/cleveragents/cli/main.py b/src/cleveragents/cli/main.py index f92202a9c..9e556fac8 100644 --- a/src/cleveragents/cli/main.py +++ b/src/cleveragents/cli/main.py @@ -9,6 +9,7 @@ from pathlib import Path from typing import TYPE_CHECKING, Annotated, Any import typer +from typer.core import TyperGroup from cleveragents import __version__ from cleveragents.cli.formatting import OutputFormat @@ -21,6 +22,90 @@ _console: "Console | None" = None _err_console: "Console | None" = None _subcommands_registered = False +_GLOBAL_VALUE_OPTIONS = frozenset({"--format"}) +_GLOBAL_VALUE_SHORT_OPTIONS = frozenset({"-f"}) +_GLOBAL_VALUE_EQ_PREFIXES = ("--format=",) +_GLOBAL_FLAG_OPTIONS = frozenset({"--show-secrets", "--version", "-V"}) + + +def _split_short_value_option(option: str) -> tuple[str, str] | None: + for short in _GLOBAL_VALUE_SHORT_OPTIONS: + if option.startswith(short) and len(option) > len(short): + return short, option[len(short) :] + return None + + +def _promote_global_options(args: list[str]) -> list[str]: + """Move global root options before subcommands for Typer parsing.""" + + if not args: + return args + + promoted: list[str] = [] + remaining: list[str] = [] + i = 0 + length = len(args) + + while i < length: + arg = args[i] + + if arg == "--": + remaining.extend(args[i:]) + break + + if arg in _GLOBAL_FLAG_OPTIONS: + promoted.append(arg) + i += 1 + continue + + if arg in _GLOBAL_VALUE_OPTIONS: + if i + 1 < length: + promoted.extend([arg, args[i + 1]]) + i += 2 + else: + remaining.append(arg) + i += 1 + continue + + eq_prefix = next( + (prefix for prefix in _GLOBAL_VALUE_EQ_PREFIXES if arg.startswith(prefix)), + None, + ) + if eq_prefix is not None and len(arg) > len(eq_prefix): + promoted.extend([eq_prefix.rstrip("="), arg[len(eq_prefix) :]]) + i += 1 + continue + + split_short = _split_short_value_option(arg) + if split_short is not None: + short_opt, value = split_short + promoted.extend([short_opt, value]) + i += 1 + continue + + if arg in _GLOBAL_VALUE_SHORT_OPTIONS: + if i + 1 < length: + promoted.extend([arg, args[i + 1]]) + i += 2 + else: + remaining.append(arg) + i += 1 + continue + + remaining.append(arg) + i += 1 + + return promoted + remaining + + +class CleverAgentsTyperGroup(TyperGroup): + """Typer group that promotes global options before subcommand parsing.""" + + def parse_args(self, ctx: Any, args: list[str]) -> list[str]: + promoted_args = _promote_global_options(list(args)) + return super().parse_args(ctx, promoted_args) + + __all__ = [ "_print_basic_help", "app", @@ -69,6 +154,7 @@ app: Any = typer.Typer( pretty_exceptions_enable=True, pretty_exceptions_show_locals=False, rich_markup_mode="rich", + cls=CleverAgentsTyperGroup, ) @@ -740,6 +826,9 @@ def main(args: list[str] | None = None) -> int: if not args: args = ["--help"] + # Global CLI options are promoted by CleverAgentsTyperGroup.parse_args, + # so we avoid reprocessing here to prevent double handling. + # Fast paths for lightweight flags to avoid heavy imports when using the # real Typer app. If the app is monkeypatched (e.g., tests), fall back to # normal execution so patched call paths are exercised.