fix(cli): add --depth-gradient flag to project context set
CI / benchmark-publish (pull_request) Has been skipped
CI / build (pull_request) Successful in 22s
CI / helm (pull_request) Successful in 25s
CI / lint (pull_request) Successful in 3m45s
CI / quality (pull_request) Successful in 4m5s
CI / typecheck (pull_request) Successful in 4m22s
CI / security (pull_request) Successful in 4m31s
CI / integration_tests (pull_request) Successful in 7m27s
CI / unit_tests (pull_request) Successful in 7m55s
CI / docker (pull_request) Successful in 1m30s
CI / coverage (pull_request) Successful in 12m1s
CI / e2e_tests (pull_request) Successful in 22m32s
CI / status-check (pull_request) Successful in 12s
CI / build (push) Successful in 25s
CI / quality (push) Successful in 39s
CI / helm (push) Successful in 41s
CI / lint (push) Successful in 3m36s
CI / typecheck (push) Successful in 3m52s
CI / benchmark-regression (push) Has been skipped
CI / security (push) Successful in 4m3s
CI / unit_tests (push) Successful in 4m12s
CI / docker (push) Successful in 1m32s
CI / integration_tests (push) Successful in 7m3s
CI / coverage (push) Successful in 11m52s
CI / e2e_tests (push) Successful in 20m56s
CI / status-check (push) Successful in 1s
CI / benchmark-publish (push) Successful in 28m22s
CI / benchmark-regression (pull_request) Successful in 55m2s

Add the missing --depth-gradient repeatable flag to `project context set`
per specification. The flag accepts HOP:INT_OR_NAME format to control
context detail depth degradation with graph distance. Parsing validates
hop as integer and value as integer or recognized detail level name.

