Skip to content

feat: add Asana ticket detection to ticket compliance check#2430

Open
Oxygen56 wants to merge 6 commits into
The-PR-Agent:mainfrom
Oxygen56:feat/asana-ticket-provider
Open

feat: add Asana ticket detection to ticket compliance check#2430
Oxygen56 wants to merge 6 commits into
The-PR-Agent:mainfrom
Oxygen56:feat/asana-ticket-provider

Conversation

@Oxygen56

@Oxygen56 Oxygen56 commented Jun 6, 2026

Copy link
Copy Markdown

What

Fixes #2002 — Adds Asana task references detection to the PR ticket compliance check.

Problem

The ticket compliance check only detected GitHub issues and JIRA tickets. Teams using Asana had no automated ticket validation.

Solution

Adds find_asana_tickets() that detects:

  • Full Asana URLs: https://app.asana.com/0/{project_id}/{task_id}
  • Shorthand format: ASANA-123456789012

Asana references are included in the compliance check's related tickets alongside GitHub and JIRA references.

@github-actions github-actions Bot added the feature 💡 label Jun 6, 2026
@qodo-free-for-open-source-projects

Copy link
Copy Markdown
Contributor

Review Summary by Qodo

Add Asana ticket detection and fix indentation normalization

✨ Enhancement 🐞 Bug fix

Grey Divider

Walkthroughs

Description
• Add Asana ticket detection to PR compliance check
  - Supports full URLs and ASANA-123456789 shorthand format
  - Asana tickets included in related tickets list
• Fix indentation character preservation in /improve suggestions
  - Handle negative delta_spaces with dedent operation
  - Normalize tabs/spaces to match original file's indentation
Diagram
flowchart LR
  A["PR Description & Branch"] -->|find_asana_tickets| B["Asana URLs Detected"]
  B -->|extract_tickets| C["Merged Ticket List"]
  C -->|Asana URLs| D["Placeholder Content"]
  E["Code Suggestions"] -->|dedent_code| F["Normalize Indentation"]
  F -->|Detect char| G["Tab or Space"]
  G -->|Apply| H["Corrected Code"]

Loading

Grey Divider

File Changes

1. pr_agent/tools/pr_code_suggestions.py 🐞 Bug fix +18/-2

Fix indentation character preservation in code suggestions

• Move indentation character detection outside conditional block
• Add handling for negative delta_spaces using textwrap.dedent
• Normalize all suggestion lines to match original file's indentation character
• Convert spaces to tabs when original file uses tab indentation

pr_agent/tools/pr_code_suggestions.py


2. pr_agent/tools/ticket_pr_compliance_check.py ✨ Enhancement +49/-0

Add Asana ticket detection to compliance check

• Add find_asana_tickets() function to detect Asana task references
• Support both full Asana URLs and ASANA-123456789 shorthand format
• Integrate Asana ticket detection into extract_tickets() workflow
• Handle Asana URLs with placeholder content since they cannot be fetched via GitHub API

pr_agent/tools/ticket_pr_compliance_check.py


Grey Divider

Qodo Logo

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Jun 6, 2026

Copy link
Copy Markdown
Contributor

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (4) 📎 Requirement gaps (1)

Context used

Grey Divider


Action required

1. Unsafe dline[remove_count:] dedent ✓ Resolved 📘 Rule violation ≡ Correctness
Description
When delta_spaces < 0 in dedent_code(), the new dedent logic slices a fixed remove_count of
characters from each non-empty line (dline[remove_count:]) without verifying they are indentation,
which can delete real code on lines with less leading whitespace or mixed tabs/spaces. This can
corrupt suggested patches and produce invalid or behavior-changing code suggestions.
Code

pr_agent/tools/pr_code_suggestions.py[R616-626]

+                elif delta_spaces < 0:
+                    # Remove exactly -delta_spaces leading spaces from each
+                    # non-empty line.  textwrap.dedent() strips the *common*
+                    # indent which may remove too much when lines have
+                    # varying indentation levels (Qodo bug #3).
+                    remove_count = -delta_spaces
+                    dedented_lines = []
+                    for dline in new_code_snippet.split('\n'):
+                        if dline.strip():
+                            dedented_lines.append(dline[remove_count:])
+                        else:
Evidence
PR Compliance ID 3 requires anticipating and handling edge cases, and the cited negative-delta
branch removes the first remove_count characters of each non-empty line unconditionally via
dline[remove_count:], rather than stripping only leading whitespace. Because delta_spaces is
computed using lstrip()-based character counts (where tabs count as 1 character), remove_count
can exceed the actual leading indentation on some lines (especially with varying indentation or
mixed tabs/spaces), causing the slice to remove non-indentation characters and thus delete real code
content.

