Skip to content

feat: add remediation report generator for PR body output - #601

Open
ruromero wants to merge 5 commits into
guacsec:mainfrom
ruromero:TC-5413
Open

feat: add remediation report generator for PR body output#601
ruromero wants to merge 5 commits into
guacsec:mainfrom
ruromero:TC-5413

Conversation

@ruromero

@ruromero ruromero commented Aug 3, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Add src/remediation_report.js with generateReport() and generateDeduplicationKey() exports
  • Support per-dependency, bundled (grouped by severity), dry-run (tabular), and JSON output formats
  • Include provider source attribution, CVE IDs, severity, and advisory links in all report formats
  • Add deterministic deduplication key generator for PR branch/title matching

Test plan

  • Per-dependency report format with multiple CVEs
  • Bundled report format groups by severity
  • Dry-run output shows tabular summary
  • JSON format returns valid JSON
  • Provider source attributed in every entry
  • Deduplication key generation is deterministic and stable
  • Edge cases (null, empty, missing groupId, advisories without URLs)
  • All 22 tests passing
  • ESLint: 0 errors

Implements TC-5413

Summary by Sourcery

Add a remediation report generator for security dependency updates with support for multiple output formats and a deterministic deduplication key.

New Features:

  • Introduce remediation_report module that generates per-dependency and bundled markdown reports for security remediations.
  • Support dry-run tabular summaries and JSON output of remediation data for CLI and automation usage.
  • Add a deterministic deduplication key generator for remediation entries based on dependency coordinates.

Tests:

  • Add comprehensive tests covering all report formats, edge cases, and deduplication key behavior.

@sourcery-ai

sourcery-ai Bot commented Aug 3, 2026

Copy link
Copy Markdown

Reviewer's Guide

Implements a remediation report generator that transforms remediation data into multiple PR-body-friendly formats (per-dependency markdown, bundled-by-severity markdown, dry-run summary, and JSON), plus a deterministic deduplication key builder, all covered by a comprehensive new test suite.

File-Level Changes

Change Details Files
Add remediation report generator supporting multiple markdown layouts, dry-run tabular output, JSON passthrough, and deterministic deduplication keys.
  • Implement generateReport() dispatcher that handles null/empty input, switches between markdown and JSON formats, supports dependency vs bundled grouping, and an optional dry-run mode.
  • Add per-dependency markdown report rendering with one section per remediation, including provider/source metadata, CVE rows, severity, and advisory links or placeholders.
  • Add bundled markdown report rendering that groups remediations by severity, orders severities by importance, and outputs a summary table per severity including dependency, versions, provider, CVEs, and advisories.
  • Add dry-run markdown report that emits a compact table of dependency upgrades including severity and provider, without PR-specific phrasing.
  • Add helper utilities to group remediations by severity and to format advisory IDs into markdown links or plain text fallbacks.
  • Add generateDeduplicationKey() to build stable keys from groupId, artifactId, and fixedInVersion, tolerating missing groupId/artifactId/version.
src/remediation_report.js
Introduce a comprehensive test suite covering all report modes, formatting details, edge cases, and deduplication behavior.
  • Add builder helper for remediation objects used across tests to keep scenarios focused on formatting and behavior.
  • Test per-dependency report content for CVE/severity/advisory presence, multiple CVEs, multiple remediations, provider/source attribution, missing groupId handling, advisories without URLs, and no-advisory placeholder rendering.
  • Test bundled report behavior including severity grouping, severity sort order, required table columns, and omission of empty severity groups.
  • Test dry-run output for correct header, table structure, and absence of PR-specific phrasing.
  • Test JSON format to ensure valid JSON output, identity of data, and correct handling of empty input arrays.
  • Test edge cases for null/undefined/empty inputs returning the no-remediations message and verify deterministic, distinct generateDeduplicationKey() behavior across varying inputs.
test/remediation_report.test.js

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-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.

