feat(report): redesign hunt report to match the run report - #233
Conversation
The autonomous hunt report had drifted into a visually separate product from the `opfor run` report: its own colour tokens (amber accent vs red), its own type scale, a generic shield glyph instead of the Opfor wordmark, heavy card shadows, and a bespoke turn view with no navigation. Both renderers also inlined their own copy of the brand SVGs, so any rebrand had to happen twice. Rebuild the hunt renderer on the run report's visual language — black cover band, shared wordmark, red accent, section rhythm, exec strip with the safety gauge, and the transcript + turn rail with scroll-spy — on a slightly roomier type scale, since a hunt carries far more per screen than a suite run. Hunt-specific sections (recon fingerprint, vuln-class matrix, attack tree, decision log, inventions) are kept, rebuilt on the shared card primitives. Also: - Extract the wordmark + icons to report/brand.ts so both renderers share one copy. Verified byte-identical before moving; the run report is visually unchanged by it. - Surface the scout and verifier models. Both were configurable but absent from AutonomousReport, so neither could be rendered; the report showed only commander and operator despite hunt running three agents. - Give every thread a finding card with its transcript. Defended threads were previously bare chips, so runs with no confirmed findings — the common case — had no inspectable transcripts at all. - Fix executive-summary label alignment in both reports: exec-strip-item centred its content vertically, so cards with differing content heights put every label at a different height. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
WalkthroughThe PR adds scout and verifier model metadata to autonomous reports. It replaces the autonomous HTML report with a responsive hunt report containing scoring, navigation, findings, recommendations, appendices, and interactive transcripts. Shared formatting utilities and SVG branding assets support report rendering. ChangesAutonomous report
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant runAutonomous
participant AutonomousReport
participant autonomousHtmlReport
participant reportFormat
runAutonomous->>AutonomousReport: record scoutModel and verifierModel
AutonomousReport->>autonomousHtmlReport: provide report data
autonomousHtmlReport->>reportFormat: format and escape report values
reportFormat-->>autonomousHtmlReport: return formatted values and SVG gauge
autonomousHtmlReport-->>AutonomousReport: render hunt report HTML
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (4)
core/src/autonomous/orchestrator/run.ts (1)
340-341: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winConsider recording
verifierModelonly when verification ran.Line 97 sets
verifyEnabled = options.verify && Boolean(process.env.ANTHROPIC_API_KEY). WhenverifyEnabledis false, noself_checktool is granted and no verifier runs. Line 341 still records a verifier model. The hunt report then renders a "Verifier — independent self-check" row (seecore/src/autonomous/report/html.tsline 771) for a run that performed no verification.Gate the field on
verifyEnabledso the report reflects what actually ran.♻️ Proposed change
report.scoutModel = options.scoutModel; - report.verifierModel = options.verifierModel ?? options.commanderModel; + report.verifierModel = verifyEnabled + ? (options.verifierModel ?? options.commanderModel) + : undefined;The renderer already falls back to
"—"when the field is unset.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@core/src/autonomous/orchestrator/run.ts` around lines 340 - 341, Update the report model assignment in the run orchestration flow to set report.verifierModel only when verifyEnabled is true; otherwise leave it unset so the renderer displays its existing fallback. Keep report.scoutModel assignment and the existing verifier-model fallback unchanged.core/src/autonomous/report/html.ts (1)
28-49: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winFinish the shared-report extraction that
core/src/report/brand.tsstarted. This PR moved the static SVG assets intocore/src/report/brand.tsbut left the behavioral report code copied betweencore/src/autonomous/report/html.tsandcore/src/report/render.ts. Both copies will drift as one report evolves. One extraction removes both duplicates.
core/src/autonomous/report/html.ts#L28-L49: movetruncate,formatDuration,safetyColor,gaugeSvg(lines 60-69), androleLabel(lines 83-85) into a shared module such ascore/src/report/format.ts, then import them here and incore/src/report/render.ts.core/src/autonomous/report/html.ts#L866-L949: extract the copy-button handler and the transcript toggle plus turn-rail scroll-spy into a shared script constant, and keep only the rail-fade additions local to this report.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@core/src/autonomous/report/html.ts` around lines 28 - 49, The report helpers truncate, formatDuration, safetyColor, gaugeSvg, and roleLabel are duplicated across report implementations; move them into a shared core/src/report/format.ts module, then import and use them from core/src/autonomous/report/html.ts (28-49) and core/src/report/render.ts. Also extract the shared copy-button, transcript-toggle, and turn-rail scroll-spy behavior from core/src/autonomous/report/html.ts (866-949) into a shared script constant, leaving only rail-fade additions local to html.ts.core/src/report/brand.ts (1)
7-38: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueConsider namespacing the gradient ids.
The logo defines twelve gradient ids named
paint0_rthroughpaint11_r, and the paths reference them withfill="url(#paintN_r)". SVG ids share the HTML document id space. Both renderers embed the logo once per document today (core/src/report/render.tsline 435 andcore/src/autonomous/report/html.tsline 690), so the references resolve correctly.The names come from a default export and are likely to collide if a report later embeds the logo twice or embeds a second exported SVG. Prefix the ids, for example
opfor-logo-grad-0, to make the asset safe to reuse.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@core/src/report/brand.ts` around lines 7 - 38, Namespace all gradient IDs in OPFOR_LOGO_SVG with an asset-specific prefix, such as opfor-logo-grad-0 through opfor-logo-grad-11, and update every corresponding fill="url(#...)" reference to match. Keep the gradient definitions and visual output unchanged while ensuring repeated logo embeddings cannot collide.core/src/report/render.ts (1)
320-320: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueRemove the unused
.gauge-subrule.
.gauge-subis only defined incore/src/report/render.tsand is not emitted by the report template.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@core/src/report/render.ts` at line 320, Remove the unused .gauge-sub CSS rule from the report styles in render.ts, leaving the report template and all other style rules unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@core/src/autonomous/report/html.ts`:
- Around line 830-836: Update the Findings subtitle in the findings section to
use labels that explicitly identify its values as finding counts, distinguishing
them from the thread counts shown in the Executive Summary. Preserve the
existing fails, defended, and errored calculations and conditional rail-marker
text while removing ambiguity around “confirmed” and “defended.”
---
Nitpick comments:
In `@core/src/autonomous/orchestrator/run.ts`:
- Around line 340-341: Update the report model assignment in the run
orchestration flow to set report.verifierModel only when verifyEnabled is true;
otherwise leave it unset so the renderer displays its existing fallback. Keep
report.scoutModel assignment and the existing verifier-model fallback unchanged.
In `@core/src/autonomous/report/html.ts`:
- Around line 28-49: The report helpers truncate, formatDuration, safetyColor,
gaugeSvg, and roleLabel are duplicated across report implementations; move them
into a shared core/src/report/format.ts module, then import and use them from
core/src/autonomous/report/html.ts (28-49) and core/src/report/render.ts. Also
extract the shared copy-button, transcript-toggle, and turn-rail scroll-spy
behavior from core/src/autonomous/report/html.ts (866-949) into a shared script
constant, leaving only rail-fade additions local to html.ts.
In `@core/src/report/brand.ts`:
- Around line 7-38: Namespace all gradient IDs in OPFOR_LOGO_SVG with an
asset-specific prefix, such as opfor-logo-grad-0 through opfor-logo-grad-11, and
update every corresponding fill="url(#...)" reference to match. Keep the
gradient definitions and visual output unchanged while ensuring repeated logo
embeddings cannot collide.
In `@core/src/report/render.ts`:
- Line 320: Remove the unused .gauge-sub CSS rule from the report styles in
render.ts, leaving the report template and all other style rules unchanged.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 75b98b40-dfa6-41fd-abc0-3fae754b107b
📒 Files selected for processing (5)
core/src/autonomous/orchestrator/run.tscore/src/autonomous/report/html.tscore/src/autonomous/report/types.tscore/src/report/brand.tscore/src/report/render.ts
Attack Tree claimed "9 threads" directly above a tree rendering 6 nodes. summary.threads is set to findings.length in mapRunLog, but one thread can produce several findings, so every thread-labelled number was really a finding count. Derive the true count from distinct threadIds and use it for the tree subtitle, the exploration row, and the narrative; headline the executive card as "Findings" since its dots are per-finding verdicts, with the thread count as its subline. Also say "confirmed findings" in the Findings subtitle so it can't be read as a thread count. Record verifierModel only when verification actually ran. verifyEnabled gates whether the self_check tool is granted, so recording it unconditionally made the report advertise a verifier for runs that never verified. Finish the extraction brand.ts started: esc, truncate, formatDuration, formatTokenCount, safetyColor, gaugeSvg, roleLabel and SEV_HEX/SEV_ORDER were byte-identical copies in both renderers. They now live in report/format.ts. Confirmed behaviour-neutral by diffing rendered output before and after. Namespace the logo's twelve gradient ids (paintN_r → opfor-logo-grad-N). SVG ids share the document id space, so the generic names would collide if a report ever embedded the wordmark twice. Drop the unused .gauge-sub rule, which no template emitted. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Went through the four nitpicks — three applied as suggested, one applied in part. All in 44be1b4. ✅
|
There was a problem hiding this comment.
🧹 Nitpick comments (1)
core/src/report/format.ts (1)
49-54: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winMake
SEV_HEXexhaustive overSeverity.
Record<string, string>does not require a color for everySeverity. A future severity can therefore produceundefinedin generated CSS. IfSeverityis the intended palette domain, useRecord<Severity, string>.Proposed type tightening
-export const SEV_HEX: Record<string, string> = { +export const SEV_HEX: Record<Severity, string> = {🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@core/src/report/format.ts` around lines 49 - 54, Update the SEV_HEX declaration to use Record<Severity, string> instead of Record<string, string>, ensuring every Severity value requires an explicit color entry while preserving the existing palette mappings.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@core/src/report/format.ts`:
- Around line 49-54: Update the SEV_HEX declaration to use Record<Severity,
string> instead of Record<string, string>, ensuring every Severity value
requires an explicit color entry while preserving the existing palette mappings.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 4445f995-7bfc-44a0-a3c5-95c2f278c5b6
📒 Files selected for processing (5)
core/src/autonomous/orchestrator/run.tscore/src/autonomous/report/html.tscore/src/report/brand.tscore/src/report/format.tscore/src/report/render.ts
🚧 Files skipped from review as they are similar to previous changes (4)
- core/src/report/brand.ts
- core/src/autonomous/orchestrator/run.ts
- core/src/report/render.ts
- core/src/autonomous/report/html.ts
Problem
The
opfor huntreport and theopfor runreport read as two different products.core/src/autonomous/report/html.tshad drifted fromcore/src/report/render.tson almost every axis: a different accent colour (amber#f5ad5cvs the brand red#FF4D4F), a navy gradient cover instead of the black band, a generic shield glyph instead of the Opfor wordmark, heavybox-shadowon every card, a smaller type scale, and a bespoke turn view with no navigation. Anyone running both commands got two visibly unrelated documents.Three concrete defects behind that:
opfor huntruns three agents (commander / operator / scout,--scout-modeldefaulting tohaiku) plus a separate verifier for self-checks, butAutonomousReportonly declaredcommanderModelandoperatorModel. The other two were configurable yet absent from the report type, so the report claimed two agents where there were four.threadIdchip. Since most hunts finish with zero confirmed findings, the common case produced a report with no inspectable conversation at all.Separately, the executive summary in both reports rendered its labels at different heights:
.exec-strip-itemusedjustify-content:center, so each card vertically centred its own content, and cards hold differing amounts (verdict + risk badge + subline vs. gauge vs. value + dot rows). The labels stepped downward across the strip.Solution
Rebuild the hunt renderer on the run report's visual language — black cover band, shared wordmark, red accent, numbered section rhythm, executive strip with the semicircular safety gauge, and the collapsible transcript with a turn rail (click-to-jump + scroll-spy) — on a slightly roomier type scale, because a hunt carries far more per screen than a suite run (9 sections vs. 3).
Hunt-specific content is kept, not dropped, and rebuilt on the shared card/table primitives instead of bespoke CSS: recon fingerprint with guardrails/weak-points, vulnerability-class matrix, attack tree, recommendations, response patterns, invented techniques, and the decision log.
Two additions on top of the design port:
Safety score is derived as the inverse of attack success rate (
N/Awhen nothing was conclusively scored) so hunt reports carry the same 0–100 headline metric as run reports. Duration is computed fromstartedAt→generatedAt, guarded because older reports predatestartedAt.Changes
core/(@keyvaluesystems/agent-opfor-core)core/src/report/brand.tscore/src/report/render.tscore/src/autonomous/report/html.tscore/src/autonomous/report/types.tsAutonomousReportgains optionalscoutModelandverifierModel. Optional so existing report JSON still parses.core/src/autonomous/orchestrator/run.tsselfCheck.ts.No runner changes —
writeAutonomousReportand the CLI/SDK entry points are untouched.Issue
N/A
How to test
npm run build npm run typecheck npm testThen render a hunt report. Any existing run works — no need to spend a live hunt:
Or end to end:
opfor hunt --endpoint <url> --objective "<text>".Worth checking:
opfor runoutput side by side (black band, Opfor wordmark, red accent).—for scout/verifier; a freshopfor huntpopulates them.Verified locally against two real hunt reports (one with 6 confirmed findings, one fully defended) and two real run reports: no HTML tag mismatches, no
undefined/NaNleaks, correct fallbacks on reports missingstartedAt/scoutModel, and 188 tests passing.Screenshots
Summary by CodeRabbit