ISSUES CLOSED: #889
This commit was merged in pull request #1186.
This commit is contained in:
2026-03-29 03:35:30 +00:00
committed by Forgejo
parent cbcb873e17
commit 4eddbcf489
3 changed files with 230 additions and 0 deletions
@@ -0,0 +1,49 @@
Feature: Project context depth-gradient flag (B2.cli)
As a CleverAgents user
I want to set per-hop depth gradients via ``project context set --depth-gradient``
So that context detail depth degrades with graph distance from focus nodes
Background:
Given a project context CLI in-memory database is initialized
And a project "local/ctx-app" exists for context CLI
# ── single depth-gradient value ─────────────────────────────
Scenario: Set a single depth-gradient with integer value
When I run context set on "local/ctx-app" with depth-gradient "0:9"
Then the context set command should succeed
And the stored ACMS depth gradient should have hop "0" with value 9
Scenario: Set a single depth-gradient with named level value
When I run context set on "local/ctx-app" with depth-gradient "1:SIGNATURES"
Then the context set command should succeed
And the stored ACMS depth gradient should have hop "1" with string value "SIGNATURES"
# ── multiple depth-gradient values ──────────────────────────
Scenario: Set multiple depth-gradient values
When I run context set on "local/ctx-app" with depth-gradients "0:9,1:4,2:MODULE_LISTING"
Then the context set command should succeed
And the stored ACMS depth gradient should have hop "0" with value 9
And the stored ACMS depth gradient should have hop "1" with value 4
And the stored ACMS depth gradient should have hop "2" with string value "MODULE_LISTING"
# ── invalid format ──────────────────────────────────────────
Scenario: Invalid depth-gradient missing colon fails
When I run context set on "local/ctx-app" with depth-gradient "bad"
Then the context set command should fail
Scenario: Invalid depth-gradient non-integer hop fails
When I run context set on "local/ctx-app" with depth-gradient "abc:5"
Then the context set command should fail
# ── display in show command ─────────────────────────────────
Scenario: Depth gradient is displayed in context show output
Given I have set depth-gradient "0:9,1:SIGNATURES" on "local/ctx-app"
When I run context show on "local/ctx-app" without options
Then the context show command should succeed
And the context show output should contain "Depth gradient"
And the context show output should contain "0:9"
And the context show output should contain "1:SIGNATURES"
@@ -0,0 +1,132 @@
"""Step definitions for ``project_context_depth_gradient.feature``.
Tests the ``--depth-gradient`` flag on ``agents project context set``
and its display in ``agents project context show``.
"""
from __future__ import annotations
from typing import Any
from behave import given, then, when # type: ignore[import-untyped]
# ------------------------------------------------------------------
# Helpers — reuse the shared infrastructure from
# project_context_cli_steps (background steps, _run_with_container,
# _mock_container, etc. are already registered).
# ------------------------------------------------------------------
def _run_with_container_local(
context: Any, func: Any, *args: Any, **kwargs: Any
) -> None:
"""Delegate to the shared test runner from project_context_cli_steps."""
from features.steps.project_context_cli_steps import (
_run_with_container,
)
_run_with_container(context, func, *args, **kwargs)
# ------------------------------------------------------------------
# When steps
# ------------------------------------------------------------------
@when(
'I run context set on "{project}" with depth-gradient "{gradient}"',
)
def step_ctx_set_depth_gradient_single(
context: Any, project: str, gradient: str
) -> None:
from cleveragents.cli.commands.project_context import (
context_set,
)
_run_with_container_local(
context,
context_set,
project=project,
view="default",
depth_gradient=[gradient],
)
@when(
'I run context set on "{project}" with depth-gradients "{gradients}"',
)
def step_ctx_set_depth_gradient_multi(
context: Any, project: str, gradients: str
) -> None:
from cleveragents.cli.commands.project_context import (
context_set,
)
gradient_list = [g.strip() for g in gradients.split(",")]
_run_with_container_local(
context,
context_set,
project=project,
view="default",
depth_gradient=gradient_list,
)
# ------------------------------------------------------------------
# Given steps
# ------------------------------------------------------------------
@given('I have set depth-gradient "{gradients}" on "{project}"')
def step_given_depth_gradient(context: Any, gradients: str, project: str) -> None:
from cleveragents.cli.commands.project_context import (
context_set,
)
gradient_list = [g.strip() for g in gradients.split(",")]
_run_with_container_local(
context,
context_set,
project=project,
view="default",
depth_gradient=gradient_list,
)
# ------------------------------------------------------------------
# Then steps
# ------------------------------------------------------------------
def _read_stored_acms(context: Any) -> dict[str, Any]:
"""Read the stored ACMS config from the test DB."""
from cleveragents.cli.commands.project_context import (
_read_acms_config,
)
return _read_acms_config(context.ctx_session_factory, "local/ctx-app")
@then('the stored ACMS depth gradient should have hop "{hop}" with value {value:d}')
def step_depth_gradient_int(context: Any, hop: str, value: int) -> None:
acms = _read_stored_acms(context)
dg = acms.get("depth_gradient", {})
assert hop in dg, f"Hop '{hop}' not in depth_gradient: {dg}"
assert dg[hop] == value, f"Expected {value} for hop {hop}, got {dg[hop]}"
@then(
'the stored ACMS depth gradient should have hop "{hop}" with string value "{value}"'
)
def step_depth_gradient_str(context: Any, hop: str, value: str) -> None:
acms = _read_stored_acms(context)
dg = acms.get("depth_gradient", {})
assert hop in dg, f"Hop '{hop}' not in depth_gradient: {dg}"
assert dg[hop] == value, f"Expected '{value}' for hop {hop}, got {dg[hop]}"
@then('the context show output should contain "{text}"')
def step_show_output_contains(context: Any, text: str) -> None:
assert text in context.ctx_output, (
f"Expected '{text}' in output, got: {context.ctx_output!r}"
)
@@ -543,6 +543,16 @@ def context_set(
help="Temporal scope: current, recent, or all",
),
] = None,
depth_gradient: Annotated[
list[str] | None,
typer.Option(
"--depth-gradient",
help=(
"Per-hop detail depth override (repeatable). "
"Format: HOP:INT_OR_NAME, e.g. 0:9, 1:SIGNATURES"
),
),
] = None,
auto_refresh: Annotated[
bool | None,
typer.Option(
@@ -642,6 +652,37 @@ def context_set(
acms["temporal_scope"] = temporal_scope
if auto_refresh is not None:
acms["auto_refresh"] = auto_refresh
if depth_gradient is not None:
parsed_gradient: dict[str, int | str] = {}
for entry in depth_gradient:
if ":" not in entry:
err_console.print(
f"[red]Invalid depth-gradient '{entry}': "
f"expected HOP:INT_OR_NAME format[/red]"
)
raise typer.Exit(1)
hop_str, _, value_str = entry.partition(":")
try:
hop = int(hop_str)
except ValueError:
err_console.print(
f"[red]Invalid depth-gradient hop '{hop_str}': "
f"hop must be a non-negative integer[/red]"
)
raise typer.Exit(1) from None
if hop < 0:
err_console.print(
f"[red]Invalid depth-gradient hop '{hop}': "
f"hop must be a non-negative integer[/red]"
)
raise typer.Exit(1)
# Value: try int first, else keep as named level string
try:
parsed_value: int | str = int(value_str)
except ValueError:
parsed_value = value_str
parsed_gradient[str(hop)] = parsed_value
acms["depth_gradient"] = parsed_gradient
_write_policy(session_factory, project, policy, acms)
@@ -852,6 +893,14 @@ def context_show(
f" Default breadth: {acms.get('default_breadth', _DEFAULT_BREADTH)}"
)
lines.append(f" Default depth: {acms.get('default_depth', _DEFAULT_DEPTH)}")
dg = acms.get("depth_gradient", {})
if dg:
gradient_parts = [
f"{h}:{v}" for h, v in sorted(dg.items(), key=lambda x: int(x[0]))
]
lines.append(f" Depth gradient: {', '.join(gradient_parts)}")
else:
lines.append(" Depth gradient: (none)")
lines.append(
f" Skeleton ratio: {acms.get('skeleton_ratio', _DEFAULT_SKELETON_RATIO)}"
)