feat(pr): standardise PR title, body, and task slug for Jira tickets#235
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>
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughThis PR threads Jira ticket metadata from the CLI through the orchestration pipeline into GitHub PR creation. When Possibly related PRs
🚥 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
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
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.
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!
Removes two mode-160000 subproject pointers (.claude/worktrees/kind-ishizaka-4e3c7e and root agent-cube) that were accidentally committed from inside a worktree. Adds .claude/worktrees/ to .gitignore to prevent this from happening again. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
jira_metadata was only populated from the --jira CLI flag and stored in WorkflowContext (memory). On resume, it was lost because WorkflowState had no corresponding field. This caused create_pr() to fall back to generic titles instead of Jira-formatted ones. Follows the existing mode/writer_key persistence pattern: add field to WorkflowState, persist via phase 1 state_updates, rehydrate on resume. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
_first_sentences() now caps output at 200 chars with word-boundary truncation, preventing long punctuation-free Jira descriptions from bloating the PR body. _build_pr_title() no longer produces "feat(AP-878): AP-878" when both key and summary fall back to task_id — emits "feat: AP-878" instead. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
🤖 Agent Cube ReviewPer-judge:
🤖 Agent Cube Peer Review |
🤖 Agent Cube Review correctionThe previous Agent Cube comment was posted as a normal PR comment because GitHub rejects Correction: the original panel result was 3/5 judges produced decisions:
GitHub review posting was downgraded to a plain comment; no new deduped inline issues were posted. 🤖 Agent Cube Peer Review |
EllaLiu0401
left a comment
There was a problem hiding this comment.
🤖 Agent Cube Review
Issues:
- Several unresolved-thread replies appear to claim fixes that are not present on the PR head. Judges report the fixes for gitlink removal, Jira metadata resume persistence,
feat(KEY): KEYtitle duplication, and_first_sentencestruncation live onclaude/gifted-jang-429475rather than the PR branchclaude/confident-thompson-6889a7; either land those commits on this PR or correct the replies before approval.
Per-judge:
- judge_1: REQUEST_CHANGES
- judge_2: REQUEST_CHANGES
- judge_3: REQUEST_CHANGES
- judge_4: REQUEST_CHANGES
- judge_5: APPROVED
🤖 Agent Cube Peer Review
EllaLiu0401
left a comment
There was a problem hiding this comment.
🤖 Agent Cube Review
Per-judge:
- judge_1: APPROVED
- judge_2: APPROVED
- judge_3: APPROVED
- judge_4: REQUEST_CHANGES
- judge_5: APPROVED
🤖 Agent Cube Peer Review
EllaLiu0401
left a comment
There was a problem hiding this comment.
🤖 Agent Cube Review
Per-judge:
- judge_1: REQUEST_CHANGES
- judge_2: REQUEST_CHANGES
- judge_3: REQUEST_CHANGES
- judge_4: REQUEST_CHANGES
- judge_5: APPROVED
🤖 Agent Cube Peer Review
The direct `cube pr` path loaded WorkflowState but did not pass jira_metadata to create_pr(), causing Jira-backed tasks to get generic PR titles when using the manual PR creation command. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Uses rsplit to find the last space before the 69-char limit, matching the same pattern already used in _first_sentences(). Falls back to hard cut if word-boundary trim would lose too much content. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
EllaLiu0401
left a comment
There was a problem hiding this comment.
🤖 Agent Cube Review
✅ All 5/5 judges approved this PR.
✅ Cube panel: 5/5 approved with no blockers.
Per-judge:
- judge_1: APPROVED
- judge_2: APPROVED
- judge_3: APPROVED
- judge_4: APPROVED
- judge_5: APPROVED
Pass --allow-approve to have cube post APPROVE when the gate is HIGH-confidence-clear.
🤖 Agent Cube Peer Review
There was a problem hiding this comment.
🤖 Agent Cube Review
Per-judge:
- judge_1: APPROVED
- judge_2: APPROVED
- judge_5: APPROVED
🤖 Agent Cube Peer Review
leobaldock
left a comment
There was a problem hiding this comment.
Architecture suggestion, not a correctness blocker: I think the cleanest follow-up here is to keep Jira task identity separate from the nicer human-readable task filename.
Right now the generated task file name includes the summary slug, and the rest of the workflow derives task_id from that filename. That means a display/detail choice can leak into state keys, branches, worktrees, sessions, decision files, and manual commands. For Jira-backed tasks, I would keep task_id canonical as the Jira key (AP-878) and treat .prompts/AP-878-standardise-pr-title.md as just the task file path.
Concretely, I would structure it like this:
cube auto --jira AP-878setstask_id=ticket.keyexplicitly, even though the generated task file can keep the slugged filename.WorkflowStatepersists the resolvedtask_file/task_path, and resume/-pfeedback use that stored path before falling back to guessed.prompts/{task_id}.mdnames.- Jira metadata becomes a small typed object/dataclass with
from_ticket,from_dict, andto_dict, so normalization happens once instead of passing a loose dict through CLI, workflow context, state, and PR formatting helpers. create_pr()then acceptsJiraMetadata | None, keeping the title/body helpers focused on formatting rather than defensive schema cleanup.
That keeps the nicer filename this PR introduces, but preserves the stable workflow identity users will naturally reach for in commands like cube status AP-878, cube pr AP-878, and later resumes. It also gives the persisted Jira metadata an explicit schema, which should make future PR body/template changes easier to maintain.
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
This PR integrates Jira ticket metadata into PR creation and task file naming, automating the generation of well-formatted PR titles and bodies whilst improving task file discoverability.
Key Changes
PR Title & Body Generation
PR titles now follow Conventional Commits format derived from Jira issue type:
fix(AP-XXX): <description>feat(AP-XXX): <description>chore(AP-XXX): <description>PR bodies use issue-type-specific templates:
Non-Jira workflows fall back to original formatting unchanged.
Task File Naming
Task files now use human-readable slugified filenames (
AP-878-standardise-pr-title.mdinstead ofAP-878.md). The slug generator:Metadata Threading
Jira metadata flows from CLI → orchestration layer → PR creation, with persistence across workflow resumptions. The
jira_metadatadictionary (keyed on ticket key, summary, issue type, priority, URL, description) is stored inWorkflowStateand restored on resume.Testing
New tests cover slug generation edge cases, PR title/body formatting for each issue type, Jira metadata threading through the CLI, and state persistence across resumptions.