feat(pr): standardise PR title, body, and task slug for Jira tickets#235
feat(pr): standardise PR title, body, and task slug for Jira tickets#235EllaLiu0401 wants to merge 4 commits into
Conversation
Wire Jira ticket metadata (summary, issue_type, priority, url) from `cube auto --jira` through the workflow context into `create_pr()`. - Task files now named with human-readable slugs (AP-878-my-title.md) instead of bare Jira keys (AP-878.md) - PR titles use Conventional Commits mapped from Jira issue type (Bug→fix, Story→feat, Task→chore) - PR bodies use type-specific templates: Bug Fix (root cause/fix/test), Feature (what/changes/test), Chore (summary/changes) - Non-Jira tasks fall back to the original format unchanged Closes AP-878 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (3)
📜 Recent review details🔇 Additional comments (3)
WalkthroughCLI captures Jira fields, threads them into the auto orchestration WorkflowContext; handlers forward the metadata into create_pr which builds Jira-aware PR titles/bodies; Jira task filenames are slugified. Separately, a SteeringRuntime is added and a git submodule pointer is updated. ChangesJira Metadata Threading and PR Formatting
Steering runtime and repo pointer
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Warning Review ran into problems🔥 ProblemsErrors were encountered while retrieving linked issues. Errors (2)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@python/cube/commands/orchestrate/pr.py`:
- Around line 20-27: The _build_pr_title function (and similar PR creation code
around lines referenced) assumes jira["issue_type"], jira["key"], and
jira["summary"] are non-null strings; normalize and guard these fields before
calling string methods by reading them with jira.get("issue_type"),
jira.get("key"), jira.get("summary") and coercing None to safe defaults (e.g.,
empty string or fallback values) and call .lower()/.strip() only after that
normalization; update _ISSUE_TYPE_TO_CC lookup to use the normalized issue_type
and ensure key/summary fallbacks avoid KeyError/TypeError so PR title building
never invokes .lower()/.strip() on None.
- Line 132: The printed fallback command uses raw interpolated variables (branch
and title) which breaks if title contains apostrophes or shell metacharacters;
update the console.print call(s) (the line printing "gh pr create --base main
--head {branch} --title '{title}'" and the similar one at the later occurrence)
to shell-quote both branch and title (e.g., use Python's shlex.quote or an
equivalent quoting helper) before interpolation so the suggested command is safe
to paste into a shell regardless of characters in the Jira-derived title.
In `@python/cube/integrations/jira.py`:
- Around line 550-559: The slug generation can produce an empty title (so full
becomes "<KEY>-"), causing malformed filenames; update the logic after computing
words/title_part to handle an empty title by falling back to the key-only form
(e.g., if not words or title_part == "" then set full = key) before applying the
max_len truncation, and preserve existing truncation behavior (rsplit on "-"
when trimming) so filenames become "<KEY>" (and thus "<KEY>.md") instead of
"<KEY>-".
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 0e9e2775-d3a9-431e-926e-4f356a7280ec
📒 Files selected for processing (7)
python/cube/cli.pypython/cube/commands/orchestrate/handlers.pypython/cube/commands/orchestrate/main.pypython/cube/commands/orchestrate/phases_registry.pypython/cube/commands/orchestrate/pr.pypython/cube/commands/orchestrate/workflow.pypython/cube/integrations/jira.py
📜 Review details
🔇 Additional comments (6)
python/cube/integrations/jira.py (1)
562-568: LGTM!python/cube/cli.py (1)
699-700: LGTM!Also applies to: 723-730, 855-855
python/cube/commands/orchestrate/main.py (1)
16-16: LGTM!Also applies to: 30-30, 50-50
python/cube/commands/orchestrate/phases_registry.py (1)
40-41: LGTM!python/cube/commands/orchestrate/workflow.py (1)
48-48: LGTM!Also applies to: 97-97
python/cube/commands/orchestrate/handlers.py (1)
464-464: LGTM!Also applies to: 542-542, 553-553, 562-562
| return ( | ||
| f"## Bug Fix\n\n{jira_line}\n\n" | ||
| f"### What was the bug?\n{desc_preview}\n\n" | ||
| f"### Root cause\n_To be filled by reviewer_\n\n" |
There was a problem hiding this comment.
[NITPICK] The Jira PR templates include literal _To be filled by reviewer_ placeholders for sections like Root cause, Fix, and How to test. Since these PRs are created autonomously, consider either populating those sections from the writer's notes or omitting empty sections so merged PR descriptions do not retain reviewer placeholders.
— Agent Cube / Judge PO/QA, Judge Principal 🤖
There was a problem hiding this comment.
Acknowledged — keeping the placeholders for now. They serve as a review checklist for the human reviewer to fill in; removing them would lose that prompt.
| fresh_writer=fresh_writer, | ||
| fresh_judges=fresh, | ||
| resume_prompt=prompt, | ||
| jira_metadata=_jira_metadata, |
There was a problem hiding this comment.
[WARNING] Jira metadata is only held in memory for the current cube auto --jira invocation. If a Jira-originated run is resumed later via cube auto <task-file> --resume or cube continue, PR creation receives jira_metadata=None and falls back to the generic title/body instead of the Jira-aware format.
— Agent Cube / Judge Backend, Judge Frontend & UX 🤖
There was a problem hiding this comment.
Acknowledged — persisting jira_metadata to disk for resume would require changing the task-file format or adding a sidecar file. Keeping as a known limitation for now; the fallback to generic title/body is safe.
| f"{footer}" | ||
| ) | ||
|
|
||
| if issue_type in ("story", "epic"): |
There was a problem hiding this comment.
[WARNING] Jira issue type Feature gets a feat(...) title but does not enter the feature body template, which only handles story and epic. It falls through to ## Changes and omits the feature PR testing section.
— Agent Cube / Judge Frontend & UX 🤖
There was a problem hiding this comment.
Fixed in dfbaf82 — added "feature" to both the _ISSUE_TYPE_TO_CC mapping and the body template condition.
| ) | ||
|
|
||
|
|
||
| def _first_sentences(text: str, n: int) -> str: |
There was a problem hiding this comment.
[NITPICK] _first_sentences is only used once. Inlining the small regex snippet at the call site would remove a single-use helper and local import.
— Agent Cube / Judge Principal 🤖
There was a problem hiding this comment.
Acknowledged — keeping _first_sentences as a named helper for readability. It's small but the intent is clearer than an inline regex.
| from .core.output import print_error, print_info | ||
| from .core.user_config import get_default_writer, is_single_mode_default, resolve_writer_alias | ||
|
|
||
| _jira_metadata = None |
There was a problem hiding this comment.
[NITPICK] _jira_metadata is a live local value that is passed into orchestration, so the leading underscore reads like an unused/private temporary. jira_metadata would better match how the variable is used.
— Agent Cube / Judge Principal 🤖
There was a problem hiding this comment.
Fixed in dfbaf82 — renamed _jira_metadata → jira_metadata.
Addressed feedback: - python/cube/commands/orchestrate/pr.py: _⚠️ Potential issue_ | _🟠 Major_ | _⚡ Qu... - python/cube/commands/orchestrate/pr.py: _⚠️ Potential issue_ | _🟡 Minor_ | _⚡ Qu... - python/cube/integrations/jira.py: _⚠️ Potential issue_ | _🟡 Minor_ | _⚡ Qu... - general: <!-- This is an auto-generated comment:... - general: **Actionable comments posted: 3** <deta... Automated fixes via cube auto --fix-comments
|
Addressed the actionable review feedback: Jira PR metadata is normalized before string operations, suggested manual gh commands are shell-quoted, and empty Jira summary slugs now fall back to the key. Addressed 2 top-level review comment(s):
Agent Cube |
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
python/cube/integrations/jira.py (1)
546-561:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winAdd defensive handling for truncation edge cases.
If
max_lenis very short or truncation lands mid-key, line 560'srsplitcould lose the issue number or leave trailing hyphens. The past review comment suggested using.rstrip("-")before the conditionalrsplitand adding anor key[:max_len]fallback to handle empty results.🛡️ Proposed fix
def _slugify_title(key: str, summary: str, max_len: int = 50) -> str: """Build a human-readable slug: AP-123-some-descriptive-words.""" import re slug = re.sub(r"[^a-z0-9]+", "-", summary.lower()).strip("-") words = [word for word in slug.split("-") if word] if not words: return key[:max_len] if len(words) <= 7: title_part = "-".join(words) else: title_part = "-".join(words[:5]) full = f"{key}-{title_part}" if title_part else key if len(full) > max_len: - full = full[:max_len].rsplit("-", 1)[0] + full = full[:max_len].rstrip("-") + if "-" in full: + full = full.rsplit("-", 1)[0] or key[:max_len] return full🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@python/cube/integrations/jira.py` around lines 546 - 561, The _slugify_title function can drop the issue key or leave trailing hyphens when truncating; update the truncation logic in _slugify_title to rstrip("-") before performing rsplit so you don't leave trailing hyphens, and if the rsplit yields an empty string (e.g., very small max_len), fall back to returning key[:max_len]; ensure this change is applied to the branch that assigns to full when len(full) > max_len so the function never returns an empty or mid-key slug.
♻️ Duplicate comments (1)
python/cube/commands/orchestrate/pr.py (1)
68-75:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winIssue type "feature" doesn't enter the feature template.
The condition only checks for lowercase
"story"and"epic". If Jira returns issue_type "Feature" (which normalises to"feature"), it falls through to the generic"## Changes"template and omits the feature-specific testing section.🔧 Proposed fix
- if issue_type in ("story", "epic"): + if issue_type in ("story", "epic", "feature"): return ( f"## Feature\n\n{jira_line}\n\n"🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@python/cube/commands/orchestrate/pr.py` around lines 68 - 75, The feature-specific template never runs when Jira returns "feature" because the condition currently only checks issue_type in ("story", "epic"); update the condition (where issue_type is evaluated in pr.py) to include "feature" (or ensure issue_type is normalized and check for "feature" alongside "story" and "epic") so that the Feature template block (the f-string that builds "## Feature...### How to test") is used for feature issues.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@python/cube/integrations/jira.py`:
- Around line 546-561: The _slugify_title function can drop the issue key or
leave trailing hyphens when truncating; update the truncation logic in
_slugify_title to rstrip("-") before performing rsplit so you don't leave
trailing hyphens, and if the rsplit yields an empty string (e.g., very small
max_len), fall back to returning key[:max_len]; ensure this change is applied to
the branch that assigns to full when len(full) > max_len so the function never
returns an empty or mid-key slug.
---
Duplicate comments:
In `@python/cube/commands/orchestrate/pr.py`:
- Around line 68-75: The feature-specific template never runs when Jira returns
"feature" because the condition currently only checks issue_type in ("story",
"epic"); update the condition (where issue_type is evaluated in pr.py) to
include "feature" (or ensure issue_type is normalized and check for "feature"
alongside "story" and "epic") so that the Feature template block (the f-string
that builds "## Feature...### How to test") is used for feature issues.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 6b0c7c31-a7fe-481c-b2fc-09e74ab8db78
📒 Files selected for processing (2)
python/cube/commands/orchestrate/pr.pypython/cube/integrations/jira.py
📜 Review details
🔇 Additional comments (5)
python/cube/commands/orchestrate/pr.py (4)
21-31: LGTM!
140-152: LGTM!
3-3: LGTM!
93-105: LGTM!python/cube/integrations/jira.py (1)
564-570: LGTM!
- Add "feature" to issue type condition so Feature tickets use the
feature PR body template instead of falling through to generic
- Add rstrip("-") and key fallback in slug truncation to prevent
trailing hyphens or empty slugs on edge-case max_len
- Rename _jira_metadata → jira_metadata (not a private/unused temp)
Co-authored-by: Cursor <cursoragent@cursor.com>
|
Both remaining items from CodeRabbit's second review are fixed in dfbaf82:
Also addressed Agent Cube review comments: renamed |
Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
🤖 Agent Cube Review
❌ 2 judge(s) requested changes.
Per-judge:
- judge_3: REQUEST_CHANGES
- judge_4: REQUEST_CHANGES
🤖 Agent Cube Peer Review
| @@ -0,0 +1 @@ | |||
| Subproject commit 28fd38c74f98a4605538301ce288c6b83b5489aa | |||
There was a problem hiding this comment.
[WARNING] Could these gitlink additions be removed from the PR? This path and the root agent-cube entry are committed as mode-160000 subproject pointers without a .gitmodules entry, and they appear unrelated to the Jira PR metadata change. Fresh checkouts will inherit unexplained, unusable gitlinks instead of normal project files.
— Agent Cube / Judge Backend, Judge Frontend & UX 🤖
| fresh_writer=fresh_writer, | ||
| fresh_judges=fresh, | ||
| resume_prompt=prompt, | ||
| jira_metadata=jira_metadata, |
There was a problem hiding this comment.
[WARNING] Jira metadata appears to be lost on resume. jira_metadata starts as None and is only populated during the current cube auto --jira invocation, so a later cube auto --resume / cube continue can reach PR creation with jira_metadata=None and fall back to the generic feat: <task_id> title/body instead of the Jira-specific formatting.
— Agent Cube / Judge Backend, Judge Frontend & UX 🤖
There was a problem hiding this comment.
🤖 Agent Cube Review
❌ 2 judge(s) requested changes.
Per-judge:
- judge_3: REQUEST_CHANGES
- judge_4: REQUEST_CHANGES
🤖 Agent Cube Peer Review
There was a problem hiding this comment.
🤖 Agent Cube Review
❌ 2 judge(s) requested changes.
Per-judge:
- judge_3: REQUEST_CHANGES
- judge_4: REQUEST_CHANGES
🤖 Agent Cube Peer Review
roy-songzhe-li
left a comment
There was a problem hiding this comment.
Prior concerns addressed well — Feature routing fixed, null normalization added, shell quoting done, slug fallback implemented. Two remaining issues: gitlink artifacts polluting the diff and a test gap around slug collisions.
| @@ -0,0 +1 @@ | |||
| Subproject commit 28fd38c74f98a4605538301ce288c6b83b5489aa | |||
There was a problem hiding this comment.
Contract: This file and the root agent-cube entry are mode-160000 subproject commits with no .gitmodules file. They appear unrelated to Jira PR metadata and will create broken gitlinks on fresh clones. Should be removed from this PR before merge.
| parts = re.split(r"(?<=[.!?])\s+", text.strip(), maxsplit=n) | ||
| return " ".join(parts[:n]) | ||
|
|
||
|
|
There was a problem hiding this comment.
Bug: _first_sentences splits on re.split(r"(?<=[.!?])\s+", text, maxsplit=n) and returns parts[:n]. If text contains no sentence-ending punctuation (common for short Jira descriptions like "Fix broken PR title"), the split produces a single element and the function returns the entire text unchanged. For short descriptions this is fine, but for long single-sentence Jira descriptions (200+ chars), the full blob becomes the PR body preview without truncation. Consider adding a character-length fallback (e.g., truncate to 200 chars with ellipsis if result exceeds a threshold).
| full = full[:max_len].rstrip("-") | ||
| if "-" in full: | ||
| full = full.rsplit("-", 1)[0] or key[:max_len] | ||
| return full |
There was a problem hiding this comment.
Test Gap: No test covers the case where two Jira tickets produce the same slug. Since write_task_file uses path.write_text(), the second call silently overwrites the first task file. For a solo cube auto --jira invocation this is fine, but if used in batch/parallel contexts (multiple tickets queued), data loss is possible. Worth at least a comment documenting this limitation or a test asserting the overwrite behavior is intentional.
| summary = str(jira.get("summary") or task_id).strip() | ||
| cc = _ISSUE_TYPE_TO_CC.get(issue_type, "feat") | ||
| title = f"{cc}({key}): {summary}" | ||
| if len(title) > 72: |
There was a problem hiding this comment.
Unclear: When jira.get("key") is None and jira.get("summary") is None, both fall back to task_id. The resulting title becomes feat(task_id): task_id — e.g., feat(AP-878): AP-878. The test asserts exactly this, but is the duplication intentional UX or would something like feat(AP-878): <no summary> be more informative to the reviewer?
roy-songzhe-li
left a comment
There was a problem hiding this comment.
⚠️ One merge blocker + solid feature work
Re-review via cursor-agent + code-review skill. Prior concerns are resolved and test coverage is strong. One blocking item before merge.
Prior Concerns ✅
- Feature template routing fixed
_jira_metadata→jira_metadata- Shell-quoting via
shlex.quote() - Null field normalization
- Empty summary slug fallback
- Metadata resume limitation acknowledged (safe fallback)
Test Coverage ✅
15 tests covering:
- Slug generation (empty, truncation, filename)
- PR title (null normalization)
- PR body (Feature template)
- PR creation (Bug type + jira_metadata)
- CLI threading (
--jiraflag)
Merge Blocker ❌
Gitlink artifacts (.claude/worktrees/kind-ishizaka-4e3c7e and agent-cube) must be removed. These are broken subproject pointers with no .gitmodules and will break fresh clones.
Minor Issues (not blockers)
- Long single-sentence Jira descriptions not truncated (no
.!?punctuation) - No test for slug collision (same ticket + similar summaries)
- Duplicated fallback when key and summary both None
Once the gitlinks are removed, this is ready to merge. Great work on addressing the feedback!
Summary
cube auto --jiraworkflow into PR creationAP-878-standardise-pr-title.mdinstead ofAP-878.md)fix, Story/Feature→feat, Task→chore)Context
AP-878 — after
cube auto --jira AP-123, PRs had generic titles (feat: AP-123) and minimal bodies. Jacob also suggested giving cube tasks sensible human-readable names instead of bare Jira keys.Changes
jira.py_slugify_title()— smart truncation: ≤7 words kept whole, >7 words takes first 5, 50 char maxpr.py_build_pr_title(),_build_pr_body()with 3 type-based templates + fallbackphases_registry.pyjira_metadatafield toWorkflowContextcli.pyJiraTicketand passes it throughmain.py,workflow.py,handlers.pyjira_metadatafrom CLI → orchestrator → Phase 10 →create_pr()tests/Test plan
python -m pytest tests/core/test_orchestrate_pr.py tests/core/test_jira_integration.py tests/cli/test_single_writer.py--jiratest verifies fetched ticket metadata is forwarded into orchestrationpy_compile)cube auto --jira <ticket>end-to-end and verify PR title/body format🤖 Generated with Claude Code
Overview
Integrates Jira ticket metadata into the auto-orchestration flow so PR titles, PR bodies and task filenames can be generated from Jira fields while preserving the original behaviour when no Jira metadata is present.
Key changes
python/cube/integrations/jira.py
python/cube/commands/orchestrate/pr.py
Metadata threading (CLI → Orchestrator → Workflow → PR creation)
Tests & safety
Notes