Rule 3: Robust Error Handling
pr_agent/tools/pr_code_suggestions.py[616-626]
pr_agent/tools/pr_code_suggestions.py[607-648]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`dedent_code()` handles `delta_spaces < 0` by slicing `dline[remove_count:]` for each non-empty line, which removes raw characters rather than only indentation; if a line has fewer than `remove_count` leading whitespace characters (or uses tabs/spaces inconsistently), this can strip real code and corrupt the resulting suggestion.
## Issue Context
This logic runs on generated/published code suggestions, so incorrect slicing can produce syntactically broken or behavior-changing patches and mislead users into applying invalid edits. A concrete failure case: original indentation starts with `\t` (so `original_initial_spaces == 1`), the suggested first line uses 4 spaces (so `suggested_initial_spaces == 4`), yielding `delta_spaces = -3` and `remove_count = 3`, and a later suggested line like `"  foo()"` (2 leading spaces) becomes `"o()"` after slicing.
## Fix Focus Areas
- pr_agent/tools/pr_code_suggestions.py[607-648]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Unused pytest import 📘 Rule violation ⚙ Maintainability
Description
tests/unittest/test_ticket_compliance.py imports pytest but never uses it, which will trigger
Ruff/Flake8 unused-import checks and can fail CI/linting. This should be removed or the import
should be used (e.g., fixtures/parametrize).
Code

tests/unittest/test_ticket_compliance.py[R9-10]

+import pytest
+from pr_agent.tools.ticket_pr_compliance_check import find_asana_tickets
Evidence
PR Compliance ID 12 requires Flake8 findings to be fixed. The newly added test file introduces an
unused import (import pytest) with no references in the file.

AGENTS.md: All Flake8 Findings Must Be Fixed Without Silent Behavior Changes: AGENTS.md: All Flake8 Findings Must Be Fixed Without Silent Behavior Changes: AGENTS.md: All Flake8 Findings Must Be Fixed Without Silent Behavior Changes: AGENTS.md: All Flake8 Findings Must Be Fixed Without Silent Behavior Changes: AGENTS.md: All Flake8 Findings Must Be Fixed Without Silent Behavior Changes: AGENTS.md: All Flake8 Findings Must Be Fixed Without Silent Behavior Changes: AGENTS.md: All Flake8 Findings Must Be Fixed Without Silent Behavior Changes: AGENTS.md: All Flake8 Findings Must Be Fixed Without Silent Behavior Changes
tests/unittest/test_ticket_compliance.py[9-10]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The test module imports `pytest` but does not use it, causing an unused-import lint violation.
## Issue Context
Repository linting rules (Ruff/Flake8) typically fail on unused imports.
## Fix Focus Areas
- tests/unittest/test_ticket_compliance.py[9-10]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Asana detection lacks tests 📘 Rule violation ☼ Reliability
Description
The PR introduces new Asana ticket parsing and merges results into extract_tickets() but does not
add/update pytest coverage for this new behavior. This increases regression risk (e.g., parsing
edge-cases and ticket list truncation behavior) without automated validation.
Code

pr_agent/tools/ticket_pr_compliance_check.py[R48-66]

+def find_asana_tickets(text: str) -> list:
+    """Extract Asana task references from text.
+
+    Supports both full Asana URLs and shorthand ``ASANA-123456789012``
+    format.  Returns a list of unique task URLs.
+
+    Args:
+        text: The text to scan for Asana task references.
+
+    Returns:
+        A list of Asana task URLs.
+    """
+    tickets = set()
+    for match in re.finditer(r'https://app\.asana\.com/0/(\d+)/(\d+)', text):
+        tickets.add(match.group(0))
+    for match in re.finditer(r'(?:^|[^A-Za-z0-9])(?:ASANA|asana)[- ]?(\d{12,20})', text):
+        task_id = match.group(1)
+        tickets.add(f"https://app.asana.com/0/0/{task_id}")
+    return sorted(tickets)
Evidence
PR Compliance ID 15 requires adding/updating pytest tests for new/changed behavior. This PR adds
find_asana_tickets() and integrates it into extract_tickets() (new behavior) but does not
include any corresponding test additions in the changed files.

