Metadata-Version: 2.4
Name: sonar-taskgen
Version: 0.2.0
Summary: Fetch SonarQube issues and generate task-grouped markdown task lists
License: MIT License
        
        Copyright (c) 2026 ugurcsen
        
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
        
Keywords: sonarqube,jira,tasks,code-quality
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Software Development :: Quality Assurance
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Dynamic: license-file

# sonar-taskgen

Fetch unresolved issues from a [SonarQube](https://www.sonarsource.com/products/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.

## Pipeline

```
SonarQube ── sonar-fetch ──> sonarqube_issues_<project>.json ──> sonar-taskgen ──> tasks/*.md + README.md
```

1. **`sonar-fetch`** queries the SonarQube issues API and saves the raw issues (plus component paths, totals, metadata) to a JSON file.
2. **`sonar-taskgen`** reads that JSON, filters/grouping issues, and writes one markdown file per task into `tasks/<module>/NNN-<rule>-<slug>.md`, plus a `tasks/README.md` index.

## Requirements

- Python 3.9+
- A SonarQube server with API access and a [token](https://docs.sonarsource.com/sonarqube/latest/user-guide/user-account/generating-and-revoking-tokens/)

## Installation

```sh
pip install .
# or editable, for development:
pip install -e .
```

Two console scripts are installed:

```sh
sonar-fetch      # step 1: fetch issues
sonar-taskgen    # step 2: generate tasks
```

Prefer 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:

```sh
PYTHONPATH=src python -m sonar_taskgen fetch --project <key>
PYTHONPATH=src python -m sonar_taskgen generate --input sonarqube_issues_*.json
```

Or, since each script is self-contained (stdlib only), execute it directly without any environment setup:

```sh
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.json
```

## Quick start

```sh
export 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-taskgen
```

This writes the tasks under `tasks/`. Open `tasks/README.md` for the index.

## CLI reference

### `sonar-fetch`

| 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`.

### `sonar-taskgen`

| 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, grouping and effort

**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 before generating

`--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:

```sh
sonar-taskgen --input examples/sample_issues.json --preview
```

```
Task 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.

## Output format

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.

## Example: one task per rule within sub-modules

```sh
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.

## Development

```sh
# 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.json
```

## License

MIT. See [LICENSE](LICENSE).
