feat(init): show AI-generated feature blurbs in setup summary#982
feat(init): show AI-generated feature blurbs in setup summary#982betegon wants to merge 11 commits into
Conversation
Adds a "Here's what we set up" two-column table at the end of sentry init — one row per enabled feature with a project-specific sentence generated by a new feature-blurb-writer agent. The blurb column wraps to terminal width using the same renderTextTable renderer that sentry issue list / project list use. The Features key-value row is suppressed when blurbs are present (the table already covers it). The success preamble "Nice, setup made it through." is removed so the feedback prompt follows the summary directly. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
|
- Replace O(n²) find-in-map loop in buildSummary with a Map for blurb lookup
- Remove redundant marginTop={0} from featureBlurbs row in SummaryPanel
- Drop trailing colon from "Here's what we set up" in LoggingUI to match ink-report and ink-app
- Update feedback.test.ts to reflect removed success preamble
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Codecov Results 📊✅ 7009 passed | Total: 7009 | Pass Rate: 100% | Execution Time: 0ms 📊 Comparison with Base Branch
All tests are passing successfully. ✅ Patch coverage is 95.12%. Project has 14173 uncovered lines. Files with missing lines (1)
Coverage diff@@ Coverage Diff @@
## main #PR +/-##
==========================================
+ Coverage 77.04% 77.12% +0.08%
==========================================
Files 320 320 —
Lines 61872 61940 +68
Branches 0 0 —
==========================================
+ Hits 47664 47767 +103
- Misses 14208 14173 -35
- Partials 0 0 —Generated by Codecov Action |
The LLM can echo back arbitrary strings in the feature field of the blurbs response. Instead of keying labels off the LLM output, pair blurbs positionally against output.features — the canonical ordered list of selected feature IDs already in the workflow output. Labels are always resolved via featureLabel() on the canonical IDs. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…urbs - Fix Biome import order in logging-ui.ts (renderTextTable after markdown) - Fix Biome format: collapse single-item success array, split long JSX condition - Fix LoggingUI.summary() early-return guard to account for featureBlurbs - Fix buildSummary null guard to include featureBlurbs in the emptiness check - Add test coverage: formatters (blurb population, suppressed Features row, positional label matching, display-order sort), ink-report (blurbs section), logging-ui (blurbs table, no section when absent) Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Features silently disappear when server returns blurbs with empty strings (src/lib/init/formatters.ts:44)
The plain "Features" field is suppressed whenever output.featureBlurbs is non-empty (line 44), but the blurbs table is only rendered after filtering out null/empty entries. If the server returns any featureBlurbs entries where every blurb is an empty string, the Features row is suppressed yet the blurbs table is also empty — no feature information appears in the summary at all.
Verification
Trace: output.featureBlurbs = [{feature:'errorMonitoring', blurb:''}] → output.featureBlurbs?.length = 1 → !1 = false → guard at line 44 fails → 'Features' field NOT added. Then line 72: blurbsInOrder[0]?.blurb = '' which is falsy → .map() returns null → .filter() produces featureBlurbs = []. Line 88: featureBlurbs.length === 0 → featureBlurbs NOT included in summary. Neither the Features field row nor the featureBlurbs table is rendered. The suppression guard (raw count) and the rendering gate (processed count) are out of sync.
Identified by Warden find-bugs
- Add snapshot test: SummaryPanel renders featureBlurbs section - Add formatters test: blurbs array shorter than features drops extras - Fix LoggingUI.summary() early-return guard to include featureBlurbs Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
- Revert to Map-by-feature-ID lookup (positional matching silently assigns wrong blurbs when agent omits or reorders entries; Map skips the missing feature instead) - Strip ANSI sequences from server-supplied blurbs in buildSummary to prevent terminal injection - Update tests: wrong-ID case now expects undefined (omitted), add omitted-feature case and stripAnsi sanitization case Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 5fa2f88. Configure here.
- Move featureBlurbs computation before fields in buildSummary so the Features row check uses the resolved length; when the agent echoes wrong IDs all blurbs drop out and the Features row renders correctly - Move "Here's what we set up" section before "Changed files" in all three renderers (ink-report, logging-ui, ink-app) to match the intended layout: key/value → blurbs → changed files Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Previous regex only stripped SGR colour codes (\x1b[...m); cursor movement and screen-manipulation sequences (\x1b[2J, \x1b[A, etc.) passed through to the terminal. Extend to match any CSI sequence (\x1b[...LETTER) so server-supplied blurb strings are fully sanitised. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
… bytes Previous regex missed sequences with intermediate bytes (0x20-0x2F) such as \x1b[!p (Soft Terminal Reset) or \x1b[1 q (Set Cursor Style). Use the correct CSI structure: parameter bytes (0x30-0x3F) + optional intermediate bytes (0x20-0x2F) + final byte (0x40-0x7E). Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
| export function stripAnsi(text: string): string { | ||
| return ( | ||
| text | ||
| // biome-ignore lint/suspicious/noControlCharactersInRegex: ANSI escape detection requires matching \x1b and \x07 | ||
| .replace(/\x1b\[[0-9;]*m/g, "") | ||
| // Full CSI spec: \x1b[ + parameter bytes (0x30-0x3F) + intermediate | ||
| // bytes (0x20-0x2F, e.g. space, !, ") + final byte (0x40-0x7E). | ||
| // The narrower [0-9;?]*[A-Za-z] missed sequences with intermediate | ||
| // bytes like \x1b[!p (Soft Terminal Reset) or \x1b[1 q (cursor style). | ||
| // biome-ignore lint/suspicious/noControlCharactersInRegex: ANSI escape detection requires matching \x1b | ||
| .replace(/\x1b\[[\x30-\x3f]*[\x20-\x2f]*[\x40-\x7e]/g, "") | ||
| // biome-ignore lint/suspicious/noControlCharactersInRegex: OSC 8 hyperlink sequences use \x1b and \x07 | ||
| .replace(/\x1b\]8;;[^\x07]*\x07/g, "") |
There was a problem hiding this comment.
Bug: The stripAnsi function's regex is incomplete, allowing unsanitized terminal escape sequences from LLM-generated text to be executed, leading to a terminal injection vulnerability.
Severity: HIGH
Suggested Fix
Update the regex in the stripAnsi function to correctly strip all ANSI escape sequences, not just CSI and OSC 8 hyperlinks. Consider using a more comprehensive pattern or a dedicated third-party library for robust ANSI stripping to prevent terminal injection.
Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.
Location: src/lib/formatters/plain-detect.ts#L82-L92
Potential issue: The `stripAnsi` function, intended to sanitize LLM-supplied feature
blurbs, uses an incomplete regular expression. While it handles some CSI sequences and
OSC 8 hyperlinks, it fails to remove other terminal escape sequences, such as OSC
commands for changing a window title (`\x1b]0;...\x07`), Device Control Strings
(`\x1bP...`), or terminal resets (`\x1bc`). Because the output of `stripAnsi` is later
printed directly to `process.stdout` via `LoggingUI.summary()`, an LLM providing a
feature blurb with these unhandled sequences could execute them on the user's terminal,
creating a terminal injection vulnerability.

Summary
Ends the wizard with a "Here's what we set up" two-column table — one row per enabled feature with a sentence specific to the project that was just instrumented. The blurbs come from a new server-side agent that uses the tech-stack description already captured during platform detection (e.g. "Next.js 14 with Drizzle ORM and Cloudflare D1") to produce context-aware copy rather than generic marketing text.
Depends on getsentry/cli-init-api#156 for the server-side agent.
Changes
WizardSummaryandWizardOutputgain an optionalfeatureBlurbsfieldbuildSummary()populates it from the workflow output and suppresses the plain "Features" row when blurbs are present (the table makes it redundant)ink-report.ts(post-dispose chalk output) andlogging-ui.tsrender the blurbs as a proper two-column table viarenderTextTable— same renderer used bysentry issue list/sentry project list, so wrapping and column fitting are handled automaticallySummaryPanelinink-app.tsxrenders the live Ink variant with flex layoutTest Plan
bun run devincli-init-api, then runMASTRA_API_URL=http://localhost:8787 bun run src/bin.ts initagainst a real project--no-tuiand confirm the plain-text table renders the same contentopen-sentry-ui.ts)