AGENTS.md: Add/Update Pytest Tests in the Appropriate Test Suite for Code Changes: AGENTS.md: Add/Update Pytest Tests in the Appropriate Test Suite for Code Changes: AGENTS.md: Add/Update Pytest Tests in the Appropriate Test Suite for Code Changes: AGENTS.md: Add/Update Pytest Tests in the Appropriate Test Suite for Code Changes: AGENTS.md: Add/Update Pytest Tests in the Appropriate Test Suite for Code Changes: AGENTS.md: Add/Update Pytest Tests in the Appropriate Test Suite for Code Changes: AGENTS.md: Add/Update Pytest Tests in the Appropriate Test Suite for Code Changes: AGENTS.md: Add/Update Pytest Tests in the Appropriate Test Suite for Code Changes: AGENTS.md: Add/Update Pytest Tests in the Appropriate Test Suite for Code Changes: AGENTS.md: Add/Update Pytest Tests in the Appropriate Test Suite for Code Changes: AGENTS.md: Add/Update Pytest Tests in the Appropriate Test Suite for Code Changes: AGENTS.md: Add/Update Pytest Tests in the Appropriate Test Suite for Code Changes: AGENTS.md: Add/Update Pytest Tests in the Appropriate Test Suite for Code Changes: AGENTS.md: Add/Update Pytest Tests in the Appropriate Test Suite for Code Changes: AGENTS.md: Add/Update Pytest Tests in the Appropriate Test Suite for Code Changes: AGENTS.md: Add/Update Pytest Tests in the Appropriate Test Suite for Code Changes: AGENTS.md: Add/Update Pytest Tests in the Appropriate Test Suite for Code Changes: AGENTS.md: Add/Update Pytest Tests in the Appropriate Test Suite for Code Changes: AGENTS.md: Add/Update Pytest Tests in the Appropriate Test Suite for Code Changes: AGENTS.md: Add/Update Pytest Tests in the Appropriate Test Suite for Code Changes: AGENTS.md: Add/Update Pytest Tests in the Appropriate Test Suite for Code Changes: AGENTS.md: Add/Update Pytest Tests in the Appropriate Test Suite for Code Changes: AGENTS.md: Add/Update Pytest Tests in the Appropriate Test Suite for Code Changes: AGENTS.md: Add/Update Pytest Tests in the Appropriate Test Suite for Code Changes: AGENTS.md: Add/Update Pytest Tests in the Appropriate Test Suite for Code Changes: AGENTS.md: Add/Update Pytest Tests in the Appropriate Test Suite for Code Changes: AGENTS.md: Add/Update Pytest Tests in the Appropriate Test Suite for Code Changes: AGENTS.md: Add/Update Pytest Tests in the Appropriate Test Suite for Code Changes: AGENTS.md: Add/Update Pytest Tests in the Appropriate Test Suite for Code Changes: AGENTS.md: Add/Update Pytest Tests in the Appropriate Test Suite for Code Changes: AGENTS.md: Add/Update Pytest Tests in the Appropriate Test Suite for Code Changes: AGENTS.md: Add/Update Pytest Tests in the Appropriate Test Suite for Code Changes: AGENTS.md: Add/Update Pytest Tests in the Appropriate Test Suite for Code Changes: AGENTS.md: Add/Update Pytest Tests in the Appropriate Test Suite for Code Changes: AGENTS.md: Add/Update Pytest Tests in the Appropriate Test Suite for Code Changes: AGENTS.md: Add/Update Pytest Tests in the Appropriate Test Suite for Code Changes: AGENTS.md: Add/Update Pytest Tests in the Appropriate Test Suite for Code Changes: AGENTS.md: Add/Update Pytest Tests in the Appropriate Test Suite for Code Changes: AGENTS.md: Add/Update Pytest Tests in the Appropriate Test Suite for Code Changes: AGENTS.md: Add/Update Pytest Tests in the Appropriate Test Suite for Code Changes: AGENTS.md: Add/Update Pytest Tests in the Appropriate Test Suite for Code Changes: AGENTS.md: Add/Update Pytest Tests in the Appropriate Test Suite for Code Changes: AGENTS.md: Add/Update Pytest Tests in the Appropriate Test Suite for Code Changes: AGENTS.md: Add/Update Pytest Tests in the Appropriate Test Suite for Code Changes: AGENTS.md: Add/Update Pytest Tests in the Appropriate Test Suite for Code Changes: AGENTS.md: Add/Update Pytest Tests in the Appropriate Test Suite for Code Changes: AGENTS.md: Add/Update Pytest Tests in the Appropriate Test Suite for Code Changes: AGENTS.md: Add/Update Pytest Tests in the Appropriate Test Suite for Code Changes: AGENTS.md: Add/Update Pytest Tests in the Appropriate Test Suite for Code Changes: AGENTS.md: Add/Update Pytest Tests in the Appropriate Test Suite for Code Changes: AGENTS.md: Add/Update Pytest Tests in the Appropriate Test Suite for Code Changes: AGENTS.md: Add/Update Pytest Tests in the Appropriate Test Suite for Code Changes: AGENTS.md: Add/Update Pytest Tests in the Appropriate Test Suite for Code Changes: AGENTS.md: Add/Update Pytest Tests in the Appropriate Test Suite for Code Changes: AGENTS.md: Add/Update Pytest Tests in the Appropriate Test Suite for Code Changes: AGENTS.md: Add/Update Pytest Tests in the Appropriate Test Suite for Code Changes: AGENTS.md: Add/Update Pytest Tests in the Appropriate Test Suite for Code Changes: AGENTS.md: Add/Update Pytest Tests in the Appropriate Test Suite for Code Changes: AGENTS.md: Add/Update Pytest Tests in the Appropriate Test Suite for Code Changes: AGENTS.md: Add/Update Pytest Tests in the Appropriate Test Suite for Code Changes: AGENTS.md: Add/Update Pytest Tests in the Appropriate Test Suite for Code Changes: AGENTS.md: Add/Update Pytest Tests in the Appropriate Test Suite for Code Changes: AGENTS.md: Add/Update Pytest Tests in the Appropriate Test Suite for Code Changes: AGENTS.md: Add/Update Pytest Tests in the Appropriate Test Suite for Code Changes
pr_agent/tools/ticket_pr_compliance_check.py[48-66]
pr_agent/tools/ticket_pr_compliance_check.py[158-186]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
New Asana ticket detection behavior was added, but there are no corresponding pytest tests validating URL/shorthand parsing and integration into `extract_tickets()`.
## Issue Context
`find_asana_tickets()` is new parsing logic and its output is merged into the ticket list used by the compliance workflow. This should be covered with unit tests to prevent regressions and ensure expected output/ordering.
## Fix Focus Areas
- pr_agent/tools/ticket_pr_compliance_check.py[48-66]
- pr_agent/tools/ticket_pr_compliance_check.py[158-186]
- tests/unittest/test_ticket_pr_compliance_check_asana.py[1-200]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View more (4)
4. Asana tickets truncated away ✓ Resolved 🐞 Bug ≡ Correctness
Description
In extract_tickets(), Asana links are appended after GitHub/branch tickets and then the merged list
is truncated to 3, so Asana references can be silently dropped whenever the first three tickets are
non-Asana. This makes Asana detection unreliable in multi-ticket PR descriptions/branches.
Code

