Generate grouped Jira ticket descriptions from AIST findings by clustering related issues into remediation families and splitting them by project-specific source path.
Generate Jira ticket descriptions from AIST findings, which don't have connected work item link. Findings are semantically grouped into remediation families; each family becomes one ticket. Within each ticket, findings are subdivided by project path prefix. Each Jira row must be developer-ready, line-accurate, and tied to an immutable source revision.
This skill is not a generic summarizer. The output must tell a developer:
Do not stop after first-pass generation. This skill includes a mandatory validation pass over the generated markdown.
project_name (required): AIST project name; source tree is under /tmp/aist/projects/dev/<project_name>/severity (optional): critical, high, medium, low, any (default: any)output_dir (optional): directory to save generated .md files (default: triage/jira_<project_name>)Query active true-positive findings for the project,which don't have related work item link, including the linked project_version.
You need the project version commit hash to build immutable source links.
docker compose exec uwsgi python manage.py shell -c "
from aist.models import AISTProject
from dojo.models import Finding
project = AISTProject.objects.get(name='<project_name>')
versions = list(project.aistprojectversion_set.all())
engagement_to_version = {v.engagement_id: v for v in versions}
engagements = [v.engagement_id for v in versions]
findings = Finding.objects.filter(
test__engagement_id__in=engagements,
active=True,
false_p=False,
work_item_links__isnull=True,
).distinct().order_by('file_path', 'line', 'id')
for fn in findings:
version = engagement_to_version.get(fn.test.engagement_id)
print('|'.join([
str(fn.id),
fn.severity or '',
fn.title or '',
str(fn.cwe or ''),
fn.vuln_id_from_tool or '',
fn.file_path or '',
str(fn.line or ''),
fn.sourcefile_link or '',
version.version if version and version.version else '',
str(version.id) if version else '',
]))
"
Filter by severity input if provided.
Retain at least these fields per finding:
finding.idfinding.file_pathfinding.linefinding.sourcefile_linkproject_version.idproject_version.versionproject_version.version is the canonical git commit hash for the source link.
Do not replace it with a branch name.
AIST has no built-in grouping. Group findings yourself using the following signals, in priority order:
Signal 1 — vuln_id_from_tool (strongest)
Findings with the same SAST rule ID usually describe the same vulnerability family.
Signal 2 — CWE
Use this when vuln_id_from_tool is absent or too granular.
Signal 3 — Normalized title Strip finding-specific tokens such as file names, function names, variables, line numbers, and service names.
Examples:
Missing USER directiveSQL injectionHardcoded passwordSignal 4 — File pattern + title combination When needed, combine:
Produce a list of groups, each with:
family_namefindingsgrouping_rationaleEach finding belongs to exactly one family. If a finding truly does not fit, place it in Miscellaneous.
After semantic grouping, build Jira rows by remediation family, not by raw finding.
Goal:
Merge findings into one row when all of the following are true:
Examples that should usually be merged:
When merged:
Location links in one cell, comma-separatedSAST URL links in one cell, comma-separatedDo not keep separate rows only because:
Keep separate rows when:
Prefer the most specific local source checkout available.
Typical options:
/tmp/aist/projects/dev/<project_name>//tmp/aist/projects/dev/<project_name>/develop/runs/<pipeline_id>/dev_<project_name>Use the local checkout to inspect the real code. Do not rely on remote provider rendering to infer lines.
For each finding:
You must determine:
Do not blindly trust finding.line.
The final Jira link must point to the actual dangerous moment, not a nearby helper, blank line, listener registration, or wrapper code.
If the file cannot be read:
Every Location entry must use an immutable commit-based blob URL.
Rules:
blob/...project_version.version as the blob ref for that finding#L<line>sourcefile_link when availableCorrect pattern:
https://<provider>/<org>/<repo>/-/blob/<commit_hash>/<path>#L<line>Incorrect patterns:
.../blob/develop/....../blob/main/...If a merged row contains findings from different commits:
If the provider URL exists in sourcefile_link:
Description must explain what the developer needs to fix.
It should:
It must not:
State the concrete impact. Avoid overclaiming.
If the case is conditional or depends on downstream validation, say so explicitly.
Every ticket must include a Why This Is Exploitable section.
This section is mandatory and must be concrete:
Good examples:
innerHTML and executes in the admin originevent.data without validating origin or sourceConditional wording is required when appropriate, for example:
redirect_uri for the supplied client."Expected Fix is the family-level remediation pattern.
It must:
Do not use fake simplifications that would not really work in this codebase.
Each row must include a concrete, applicable remediation with a short example.
It must:
It must not:
For each finding family, generate one .md file with this structure:
## Summary
<one line: "Remediate <vulnerability type> in <project_name>">
## Description
<2-3 sentences: what the problem is and what developers need to change>
<bulleted general remediation approach>
## Impact
<2-4 sentences: concrete impact, with careful wording if conditional>
## Why This Is Exploitable
<2-5 sentences: concrete exploit path, attacker control, sink, and preconditions>
## Expected Fix
<general fix pattern with representative code snippet(s)>
## Affected findings
### <project/path/prefix>
| Location | Suggested remediation | SAST URL |
|---|---|---|
| [path/to/File@<commit>#LNN](<provider_blob_url_with_commit_hash>) | <specific developer-ready fix with example> | [<id>](https://aist.itsec-europe.com/findings/<id>) |
Project path prefix:
Multiple findings in one row:
Location links in the same cell, comma-separatedSAST URL links in the same cell, comma-separatedOutput must be Jira-friendly markdown.
Rules:
<br> inside table cellsSAST URL in every rowjira_<family_slug>_<project_name>.mdfamily_slug = lowercased family name with spaces/special chars replaced by _, max 40 charsoutput_dirIf multiple families were processed, also print an index:
Generated N Jira descriptions for project '<project_name>':
1. jira_docker_root_<project_name>.md — "Remediate Docker images running as root" (12 findings, 4 path groups)
2. jira_sql_injection_<project_name>.md — "Remediate SQL injection" (3 findings, 2 path groups)
Re-open the generated markdown and validate it against local source before finalizing.
For every row:
Typical mistakes that must be corrected:
innerHTML, redirect(...), verify=False, etc.Do not skip this pass.
family_name reflects the remediation family clearlyDescription explains what to fix, not why grouping happenedWhy This Is Exploitable is present and concreteImpact wording matches the certainty levelLocation link uses an immutable commit hash, never a branch refLocation link has a line anchor<br> or broken markdown/quote formatting remainsSAST URL is present in every row.md/aist-jira-description project_name=nx
/aist-jira-description project_name=nx severity=critical
/aist-jira-description project_name=nx severity=high output_dir=docs/jira