Improve plan review UX with enriched rows, TUI filters, and execute gate.

Make review-plan easier to act on: Chinese risk labels, relative paths,
verdict/next-step footer, optional identity/analysis enrichment, grouping,
spot-check sampling, and structure preview. Extend the TUI with filters,
duplicate-group reject, and quality context. Persist review_context on
plan operations and add --require-review for confirmed execute.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
windyboy
2026-05-21 09:27:12 +08:00
co-authored by Cursor
parent c9d22d5136
commit 5f0b531269
13 changed files with 1050 additions and 61 deletions
+136 -5
View File
@@ -17,7 +17,14 @@ from click.core import ParameterSource
from vlm.config import Config, load_config, create_default_config, validate_config
from vlm.context import CLIContext, pass_context
from vlm.logging_config import setup_logging, get_logger
from vlm.plan_render import fallback_plan_summary, preferred_plan_summary, render_review_preview
from vlm.plan_render import (
fallback_plan_summary,
preferred_plan_summary,
render_review_footer,
render_review_preview,
render_review_verdict,
duplicate_groups_from_plan,
)
from vlm.utils import format_size
@@ -538,6 +545,38 @@ def plan(ctx: CLIContext, input: Path, output: Path, analysis: Path | None):
default=False,
help='Interactive Textual UI (requires: uv pip install -e ".[tui]")'
)
@click.option(
'--identities',
type=click.Path(path_type=Path),
default=None,
help='Identities JSON for enriched review rows (default: artifacts/identities.json if present)'
)
@click.option(
'--analysis',
type=click.Path(path_type=Path),
default=None,
help='Analysis JSON for duplicate grouping and TUI quality pane'
)
@click.option(
'--group-by',
type=click.Choice(['none', 'reason', 'title', 'duplicate'], case_sensitive=False),
default='none',
show_default=True,
help='Reorder review rows for display/export'
)
@click.option(
'--sample-safe',
type=int,
default=0,
show_default=True,
help='Include N random non-high-risk move operations for spot-checking'
)
@click.option(
'--structure-preview',
type=click.Path(path_type=Path),
default=None,
help='Write target library tree preview to this file (e.g. artifacts/plan_structure.txt)'
)
@pass_context
def review_plan_cmd(
ctx: CLIContext,
@@ -548,10 +587,17 @@ def review_plan_cmd(
preview_limit: int,
show_all: bool,
tui: bool,
identities: Optional[Path],
analysis: Optional[Path],
group_by: str,
sample_safe: int,
structure_preview: Optional[Path],
):
"""Review a plan and export high-risk operations for manual confirmation."""
from vlm.io import load_analysis_json, load_identities_json
from vlm.planner import load_plan
from vlm.plan_review import review_plan, save_review_csv
from vlm.plan_review import GROUP_BY_CHOICES, prepare_review_rows, save_review_csv
from vlm.plan_structure_preview import write_structure_preview
logger = ctx.logger
@@ -563,6 +609,13 @@ def review_plan_cmd(
if preview_limit < 1:
click.echo("Error: --preview-limit must be >= 1", err=True)
sys.exit(1)
if sample_safe < 0:
click.echo("Error: --sample-safe must be >= 0", err=True)
sys.exit(1)
group_by = group_by.lower()
if group_by not in GROUP_BY_CHOICES:
click.echo(f"Error: invalid --group-by {group_by}", err=True)
sys.exit(1)
if tui:
if not _review_plan_tui_streams_ok():
@@ -571,15 +624,46 @@ def review_plan_cmd(
click.echo(f"Loading plan: {input}")
execution_plan = load_plan(input)
rows, counters = review_plan(
identities_data = None
identities_path = identities
if identities_path is None:
default_id = default_artifact_path("identities.json")
if default_id.is_file():
identities_path = default_id
if identities_path is not None and identities_path.is_file():
identities_data = load_identities_json(identities_path)
click.echo(f"Loaded identities: {identities_path}")
analysis_data = None
if analysis is not None and analysis.is_file():
analysis_data = load_analysis_json(analysis)
click.echo(f"Loaded analysis: {analysis}")
rows, counters = prepare_review_rows(
execution_plan,
season_threshold=season_threshold,
episode_threshold=episode_threshold,
library_root=ctx.config.library_root,
identities_data=identities_data,
analysis_data=analysis_data,
sample_safe=sample_safe,
group_by=group_by,
)
if structure_preview is not None:
write_structure_preview(
execution_plan,
ctx.config.library_root,
structure_preview,
)
click.echo(f"Wrote structure preview: {structure_preview}")
if tui:
from vlm.plan_review import build_duplicate_path_maps
from vlm.review_tui import ReviewTUIContext, run_plan_review_tui
_, path_to_quality = build_duplicate_path_maps(analysis_data)
tui_ctx = ReviewTUIContext(
rows=rows,
counters=counters,
@@ -587,6 +671,7 @@ def review_plan_cmd(
output_csv=output,
plan_input=input,
summary_text=preferred_plan_summary(execution_plan),
path_to_quality=path_to_quality,
)
rc = run_plan_review_tui(tui_ctx)
if rc != 0:
@@ -617,6 +702,9 @@ def review_plan_cmd(
click.echo(f" high_episode: {counters['high_episode']}")
click.echo(f" conflicts: {counters['conflicts']}")
click.echo()
click.echo(render_review_verdict(counters))
click.echo()
click.echo("High-risk operations preview:")
if rows:
@@ -624,6 +712,7 @@ def review_plan_cmd(
rows,
preview_limit=preview_limit,
show_all=show_all,
library_root=ctx.config.library_root,
)
for line in preview_lines:
click.echo(line)
@@ -638,6 +727,16 @@ def review_plan_cmd(
click.echo()
click.echo(f"Saved manual review CSV to: {output}")
dup_groups = duplicate_groups_from_plan(execution_plan)
click.echo()
for line in render_review_footer(
counters=counters,
output_csv=output,
plan_input=input,
duplicate_groups=dup_groups,
):
click.echo(line)
logger.info(
"Plan review completed: total=%s high_risk=%s output=%s",
counters["total_operations"],
@@ -764,8 +863,30 @@ def apply_review_cmd(
default=False,
help='Enable safe mode: prevent any operations that would destroy directories'
)
@click.option(
'--require-review',
is_flag=True,
default=False,
help='With --confirm, require a current plan_manual_review.csv when high-risk ops exist'
)
@click.option(
'--review-csv',
type=click.Path(path_type=Path),
default=None,
help='Review CSV path for --require-review (default: beside plan file)'
)
@pass_context
def execute(ctx: CLIContext, plan: Path, confirm: bool, yes: bool, verbose_ops: bool, preserve_directories: bool, safe_mode: bool):
def execute(
ctx: CLIContext,
plan: Path,
confirm: bool,
yes: bool,
verbose_ops: bool,
preserve_directories: bool,
safe_mode: bool,
require_review: bool,
review_csv: Optional[Path],
):
"""Execute plan (defaults to dry-run, requires --confirm).
Executes file operations from a plan. Defaults to dry-run mode which
@@ -782,7 +903,17 @@ def execute(ctx: CLIContext, plan: Path, confirm: bool, yes: bool, verbose_ops:
try:
plan = resolve_legacy_default_input_path(plan, "plan", "plan.json", "--plan")
from vlm.commands.execute import execute_cmd
execute_cmd(ctx, plan, confirm, yes, verbose_ops, preserve_directories, safe_mode)
execute_cmd(
ctx,
plan,
confirm,
yes,
verbose_ops,
preserve_directories,
safe_mode,
require_review=require_review,
review_csv=review_csv,
)
except FileNotFoundError:
_command_error(
ctx,