Skip to content

feat(cli): readable run summary and preview output - #7

Open
chethanuk wants to merge 1 commit into
mainfrom
feat/cli-run-summary
Open

chethanuk wants to merge 1 commit into
mainfrom
feat/cli-run-summary

Conversation

@chethanuk

@chethanuk chethanuk commented Sep 23, 2026 •

Copy link
Copy Markdown
Owner

Related Issue

Upstream issue 105 will be linked with a closing keyword when this is raised upstream. Issue is not yet triaged; the branch name should follow CONTRIBUTING (for example chethanuk/feature/105-cli-run-summary) at raise time.

Plan Document

plans/105/cli-run-summary.md. It supersedes the plan in the open upstream draft PR 125, following its review threads.

Summary

preview prints result.dataframe.to_string(max_colwidth=80), which wraps into a wide block, and run prints only Output written to: <path>. The CLI never shows failed_records, and control bytes in record text go straight to the terminal.

run keeps Output written to: <path> as its first line (never styled), so scripts that parse it still work. Below it come Mode, Source, Output rows and size, a Failed count and Elapsed, then failed records grouped by (step, reason) with a count, most frequent first.

  • Mode comes from result.replace_method / result.rewrite_config, not from column names.
  • --trace PATH writes the trace dataset and --failed-output PATH writes record_id, step, reason. --output, --trace and --failed-output are validated before the pipeline starts: bad suffix, existing directory, unwritable parent, existing file that is read-only, or a path equal to --source or another output.
  • The failure report is written before the trace, and the summary is always printed. A trace that parquet cannot store exits 1 with the normal Error: message instead of a traceback, and the summary lists only files actually written.
  • preview prints one block per record with one column : value line per output column, values cut to shutil.get_terminal_size(), and final_entities shown as labels.
  • C0 and C1 control bytes are stripped from every record-derived string before printing.
  • Styling (bold headers, red failure header, dim keys) uses plain ANSI codes and is on only for a TTY with NO_COLOR unset or empty and no --no-color. --color/--no-color is on run and preview only. No new dependencies.
  • docs/troubleshooting.md no longer says the CLI cannot show failed rows; it points at --trace and --failed-output.

Left out, already covered above or in the plan: a per-label entity table, rewrite averages, a source row count (the result does not carry one), side-file flags on preview.

Limitations: truncation counts characters, not terminal cells, so wide CJK text can still wrap. --trace parquet is only tested with synthetic traces, not a real pipeline trace. If a side-file write fails after the run, the main output is kept and the command exits 1.

Before / After