pr_agent/tools/ticket_pr_compliance_check.py[R158-166]

+            # Also detect Asana ticket references in the PR description
+            asana_tickets = find_asana_tickets(user_description)
+            for link in asana_tickets:
+                if link not in seen:
+                    seen.add(link)
+                    merged.append(link)
      if len(merged) > 3:
          get_logger().info(f"Too many tickets (description + branch): {len(merged)}")
          tickets = merged[:3]
Evidence
The code appends Asana matches to merged and then truncates merged to 3 items, which can drop
Asana entries if they were appended after three existing tickets.

pr_agent/tools/ticket_pr_compliance_check.py[143-168]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`extract_tickets()` appends Asana tickets to `merged`, then enforces a global `merged[:3]` cap. If there are already 3 GitHub/branch tickets, all Asana references are dropped, defeating the purpose of adding Asana detection.
### Issue Context
The ticket list is used to build `tickets_content` and then `related_tickets` for prompts; losing Asana links means they never appear downstream.
### Fix Focus Areas
- pr_agent/tools/ticket_pr_compliance_check.py[154-168]
### Suggested fix
Adjust the capping logic so Asana tickets are not systematically pushed out. For example:
- Build `github_like = description_tickets + branch_tickets` and `asana = find_asana_tickets(user_description)`.
- If `asana` is non-empty, reserve at least 1 slot for the first Asana ticket (e.g., `tickets = unique(github_like)[:2] + unique(asana)[:1]`) and then fill remaining slots up to 3.
- Alternatively, increase the cap or apply separate caps per provider, but ensure the behavior is deterministic and tested.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


5. Asana tickets not fetchable 📎 Requirement gap ≡ Correctness
Description
The PR explicitly skips fetching Asana ticket details and only adds a placeholder entry, so Asana
tickets are not integrated as a supported provider in the ticket-fetching pipeline. This fails the
requirement to retrieve Asana ticket data similarly to other providers.
Code

pr_agent/tools/ticket_pr_compliance_check.py[R174-183]

+                    # Skip Asana URLs — these are external references,
+                    # included for visibility but cannot be fetched via GitHub API.
+                    if "app.asana.com" in ticket:
+                        tickets_content.append({
+                            "title": f"Asana Task: {ticket}",
+                            "url": ticket,
+                            "body": ("Asana task referenced in PR description. "
+                                     "Fetch task details from Asana for full context."),
+                        })
+                        continue
Evidence
PR Compliance ID 6 requires Asana ticket fetching/provider integration. The added logic explicitly
states Asana URLs "cannot be fetched" and short-circuits to a placeholder tickets_content entry,
demonstrating no Asana ticket retrieval is implemented.