Hey - I've found 2 issues

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="src/remediation_report.js" line_range="60-69" />
<code_context>
+			lines.push('')
+			lines.push('| CVE | Severity | Advisory |')
+			lines.push('| --- | --- | --- |')
+			for (const cve of rem.cves) {
+				const advisoryLinks = formatAdvisoryLinks(rem.advisories)
+				lines.push(`| ${cve} | ${rem.severity} | ${advisoryLinks} |`)
+			}
</code_context>
<issue_to_address>
**suggestion (performance):** Avoid recomputing advisory links inside the CVE loop.

`advisoryLinks` depends only on `rem.advisories`, so it has the same value for every CVE. Compute it once before the loop (e.g. `const advisoryLinks = formatAdvisoryLinks(rem.advisories);`) and reuse it in the loop to avoid redundant work and clarify intent.

```suggestion
		if (rem.cves.length > 0) {
			lines.push('### Vulnerabilities resolved')
			lines.push('')
			lines.push('| CVE | Severity | Advisory |')
			lines.push('| --- | --- | --- |')
			const advisoryLinks = formatAdvisoryLinks(rem.advisories)
			for (const cve of rem.cves) {
				lines.push(`| ${cve} | ${rem.severity} | ${advisoryLinks} |`)
			}
		}
```
</issue_to_address>

