Skip to content

feat(findings): render RCA readably and surface detector verdicts in findings get#59

Open
dark-sorceror wants to merge 5 commits into
mainfrom
feat/findings-get-render
Open

feat(findings): render RCA readably and surface detector verdicts in findings get#59
dark-sorceror wants to merge 5 commits into
mainfrom
feat/findings-get-render

Conversation

@dark-sorceror

@dark-sorceror dark-sorceror commented Jul 11, 2026

Copy link
Copy Markdown
Collaborator

Summary

Fixes #52

findings get is the payoff command of the detector feature, but its human output undersold the result three ways: the RCA body printed as one raw unwrapped blob with literal ##/** markdown markers, the "Category" line showed only the detector template name instead of the detector's actual classification, and the command dead-ended after the RCA with no link back to the trace or hint at what to run next. This PR fixes all three, keeping --json byte-for-byte unchanged.

How it works

finding.rca.result (raw LLM text, markdown + long lines)
  → wrapMarkdown(text, terminalWidth, styler.bold)   [src/render/wrap.ts]
      strip/style headings, **bold**, `code` spans
      hanging-indent list items
      greedy-fill paragraphs to width
  → printed under `RCA:`

finding.results[i]
  → Identified: yes/no                (result.identified)
  → Category: <data.category> (template: <template>)   when result.data.category is a usable string
             <template label>                            fallback otherwise

renderFinding footer
  → best-effort client.getTrace(finding.trace_id) → trace_url
      success → "View in TraceRoot: <url>"
      failure → link omitted, hints still shown (never fails the command)
  → "run 'traceroot traces get <trace-id>' ..."
  → "run 'traceroot traces export <trace-id>' ..."

wrapMarkdown is a pure string transform (no I/O, no width detection) so it's independently unit-testable; renderFinding passes process.stdout.columns ?? 80 and styler.bold. The trace-URL fetch only happens in human mode — --json returns the bare FindingDetail with no extra API call, same as before.

Before:

RCA:
## Root Cause
The **tool call** failed because this trace.git_repo is null. Let me examine the error spans.The failing_tool span shows a timeout after 30s with no retry configured in the agent's tool_choice policy, which is a long line that just keeps going well past a normal terminal width without ever wrapping...

After:

RCA:
Root Cause
The tool call failed because this trace.git_repo is null. Let me
examine the error spans. The failing_tool span shows a timeout
after 30s with no retry configured in the agent's tool_choice
policy.

View in TraceRoot: https://app.traceroot.ai/t/...
run 'traceroot traces get <trace-id>' for spans and context
run 'traceroot traces export <trace-id>' to save a full bundle

What's included

Rendering

  • src/render/wrap.ts (new) — wrapMarkdown(text, width, bold?): width-aware wrapping with minimal markdown treatment (heading/**bold**/`code` markers stripped or styled, never printed literally; list items get a hanging indent so continuations align under the item text, not the marker).

Findings get

  • src/commands/findings/get.ts
    • renderFinding now wraps finding.rca.result through wrapMarkdown instead of splitting on \n and printing verbatim.
    • New dataCategory / detectorCategory helpers: prefer result.data.category (loosely typed, only trusted when a non-empty string) annotated with the raw template id, e.g. Tool call error (template: failure); fall back to the existing template→label map when data has no usable category.
    • New Identified: line per result, surfacing result.identified.
    • runGet does a best-effort client.getTrace(finding.trace_id) (human mode only) to print a View in TraceRoot: link from the backend-provided trace_url; any failure degrades silently to no link. The output always ends with traceroot traces get <trace-id> / traceroot traces export <trace-id> hints, closing the loop that traces get's finding block opens.

Tests

  • tests/render/wrap.test.ts (new) — 12 unit tests covering width wrapping, paragraph joining, blank-line paragraph separation, heading/bold/code marker handling with and without a styler, list hanging indent, and the long-single-word edge case.
  • tests/commands/findings-get.test.ts — extended with cases for the data.category fallback matrix (valid string, wrong type, empty string, null, non-object, array), the Identified: line, long-paragraph wrapping, literal-markdown stripping, the trace-link footer (present and omitted-on-error), and a --json assertion that no extra getTrace call is made.

Test plan

  • npm test — 611/611 vitest tests pass, including 12 new wrap.ts unit tests and the extended findings-get suite (data-category fallback matrix, Identified: line, wrap/markdown-stripping cases, footer link present/omitted, --json no-extra-getTrace assertion)
  • Typecheck and lint/format clean
  • Live e2e against a real finding with a completed RCA: zero literal ##/** in the output, RCA wrapped at 80 columns, Identified: yes and Category: Tool call error (template: failure) rendered correctly, footer shows the backend-provided View in TraceRoot URL plus both traces get/traces export hints
  • --json byte-shape unchanged and issues no extra API call (verified no getTrace fetch happens in JSON mode)

Known minor limitations (non-blocking, within the "minimal markdown" scope this issue asked for): an unclosed ** passes through literally rather than being auto-closed, and fenced code blocks get reflowed like ordinary paragraphs rather than preserved verbatim.


Summary by cubic

Improves findings get human output by wrapping and lightly styling RCA text, surfacing detector verdicts, and adding a trace link with next-step hints. Fixes #52 while keeping --json output byte-for-byte unchanged and with no extra API calls.

  • New Features

    • Wrap RCA to terminal width via wrapMarkdown; strip heading/bold/code markers and apply bold where supported.
    • Show Identified: yes/no and improved Category: per result: prefer result.data.category (adds (template: <id>)), else fall back to the template label.
    • Add footer: "View in TraceRoot: " when available, plus traceroot traces get <id> and traceroot traces export <id> hints; trace URL is fetched only in human mode and failures degrade silently.
  • Bug Fixes

    • Preserve punctuation adjacency around bold spans so no extra spaces appear (e.g., **Note**: renders as Note:).
    • Keep indented continuation lines inside their list items so wrapped bullets align with a hanging indent.

Written for commit 052478e. Summary will update on new commits.

Review in cubic

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 4 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread src/render/wrap.ts Outdated
Comment thread src/render/wrap.ts
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

findings get buries the RCA in raw text, hides the detector's classification, and dead-ends the workflow

1 participant