Add Asana as a supported ticket provider for fetching tickets and compliance
pr_agent/tools/ticket_pr_compliance_check.py[174-183]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Asana URLs are detected but explicitly skipped from fetching, and a placeholder ticket body is inserted instead of retrieving task details from Asana.
## Issue Context
The compliance requirement expects an Asana Ticket Provider integrated into the existing ticket fetching pipeline so compliance can consume Asana ticket data like other providers.
## Fix Focus Areas
- pr_agent/tools/ticket_pr_compliance_check.py[158-184]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


6. Asana ticket schema mismatch ✓ Resolved 🐞 Bug ☼ Reliability
Description
extract_tickets() appends Asana tickets with keys like url (and omits ticket_url/labels),
but prompt templates render related_tickets using ticket.ticket_url and ticket.labels under
StrictUndefined, which will raise at render time when an Asana reference is present.
Code

pr_agent/tools/ticket_pr_compliance_check.py[R174-183]

+                    # Skip Asana URLs — these are external references,
+                    # included for visibility but cannot be fetched via GitHub API.
+                    if "app.asana.com" in ticket:
+                        tickets_content.append({
+                            "title": f"Asana Task: {ticket}",
+                            "url": ticket,
+                            "body": ("Asana task referenced in PR description. "
+                                     "Fetch task details from Asana for full context."),
+                        })
+                        continue
Evidence
The Asana branch appends a dict with url (not ticket_url) and no labels, while both reviewer
and description prompts access ticket.ticket_url and/or ticket.labels. Because prompts are
rendered with Environment(undefined=StrictUndefined), missing keys will raise during rendering.

pr_agent/tools/ticket_pr_compliance_check.py[173-183]
pr_agent/settings/pr_reviewer_prompts.toml[211-224]
pr_agent/settings/pr_description_prompts.toml[96-112]
pr_agent/tools/pr_reviewer.py[213-219]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Asana tickets are added to `tickets_content` with a different dict shape (`url` instead of `ticket_url`, and missing `labels`). Jinja templates used by PR review/description access `ticket.ticket_url` and `ticket.labels` while rendering under `StrictUndefined`, which throws on missing fields and can crash the flow.
## Issue Context
Existing ticket entries (GitHub/Azure) use `ticket_url` (and often `labels`), and templates assume these fields exist.
## Fix Focus Areas
- pr_agent/tools/ticket_pr_compliance_check.py[173-183]
- pr_agent/settings/pr_reviewer_prompts.toml[211-224]
- pr_agent/settings/pr_description_prompts.toml[96-112]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


7. Indent conversion loses whitespace ✓ Resolved 🐞 Bug ≡ Correctness
Description
dedent_code() now calls textwrap.dedent() whenever delta_spaces < 0 (can remove more
indentation than the delta) and converts leading spaces to tabs using leading_spaces // 4, which
drops remainder spaces and can change alignment/meaning in tab-sensitive suggestions.
Code

pr_agent/tools/pr_code_suggestions.py[R616-631]

