Fetch unresolved issues from a SonarQube server and turn them into small, task-tracker-ready markdown tasks. Issues are grouped by rule × module, so each task covers one type of fix in one area of the codebase — perfect for handing out as work items.
Pure Python standard library. No runtime dependencies.
SonarQube ── sonar-fetch ──> sonarqube_issues_<project>.json ──> sonar-taskgen ──> tasks/*.md + README.md
sonar-fetchqueries the SonarQube issues API and saves the raw issues (plus component paths, totals, metadata) to a JSON file.sonar-taskgenreads that JSON, filters/grouping issues, and writes one markdown file per task intotasks/<module>/NNN-<rule>-<slug>.md, plus atasks/README.mdindex.
- Python 3.9+
- A SonarQube server with API access and a token
pip install .
# or editable, for development:
pip install -e .Two console scripts are installed:
sonar-fetch # step 1: fetch issues
sonar-taskgen # step 2: generate tasksPrefer not to install? No problem — you can run the scripts directly from the source tree. Two options:
Run the module with the src directory on the path:
PYTHONPATH=src python -m sonar_taskgen fetch --project <key>
PYTHONPATH=src python -m sonar_taskgen generate --input sonarqube_issues_*.jsonOr, since each script is self-contained (stdlib only), execute it directly without any environment setup:
python3 src/sonar_taskgen/fetch_issues.py --project com.example:my-app
python3 src/sonar_taskgen/generate_tasks.py --input sonarqube_issues_com.example_my-app.jsonexport SONARQUBE_TOKEN=sqa_xxx
# 1. Fetch issues for a project (defaults to statuses OPEN,CONFIRMED)
sonar-fetch --project com.example:my-app
# 2. Generate tasks from the fetched JSON (auto-picks the newest sonarqube_issues_*.json)
sonar-taskgenThis writes the tasks under tasks/. Open tasks/README.md for the index.
| Flag | Default | Description |
|---|---|---|
--project |
(required) | SonarQube project key, e.g. com.example:my-app |
-H, --host |
http://localhost:9000 (or SONARQUBE_HOST) |
SonarQube base URL |
--statuses |
OPEN,CONFIRMED |
Comma-separated issue statuses |
--token |
SONARQUBE_TOKEN |
API token (overrides the env var) |
--output |
sonarqube_issues_<project>.json |
Output JSON path |
The project key resolution is lenient: it first tries the exact key, then the key with a -PR<n> suffix stripped (common for PR/branch builds), then fuzzy-matches against /api/projects/search. The key that actually produced issues is recorded in the JSON as working_key.
| Flag | Default | Description |
|---|---|---|
--input |
newest sonarqube_issues_*.json in cwd |
Input JSON from sonar-fetch |
--output-dir |
tasks |
Output directory |
--grouping |
rule-module |
Grouping strategy: rule-module, rule, module, file, severity, type |
--include-areas |
(empty = everything) | Comma-separated path prefixes to include, e.g. src/app,src/server |
--exclude-areas |
(empty) | Comma-separated path prefixes to exclude, e.g. projects,src/assets |
--module-depth |
2 |
Path segments per module beyond the matched area |
--min-group |
3 |
Minimum issues for a standalone task; smaller groups merge into a MISC task (only affects rule-module and rule) |
--multiplier |
2.0 |
Effort multiplier (see below) |
--round-to |
15 |
Round effort estimates up to this many minutes |
--verify-command |
(none) | Command shown in each task's verification suggestion |
--preview |
(off) | Print task-count and effort estimates for every grouping strategy without writing files |
Environment variables: SONARQUBE_TOKEN (required for sonar-fetch), SONARQUBE_HOST (optional host override).
Scope. By default every issue is in scope. --include-areas restricts processing to components under the given prefixes; --exclude-areas drops matching prefixes regardless. Prefixes are matched against the issue component path.
Grouping. Pick a strategy with --grouping:
| Strategy | Task = | Output layout |
|---|---|---|
rule-module |
rule × module; small groups merge into a MISC task per module |
tasks/<module>/NNN-<rule>-….md |
rule |
one task per rule (project-wide); small groups merge into a single MISC task |
tasks/NNN-<rule>-….md (flat) |
module |
one task per module | tasks/<module>/NNN-module-….md |
file |
one task per affected file | tasks/<parent-dir>/NNN-file-….md |
severity |
one task per severity level | tasks/NNN-<severity>-….md (flat) |
type |
one task per issue type (BUG, CODE_SMELL, …) | tasks/NNN-<type>-….md (flat) |
When an include area is matched, the module is that area prefix plus up to --module-depth extra path segments (e.g. area src/app/storefront + depth 2 → src/app/storefront/features/product). With no include areas, the module is simply the first --module-depth segments of each file's directory. --min-group only applies to rule-module and rule.
Effort. Per task, the SonarQube effort values of its issues are summed, multiplied by --multiplier, and rounded up to the nearest --round-to minutes. Each task file shows both the raw SonarQube total and the realistic estimate.
--preview computes the task plan for every grouping strategy and prints how many tasks each would create plus the average, minimum and maximum task effort — without writing anything:
sonar-taskgen --input examples/sample_issues.json --previewTask plan preview (no files written):
Grouping | Tasks | Total effort | Avg/task | Min | Max
-------------+-------+--------------+----------+--------+-------
rule-module* | 3 | 7h 15m | 2h 30m | 30m | 3h 45m
rule | 2 | 7h 15m | 3h 45m | 3h 30m | 3h 45m
module | 2 | 7h | 3h 30m | 3h | 4h
...
The row matching your --grouping selection is marked with *. Because per-task effort is rounded up, totals can differ slightly between strategies with different task counts. A normal run also reports Average task effort in its summary.
Every generated task file has the same structure:
# <rule> – <first issue message>
| Rule | Type | Severity | Issues | Effort (est.) | SonarQube | Affected files |
## Description
## Affected files
## Suggestions
tasks/README.md is regenerated from the task files themselves (it re-parses the table headers), so the format of task files and the index must stay in sync.
sonar-fetch --project com.example:my-app \
--host http://sonarqube.example.com:9000
sonar-taskgen \
--include-areas src/app,src/server \
--exclude-areas vendor,generated \
--module-depth 2 \
--verify-command 'yarn lint && yarn test'Note that --module-depth is a single global value; if your project needs a different depth per area, pick one value or split the run into several sonar-taskgen invocations.
# run the test suite (stdlib unittest, no deps)
python -m unittest discover -s tests
# try the generator offline with the bundled sample data
sonar-taskgen --input examples/sample_issues.jsonMIT. See LICENSE.