Skip to main content

assessment-report-converter

Convert an arbitrary CSV report (e.g. a Black Duck export or a custom migration-issue inventory) into a schema-valid assessment `report.json` the modernization pipeline can consume — so it appears in the assessment UI and migration solutions resolve automatically. This skill is LLM-driven: you read and interpret the CSV yourself and author `report.json` by hand against the schema. A small helper script only does deterministic lookups (migration solutions, the ruleId for a solution) and validates the finished report. There is NO "convert everything" script and NO assumed column layout. Triggers: "convert csv to assessment report", "import csv report", "turn this spreadsheet into a report.json", "Black Duck csv to report", "build report.json from csv", "migrate a third-party assessment export". NOT for: AppCAT-style analysis from source (use `assessment`), generating a modernization plan (use `create-modernization-plan`), or editing a report.json the pipeline already produced.

Source facts

Repository
microsoft/github-copilot-modernization
Last source activity
September 22, 2026 at 08:42
Detected SKILL.md language
English
Stars
49
Forks
14

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

File Explorer
7 files

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
assessment-report-converter
description
Convert an arbitrary CSV report (e.g. a Black Duck export or a custom migration-issue inventory) into a schema-valid assessment `report.json` the modernization pipeline can consume — so it appears in the assessment UI and migration solutions resolve automatically. This skill is LLM-driven: you read and interpret the CSV yourself and author `report.json` by hand against the schema. A small helper script only does deterministic lookups (migration solutions, the ruleId for a solution) and validates the finished report. There is NO "convert everything" script and NO assumed column layout. Triggers: "convert csv to assessment report", "import csv report", "turn this spreadsheet into a report.json", "Black Duck csv to report", "build report.json from csv", "migrate a third-party assessment export". NOT for: AppCAT-style analysis from source (use `assessment`), generating a modernization plan (use `create-modernization-plan`), or editing a report.json the pipeline already produced.
# Assessment Report Converter (CSV → report.json) ## What this skill does You take a **row-oriented CSV** of migration issues and produce a canonical **`report.json`** conforming to the assessment report schema. Once written to the versioned reports directory, the report is picked up by the assessment UI and by the solution-resolution logic — exactly like a native AppCAT report. **This is an LLM-driven conversion, not a fixed mapping script.** CSV layouts vary between tools and change over time, so *you* read the file, decide what each column means, classify every row, and author `report.json`. You do **not** rely on hard-coded column names. A small helper script — [scripts/report_tools.sh](scripts/report_tools.sh) (bash + jq) or its PowerShell twin [scripts/report_tools.ps1](scripts/report_tools.ps1) (PowerShell 7+) — provides only the deterministic pieces you should never guess: - **`list-solutions`** — discover the available migration solutions. - **`rules-for-solution`** — get the canonical `ruleId`(s) for a chosen solution. - **`upgrade-solutions`** — the canonical JDK / Spring Boot / Spring Framework / Jakarta EE upgrade solutions + ruleIds. - **`validate`** — structural + consistency validation of the finished `report.json`. ```mermaid flowchart LR A[Read CSV] --> B[Understand columns] --> C[Find projects] --> D{Audit each row} D -->|CVE / CWE| E[Security finding] D -->|Needs upgrade| F[Upgrade incident] D -->|Other| G[Solution → ruleId → incident] E --> H[Assemble report] F --> H G --> H H --> I[Validate] --> J[Save report.json] --> K[Summarize] ``` ## Input parameters - `csv-path` (mandatory): Path to the source CSV file. - `workspace-path` (optional): Output root. Defaults to the current directory. The report is written to `{workspace-path}/.github/modernize/assessment/reports/report-{reportId}/report.json`. - `producer` (optional): A label identifying the source tool, stored in `report.producer` (e.g. `"Black Duck"`). Defaults to `"CSV import"`. ## When to use this skill Use this when you have a **CSV** — not an AppCAT `report.json` — and you want it to behave like a real assessment report: a Black Duck / third-party export, or a hand-maintained spreadsheet of migration issues. Do **not** use it to run analysis from source code (that is the `assessment` skill). ## The helper script The helper ships as two interchangeable implementations of the same CLI — use whichever fits the machine: [scripts/report_tools.sh](scripts/report_tools.sh) (**bash + jq**) and [scripts/report_tools.ps1](scripts/report_tools.ps1) (**PowerShell 7+**, no extra dependencies). Both behave identically and never read the CSV. Run them from the `scripts/` directory. ```bash # bash + jq ./report_tools.sh list-solutions [--query KW] [--type Formula|Chat] [--ids-only] ./report_tools.sh rules-for-solution <solutionId> ./report_tools.sh upgrade-solutions ./report_tools.sh validate <path/to/report.json> ``` ```powershell # PowerShell 7+ pwsh ./report_tools.ps1 list-solutions [--query KW] [--type Formula|Chat] [--ids-only] pwsh ./report_tools.ps1 rules-for-solution <solutionId> pwsh ./report_tools.ps1 upgrade-solutions pwsh ./report_tools.ps1 validate <path/to/report.json> ``` - `list-solutions` prints the matching solutions from [scripts/solution-mapping.json](scripts/solution-mapping.json) as whole JSON objects (each always has `solutionId`, `name`, `type`, and `tooltip`; some also carry `effort` / `prompt`); `--query` filters by substring on id/name/tooltip. - `rules-for-solution` prints `{ solutionId, ruleCount, rules: [{ruleId, sourceCategory}], preferredRuleId }`. An empty list means the solution has **no** rule (e.g. a security-only solution) — do not invent a ruleId. - `upgrade-solutions` prints, per component (`jdk`, `spring-boot`, `spring-framework`, `jakarta-ee`), the `solutionId` and its resolved `preferredRuleId`. - `validate` runs structural + cross-field consistency checks (incident→rule references, required `domain`/`category` rule fields, enum values, security-finding shape and dedupe, and `metadata.domains`↔content consistency). Exit code is `0` valid, `1` invalid, `2` when the report can't be read/parsed. It accepts a `--schema <path>` flag for CLI compatibility but ignores it — the checks are self-contained. ## Reference material Detailed reference material is consolidated in the [Reference](#reference) section at the end of this file. The workflow below links to the relevant part at the step where you need it — you do not have to open any separate files: - [Report structure](#report-structure) — the top-level `report.json` shape and the authoring conventions the schema can't express (the schema owns the field shape). - [Assessment domains](#assessment-domains) — the `metadata.domains` values and when to include each. - [Security severity mapping](#security-severity-mapping) — normalizing a source CVE/CWE severity to the security `mandatory|potential|optional` scale. - [Rule classification](#rule-classification) — the required `domain` / `category` rule fields that group and render a rule's incidents. ## Workflow ### Step 1 — Understand the report structure (do this first) Read the authoritative schema [scripts/assessment-report.schema.json](scripts/assessment-report.schema.json) — it is the source of truth for the field shape. The report is a single root object `{ version, producer, metadata, projects, rules, security? }`, and `additionalProperties` is `false` almost everywhere (there is **no** `summary` object). The [report structure reference](#report-structure) adds the authoring conventions the schema can't express. You must know the required fields before authoring anything. ### Step 2 — Read and understand the CSV Read the CSV file directly (headers + a representative sample of rows). Then **interpret the columns by meaning**, since names and order vary by tool. Identify whichever of these the CSV actually carries (any may be absent): - a **project / module / service** identifier, - an **application** name, - an issue **title** and **description**, - a **severity / criticality / priority**, - a **category / domain / type**, - a **CVE / CWE** identifier, - an **affected component / package / library** and its **version**, - a **file** path and **line**, - an **effort / story-point** estimate, - a **reference / URL**. When a concept is missing, leave the corresponding report field empty or at its default — never fabricate data. ### Step 3 — Determine the projects Work out **how many projects** the CSV describes and group rows accordingly: - If a project/module/service column exists, group rows by it — one project per distinct value, using that value as `project.path`. - If the CSV has **no project information**, create a **single project** with `path` `"."` and minimal properties (`appName` `""`; leave optional `jdkVersion` / `frameworks` / `languages` / `tools` off or empty). - Populate `project.properties` **only** from what the CSV actually provides; otherwise keep them empty. Only `appName` is required. Every incident you create later belongs to exactly one of these projects. ### Step 4 — Audit each row, within its project Classify every row into exactly one of three branches and attach the result to the row's project. Every rule you add to `rules{}` must set its `domain` and `category` **fields** (that is what groups and renders its incidents) — see [rule classification](#rule-classification). Incidents do **not** require any label. 1. **CVE / CWE → security finding (directly).** When a row carries a `CVE-…` / `CWE-…` identifier (or unambiguously describes one), add a security finding to `report.security[]`. Capture **as much of the column as possible**: `id` (the CVE/CWE token), `title`, `category`, `severity` (the security scale `mandatory | potential | optional` — normalize the source severity per [security issue severity mapping](#security-severity-mapping)), `description`, `evidence.files` (affected paths), `evidence.explanation`, and optional `storyPoint`. **Merge by id** — one finding per CVE/CWE; accumulate evidence files and keep the strongest severity (`mandatory` > `potential` > `optional`). Security findings are **not** incidents; they live only in `report.security[]` (there is no summary to count them in). 2. **Needs a major-component version upgrade → upgrade incident.** If the row implies upgrading a major component — **JDK**, **Spring Boot**, **Spring Framework**, or **Java EE / Jakarta EE** (e.g. a CVE against `spring-boot`, an out-of-support runtime, an explicit "upgrade JDK") — run the `upgrade-solutions` command, pick the component, and use its `preferredRuleId`. Add that rule to `rules{}` (`severity: "mandatory"`, `domain: "java-upgrade"`, a `category`, a reasonable `effort`) and add an **incident** to the project — one upgrade rule per component per project, one incident per triggering row. A CVE that implies an upgrade produces **both** a security finding (branch 1) **and** an upgrade incident — that is what makes the upgrade resolve as a migration solution. 3. **Any other issue → solution → ruleId → incident.** Find the migration solution that fits the issue with the `list-solutions --query <keyword>` command, then get its canonical ruleId with `rules-for-solution <solutionId>` (use `preferredRuleId`). Add that rule to `rules{}` (with `domain`, `category`, `severity`, `effort`) and an incident to the project. If no solution fits, you may still record the issue with a clear **synthetic** ruleId (it just won't carry an automatic Formula solution) — or leave it for the "remaining" list in your summary. Either way, report it. #### Process rows in parallel Classifying a row is independent work, so for large CSVs do it concurrently rather than one row at a time: - **Batch the rows** (e.g. 20–50 per batch, or one batch per project) and dispatch the batches **in parallel** — launch several `Explore`/worker subagents at once, each auditing its batch into a partial result (security findings, upgrade hits, and ordinary incidents with their resolved ruleIds). Ask each worker to return structured JSON; it does **not** write files. - **Share the deterministic lookups.** Run the `upgrade-solutions` command and the `list-solutions` / `rules-for-solution` queries **once up front** (results are stable) and pass them to the workers, so parallel batches don't repeat the same lookups or race on them. - **Keep workers side-effect free**, then **merge sequentially** in one place so shared state stays correct: - **Security findings** — merge by `id` (one finding per CVE/CWE; union `evidence.files`, keep the strongest severity). - **Upgrade rules** — collapse to one rule per `(project, component)`; keep every triggering incident. - **`incidentId`s** — assign `"<ruleId>/<n>"` **after** the merge, never inside a worker (so ids are deterministic regardless of batch order). - If the CSV is small, just process the rows sequentially — the parallel split only pays off at scale. ### Step 5 — Assemble the report and finalize metadata There is **no** `summary` object to compute — assemble the top-level document and fill `metadata`: - `projects[]` = your projects, each with `properties` (only `appName` required) and its `incidents[]`. - `rules{}` = every distinct rule you referenced, keyed by ruleId, each with `id`, `title`, `severity`, `effort`, `domain`, and `category`. - `report.security[]` = the deduped findings (omit or leave empty when there are none). - `metadata.domains` = the assessment domains your report actually has content for, consistent with your `rule.domain` values — see [assessment domains](#assessment-domains). - `metadata.mode` (optional) = `"full"` when any security finding exists, else `"issue-only"`. - `metadata.status` = `"completed"`; `metadata.targetIds` = the target ids in scope (may be empty). - `metadata.id` (and the report-directory id) = `analysisStartTime` formatted `yyyyMMddHHmmss` (UTC); use the current UTC time when the CSV has no timestamp. ### Step 6 — Write and validate Write `report.json` to the versioned location ([Output location](#output-location)), then validate and fix until clean: ```bash # bash + jq ./report_tools.sh validate "<workspace>/.github/modernize/assessment/reports/report-<id>/report.json" # …or PowerShell 7+ pwsh ./report_tools.ps1 validate "<workspace>/.github/modernize/assessment/reports/report-<id>/report.json" ``` Resolve every reported consistency error before finishing. ### Step 7 — Summarize for the user Report a concise conversion summary: - **Converted rows** — number of security findings (CVE/CWE), upgrade incidents (with components), and ordinary issue incidents (with the solutions they mapped to); plus the project / rule / incident counts and the report path + id. - **Remaining rows** — rows you could not confidently map, and *why* (no matching solution, ambiguous column, missing id). - **Suggestions** — concrete next steps (e.g. pick a specific solution for a remaining row, add a project column to the CSV, supply severities), so the user can close the gaps. ## Output location - `{workspace-path}/.github/modernize/assessment/reports/report-{reportId}/report.json` - `reportId` = the report's `metadata.id` from Step 5 (`analysisStartTime` as `yyyyMMddHHmmss`, UTC). - Consider copying the original CSV next to `report.json` as `source.csv` for provenance. ## Success criteria - ✅ `report.json` is written to the versioned reports directory and the `validate` command reports **VALID**. - ✅ Projects reflect the CSV (one per module/service, or a single project with `appName: ""` when none is given). - ✅ CVE/CWE rows are `security[]` findings (deduped by id) with a `mandatory|potential|optional` severity; `mode` is `full`. - ✅ Rows implying a JDK / Spring Boot / Spring Framework / Jakarta EE upgrade add a mandatory upgrade incident whose `ruleId` came from the `upgrade-solutions` command. - ✅ Other issues map to a solution's canonical `ruleId` (via `list-solutions` + `rules-for-solution`) wherever one fits. - ✅ Every rule carries `domain` and `category` fields; every incident's `ruleId` resolves to a rule in `rules{}`; CVE/CWE findings stay in `security[]`, not `incidents[]`. - ✅ `metadata.domains` matches the report's content and the emitted `rule.domain` values (enforced by `validate`). - ✅ The user gets a summary of converted rows, remaining rows, and suggestions. ## Troubleshooting - **`validate` reports a consistency error** (e.g. an incident `ruleId` with no matching rule, or a `domains`/`security` mismatch) — the message names the exact field; fix that field. - **Don't add fields the schema doesn't allow** (e.g. a `summary` object, or `issues`/`storyPoints` on a project). The helper's `validate` is lenient about extra keys, but the app's importer enforces `additionalProperties: false` and will reject the report — this schema has no summary. Keep to the documented shape. - **A row has a CVE/CWE *and* needs an upgrade** — emit both: a `security[]` finding **and** an upgrade incident. They are not duplicates; the finding documents the vulnerability, the incident drives the upgrade solution. - **No solution fits an issue** — `list-solutions --query` returns nothing useful. Record the issue with a synthetic, descriptive `ruleId` (no Formula will attach) or list it under "remaining" with a suggestion.
View on GitHub
This SKILL.md is very large, so SkillsMP previews the first section here. View on GitHub