+                elif delta_spaces < 0:
+                    new_code_snippet = textwrap.dedent(new_code_snippet).rstrip('\n')
+                # Normalize all lines in the suggestion to use the original's
+                # indentation character. This fixes the bug where /improve
+                # replaces tabs with spaces in Go, Makefile, and other
+                # tab-indented codebases (#1858).
+                if indent_char == '\t':
+                    new_lines = []
+                    for line in new_code_snippet.split('\n'):
+                        stripped = line.lstrip(' ')
+                        leading_spaces = len(line) - len(stripped)
+                        if leading_spaces > 0:
+                            new_lines.append('\t' * (leading_spaces // 4) + stripped)
+                        else:
+                            new_lines.append(line)
+                    new_code_snippet = '\n'.join(new_lines)
Evidence
dedent_code() is applied to every published suggestion, and its new branches (dedent on negative
delta + lossy space→tab conversion) directly mutate the suggestion text that becomes the patch
content.

pr_agent/tools/pr_code_suggestions.py[561-563]
pr_agent/tools/pr_code_suggestions.py[581-631]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The new indentation logic can corrupt generated ` ```suggestion``` ` patches:
- For `delta_spaces < 0`, `textwrap.dedent()` removes the *common* indent across all lines (not necessarily exactly `-delta_spaces`), so it may unindent too much.
- When normalizing to tabs, converting `leading_spaces // 4` loses leftover spaces (e.g., 6 spaces becomes 1 tab, losing 2 spaces), breaking alignment and potentially semantics.
## Issue Context
`dedent_code()` is invoked before publishing code suggestions, so this directly impacts the patch the user applies.
## Fix Focus Areas
- pr_agent/tools/pr_code_suggestions.py[561-563]
- pr_agent/tools/pr_code_suggestions.py[581-631]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

8. Ticket truncation reorders merged 📘 Rule violation ☼ Reliability ⭐ New
Description
When more than 3 tickets are found, the new selection logic rebuilds the ticket list as
github_like[:gh_slots] + asana_slot, which can change the original merged ordering and move an
Asana link to the end. This violates the requirement to preserve ordering when merging/truncating
collections and may make ticket selection/priority inconsistent.
Code

pr_agent/tools/ticket_pr_compliance_check.py[R165-173]

+            asana_links = [t for t in merged if t.startswith("https://app.asana.com/")]
+            github_like = [t for t in merged if not t.startswith("https://app.asana.com/")]
            if len(merged) > 3:
                get_logger().info(f"Too many tickets (description + branch): {len(merged)}")
-                tickets = merged[:3]
+                # Reserve at least one slot for an Asana reference when
+                # present so it is not systematically dropped.
+                asana_slot = asana_links[:1]
+                gh_slots = 3 - len(asana_slot)
+                tickets = github_like[:gh_slots] + asana_slot
Evidence
PR Compliance ID 22 requires preserving ordering when merging/truncating collections. The new code
explicitly reconstructs the truncated list by concatenating the first N non-Asana tickets with an
Asana slot, which can reorder elements relative to merged.

pr_agent/tools/ticket_pr_compliance_check.py[165-173]
Best Practice: Learned patterns

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The truncation logic for `merged` tickets (when `len(merged) > 3`) reorders the list by splitting into `asana_links`/`github_like` and recombining as `github_like[:gh_slots] + asana_slot`. This can change the original ordering from `merged`, which violates the ordering-preservation requirement for merge/truncation logic.

## Issue Context
The compliance checklist requires preserving ordering when truncating collections. We still want to guarantee at least one Asana ticket is included when present, but we should do so with minimal ordering disruption.

## Fix Focus Areas
- pr_agent/tools/ticket_pr_compliance_check.py[165-173]

## Suggested approach
- Start with `tickets = merged[:3]` (preserves original order).
- If any Asana links exist in `merged` and none are present in `tickets`, replace `tickets[-1]` with the first Asana link from `merged` that is not already included.
- Avoid reconstructing the list via category splits that change ordering.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


9. Asana shorthand case sensitive ✓ Resolved 🐞 Bug ≡ Correctness
Description
find_asana_tickets() only matches shorthand prefixes 'ASANA' or 'asana', so mixed-case forms like
'Asana-123…' are not detected (URL detection is unaffected). This can cause Asana shorthand
references to be missed and omitted from related_tickets.
Code

pr_agent/tools/ticket_pr_compliance_check.py[R63-65]

+    for match in re.finditer(r'(?:^|[^A-Za-z0-9])(?:ASANA|asana)[- ]?(\d{12,20})', text):
+        task_id = match.group(1)
+        tickets.add(f"https://app.asana.com/0/0/{task_id}")
Evidence
The shorthand regex hardcodes only two cases (ASANA|asana) and the only integration point is
extract_tickets() calling find_asana_tickets(user_description), so missed matches will never
enter the extracted ticket set.

pr_agent/tools/ticket_pr_compliance_check.py[48-66]
pr_agent/tools/ticket_pr_compliance_check.py[139-164]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The shorthand Asana regex explicitly matches only `ASANA` or `asana`, missing mixed-case variants (e.g., `Asana-123...`).
### Issue Context
URL detection uses a separate URL regex and is unaffected; this issue applies only to shorthand detection.
### Fix Focus Areas
- pr_agent/tools/ticket_pr_compliance_check.py[61-66]
### Suggested fix
Use a case-insensitive pattern, e.g.:
- `re.finditer(r'(?:^|[^A-Za-z0-9])ASANA[- ]?(\d{12,20})', text, flags=re.IGNORECASE)`
or
- `r'(?i:(?:^|[^A-Za-z0-9])asana)[- ]?(\d{12,20})'`
Ensure tests cover `ASANA-...`, `asana-...`, and `Asana-...`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


10. Unused _ASANA_TASK_*_PATTERN constants ✓ Resolved 📘 Rule violation ⚙ Maintainability
Description
The PR adds compiled Asana regex constants that are never used because find_asana_tickets()
re-defines patterns inline. This is dead code that adds maintenance overhead and can confuse future
changes.
Code

pr_agent/tools/ticket_pr_compliance_check.py[R38-66]

+# Compiled Asana task patterns
+_ASANA_TASK_URL_PATTERN = re.compile(
+    r'https://app\.asana\.com/0/(\d+)/(\d+)'
+)
+_ASANA_TASK_SHORT_PATTERN = re.compile(
+    r'\b(?:ASANA|asana)[- ]?(\d{12,20})\b'
+    r'|https://app\.asana\.com/0/\d+/\d+'
+)
+
+
+def find_asana_tickets(text: str) -> list:
+    """Extract Asana task references from text.
+
+    Supports both full Asana URLs and shorthand ``ASANA-123456789012``
+    format.  Returns a list of unique task URLs.
+
+    Args:
+        text: The text to scan for Asana task references.
+
+    Returns:
+        A list of Asana task URLs.
+    """
+    tickets = set()
+    for match in re.finditer(r'https://app\.asana\.com/0/(\d+)/(\d+)', text):
+        tickets.add(match.group(0))
+    for match in re.finditer(r'(?:^|[^A-Za-z0-9])(?:ASANA|asana)[- ]?(\d{12,20})', text):
+        task_id = match.group(1)
+        tickets.add(f"https://app.asana.com/0/0/{task_id}")
+    return sorted(tickets)
Evidence
PR Compliance ID 2 disallows dead/unused code. The diff introduces _ASANA_TASK_URL_PATTERN and
_ASANA_TASK_SHORT_PATTERN, but the extraction logic uses new inline regexes via re.finditer(...)
instead, leaving the compiled constants unused.

Rule 2: No Dead or Commented-Out Code
pr_agent/tools/ticket_pr_compliance_check.py[38-66]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`_ASANA_TASK_URL_PATTERN` and `_ASANA_TASK_SHORT_PATTERN` are introduced but not used; `find_asana_tickets()` uses inline `re.finditer(...)` patterns instead.
## Issue Context
This violates the no-dead-code requirement and increases maintenance cost.
## Fix Focus Areas
- pr_agent/tools/ticket_pr_compliance_check.py[38-66]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View more (2)
11. New code uses single quotes 📘 Rule violation ⚙ Maintainability
Description
New Python code in this PR introduces several single-quoted strings (e.g., regexes and literals),
which conflicts with the stated preference for double quotes. This can lead to inconsistent style
and avoidable lint churn.
Code

pr_agent/tools/ticket_pr_compliance_check.py[R39-65]

+_ASANA_TASK_URL_PATTERN = re.compile(
+    r'https://app\.asana\.com/0/(\d+)/(\d+)'
+)
+_ASANA_TASK_SHORT_PATTERN = re.compile(
+    r'\b(?:ASANA|asana)[- ]?(\d{12,20})\b'
+    r'|https://app\.asana\.com/0/\d+/\d+'
+)
+
+
+def find_asana_tickets(text: str) -> list:
+    """Extract Asana task references from text.
+
+    Supports both full Asana URLs and shorthand ``ASANA-123456789012``
+    format.  Returns a list of unique task URLs.
+
+    Args:
+        text: The text to scan for Asana task references.
+
+    Returns:
+        A list of Asana task URLs.
+    """
+    tickets = set()
+    for match in re.finditer(r'https://app\.asana\.com/0/(\d+)/(\d+)', text):
+        tickets.add(match.group(0))
+    for match in re.finditer(r'(?:^|[^A-Za-z0-9])(?:ASANA|asana)[- ]?(\d{12,20})', text):
+        task_id = match.group(1)
+        tickets.add(f"https://app.asana.com/0/0/{task_id}")
Evidence
PR Compliance ID 10 requires following repository Python style guidance including preferring double
quotes. The added Asana patterns and extraction logic use single-quoted strings (regex patterns and
literals), making the new code inconsistent with that standard.

AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-char Lines, isort Import Ordering, Prefer Double Quotes): AGENTS.md: Python Code Must Comply with Ruff/pyproject.toml Style (120-c...

Comment thread pr_agent/tools/ticket_pr_compliance_check.py
Comment thread pr_agent/tools/ticket_pr_compliance_check.py
Comment thread pr_agent/tools/pr_code_suggestions.py
@naorpeled

Copy link
Copy Markdown
Member

Hey @Oxygen56,
Thanks for working on this!

Can you please add tests to validate the new functionality? 🙏

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Jun 6, 2026

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit a2bf77a

Comment thread pr_agent/tools/ticket_pr_compliance_check.py Outdated
Comment thread pr_agent/tools/ticket_pr_compliance_check.py
Comment thread pr_agent/tools/ticket_pr_compliance_check.py
Comment thread pr_agent/tools/ticket_pr_compliance_check.py
Comment thread pr_agent/tools/ticket_pr_compliance_check.py Outdated
@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Jun 6, 2026

Copy link
Copy Markdown
Contributor

Code Review by Qodo

Grey Divider

New Review Started

This review has been superseded by a new analysis

Grey Divider

Qodo Logo

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Jun 6, 2026

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit a0c62b4

Comment thread pr_agent/tools/pr_code_suggestions.py
Comment thread tests/unittest/test_ticket_compliance.py Outdated
@Oxygen56

Oxygen56 commented Jun 6, 2026

Copy link
Copy Markdown
Author

感谢所有 review 意见,我已在最新提交中处理了以下问题:

Bug 修复:

  • ✅ 不安全的 dline[remove_count:] 去缩进 → 改为只剥离每行实际的空白字符
  • ✅ Asana ticket 被截断丢弃 → 重构截断逻辑,存在 Asana ticket 时至少保留 1 个位置
  • ✅ Asana 缩写大小写敏感 → 添加 re.IGNORECASE,现在支持 ASANA-/asana-/Asana-
  • ✅ Asana 缩写过度匹配 → 添加 \b 单词边界

规范/安全修复:

  • ✅ 移除未使用的 import pytest
  • _ASANA_TASK_*_PATTERN 常量现在在 find_asana_tickets() 中使用
  • ✅ 单引号改为双引号
  • ✅ CodeQL: 使用 startswith() 替代子串匹配

测试补充:

  • ✅ 新增 test_shorthand_mixed_case_asana 测试
  • ✅ 新增 test_shorthand_respects_word_boundary 测试

Comment thread tests/unittest/test_ticket_compliance.py Outdated
@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Code Review by Qodo

Grey Divider

New Review Started

This review has been superseded by a new analysis

Grey Divider

Qodo Logo

Oxygen56 and others added 6 commits June 8, 2026 23:27
The dedent_code method only adjusted indentation when delta_spaces > 0
and never normalized the indentation character. This caused /improve to
replace tabs with spaces in Go, Makefile, and other tab-indented codebases.

Changes:
- Handle delta_spaces < 0 case with dedent (not just > 0)
- Normalize all suggestion lines to use the original file's indentation
  character (tab or space), auto-detected from the existing code

Fixes The-PR-Agent#1858

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds support for detecting Asana task references in PR descriptions
and branch names. Supports both full URLs and ASANA-123456789 shorthand
format.

- find_asana_tickets() extracts Asana task URLs from text
- Asana tickets are included in the compliance check's related tickets
- Graceful handling: Asana URLs are shown with a placeholder body since
  they cannot be fetched via the GitHub API

Fixes The-PR-Agent#2002

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Use ticket_url instead of url (templates access ticket.ticket_url)
- Add labels field (templates access ticket.labels under StrictUndefined)
- Add ticket_id field for consistency with GitHub ticket entries
- Replace textwrap.dedent() with exact -delta_spaces removal to avoid
  stripping too much indentation when lines have varying levels
- Preserve remainder spaces when converting spaces to tabs
  (e.g. 6 spaces → 1 tab + 2 spaces instead of silently dropping 2)
…check

- Covers full Asana URLs, shorthand ASANA- prefix, case-insensitivity
- Tests deduplication, sorting, empty input, and mixed PR description content
- Validates GitHub URLs are not misidentified as Asana tickets
- Use compiled Asana patterns (_ASANA_TASK_URL_PATTERN,
  _ASANA_TASK_SHORT_PATTERN) in find_asana_tickets() instead of
  inline regex (CodeQL unused-variable, comments The-PR-Agent#5/The-PR-Agent#6).
- Add re.IGNORECASE flag to _ASANA_TASK_SHORT_PATTERN for proper
  case-insensitive matching.
- Replace substring match ('app.asana.com' in ticket) with
  prefix check (ticket.startswith()) to fix CodeQL URL
  sanitization warning (comment The-PR-Agent#4).
- Reserve at least one slot for Asana tickets during
  truncation when there are 3+ GitHub/branch tickets
  (comment The-PR-Agent#8).
- Dedent only actual leading whitespace instead of blindly
  slicing dline[remove_count:] to avoid deleting real code
  on lines with less indentation (comments The-PR-Agent#3/The-PR-Agent#9).
- Remove unused 'import pytest' from test file
  (comments The-PR-Agent#10/The-PR-Agent#20).
- Add test_shorthand_mixed_case_asana and
  test_shorthand_respects_word_boundary test cases.
@Oxygen56 Oxygen56 force-pushed the feat/asana-ticket-provider branch from 2921c2b to 3ce5450 Compare June 8, 2026 15:29
@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit 3ce5450

@Oxygen56

Oxygen56 commented Jun 8, 2026

Copy link
Copy Markdown
Author

Hey @naorpeled, thanks for the review!

Tests have been added — there are now 12 test cases in tests/unittest/test_ticket_compliance.py covering:

  • Full Asana URL detection (https://app.asana.com/0/...)
  • Shorthand format (ASANA-123456789012)
  • Case-insensitive matching (asana-, Asana-)
  • Word boundary enforcement (no false matches on NOTASANA-...)
  • Deduplication of identical tickets
  • Multiple tickets in mixed content
  • Empty input / no-ticket text
  • GitHub URL exclusion
  • Sorted output

The test file was added in commit a0c62b4 and extended with two additional edge cases in the latest push (3ce5450). Let me know if anything else is needed!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add Support for Asana Ticket Provider

3 participants