### Comment 2
<location path="test/remediation_report.test.js" line_range="257-274" />
<code_context>
+		})
+	})
+
+	suite('JSON format', () => {
+		/** Verifies that JSON format returns valid JSON string. */
+		test('returns valid JSON string of remediations', () => {
+			const remediations = [buildRemediation()]
+
+			const report = generateReport(remediations, { format: 'json' })
+			const parsed = JSON.parse(report)
+
+			expect(parsed).to.deep.equal(remediations)
+		})
+
+		/** Verifies that empty remediations produce empty JSON array. */
+		test('returns empty JSON array for no remediations', () => {
+			const report = generateReport([], { format: 'json' })
+
+			expect(report).to.equal('[]')
+		})
+	})
+
+	suite('edge cases', () => {
</code_context>
<issue_to_address>
**suggestion (testing):** Consider adding tests for precedence when `format: 'json'` is combined with `dryRun` or `groupBy` options.

Because the logic returns early when `format === 'json'`, we should add cases like `generateReport(remediations, { format: 'json', dryRun: true })` and `{ format: 'json', groupBy: 'bundle' }` to document and lock in the intended precedence (e.g., JSON always winning) and guard against regressions in future refactors.

```suggestion
	suite('JSON format', () => {
		/** Verifies that JSON format returns valid JSON string. */
		test('returns valid JSON string of remediations', () => {
			const remediations = [buildRemediation()]

			const report = generateReport(remediations, { format: 'json' })
			const parsed = JSON.parse(report)

			expect(parsed).to.deep.equal(remediations)
		})

		/** Verifies that empty remediations produce empty JSON array. */
		test('returns empty JSON array for no remediations', () => {
			const report = generateReport([], { format: 'json' })

			expect(report).to.equal('[]')
		})

		/** Verifies that JSON format takes precedence over dry-run option. */
		test('JSON format takes precedence over dry-run option', () => {
			const remediations = [buildRemediation()]

			const report = generateReport(remediations, { format: 'json', dryRun: true })
			const parsed = JSON.parse(report)

			expect(parsed).to.deep.equal(remediations)
		})

		/** Verifies that JSON format takes precedence over groupBy option. */
		test('JSON format takes precedence over groupBy option', () => {
			const remediations = [buildRemediation()]

			const report = generateReport(remediations, { format: 'json', groupBy: 'bundle' })
			const parsed = JSON.parse(report)

			expect(parsed).to.deep.equal(remediations)
		})
	})
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread src/remediation_report.js Outdated
Comment on lines +257 to +274
suite('JSON format', () => {
/** Verifies that JSON format returns valid JSON string. */
test('returns valid JSON string of remediations', () => {
const remediations = [buildRemediation()]

const report = generateReport(remediations, { format: 'json' })
const parsed = JSON.parse(report)

expect(parsed).to.deep.equal(remediations)
})

/** Verifies that empty remediations produce empty JSON array. */
test('returns empty JSON array for no remediations', () => {
const report = generateReport([], { format: 'json' })

expect(report).to.equal('[]')
})
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

suggestion (testing): Consider adding tests for precedence when format: 'json' is combined with dryRun or groupBy options.

Because the logic returns early when format === 'json', we should add cases like generateReport(remediations, { format: 'json', dryRun: true }) and { format: 'json', groupBy: 'bundle' } to document and lock in the intended precedence (e.g., JSON always winning) and guard against regressions in future refactors.

Suggested change
suite('JSON format', () => {
/** Verifies that JSON format returns valid JSON string. */
test('returns valid JSON string of remediations', () => {
const remediations = [buildRemediation()]
const report = generateReport(remediations, { format: 'json' })
const parsed = JSON.parse(report)
expect(parsed).to.deep.equal(remediations)
})
/** Verifies that empty remediations produce empty JSON array. */
test('returns empty JSON array for no remediations', () => {
const report = generateReport([], { format: 'json' })
expect(report).to.equal('[]')
})
})
suite('JSON format', () => {
/** Verifies that JSON format returns valid JSON string. */
test('returns valid JSON string of remediations', () => {
const remediations = [buildRemediation()]
const report = generateReport(remediations, { format: 'json' })
const parsed = JSON.parse(report)
expect(parsed).to.deep.equal(remediations)
})
/** Verifies that empty remediations produce empty JSON array. */
test('returns empty JSON array for no remediations', () => {
const report = generateReport([], { format: 'json' })
expect(report).to.equal('[]')
})
/** Verifies that JSON format takes precedence over dry-run option. */
test('JSON format takes precedence over dry-run option', () => {
const remediations = [buildRemediation()]
const report = generateReport(remediations, { format: 'json', dryRun: true })
const parsed = JSON.parse(report)
expect(parsed).to.deep.equal(remediations)
})
/** Verifies that JSON format takes precedence over groupBy option. */
test('JSON format takes precedence over groupBy option', () => {
const remediations = [buildRemediation()]
const report = generateReport(remediations, { format: 'json', groupBy: 'bundle' })
const parsed = JSON.parse(report)
expect(parsed).to.deep.equal(remediations)
})
})

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

[sdlc-workflow/verify-pr] Classified as suggestion — this proposes adding option-precedence tests. CONVENTIONS.md requires 82% line coverage (met at 98.93%) but has no rules about combinatorial option testing or precedence coverage. Valid test coverage improvement but not convention-backed. No sub-task created.

@codecov-commenter

codecov-commenter commented Aug 3, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 98.94180% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 91.22%. Comparing base (00529d4) to head (f26910c).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
src/remediation_report.js 98.93% 2 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main     #601      +/-   ##
==========================================
+ Coverage   90.95%   91.22%   +0.27%     
==========================================
  Files          41       42       +1     
  Lines        8984     9175     +191     
  Branches     1573     1624      +51     
==========================================
+ Hits         8171     8370     +199     
+ Misses        813      805       -8     
Flag Coverage Δ
unit-tests 91.22% <98.94%> (+0.27%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
src/remediation.js 88.53% <100.00%> (ø)
src/remediation_report.js 98.93% <98.93%> (ø)

... and 1 file with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@ruromero

ruromero commented Aug 3, 2026

Copy link
Copy Markdown
Collaborator Author

[sdlc-workflow/verify-pr] Re: @sourcery-ai review — Classified as suggestion — the review body summarizes the same 2 issues raised as inline comments (loop-invariant hoisting and option-precedence tests). Both are valid code quality suggestions but neither matches a CONVENTIONS.md rule or established codebase pattern. No sub-task created.

@ruromero

ruromero commented Aug 3, 2026

Copy link
Copy Markdown
Collaborator Author

Verification Report for TC-5413 (commit 431fc33)

Check Result Details
Review Feedback PASS 2 suggestions from sourcery-ai (loop-invariant hoisting, precedence tests); no code change requests
Root-Cause Investigation N/A No sub-tasks created
Scope Containment PASS PR files exactly match task's Files to Create (2/2)
Diff Size PASS 530 insertions across 2 new files; test:code ratio 1.8:1
Commit Traceability PASS Single commit references TC-5413 in body
Sensitive Patterns PASS No secrets or credentials in 530 added lines
CI Status PASS All 5 checks pass (lint Node 22/24, Sourcery, PR title, commit messages)
Acceptance Criteria WARN 4/5 fully met; CVSS score absent (spec inconsistency — upstream extractor doesn't provide CVSS data)
Test Quality PASS 22 tests, all documented with JSDoc, no repetitive patterns; Eval Quality: N/A
Test Change Classification ADDITIVE New test file with 342 lines of coverage
Verification Commands PASS npm run lint: 0 errors; npm test: 22/22 passing

Overall: WARN

The only non-PASS finding is the CVSS score column mentioned in Acceptance Criterion 1 but absent from the implementation. This is a spec inconsistency, not a code defect — the upstream remediation extractor (TC-5410) does not extract CVSS data, so the report module has no CVSS values to render. The task description's template included a CVSS column, but the input data shape doesn't support it. Recommend updating the Jira task's AC1 to remove the CVSS reference, or adding CVSS extraction to the upstream extractor as a follow-up.


This comment was AI-generated by sdlc-workflow/verify-pr v0.13.8.

@ruromero
ruromero requested a review from a-oren August 3, 2026 12:42
Add report generator that transforms remediation extractor output into
structured markdown for PR bodies, CLI dry-run output, and JSON format.
Includes per-dependency and bundled report modes, provider source attribution,
and a deterministic deduplication key generator for PR branch/title matching.

Implements TC-5413

Assisted-by: Claude Code
Address review feedback from sourcery-ai: move formatAdvisoryLinks() call
above the CVE loop since it depends only on rem.advisories (same value
every iteration), and add tests documenting that JSON format takes
precedence over dryRun and groupBy options.

Implements TC-5413

Assisted-by: Claude Code

@a-oren a-oren left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

src/index.js re-exports extractRemediations from remediation.js as part of the public API surface. The new generateReport and generateDeduplicationKey are not re-exported. Every other public function in this library is accessible through the index, this omission breaks that convention and forces consumers to import from a deep path.

Comment thread src/remediation_report.js Outdated
* markdown for PR bodies, CLI dry-run output, and JSON.
*/

const SEVERITY_ORDER = ['UNKNOWN', 'LOW', 'MEDIUM', 'HIGH', 'CRITICAL']

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

src/remediation_report.js:6 defines SEVERITY_ORDER identically to src/remediation.js. This is a DRY violation, the constant should be imported from remediation.js (or extracted to a shared module) rather than duplicated.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

[sdlc-workflow/verify-pr] Classified as code change request — SEVERITY_ORDER is defined identically in both src/remediation.js:335 and src/remediation_report.js:6. Sub-task TC-5513 created to address this feedback.

Comment thread src/remediation_report.js Outdated
'',
]

if (rem.cves.length > 0) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

will throw a TypeError if cves is undefined or null. Same issue in generateBundledReport (line 102) with rem.cves.join(', ')

Suggested change
if (rem.cves.length > 0) {
if (rem.cves && rem.cves.length > 0) {

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

[sdlc-workflow/verify-pr] Classified as code change request — rem.cves access without null guard will throw TypeError if cves is undefined/null. Same issue in generateBundledReport at line 102. Sub-task TC-5514 created to address this feedback.

@ruromero

ruromero commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator Author

[sdlc-workflow/verify-pr] Re: @a-oren review — Classified as code change request — generateReport and generateDeduplicationKey are not re-exported from src/index.js, breaking the barrel-export convention established by all other public functions. Sub-task TC-5515 created to address this feedback.

@ruromero

ruromero commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator Author

Verification Report for TC-5413 (commit 4bdfeba)

Check Result Details
Review Feedback WARN 3 code change requests from a-oren; sub-tasks TC-5513, TC-5514, TC-5515 created
Root-Cause Investigation DONE 2 root-cause tasks: TC-5516 (implement-task skill gap), TC-5517 (convention gap)
Scope Containment PASS PR files match task spec (2/2 created)
Diff Size PASS 550 lines across 2 new files; test:code ratio 1.9:1
Commit Traceability PASS Both commits reference TC-5413
Sensitive Patterns PASS No secrets detected in 550 added lines
CI Status PASS All 5 checks pass
Acceptance Criteria WARN 4/5 met; CVSS score absent (spec inconsistency — upstream extractor doesn't provide CVSS data)
Test Quality WARN Repetitive Test Detection: WARN (dedup key + edge case tests parameterizable); Test Documentation: PASS; Eval Quality: N/A
Test Change Classification ADDITIVE New test file with 362 lines
Verification Commands PASS Lint 0 errors, 24/24 tests passing

Overall: WARN

Review feedback (3 code change requests from a-oren):

  1. TC-5513 — SEVERITY_ORDER constant duplicated between src/remediation.js and src/remediation_report.js (DRY violation)
  2. TC-5514rem.cves.length and rem.cves.join() will throw TypeError if cves is null/undefined
  3. TC-5515generateReport and generateDeduplicationKey not re-exported from src/index.js, breaking barrel-export convention

Acceptance Criteria: CVSS score column mentioned in AC1 but absent — upstream extractor (TC-5410) doesn't extract CVSS data. Spec inconsistency, not code defect.

Root-cause investigation: Two systemic improvements identified:

  • TC-5516 (implement-task skill gap): skill should search for existing symbols before re-declaring constants, and add null guards for external data property access
  • TC-5517 (convention gap): barrel-export convention is implicit but undocumented in CONVENTIONS.md

This comment was AI-generated by sdlc-workflow/verify-pr v0.13.8.

Address review feedback: add generateReport and generateDeduplicationKey
to the barrel export in src/index.js, matching the convention that all
public functions are accessible through the package entry point.

Implements TC-5515

Assisted-by: Claude Code
Address review feedback: deduplicate the SEVERITY_ORDER constant by
exporting it from remediation.js and importing it in remediation_report.js.

Implements TC-5513

Assisted-by: Claude Code
Address review feedback: add defensive null checks before accessing
rem.cves.length in generatePerDependencyReport and rem.cves.join()
in generateBundledReport to prevent TypeError when cves is null or
undefined. Add 4 test cases covering both report modes.

Implements TC-5514

Assisted-by: Claude Code
@ruromero

ruromero commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator Author

Verification Report for TC-5413 (commit f26910c)

Check Result Details
Review Feedback PASS All 3 prior code change requests addressed; no new unclassified feedback
Root-Cause Investigation N/A No new sub-tasks; prior investigation completed (TC-5516, TC-5517)
Scope Containment WARN 4 files vs 2 specified; extra 2 (src/index.js, src/remediation.js) justified by review feedback sub-tasks
Diff Size PASS 584 additions across 4 files; 99.7% in task-specified files
Commit Traceability PASS All 5 commits reference TC-5413 or its sub-tasks (TC-5513, TC-5514, TC-5515)
Sensitive Patterns PASS No secrets detected in 584 added lines
CI Status WARN 3/5 checks pass, 2 pending (Node 22/24 lint+test matrix)
Acceptance Criteria WARN 4/5 met; CVSS score absent (spec inconsistency — upstream extractor doesn't provide CVSS data)
Test Quality WARN Repetitive Test Detection: WARN (3 parameterizable groups); Test Documentation: PASS (28/28 documented); Eval Quality: N/A
Test Change Classification ADDITIVE New test file with 394 lines, 28 test cases
Verification Commands PASS Lint 0 errors, 28/28 tests passing

Overall: WARN

Review feedback resolved: All 3 code change requests from a-oren (SEVERITY_ORDER DRY violation, null guard for rem.cves, missing barrel exports) have been addressed in fix commits b943125, f26910c, and 6e72189 respectively.

Remaining WARNs:

  • Scope Containment: 2 extra files (src/index.js, src/remediation.js) are minimal, justified modifications from review feedback sub-tasks
  • CI Status: Node 22/24 matrix jobs still pending at time of verification; prior run passed all 5 checks
  • Acceptance Criteria: CVSS score in AC1 is a known spec inconsistency (upstream extractor TC-5410 doesn't extract CVSS); not a code defect

This comment was AI-generated by sdlc-workflow/verify-pr v0.13.8.

@ruromero
ruromero requested a review from a-oren August 4, 2026 19:20
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.

3 participants