Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Read findings JSONL files from cluster 1-4 skills, deduplicate by fingerprint, group by severity, and compose a deliverable- grade markdown vulnerability report with per-finding sections (title, severity, target, detail, remediation, evidence) and a top-level summary table. The canonical written artifact a customer receives at engagement close; precise, reproducible, machine- checkable against source findings. Use when: closing an engagement, generating an interim report, regenerating after CVE
.claude/skills/jeremylongshore-composing-vulnerability-report/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-02 | ✗→✓ | ▲ Improved | 1062% | 0% |
| case-03 | ✗→✓ | ▲ Improved | 12% | 0% |
| case-04 | ✗→✓ | ▲ Improved | -5% | 0% |
| case-09 | ✗→✓ | ▲ Improved | 94% | 0% |
| case-11 | ✗→✓ | ▲ Improved | 9% | 0% |
After cluster 1-4 scan skills run, each one produces a Findings file. A typical engagement ends up with eight to twenty such files across the different skill categories. The customer wants ONE vulnerability report — comprehensive, deduplicated, organized by severity, with each finding cross-referenced to its source skill and target.
This skill consumes one or more findings files (JSONL preferred, JSON list also accepted), deduplicates entries by the canonical fingerprint defined in lib/finding.py, enriches each finding with a CVSS v3.1 vector when one isn't present (using a deterministic heuristic based on severity + category — explicitly noted as "derived, not assigned by NVD" in the output), and emits a single markdown report with per-finding sections plus a top-level summary table.
The report has a defined structure that downstream tools (next two skills in cluster 6) consume:
LOW, INFO
remediation, evidence, references
| Finding | Severity | Threshold | Affected control | |---|---|---|---| | Source file unparseable | HIGH | JSON/JSONL parse fails | (operational) | | Finding missing required field | HIGH | A finding record is missing title, severity, target, detail, or remediation | (operational) | | Duplicate fingerprint across files | INFO | Same finding appears in N>1 sources; reported as deduplication count | (informational) | | Source file has zero findings | INFO | Empty or all-info-only file; reported but not an error | (informational) | | Report generated cleanly | INFO | Positive confirmation | (informational) |
any cluster 1-4 scan skill (which all share lib/finding.py schema)
By default the skill reads every file matching engagement/findings/*.json and engagement/findings/*.jsonl. Override with --source FILE (repeatable).
bashpython3 ./scripts/compose_report.py engagements/acme-2026-q2/
Options:
Usage: compose_report.py PATH [OPTIONS]
Options:
--source FILE Specific findings file (repeatable; overrides default glob)
--report-output FILE Write the composed report here (default:
PATH/reports/vulnerability-report.md)
--engagement-id ID Override the engagement ID (default: parse from PATH/roe.yaml)
--output FILE Operational findings output (this skill's own findings)
--format FMT json | jsonl | markdown (default: markdown)
--min-severity SEV Filter report to findings at or above this severity
--include-info Include INFO-severity findings in the report (default: omit)The output report has a predictable structure. The header identifies the engagement, the source files, and the generation timestamp. The summary table shows finding counts by severity. Per-severity sections follow.
Each finding subsection includes a stable anchor (the fingerprint) so cross-references from later artifacts (executive summary, OWASP mapping) resolve into the report cleanly.
bashpython3 ./scripts/compose_report.py engagements/acme-2026-q2/ --format json --output /tmp/compose-findings.json jq '.[] | select(.severity == "high")' /tmp/compose-findings.json
If the report references a finding the operator didn't expect, trace back via the finding's skill_id + target to the source file.
bashpython3 ./scripts/compose_report.py engagements/acme-2026-q2/ \ --report-output engagements/acme-2026-q2/reports/vulnerability-report.md
bashpython3 ./scripts/compose_report.py engagements/acme-2026-q2/ \ --min-severity high \ --report-output engagements/acme-2026-q2/reports/interim-2026-06-15.md
--min-severity high produces an interim report covering only HIGH and CRITICAL findings — useful for in-engagement customer syncs.
bashpython3 ./scripts/compose_report.py engagements/acme-2026-q2/ \ --source engagements/acme-2026-q2/findings/all-findings-with-owasp.jsonl \ --report-output engagements/acme-2026-q2/reports/vulnerability-report-v2.md
Re-run after mapping-findings-to-owasp-top10 has enriched each finding with its OWASP category; the regenerated report includes the OWASP tag in each per-finding subsection.
JSON / JSONL / Markdown per lib/report.py for the skill's own operational findings. The PRIMARY output is the composed vulnerability report, written as standalone Markdown to the --report-output path.
Each operational Finding includes:
id — compose::<issue>::<source-file>severity — CRITICAL / HIGH / MEDIUM / INFOcategory — report-compositionsummary — what went wrong (or right) during compositionevidence — source files, finding counts, dedup statsexits 1.
skips the file, continues with remaining sources.
exits 1.
finding, exits 1.
finding, omits that record from the report, continues.
references/THEORY.md — Vulnerability-report structure history(NIST SP 800-115, OWASP Testing Guide), CVSS v3.1 vector composition, severity scoring tradeoffs (CVSS vs intrinsic vs EPSS), finding-deduplication theory, why fingerprint-based dedup beats title-based
references/PLAYBOOK.md — Report-template variants peraudience (technical, executive, regulatory), per-finding remediation phrasing patterns, evidence-redaction patterns for distributed reports, cross-reference protocol with the OWASP-mapping and exec-summary skills
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→fail | 23,837 | 29,870 | +25% | 1 | 1 | 0% | 5,092 | 7,880 | +55% | 0 | 0 | — |
case-02 | fail→pass | 7,186 | 5,592 | -22% | 1 | 1 | 0% | 233 | 2,708 | +1062% | 0 | 0 | — |
case-03 | fail→pass | 14,591 | 7,986 | -45% | 1 | 1 | 0% | 2,807 | 3,152 | +12% | 0 | 0 | — |
case-04 | fail→pass | 12,051 | 4,476 | -63% | 1 | 1 | 0% | 2,100 | 2,002 | -5% | 0 | 0 | — |
case-05 | pass→pass | 7,739 | 1,649 | -79% | 1 | 1 | 0% | 1,307 | 1,917 | +47% | 0 | 0 | — |
case-06 | pass→pass | 12,477 | 1,611 | -87% | 1 | 1 | 0% | 2,241 | 1,977 | -12% | 0 | 0 | — |
case-07 | pass→pass | 18,631 | 2,966 | -84% | 1 | 1 | 0% | 3,352 | 2,251 | -33% | 0 | 0 | — |
case-08 | pass→pass | 7,288 | 2,781 | -62% | 1 | 1 | 0% | 1,264 | 2,130 | +69% | 0 | 0 | — |
case-09 | fail→pass | 5,586 | 2,635 | -53% | 1 | 1 | 0% | 1,080 | 2,092 | +94% | 0 | 0 | — |
case-10 | pass→pass | 7,603 | 2,977 | -61% | 1 | 1 | 0% | 1,287 | 2,121 | +65% | 0 | 0 | — |
case-11 | fail→pass | 18,492 | 2,389 | -87% | 1 | 1 | 0% | 1,888 | 2,049 | +9% | 0 | 0 | — |
case-12 | pass→fail | 6,349 | 3,949 | -38% | 1 | 1 | 0% | 1,031 | 2,187 | +112% | 0 | 0 | — |
case-13 | fail→pass | 11,193 | 2,715 | -76% | 1 | 1 | 0% | 1,792 | 2,085 | +16% | 0 | 0 | — |
case-14 | fail→pass | 11,150 | 4,114 | -63% | 1 | 1 | 0% | 1,957 | 2,345 | +20% | 0 | 0 | — |
case-15 | fail→pass | 10,030 | 2,758 | -73% | 1 | 1 | 0% | 1,625 | 2,162 | +33% | 0 | 0 | — |
case-16 | pass→pass | 12,919 | 3,598 | -72% | 1 | 1 | 0% | 2,143 | 2,368 | +10% | 0 | 0 | — |
case-17 | pass→pass | 9,325 | 2,748 | -71% | 1 | 1 | 0% | 1,540 | 2,183 | +42% | 0 | 0 | — |
case-18 | fail→pass | 13,743 | 4,314 | -69% | 1 | 1 | 0% | 2,364 | 2,418 | +2% | 0 | 0 | — |
case-19 | fail→fail | 11,395 | 2,249 | -80% | 1 | 1 | 0% | 1,769 | 2,061 | +17% | 0 | 0 | — |
case-20 | fail→pass | 15,400 | 9,138 | -41% | 1 | 1 | 0% | 3,296 | 3,521 | +7% | 0 | 0 | — |
case-21 | fail→pass | 12,067 | 10,415 | -14% | 1 | 1 | 0% | 1,420 | 2,797 | +97% | 0 | 0 | — |
case-22 | fail→fail | 22,534 | 17,368 | -23% | 1 | 1 | 0% | 3,354 | 4,587 | +37% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted, and 21 counted toward the lift figure. The other 1 produced results that are not comparable between the two arms, so they are excluded from the headline rather than averaged into it. The headline lift of +45 percentage points is the difference between those two pass rates over the 21 comparable cases. 1 case got worse with the skill loaded, and it is included in that figure.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.