Both blocks come from one driver, drive.py, run for real at each commit. It calls the real anonymizer.interface.cli.main.app(sys.argv[1:]) with Anonymizer patched to return one synthetic result, because the pipeline needs live model endpoints. The result has 2 records (the first contains a newline), matching text_replaced and final_entities, and 4 failures, one with a reason containing \x1b]0;pwned\x07\x1b[2J. COLUMNS=100, output piped through cat -v.

Before, main at d13d260:

$ python drive.py run --output out.csv --source data.csv --replace redact
Output written to: out.csv
$ python drive.py preview --source data.csv --replace redact
                                                     text                                                                    text_replaced                                                                   final_entities
0  Hi, I'm Jane Roe from Acme Corp.\nCall me on 555-0100.  Hi, I'm [REDACTED_FIRST_NAME] [REDACTED_LAST_NAME] from [REDACTED_COMPANY_NA...  [{'label': 'first_name'}, {'label': 'last_name'}, {'label': 'company_name'},...
1                        Order 42 shipped to Springfield.                                             Order 42 shipped to [REDACTED_CITY].                                                              [{'label': 'city'}]

After, this PR at 88f4878:

$ python drive.py run --output out.csv --source data.csv --replace redact
Output written to: out.csv
Mode    : replace (redact)
Source  : data.csv
Output  : 2 rows, 405 B
Failed  : 4
Elapsed : 0.0s

--- Failed Records (4) ---
  detect   LLM timeout          x 3
  replace  bad]0;pwned[2J json  x 1

Tip: rerun with --debug to log each failed record, or pass --failed-output failed.csv to export them for triage.
$ python drive.py preview --source data.csv --replace redact
--- Preview: 2 records, replace (redact) ---

Record 1
  text           : Hi, I'm Jane Roe from Acme Corp. Call me on 555-0100.
  text_replaced  : Hi, I'm [REDACTED_FIRST_NAME] [REDACTED_LAST_NAME] from [REDACTED_COMPANY_NAME...
  final_entities : first_name, last_name, company_name, phone_number

Record 2
  text           : Order 42 shipped to Springfield.
  text_replaced  : Order 42 shipped to [REDACTED_CITY].
  final_entities : city

--- Failed Records (4) ---
  detect   LLM timeout          x 3
  replace  bad]0;pwned[2J json  x 1

Tip: rerun with --debug to log each failed record.

The ESC and BEL bytes in the crafted reason are gone.

Type of Change

  • Bug fix
  • New feature
  • Breaking change
  • Documentation update
  • Refactoring
  • CI, release, or contributor workflow update

Contributor Checklist

  • PR title follows Conventional Commits, for example fix: handle empty entity list
  • Related issue is linked, or a maintainer-owned no-issue reason is documented above
  • For non-trivial changes, a plan document is linked above, or the no-plan reason is documented above
  • Public API impact checked; skills/anonymizer/SKILL.md updated if needed
  • No real PII added to tests, docs, notebooks, fixtures, or artifacts
  • No API keys, service tokens, private keys, credentials, or real endpoint secrets added

Validation

  • Commands run, at 88f4878:
    • pytest (full suite): 1514 passed
    • make format-check (format and ruff), make typecheck, make copyright-check, uv lock --check: all clean
    • mkdocs build --strict: passes
    • tests/interface/cli/ drives app(["run", ...]) with the pipeline patched out; the new tests are table-driven and cover context rows per mode, failure grouping and order, Tip variants, truncation and control-byte stripping, entity labels, side-file round trips (csv, parquet, header-only), bad or read-only side-file and output paths rejected before the run, trace files parquet cannot store (mixed struct/str and all-empty dict columns), and the colour gate over TTY, NO_COLOR (including empty) and --no-color for both run and preview.
  • Skipped: make test-e2e needs live model endpoints; this change is output formatting after the pipeline returns.

Documentation and Artifacts

  • Docs updated, or not needed
  • If docs changed: make docs-build passes locally
  • If tutorial sources changed: notebooks regenerated by make convert-notebooks
  • If e2e, benchmark, or model-provider behavior changed: relevant validation is listed above

@codeant-ai

codeant-ai Bot commented Sep 23, 2026 •

Copy link
Copy Markdown

🤖 CodeAnt AI — Review Status

Status Commit Started (UTC) Finished (UTC)
✅ Incremental review completed 88f4878 Sep 30, 2026 · 14:14 14:15
✅ Incremental review completed 26664d4 Sep 30, 2026 · 12:30 12:31
✅ Incremental review completed adbdf62 Sep 30, 2026 · 05:44 05:45
✅ Incremental review completed bc8d267 Sep 30, 2026 · 02:25 02:26
✅ Reviewed your PR b37bff5 Sep 23, 2026 · 01:54 01:57

@codeant-ai

codeant-ai Bot commented Sep 23, 2026

Copy link
Copy Markdown

Thanks for using CodeAnt! 🎉

We're free for open-source projects. if you're enjoying it, help us grow by sharing.

Share on X ·
Reddit ·
LinkedIn

@coderabbitai

coderabbitai Bot commented Sep 23, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: e7da8479-d6d1-4235-8648-cd185a9209d4

📥 Commits

Reviewing files that changed from the base of the PR and between bc8d267 and adbdf62.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (4)
  • plans/105/cli-run-summary.md
  • src/anonymizer/interface/cli/_output.py
  • src/anonymizer/interface/cli/main.py
  • tests/interface/cli/test_cli_output.py

Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

This pull request adds configurable label examples and effective-label reporting to detection, adds formatted CLI output with optional trace and failed-record exports, and updates linked-issue checks for external pull requests. It also revises anonymizer skill benchmark results and related metadata.

Changes

Entity Label Configuration and Detection

Layer / File(s) Summary
Resolve label and example configuration
src/anonymizer/config/anonymizer_config.py, src/anonymizer/engine/detection/entity_label_examples.py, tests/config/test_anonymizer_config.py
Detect accepts normalized label examples. Configuration validation checks example shapes, active-label membership, exclusions, and empty effective label sets. The resolver returns effective labels and separate validator and augmenter examples.
Pass resolved labels through detection
src/anonymizer/engine/detection/*, src/anonymizer/engine/workflow_columns/detection/*, src/anonymizer/interface/anonymizer.py, tests/engine/test_detection_*, tests/interface/test_anonymizer_*, tests/test_debug_logging.py
Detection and exported detection builders receive configured examples. Prompts use resolved examples, and strict configurations filter final entities to allowed labels. Logging reports configured, effective, and excluded label counts; tests cover prompt handling and example redaction from logs and results.
Report effective labels and document behavior
src/anonymizer/interface/results.py, src/anonymizer/measurement/records/run.py, tools/measurement/*, tests/test_measurement.py, tests/tools/test_measurement_wandb_logging.py, docs/concepts/*, docs/development/observability.md, docs/troubleshooting.md, docs/notebook_source/*, docs/notebooks/*, docs/tutorials/index.md, skills/anonymizer/SKILL.md
Run and benchmark metadata include effective label lists and counts, with reporting tools preferring effective counts when available. Documentation describes label examples, exclusions, prompt handling, evaluation scope, and observability behavior.

CLI Run and Preview Output

Layer / File(s) Summary
Add run exports and formatted output
src/anonymizer/interface/cli/_output.py, src/anonymizer/interface/cli/main.py, tests/interface/cli/test_cli_output.py, README.md, plans/105/cli-run-summary.md
run validates optional trace and failed-record paths, writes requested exports, measures elapsed time, and prints a summary. preview prints formatted records and grouped failures. Color output is gated by the option, TTY status, and NO_COLOR.

External Pull Request Issue Checks

Layer / File(s) Summary
Validate references and update check results
.github/workflows/pr-linked-issue.yml, tests/test_pr_linked_issue_workflow.py, pyproject.toml
The workflow reads the current PR body and checks all distinct issue references. It updates check comments and reruns checks after issue state or triage-label changes; non-404 API errors fail the relevant step.
Recheck and close unqualified pull requests
.github/workflows/pr-linked-issue.yml, CONTRIBUTING.md, .github/PULL_REQUEST_TEMPLATE.md, tests/test_pr_linked_issue_workflow.py
On first-attempt open or reopen events, the workflow rechecks references before closing a PR without an open, triaged issue. The keep-open label prevents closure. Contributor and template guidance describe the issue requirement and exceptions.

Anonymizer Skill Benchmark Refresh

Layer / File(s) Summary
Update benchmark results and attestation
skills/anonymizer/BENCHMARK.md, skills/anonymizer/skill-card.md, skills/anonymizer/skill.oms.sig
Benchmark scores, token figures, findings, evaluation details, skill version, and signed resource digests are updated.

Priority: ➖ Normal

Estimated code review effort: 4 (Complex) | ~60 minutes

Change: Feature

Sequence Diagram(s)

sequenceDiagram
  participant AnonymizerInterface
  participant EntityDetectionWorkflow
  participant Validator
  participant Augmenter
  AnonymizerInterface->>EntityDetectionWorkflow: configured labels and examples
  EntityDetectionWorkflow->>Validator: resolved labels and examples
  EntityDetectionWorkflow->>Augmenter: active labels and configured examples
Loading
sequenceDiagram
  participant CLI
  participant AnonymizerPipeline
  participant OutputWriters
  participant RunSummary
  CLI->>AnonymizerPipeline: run with input and output options
  AnonymizerPipeline-->>CLI: result
  CLI->>OutputWriters: write requested trace and failed records
  CLI->>RunSummary: print output metadata and grouped failures
Loading
sequenceDiagram
  participant GitHubEvents
  participant LinkedIssueWorkflow
  participant GitHubAPI
  GitHubEvents->>LinkedIssueWorkflow: PR or referenced-issue event
  LinkedIssueWorkflow->>GitHubAPI: fetch PR body and issue states
  GitHubAPI-->>LinkedIssueWorkflow: references, states, and labels
  LinkedIssueWorkflow->>GitHubAPI: update check or comment
  LinkedIssueWorkflow->>GitHubAPI: close PR if no valid issue and no exemption
Loading

Suggested reviewers: binaryaaron

Merge Risk: ⚪ Minimal · up to adbdf

The CLI run summary, preview output, and side-file export changes show no remaining merge-blocking concerns in the reviewed files.

Security Architecture Review

Security architecture risk: 🔵 Low · up to adbdf

The new exports are optional and use existing local file permissions. Their file-separation checks can miss different names referring to the same file, allowing an export to overwrite input or the main result. Exposure is localized; broader detection and automation changes were not fully assessed.

Retained concerns

  • Medium · security · inferred: New side-file exports enforce separation by resolved pathname, not underlying file identity. Existing hard links with different names can pass validation: a trace path linked to the main output can subsequently replace its user-facing contents with the internal trace, while a side-file alias of the source can overwrite input. The writer’s overwrite behavior predates this PR, but the new callers introduce these additional data-role collisions. Exposure requires an explicitly selected export and an existing alias, or control over the destination filesystem; ordinary relative-path and existing-symlink aliases are normalized.
Security review details

Security Blast Radius

  • inferred — The demonstrated exposure is within explicitly requested local exports and files writable by the invoking process. Filesystem aliasing can cross source, result, and diagnostic-file roles; the inspected export path does not itself confer additional filesystem privileges.

Security Findings and Attack Paths

  • inferred — A pre-existing trace.csv hard link to output.csv can satisfy pathname-based separation. The main write occurs first, followed by direct trace serialization through the same underlying file. Consequently, a successful summary can identify a main output whose contents have been replaced by the broader trace. This is a source-derived failure path, not an executed exploit.

Trust Boundaries and Controls

  • observed — Side-file preflight rejects unsupported formats, directories, equal resolved paths, and unsuitable existing ancestors before pipeline execution. Inspected production failure constructors use fixed diagnostic reasons, providing counterevidence to raw-record disclosure through the new failure summary and export.
  • observed — Validation errors still print exception text directly to stderr, including interpolated side-file paths. The same raw-error pattern was already reachable through output-path validation in the inspected baseline; this was not retained as an independent architecture concern because no expanded attacker authority was established.

Resilience and Maintainability Implications

  • inferred — Interruption, later-write failure, or concurrent destination changes can leave an incomplete set of exports. Preflight does not reserve file identity across the pipeline. Operators should not infer that missing completion output means no result or sensitive trace file was persisted.

Hardening Proposals

  • proposed — Strengthen export separation with existing-file identity checks and an explicit publication strategy that avoids writing through aliases. Document overwrite and partial-completion behavior rather than treating preflight as a reservation or transaction.
  • proposed — Describe trace exports as potentially sensitive internal datasets, with sharing and retention guidance distinct from presentation output. Extend terminal normalization to validation-error paths for consistent protection.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 29.75% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 158 functions across 29 files. (1 skipped… Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly and concisely describes the main changes: readable CLI run summaries and preview output.
Description check ✅ Passed The description directly explains the CLI output changes, side-file options, validation, testing, limitations, and documentation updates.
Full details: Docstring Coverage

Explanation

Docstring coverage is 29.75% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 158 functions across 29 files. (1 skipped: 1 unsupported.)

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codeant-ai codeant-ai Bot added the size:XL This PR changes 500-999 lines, ignoring generated files label Sep 23, 2026
@codeant-ai

codeant-ai Bot commented Sep 23, 2026

Copy link
Copy Markdown

User description

Related Issue

N/A (fork PR)

Plan Document

plans/cli-run-summary/cli-run-summary.md

Summary

The CLI output is hard to read. preview prints result.dataframe.to_string(max_colwidth=80), which wraps into a wide block, and run prints only Output written to: <path>. The CLI never shows failed_records, and any control bytes in record text go straight to the terminal.

This builds on the earlier CLI-display plan and folds in its four review comments.

run keeps Output written to: <path> as its first line, so scripts that parse it still work. Below it:

Mode    : replace (redact)
Source  : data.csv
Output  : 2 rows, 261 B
Elapsed : 1m 23s

--- Failed Records (4) ---
  detect   LLM timeout  x 3
  replace  boom         x 1

Tip: rerun with --debug to log each failed record, or pass --failed-output failed.csv to export them for triage.
  • Mode comes from result.replace_method / result.rewrite_config. It is not guessed from the column names.
  • Failures are grouped by (step, reason) and sorted by count.
  • --trace PATH writes the trace dataset and --failed-output PATH writes record_id, step, reason. Both are validated before the pipeline starts, so a bad suffix, or a path that matches --source, --output or the other side file, fails fast instead of after a long run.

preview prints one block per record with one column : value line per output column. Values are cut to the width reported by shutil.get_terminal_size(), and final_entities shows as a list of labels. The same failure block follows.

C0 and C1 control bytes are stripped from every record-derived string before printing, so a reason like \x1b]0;x\x07\x1b[2J... can't retitle or clear the terminal. Styling uses plain ANSI codes: bold headers, a green output path, a red failure header and dim keys. It is only on when stdout is a TTY, NO_COLOR is unset or empty, and --no-color was not passed. There are no new dependencies. All of the rendering is in interface/cli/_output.py, and the formatters return list[str], which keeps them testable without capturing stdout.

Left out on purpose:

  • a per-label entity table, which the pipeline already logs at INFO
  • rewrite-mode averages in the run summary
  • a source row count, which the result doesn't carry
  • side-file flags on preview

Limitation: truncation counts characters, not terminal cells, so wide CJK text can still wrap.

Type of Change

  • Bug fix
  • New feature
  • Breaking change
  • Documentation update
  • Refactoring
  • CI, release, or contributor workflow update

Contributor Checklist

  • PR title follows Conventional Commits, for example fix: handle empty entity list
  • Related issue is linked, or a maintainer-owned no-issue reason is documented above
  • For non-trivial changes, a plan document is linked above, or the no-plan reason is documented above
  • Public API impact checked; skills/anonymizer/SKILL.md updated if needed
  • No real PII added to tests, docs, notebooks, fixtures, or artifacts
  • No API keys, service tokens, private keys, credentials, or real endpoint secrets added

Validation

  • Commands run:
    • uv run --group dev pytest tests/interface/cli/: 105 passed. The first new test drives app(["run", ...]) and fails on unmodified source at the Mode line (assert 'replace (redact)' in 'Output written to: .../out.csv\n'). The other new tests are table-driven and cover context rows per mode, failure grouping and order, Tip variants, truncation and control-byte stripping, entity labels, side-file round trips (csv, parquet, header-only), bad side-file paths rejected before the run, and the colour gate over TTY, NO_COLOR (including empty) and --no-color.
    • uv run --group dev pytest: 1426 passed. make check is clean (format, ruff, typecheck, lock, SPDX).
    • End to end with the pipeline patched out and synthetic data: under a real pty the only escapes in run output are the styling codes, and the ESC/BEL bytes from a crafted failure reason are gone. With NO_COLOR=1 there are no escapes. Piped preview output is plain. anonymizer run --help lists --trace, --failed-output and --no-color.
  • Skipped checks or known failures: make test-e2e needs live model endpoints, and this change is output formatting after the pipeline returns.

Documentation and Artifacts

  • Docs updated, or not needed
  • If docs changed: make docs-build passes locally
  • If tutorial sources changed: notebooks regenerated by make convert-notebooks
  • If e2e, benchmark, or model-provider behavior changed: relevant validation is listed above

CodeAnt-AI Description

Make CLI run results easier to understand and troubleshoot

What Changed

  • run now shows the output location first, followed by the mode, source, row count, file size, elapsed time, and any requested side-file paths
  • Failed records are grouped by step and reason with counts, while sensitive terminal control characters are removed from displayed record text
  • Added --trace and --failed-output options for exporting the full trace and failed-record details; invalid or conflicting paths are rejected before processing starts
  • preview now displays one readable block per record, truncates values to the terminal width, and renders detected entity columns as label lists
  • Terminal styling is used only when appropriate and can be disabled with NO_COLOR or --no-color; usage documentation includes the new export options

Impact

✅ Clearer run results
✅ Faster failure triage
✅ Safer terminal output

💡 Usage Guide

Checking Your Pull Request

Every time you make a pull request, our system automatically looks through it. We check for security issues, mistakes in how you're setting up your infrastructure, and common code problems. We do this to make sure your changes are solid and won't cause any trouble later.

Talking to CodeAnt AI

Got a question or need a hand with something in your pull request? You can easily get in touch with CodeAnt AI right here. Just type the following in a comment on your pull request, and replace "Your question here" with whatever you want to ask:

@codeant-ai ask: Your question here

This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.

Example

@codeant-ai ask: Can you suggest a safer alternative to storing this secret?

Preserve Org Learnings with CodeAnt

You can record team preferences so CodeAnt AI applies them in future reviews. Reply directly to the specific CodeAnt AI suggestion (in the same thread) and replace "Your feedback here" with your input:

@codeant-ai: Your feedback here

This helps CodeAnt AI learn and adapt to your team's coding style and standards.

Example

@codeant-ai: Do not flag unused imports.

Retrigger review

Ask CodeAnt AI to review the PR again, by typing:

@codeant-ai: review

Check Your Repository Health

To analyze the health of your code repository, visit our dashboard at https://app.codeant.ai. This tool helps you identify potential issues and areas for improvement in your codebase, ensuring your repository maintains high standards of code health.

Comment thread src/anonymizer/interface/cli/_output.py Outdated
@codeant-ai

codeant-ai Bot commented Sep 23, 2026

Copy link
Copy Markdown

CodeAnt Nitpicks

3 code suggestions

1. A side-file path whose parent is unwritable or a regular file passes validation, so the expensive pipeline runs before the command discovers the invalid destination.

Incomplete implementation · src/anonymizer/interface/cli/main.py:242-244


2. If a trace or failed-record write fails, the command exits after leaving the main output and earlier side files, creating partial results despite reporting failure.

Incomplete implementation · src/anonymizer/interface/cli/main.py:304-310


3. Long column names are never bounded, so preview lines can exceed the terminal width even though the value width is calculated from that width.

Logic error · src/anonymizer/interface/cli/_output.py:157-164

@chethanuk

Copy link
Copy Markdown
Owner Author

Replies to the three CodeAnt nitpicks, all in bc8d267:

  1. Side-file parent not writable / is a file: fixed. --trace and --failed-output now check the nearest existing parent before the run and fail with --failed-output parent is not a writable directory: .... Before, --failed-output data.csv/failed.csv ran the pipeline, wrote out.csv, then failed on the side file. New case parent-is-a-file in test_run_rejects_bad_side_file_paths_before_running.
  2. Partial results when a side-file write fails: leaving as is. The main output is written first and is the expensive result; deleting it because the trace or failures export failed would throw away a finished run. The command still exits 1 with the failing path in the error, and with (1) the likely causes are now caught before the run.
  3. Long column names overflow preview width: fixed. Keys are cut to a third of the terminal width, so lines stay within it. Covered by test_preview_long_column_name_fits_terminal.

@codeant-ai codeant-ai Bot added size:XXL This PR changes 1000+ lines, ignoring generated files and removed size:XL This PR changes 500-999 lines, ignoring generated files labels Sep 30, 2026
@codeant-ai

codeant-ai Bot commented Sep 30, 2026

Copy link
Copy Markdown

User description

Related Issue

N/A (fork PR)

Plan Document

plans/cli-run-summary/cli-run-summary.md

Summary

The CLI output is hard to read. preview prints result.dataframe.to_string(max_colwidth=80), which wraps into a wide block, and run prints only Output written to: <path>. The CLI never shows failed_records, and any control bytes in record text go straight to the terminal.

This builds on the earlier CLI-display plan and folds in its four review comments.

run keeps Output written to: <path> as its first line, so scripts that parse it still work. Below it:

Mode    : replace (redact)
Source  : data.csv
Output  : 2 rows, 261 B
Elapsed : 1m 23s

--- Failed Records (4) ---
  detect   LLM timeout  x 3
  replace  boom         x 1

Tip: rerun with --debug to log each failed record, or pass --failed-output failed.csv to export them for triage.
  • Mode comes from result.replace_method / result.rewrite_config. It is not guessed from the column names.
  • Failures are grouped by (step, reason) and sorted by count.
  • --trace PATH writes the trace dataset and --failed-output PATH writes record_id, step, reason. Both are validated before the pipeline starts, so a bad suffix, or a path that matches --source, --output or the other side file, fails fast instead of after a long run.

preview prints one block per record with one column : value line per output column. Values are cut to the width reported by shutil.get_terminal_size(), and final_entities shows as a list of labels. The same failure block follows.

C0 and C1 control bytes are stripped from every record-derived string before printing, so a reason like \x1b]0;x\x07\x1b[2J... can't retitle or clear the terminal. Styling uses plain ANSI codes: bold headers, a green output path, a red failure header and dim keys. It is only on when stdout is a TTY, NO_COLOR is unset or empty, and --no-color was not passed. There are no new dependencies. All of the rendering is in interface/cli/_output.py, and the formatters return list[str], which keeps them testable without capturing stdout.

Left out on purpose:

  • a per-label entity table, which the pipeline already logs at INFO
  • rewrite-mode averages in the run summary
  • a source row count, which the result doesn't carry
  • side-file flags on preview

Limitation: truncation counts characters, not terminal cells, so wide CJK text can still wrap.

Type of Change

  • Bug fix
  • New feature
  • Breaking change
  • Documentation update
  • Refactoring
  • CI, release, or contributor workflow update

Contributor Checklist

  • PR title follows Conventional Commits, for example fix: handle empty entity list
  • Related issue is linked, or a maintainer-owned no-issue reason is documented above
  • For non-trivial changes, a plan document is linked above, or the no-plan reason is documented above
  • Public API impact checked; skills/anonymizer/SKILL.md updated if needed
  • No real PII added to tests, docs, notebooks, fixtures, or artifacts
  • No API keys, service tokens, private keys, credentials, or real endpoint secrets added

Validation

  • Commands run:
    • uv run --group dev pytest tests/interface/cli/: 105 passed. The first new test drives app(["run", ...]) and fails on unmodified source at the Mode line (assert 'replace (redact)' in 'Output written to: .../out.csv\n'). The other new tests are table-driven and cover context rows per mode, failure grouping and order, Tip variants, truncation and control-byte stripping, entity labels, side-file round trips (csv, parquet, header-only), bad side-file paths rejected before the run, and the colour gate over TTY, NO_COLOR (including empty) and --no-color.
    • uv run --group dev pytest: 1426 passed. make check is clean (format, ruff, typecheck, lock, SPDX).
    • End to end with the pipeline patched out and synthetic data: under a real pty the only escapes in run output are the styling codes, and the ESC/BEL bytes from a crafted failure reason are gone. With NO_COLOR=1 there are no escapes. Piped preview output is plain. anonymizer run --help lists --trace, --failed-output and --no-color.
  • Skipped checks or known failures: make test-e2e needs live model endpoints, and this change is output formatting after the pipeline returns.

Documentation and Artifacts

  • Docs updated, or not needed
  • If docs changed: make docs-build passes locally
  • If tutorial sources changed: notebooks regenerated by make convert-notebooks
  • If e2e, benchmark, or model-provider behavior changed: relevant validation is listed above

CodeAnt-AI Description

Add guided entity examples, readable CLI results, and stricter linked-issue checks

What Changed

  • Detection accepts positive examples for default and explicitly declared non-default entity labels, while validating labels, excluding configured examples from logs and results, and keeping examples out of evaluation.
  • CLI previews now show one readable, terminal-width-aware block per record, including grouped failures and sanitized record text.
  • CLI runs now report mode, source, output size, elapsed time, and failure groups while preserving the existing output-path first line.
  • run can export the full trace and failed records to CSV or Parquet, with invalid or conflicting paths rejected before processing.
  • External pull requests must reference an open, triaged issue; issue changes recheck linked PRs, and new or reopened invalid PRs are closed unless exempt.
  • Documentation and examples explain non-default labels, positive examples, exclusions, CLI exports, and updated linked-issue requirements.

Impact

✅ Domain-specific entity formats can be detected with representative examples
✅ Clearer preview output and failure triage
✅ Fewer wasted runs from invalid export paths
✅ External PRs are checked against current issue status

💡 Usage Guide

Checking Your Pull Request

Every time you make a pull request, our system automatically looks through it. We check for security issues, mistakes in how you're setting up your infrastructure, and common code problems. We do this to make sure your changes are solid and won't cause any trouble later.

Talking to CodeAnt AI

Got a question or need a hand with something in your pull request? You can easily get in touch with CodeAnt AI right here. Just type the following in a comment on your pull request, and replace "Your question here" with whatever you want to ask:

@codeant-ai ask: Your question here

This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.

Example

@codeant-ai ask: Can you suggest a safer alternative to storing this secret?

Preserve Org Learnings with CodeAnt

You can record team preferences so CodeAnt AI applies them in future reviews. Reply directly to the specific CodeAnt AI suggestion (in the same thread) and replace "Your feedback here" with your input:

@codeant-ai: Your feedback here

This helps CodeAnt AI learn and adapt to your team's coding style and standards.

Example

@codeant-ai: Do not flag unused imports.

Retrigger review

Ask CodeAnt AI to review the PR again, by typing:

@codeant-ai: review

Check Your Repository Health

To analyze the health of your code repository, visit our dashboard at https://app.codeant.ai. This tool helps you identify potential issues and areas for improvement in your codebase, ensuring your repository maintains high standards of code health.

@codeant-ai

codeant-ai Bot commented Sep 30, 2026

Copy link
Copy Markdown

User description

Related Issue

Upstream issue NVIDIA-NeMo/Anonymizer#105 (CLI output is hard to read). Upstream draft #125 holds only a plan; this PR implements it.

Plan Document

plans/cli-run-summary/cli-run-summary.md

Summary

The CLI output is hard to read. preview prints result.dataframe.to_string(max_colwidth=80), which wraps into a wide block, and run prints only Output written to: <path>. The CLI never shows failed_records, and any control bytes in record text go straight to the terminal.

This builds on the earlier CLI-display plan and folds in its four review comments.

run keeps Output written to: <path> as its first line, so scripts that parse it still work. Below it:

Mode    : replace (redact)
Source  : data.csv
Output  : 2 rows, 261 B
Elapsed : 1m 23s

--- Failed Records (4) ---
  detect   LLM timeout  x 3
  replace  boom         x 1

Tip: rerun with --debug to log each failed record, or pass --failed-output failed.csv to export them for triage.
  • Mode comes from result.replace_method / result.rewrite_config. It is not guessed from the column names.
  • Failures are grouped by (step, reason) and sorted by count.
  • --trace PATH writes the trace dataset and --failed-output PATH writes record_id, step, reason. Both are validated before the pipeline starts, so a bad suffix, a parent that is a file or not writable, or a path that matches --source, --output or the other side file, fails fast instead of after a long run.

preview prints one block per record with one column : value line per output column. Values are cut to the width reported by shutil.get_terminal_size() (column names to a third of it), and final_entities shows as a list of labels. The same failure block follows.

C0 and C1 control bytes are stripped from every record-derived string before printing, so a reason like \x1b]0;x\x07\x1b[2J... can't retitle or clear the terminal. Styling uses plain ANSI codes: bold headers, a red failure header and dim keys. The Output written to: line is never styled. Styling is only on when stdout is a TTY, NO_COLOR is unset or empty, and --no-color was not passed. There are no new dependencies. All of the rendering is in interface/cli/_output.py, and the formatters return list[str], which keeps them testable without capturing stdout.

Left out on purpose:

  • a per-label entity table, which the pipeline already logs at INFO
  • rewrite-mode averages in the run summary
  • a source row count, which the result doesn't carry
  • side-file flags on preview

Limitation: truncation counts characters, not terminal cells, so wide CJK text can still wrap.

Before / After

Captured with drive.py, a script that calls the real anonymizer.interface.cli.main.app(sys.argv[1:]) with Anonymizer patched to return a fixed synthetic result (2 rows, 4 failures, one reason containing \x1b]0;pwned\x07\x1b[2J), since the pipeline needs live model endpoints. COLUMNS=100, stdout piped through cat -v.

Before, main @ d13d260:

$ python drive.py run --source data.csv --replace redact --output out.csv
Output written to: out.csv
$ python drive.py preview --source data.csv --replace redact
                                                     text                                                                    text_replaced                                                                   final_entities
0  Hi, I'm Jane Roe from Acme Corp.\nCall me on 555-0100.  Hi, I'm [REDACTED_FIRST_NAME] [REDACTED_LAST_NAME] from [REDACTED_COMPANY_NA...  [{'label': 'first_name'}, {'label': 'last_name'}, {'label': 'company_name'},...
1                        Order 42 shipped to Springfield.                                             Order 42 shipped to [REDACTED_CITY].                                                              [{'label': 'city'}]

After, this PR @ bc8d267 (the ESC/BEL bytes in the crafted reason are stripped; cat -v shows none left):

$ python drive.py run --source data.csv --replace redact --output out.csv
Output written to: out.csv
Mode    : replace (redact)
Source  : data.csv
Output  : 2 rows, 405 B
Elapsed : 0.0s

--- Failed Records (4) ---
  detect   LLM timeout          x 3
  replace  bad json]0;pwned[2J  x 1

Tip: rerun with --debug to log each failed record, or pass --failed-output failed.csv to export them for triage.
$ python drive.py preview --source data.csv --replace redact
--- Preview: 2 records, replace (redact) ---

Record 1
  text           : Hi, I'm Jane Roe from Acme Corp. Call me on 555-0100.
  text_replaced  : Hi, I'm [REDACTED_FIRST_NAME] [REDACTED_LAST_NAME] from [REDACTED_COMPANY_NAME...
  final_entities : first_name, last_name, company_name, phone_number

Record 2
  text           : Order 42 shipped to Springfield.
  text_replaced  : Order 42 shipped to [REDACTED_CITY].
  final_entities : city

--- Failed Records (4) ---
  detect   LLM timeout          x 3
  replace  bad json]0;pwned[2J  x 1

Tip: rerun with --debug to log each failed record.

Review fixes, previous PR head b37bff5 vs bc8d267:

# b37bff5: python drive.py run --source data.csv --replace redact --output out.csv --failed-output data.csv/failed.csv ; ls out.csv
Error: Failed to write output data to path: /tmp/claude-1000/-home-chethan-opensource/7249d4f0-6f2d-416b-b41c-80b49329edf2/scratchpad/demo/data.csv/failed.csv
exit=1
out.csv
# bc8d267: same command
Error: --failed-output parent is not a writable directory: /tmp/claude-1000/-home-chethan-opensource/7249d4f0-6f2d-416b-b41c-80b49329edf2/scratchpad/demo/data.csv
exit=1
ls: cannot access 'out.csv': No such file or directory

# b37bff5: python drive.py run --source data.csv --replace redact --output out.csv, under a pty (script -qec), first line | cat -v
Output written to: ^[[32mout.csv^[[0m^M
# bc8d267: same
Output written to: out.csv^M

Type of Change

  • Bug fix
  • New feature
  • Breaking change
  • Documentation update
  • Refactoring
  • CI, release, or contributor workflow update

Contributor Checklist

  • PR title follows Conventional Commits, for example fix: handle empty entity list
  • Related issue is linked, or a maintainer-owned no-issue reason is documented above
  • For non-trivial changes, a plan document is linked above, or the no-plan reason is documented above
  • Public API impact checked; skills/anonymizer/SKILL.md updated if needed
  • No real PII added to tests, docs, notebooks, fixtures, or artifacts
  • No API keys, service tokens, private keys, credentials, or real endpoint secrets added

Validation

  • Commands run:
    • uv run --group dev pytest tests/interface/cli/: 108 passed. The first new test drives app(["run", ...]) and fails on unmodified source at the Mode line (assert 'replace (redact)' in 'Output written to: .../out.csv\n'). The other new tests are table-driven and cover context rows per mode, failure grouping and order, Tip variants, truncation and control-byte stripping, entity labels, side-file round trips (csv, parquet, header-only), bad side-file paths rejected before the run, and the colour gate over TTY, NO_COLOR (including empty) and --no-color.
    • uv run --group dev pytest: 1500 passed on bc8d267 (rebased on main d13d260). make check is clean (format, ruff, typecheck, lock, SPDX).
    • End to end with the pipeline patched out and synthetic data: under a real pty the only escapes in run output are the styling codes, and the ESC/BEL bytes from a crafted failure reason are gone. With NO_COLOR=1 there are no escapes. Piped preview output is plain. anonymizer run --help lists --trace, --failed-output and --no-color.
  • Skipped checks or known failures: make test-e2e needs live model endpoints, and this change is output formatting after the pipeline returns.

Documentation and Artifacts

  • Docs updated, or not needed
  • If docs changed: make docs-build passes locally
  • If tutorial sources changed: notebooks regenerated by make convert-notebooks
  • If e2e, benchmark, or model-provider behavior changed: relevant validation is listed above

CodeAnt-AI Description

Add domain-specific detection examples, readable CLI results, and stricter linked-issue checks

What Changed

  • Detection accepts positive examples for default and explicitly declared non-default entity labels, while exclusions and explicit label sets are enforced consistently.
  • CLI runs now show mode, source, output size, elapsed time, and grouped failures; previews render one sanitized, terminal-width-aware block per record.
  • run can export the full trace and failed records, with invalid side-file paths rejected before processing.
  • Linked-issue checks now require an open, triaged issue for external PRs, handle multiple references, close new or reopened invalid PRs, and recheck PRs when issue status or labels change.
  • Documentation, notebooks, logs, and measurement records describe the new detection examples and effective label scope without recording configured example values.

Impact

✅ Clearer CLI run and preview output
✅ Domain-specific entity formats detected with representative examples
✅ Failed records and pipeline traces available for triage
✅ External PRs blocked without an open, triaged issue

💡 Usage Guide

Checking Your Pull Request

Every time you make a pull request, our system automatically looks through it. We check for security issues, mistakes in how you're setting up your infrastructure, and common code problems. We do this to make sure your changes are solid and won't cause any trouble later.

Talking to CodeAnt AI

Got a question or need a hand with something in your pull request? You can easily get in touch with CodeAnt AI right here. Just type the following in a comment on your pull request, and replace "Your question here" with whatever you want to ask:

@codeant-ai ask: Your question here

This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.

Example

@codeant-ai ask: Can you suggest a safer alternative to storing this secret?

Preserve Org Learnings with CodeAnt

You can record team preferences so CodeAnt AI applies them in future reviews. Reply directly to the specific CodeAnt AI suggestion (in the same thread) and replace "Your feedback here" with your input:

@codeant-ai: Your feedback here

This helps CodeAnt AI learn and adapt to your team's coding style and standards.

Example

@codeant-ai: Do not flag unused imports.

Retrigger review

Ask CodeAnt AI to review the PR again, by typing:

@codeant-ai: review

Check Your Repository Health

To analyze the health of your code repository, visit our dashboard at https://app.codeant.ai. This tool helps you identify potential issues and areas for improvement in your codebase, ensuring your repository maintains high standards of code health.

@chethanuk

chethanuk commented Sep 30, 2026 •

Copy link
Copy Markdown
Owner Author

Review fixes pushed in adbdf62 (the branch was rebased with --signoff, so the earlier commits are now bc96ea4 and be6b32a with the same tree):

  • --color/--no-color is no longer on the shared CliOpts. It is declared on run and preview only, so validate --no-color is now rejected. Test: test_validate_does_not_accept_color_flag.
  • _format_elapsed and _format_size now compare after rounding: 59.96s shows 1m 00s and 1048575 B shows 1.0 MB (previously 60.0s and 1024.0 KB). Test: test_summary_units_roll_over_after_rounding.
  • _resolve_side_file rejects a --trace/--failed-output path that is an existing directory before the pipeline starts. Before, the run completed and only the write failed. Test: new path-is-a-directory case in test_run_rejects_bad_side_file_paths_before_running.
  • The plan moved to plans/105/cli-run-summary.md, as CONTRIBUTING.md requires, and now links upstream issues 125 and 105.
  • Every commit now has a Signed-off-by trailer.

I checked that each new test fails on bc8d267 and passes on adbdf62. tests/interface/cli: 121 passed. format-check, typecheck and copyright-check are clean.

Not changed here, because they are outside this branch:

  • The PR base: fork main (30b6d82) is behind upstream d13d260, which is why the diff shows 48 files. Syncing fork main with upstream fixes this. The branch itself sits on d13d260.
  • Before this goes upstream: a maintainer needs to triage issue 105, the upstream body needs a closing keyword for it, the branch needs a name like chethanuk/feature/105-cli-run-summary, and the owner of issue 125 needs to be told about the two deviations (no source row count, unstyled output path).

@codeant-ai codeant-ai Bot added size:XL This PR changes 500-999 lines, ignoring generated files and removed size:XXL This PR changes 1000+ lines, ignoring generated files labels Sep 30, 2026
@codeant-ai

codeant-ai Bot commented Sep 30, 2026

Copy link
Copy Markdown

User description

Related Issue

Upstream issue NVIDIA-NeMo/Anonymizer#105 (CLI output is hard to read). Upstream draft #125 holds only a plan; this PR implements it.

Plan Document

plans/105/cli-run-summary.md

Summary

The CLI output is hard to read. preview prints result.dataframe.to_string(max_colwidth=80), which wraps into a wide block, and run prints only Output written to: <path>. The CLI never shows failed_records, and any control bytes in record text go straight to the terminal.

This builds on the earlier CLI-display plan and folds in its four review comments.

run keeps Output written to: <path> as its first line, so scripts that parse it still work. Below it:

Mode    : replace (redact)
Source  : data.csv
Output  : 2 rows, 261 B
Elapsed : 1m 23s

--- Failed Records (4) ---
  detect   LLM timeout  x 3
  replace  boom         x 1

Tip: rerun with --debug to log each failed record, or pass --failed-output failed.csv to export them for triage.
  • Mode comes from result.replace_method / result.rewrite_config. It is not guessed from the column names.
  • Failures are grouped by (step, reason) and sorted by count.
  • --trace PATH writes the trace dataset and --failed-output PATH writes record_id, step, reason. Both are validated before the pipeline starts, so a bad suffix, a path that is an existing directory, a parent that is a file or not writable, or a path that matches --source, --output or the other side file, fails fast instead of after a long run.

preview prints one block per record with one column : value line per output column. Values are cut to the width reported by shutil.get_terminal_size() (column names to a third of it), and final_entities shows as a list of labels. The same failure block follows.

C0 and C1 control bytes are stripped from every record-derived string before printing, so a reason like \x1b]0;x\x07\x1b[2J... can't retitle or clear the terminal. Styling uses plain ANSI codes: bold headers, a red failure header and dim keys. The Output written to: line is never styled. Styling is only on when stdout is a TTY, NO_COLOR is unset or empty, and --no-color was not passed. --color/--no-color exists on run and preview only; validate prints nothing styled. There are no new dependencies. All of the rendering is in interface/cli/_output.py, and the formatters return list[str], which keeps them testable without capturing stdout.

Left out on purpose:

  • a per-label entity table, which the pipeline already logs at INFO
  • rewrite-mode averages in the run summary
  • a source row count, which the result doesn't carry
  • side-file flags on preview

Limitation: truncation counts characters, not terminal cells, so wide CJK text can still wrap.

Before / After

Captured with drive.py, a script that calls the real anonymizer.interface.cli.main.app(sys.argv[1:]) with Anonymizer patched to return a fixed synthetic result (2 rows, 4 failures, one reason containing \x1b]0;pwned\x07\x1b[2J), since the pipeline needs live model endpoints. COLUMNS=100, stdout piped through cat -v.

Before, main @ d13d260:

$ python drive.py run --source data.csv --replace redact --output out.csv
Output written to: out.csv
$ python drive.py preview --source data.csv --replace redact
                                                     text                                                                    text_replaced                                                                   final_entities
0  Hi, I'm Jane Roe from Acme Corp.\nCall me on 555-0100.  Hi, I'm [REDACTED_FIRST_NAME] [REDACTED_LAST_NAME] from [REDACTED_COMPANY_NA...  [{'label': 'first_name'}, {'label': 'last_name'}, {'label': 'company_name'},...
1                        Order 42 shipped to Springfield.                                             Order 42 shipped to [REDACTED_CITY].                                                              [{'label': 'city'}]

After, this PR @ bc8d267, now be6b32a after a sign-off rebase with an identical tree (the ESC/BEL bytes in the crafted reason are stripped; cat -v shows none left):

$ python drive.py run --source data.csv --replace redact --output out.csv
Output written to: out.csv
Mode    : replace (redact)
Source  : data.csv
Output  : 2 rows, 405 B
Elapsed : 0.0s

--- Failed Records (4) ---
  detect   LLM timeout          x 3
  replace  bad json]0;pwned[2J  x 1

Tip: rerun with --debug to log each failed record, or pass --failed-output failed.csv to export them for triage.
$ python drive.py preview --source data.csv --replace redact
--- Preview: 2 records, replace (redact) ---

Record 1
  text           : Hi, I'm Jane Roe from Acme Corp. Call me on 555-0100.
  text_replaced  : Hi, I'm [REDACTED_FIRST_NAME] [REDACTED_LAST_NAME] from [REDACTED_COMPANY_NAME...
  final_entities : first_name, last_name, company_name, phone_number

Record 2
  text           : Order 42 shipped to Springfield.
  text_replaced  : Order 42 shipped to [REDACTED_CITY].
  final_entities : city

--- Failed Records (4) ---
  detect   LLM timeout          x 3
  replace  bad json]0;pwned[2J  x 1

Tip: rerun with --debug to log each failed record.

Review fixes, previous PR head b37bff5 vs bc8d267:

# b37bff5: python drive.py run --source data.csv --replace redact --output out.csv --failed-output data.csv/failed.csv ; ls out.csv
Error: Failed to write output data to path: /tmp/claude-1000/-home-chethan-opensource/7249d4f0-6f2d-416b-b41c-80b49329edf2/scratchpad/demo/data.csv/failed.csv
exit=1
out.csv
# bc8d267: same command
Error: --failed-output parent is not a writable directory: /tmp/claude-1000/-home-chethan-opensource/7249d4f0-6f2d-416b-b41c-80b49329edf2/scratchpad/demo/data.csv
exit=1
ls: cannot access 'out.csv': No such file or directory

# b37bff5: python drive.py run --source data.csv --replace redact --output out.csv, under a pty (script -qec), first line | cat -v
Output written to: ^[[32mout.csv^[[0m^M
# bc8d267: same
Output written to: out.csv^M

Second review round, bc8d267 vs adbdf62:

# bc8d267: python -c "print(_format_elapsed(59.96), _format_size(1048575), _format_size(1024**3-1))"
60.0s 1024.0 KB 1024.0 MB
# adbdf62
1m 00s 1.0 MB 1.0 GB

# bc8d267: mkdir d.csv; python -c "print(_resolve_side_file('--trace', 'd.csv', {}))"
/home/chethan/.cache/anon-pr7-demo/d.csv
# adbdf62
anonymizer.interface.errors.InvalidConfigError: --trace path is a directory: /home/chethan/.cache/anon-pr7-demo/d.csv

# bc8d267: anonymizer validate --help | grep -i -- --no-color
│    --color --no-color            Style CLI output with ANSI colors when      │
# adbdf62: no output

Type of Change

  • Bug fix
  • New feature
  • Breaking change
  • Documentation update
  • Refactoring
  • CI, release, or contributor workflow update

Contributor Checklist

  • PR title follows Conventional Commits, for example fix: handle empty entity list
  • Related issue is linked, or a maintainer-owned no-issue reason is documented above
  • For non-trivial changes, a plan document is linked above, or the no-plan reason is documented above
  • Public API impact checked; skills/anonymizer/SKILL.md updated if needed
  • No real PII added to tests, docs, notebooks, fixtures, or artifacts
  • No API keys, service tokens, private keys, credentials, or real endpoint secrets added

Validation

  • Commands run:
    • uv run --group dev pytest tests/interface/cli/: 121 passed on adbdf62 (108 on bc8d267). The first new test drives app(["run", ...]) and fails on unmodified source at the Mode line (assert 'replace (redact)' in 'Output written to: .../out.csv\n'). The other new tests are table-driven and cover context rows per mode, failure grouping and order, Tip variants, truncation and control-byte stripping, entity labels, side-file round trips (csv, parquet, header-only), bad side-file paths rejected before the run, and the colour gate over TTY, NO_COLOR (including empty) and --no-color.
    • uv run --group dev pytest: 1500 passed on bc8d267 (rebased on main d13d260). On adbdf62: 1512 passed, 1 failed, tests/tools/test_measurement_ingress_completion.py::test_wandb_staging_allows_repeated_basename_in_foreign_intermediate_directory, which also fails on bc8d267 in the same environment and is outside this change. make format-check, make typecheck and make copyright-check are clean.
    • End to end with the pipeline patched out and synthetic data: under a real pty the only escapes in run output are the styling codes, and the ESC/BEL bytes from a crafted failure reason are gone. With NO_COLOR=1 there are no escapes. Piped preview output is plain. anonymizer run --help lists --trace, --failed-output and --no-color.
  • Skipped checks or known failures: make test-e2e needs live model endpoints, and this change is output formatting after the pipeline returns.

Documentation and Artifacts

  • Docs updated, or not needed
  • If docs changed: make docs-build passes locally
  • If tutorial sources changed: notebooks regenerated by make convert-notebooks
  • If e2e, benchmark, or model-provider behavior changed: relevant validation is listed above

CodeAnt-AI Description

Make CLI run results easier to understand and troubleshoot

What Changed

  • run now shows the mode, source, output size and row count, elapsed time, and grouped failed records while keeping the output path as the first line
  • preview displays one readable, terminal-width-safe block per record, including entity labels and sanitized values
  • run can export the full trace and failed records to CSV or Parquet files, including an empty failed-records file when no failures occur
  • Invalid side-file paths are rejected before processing starts, and terminal styling is limited to supported interactive output
  • Added CLI documentation and usage examples for trace and failure exports

Impact

✅ Faster failure triage
✅ Clearer run summaries
✅ Safer terminal output

💡 Usage Guide

Checking Your Pull Request

Every time you make a pull request, our system automatically looks through it. We check for security issues, mistakes in how you're setting up your infrastructure, and common code problems. We do this to make sure your changes are solid and won't cause any trouble later.

Talking to CodeAnt AI

Got a question or need a hand with something in your pull request? You can easily get in touch with CodeAnt AI right here. Just type the following in a comment on your pull request, and replace "Your question here" with whatever you want to ask:

@codeant-ai ask: Your question here

This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.

Example

@codeant-ai ask: Can you suggest a safer alternative to storing this secret?

Preserve Org Learnings with CodeAnt

You can record team preferences so CodeAnt AI applies them in future reviews. Reply directly to the specific CodeAnt AI suggestion (in the same thread) and replace "Your feedback here" with your input:

@codeant-ai: Your feedback here

This helps CodeAnt AI learn and adapt to your team's coding style and standards.

Example

@codeant-ai: Do not flag unused imports.

Retrigger review

Ask CodeAnt AI to review the PR again, by typing:

@codeant-ai: review

Check Your Repository Health

To analyze the health of your code repository, visit our dashboard at https://app.codeant.ai. This tool helps you identify potential issues and areas for improvement in your codebase, ensuring your repository maintains high standards of code health.

@codeant-ai

codeant-ai Bot commented Sep 30, 2026

Copy link
Copy Markdown

User description

Related Issue

Closes #105 when raised upstream. Issue is not yet triaged; the branch name should follow CONTRIBUTING (for example chethanuk/feature/105-cli-run-summary) at raise time.

Plan Document

plans/105/cli-run-summary.md. It supersedes the plan in the open upstream draft PR 125, following its review threads.

Summary

preview prints result.dataframe.to_string(max_colwidth=80), which wraps into a wide block, and run prints only Output written to: <path>. The CLI never shows failed_records, and control bytes in record text go straight to the terminal.

run keeps Output written to: <path> as its first line (never styled), so scripts that parse it still work. Below it come Mode, Source, Output rows and size, a Failed count and Elapsed, then failed records grouped by (step, reason) with a count, most frequent first.

  • Mode comes from result.replace_method / result.rewrite_config, not from column names.
  • --trace PATH writes the trace dataset and --failed-output PATH writes record_id, step, reason. --output, --trace and --failed-output are validated before the pipeline starts: bad suffix, existing directory, unwritable parent, existing file that is read-only, or a path equal to --source or another output.
  • The failure report is written before the trace, and the summary is always printed. A trace that parquet cannot store exits 1 with the normal Error: message instead of a traceback, and the summary lists only files actually written.
  • preview prints one block per record with one column : value line per output column, values cut to shutil.get_terminal_size(), and final_entities shown as labels.
  • C0 and C1 control bytes are stripped from every record-derived string before printing.
  • Styling (bold headers, red failure header, dim keys) uses plain ANSI codes and is on only for a TTY with NO_COLOR unset or empty and no --no-color. --color/--no-color is on run and preview only. No new dependencies.
  • docs/troubleshooting.md no longer says the CLI cannot show failed rows; it points at --trace and --failed-output.

Left out, already covered above or in the plan: a per-label entity table, rewrite averages, a source row count (the result does not carry one), side-file flags on preview.

Limitations: truncation counts characters, not terminal cells, so wide CJK text can still wrap. --trace parquet is only tested with synthetic traces, not a real pipeline trace. If a side-file write fails after the run, the main output is kept and the command exits 1.

Before / After

Both blocks come from one driver, drive.py, run for real at each commit. It calls the real anonymizer.interface.cli.main.app(sys.argv[1:]) with Anonymizer patched to return one synthetic result, because the pipeline needs live model endpoints. The result has 2 records (the first contains a newline), matching text_replaced and final_entities, and 4 failures, one with a reason containing \x1b]0;pwned\x07\x1b[2J. COLUMNS=100, output piped through cat -v.

Before, main at d13d260:

$ python drive.py run --output out.csv --source data.csv --replace redact
Output written to: out.csv
$ python drive.py preview --source data.csv --replace redact
                                                     text                                                                    text_replaced                                                                   final_entities
0  Hi, I'm Jane Roe from Acme Corp.\nCall me on 555-0100.  Hi, I'm [REDACTED_FIRST_NAME] [REDACTED_LAST_NAME] from [REDACTED_COMPANY_NA...  [{'label': 'first_name'}, {'label': 'last_name'}, {'label': 'company_name'},...
1                        Order 42 shipped to Springfield.                                             Order 42 shipped to [REDACTED_CITY].                                                              [{'label': 'city'}]

After, this PR at 88f4878:

$ python drive.py run --output out.csv --source data.csv --replace redact
Output written to: out.csv
Mode    : replace (redact)
Source  : data.csv
Output  : 2 rows, 405 B
Failed  : 4
Elapsed : 0.0s

--- Failed Records (4) ---
  detect   LLM timeout          x 3
  replace  bad]0;pwned[2J json  x 1

Tip: rerun with --debug to log each failed record, or pass --failed-output failed.csv to export them for triage.
$ python drive.py preview --source data.csv --replace redact
--- Preview: 2 records, replace (redact) ---

Record 1
  text           : Hi, I'm Jane Roe from Acme Corp. Call me on 555-0100.
  text_replaced  : Hi, I'm [REDACTED_FIRST_NAME] [REDACTED_LAST_NAME] from [REDACTED_COMPANY_NAME...
  final_entities : first_name, last_name, company_name, phone_number

Record 2
  text           : Order 42 shipped to Springfield.
  text_replaced  : Order 42 shipped to [REDACTED_CITY].
  final_entities : city

--- Failed Records (4) ---
  detect   LLM timeout          x 3
  replace  bad]0;pwned[2J json  x 1

Tip: rerun with --debug to log each failed record.

The ESC and BEL bytes in the crafted reason are gone.

Type of Change

  • Bug fix
  • New feature
  • Breaking change
  • Documentation update
  • Refactoring
  • CI, release, or contributor workflow update

Contributor Checklist

  • PR title follows Conventional Commits, for example fix: handle empty entity list
  • Related issue is linked, or a maintainer-owned no-issue reason is documented above
  • For non-trivial changes, a plan document is linked above, or the no-plan reason is documented above
  • Public API impact checked; skills/anonymizer/SKILL.md updated if needed
  • No real PII added to tests, docs, notebooks, fixtures, or artifacts
  • No API keys, service tokens, private keys, credentials, or real endpoint secrets added

Validation

  • Commands run, at 88f4878:
    • pytest (full suite): 1514 passed
    • make format-check (format and ruff), make typecheck, make copyright-check, uv lock --check: all clean
    • mkdocs build --strict: passes
    • tests/interface/cli/ drives app(["run", ...]) with the pipeline patched out; the new tests are table-driven and cover context rows per mode, failure grouping and order, Tip variants, truncation and control-byte stripping, entity labels, side-file round trips (csv, parquet, header-only), bad or read-only side-file and output paths rejected before the run, trace files parquet cannot store (mixed struct/str and all-empty dict columns), and the colour gate over TTY, NO_COLOR (including empty) and --no-color for both run and preview.
  • Skipped: make test-e2e needs live model endpoints; this change is output formatting after the pipeline returns.

Documentation and Artifacts

  • Docs updated, or not needed
  • If docs changed: make docs-build passes locally
  • If tutorial sources changed: notebooks regenerated by make convert-notebooks
  • If e2e, benchmark, or model-provider behavior changed: relevant validation is listed above

CodeAnt-AI Description

Make CLI run results easier to read and troubleshoot

What Changed

  • run now shows the mode, source, output size and row count, elapsed time, failure count, and grouped failure reasons while keeping the output path as the first line
  • preview displays each record as readable, terminal-width-aware field/value blocks, including entity labels
  • run can export the full trace dataset with --trace and failed records with --failed-output; invalid or conflicting paths are rejected before processing
  • Terminal control characters are removed from displayed record data, and optional styling is limited to interactive terminals with --color and --no-color controls
  • CLI documentation now covers failure exports and trace output

Impact

✅ Clearer run results
✅ Faster failure triage
✅ Safer terminal output

💡 Usage Guide

Checking Your Pull Request

Every time you make a pull request, our system automatically looks through it. We check for security issues, mistakes in how you're setting up your infrastructure, and common code problems. We do this to make sure your changes are solid and won't cause any trouble later.

Talking to CodeAnt AI

Got a question or need a hand with something in your pull request? You can easily get in touch with CodeAnt AI right here. Just type the following in a comment on your pull request, and replace "Your question here" with whatever you want to ask:

@codeant-ai ask: Your question here

This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.

Example

@codeant-ai ask: Can you suggest a safer alternative to storing this secret?

Preserve Org Learnings with CodeAnt

You can record team preferences so CodeAnt AI applies them in future reviews. Reply directly to the specific CodeAnt AI suggestion (in the same thread) and replace "Your feedback here" with your input:

@codeant-ai: Your feedback here

This helps CodeAnt AI learn and adapt to your team's coding style and standards.

Example

@codeant-ai: Do not flag unused imports.

Retrigger review

Ask CodeAnt AI to review the PR again, by typing:

@codeant-ai: review

Check Your Repository Health

To analyze the health of your code repository, visit our dashboard at https://app.codeant.ai. This tool helps you identify potential issues and areas for improvement in your codebase, ensuring your repository maintains high standards of code health.

run keeps its "Output written to:" first line (never styled) and adds a
short context block (mode, source, output rows and size, elapsed), failed
records grouped by step and reason, and --trace / --failed-output side
files. Side-file paths are validated before the pipeline starts.
preview prints one block per record with values cut to the terminal
width. C0/C1 control bytes from record text are stripped before printing.
Styling is on only for a TTY with NO_COLOR unset and --no-color absent;
--color/--no-color exists on run and preview only.

Signed-off-by: ChethanUK <chethanuk@outlook.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XL This PR changes 500-999 lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant