-
Notifications
You must be signed in to change notification settings - Fork 94
LCORE-1632 Feature design process: improvements #1445
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
7467507
5aa09f1
c03eecd
0720d24
f60cb7d
227125f
3df0385
d712787
7664ddc
ec902d8
269853d
8d58dfa
fd3cf57
8b6088c
c79bf25
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,8 +5,16 @@ You are starting a spike for a feature in the Lightspeed Core project. | |
| Follow the process in `docs/contributing/howto-run-a-spike.md`. Use the | ||
| templates it references. | ||
|
|
||
| The user will provide context about the feature — a JIRA ticket, a description, | ||
| or a conversation. Read what is provided and ask for anything missing. | ||
| If the user provides a JIRA ticket number (e.g., "1234" or "LCORE-1234"), | ||
| fetch the ticket content by running `sh dev-tools/fetch-jira.sh <number>`. | ||
| The output includes child issues — decide which linked tickets to fetch | ||
| for additional context. | ||
|
Comment on lines
+10
to
+11
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial Make parsing instructions deterministic by naming exact output section headers. To reduce ambiguity, reference the exact fetch output labels ( 🤖 Prompt for AI Agents |
||
|
|
||
| Otherwise, the user will provide context about the feature directly. | ||
|
|
||
| When proposing JIRAs in the spike doc, specify the type for each ticket | ||
| using `<!-- type: Task -->` or `<!-- type: Story -->` (see the JIRA ticket | ||
| template). Use `dev-tools/file-jiras.sh --help` for filing details. | ||
|
|
||
| At decision points, present what you've found and ask the user before proceeding. | ||
| The user makes the decisions — you assist with the research and documenting. | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,197 @@ | ||||||||||||||||||
| #!/usr/bin/env bash | ||||||||||||||||||
| # Fetch JIRA ticket content and its linked/child tickets. | ||||||||||||||||||
| # | ||||||||||||||||||
| # Usage: | ||||||||||||||||||
| # fetch-jira.sh <ticket> | ||||||||||||||||||
| # fetch-jira.sh 1234 (defaults to LCORE-1234) | ||||||||||||||||||
| # fetch-jira.sh LCORE-1234 | ||||||||||||||||||
| # | ||||||||||||||||||
| # Prerequisites: | ||||||||||||||||||
| # ~/.config/jira/credentials.json with email, token, instance. | ||||||||||||||||||
| # | ||||||||||||||||||
| # Output: ticket summary, description, acceptance criteria, status, | ||||||||||||||||||
| # and linked/child tickets (fetched recursively one level deep). | ||||||||||||||||||
|
|
||||||||||||||||||
| set -euo pipefail | ||||||||||||||||||
|
|
||||||||||||||||||
| # shellcheck disable=SC1091 | ||||||||||||||||||
| . "$(dirname "$0")/jira-common.sh" | ||||||||||||||||||
|
|
||||||||||||||||||
| if [ $# -lt 1 ] || [ "$1" = "--help" ] || [ "$1" = "-h" ]; then | ||||||||||||||||||
| echo "Usage: fetch-jira.sh <ticket> [additional-tickets...]" | ||||||||||||||||||
| echo "" | ||||||||||||||||||
| echo "Fetches JIRA ticket content including description, status, and child issues." | ||||||||||||||||||
| echo "Bare numbers default to LCORE- prefix." | ||||||||||||||||||
| echo "" | ||||||||||||||||||
| echo "Examples:" | ||||||||||||||||||
| echo " fetch-jira.sh 1234 Fetch LCORE-1234" | ||||||||||||||||||
| echo " fetch-jira.sh LCORE-1234 Same" | ||||||||||||||||||
| echo " fetch-jira.sh 836 509 777 Fetch multiple tickets" | ||||||||||||||||||
| if [ $# -lt 1 ]; then exit 1; else exit 0; fi | ||||||||||||||||||
| fi | ||||||||||||||||||
|
|
||||||||||||||||||
| ensure_jira_credentials | ||||||||||||||||||
|
|
||||||||||||||||||
| TICKET="$1" | ||||||||||||||||||
| # If bare number, prepend LCORE- | ||||||||||||||||||
| if echo "$TICKET" | grep -qE '^[0-9]+$'; then | ||||||||||||||||||
| TICKET="LCORE-$TICKET" | ||||||||||||||||||
| fi | ||||||||||||||||||
|
|
||||||||||||||||||
| fetch_ticket() { | ||||||||||||||||||
| local key="$1" | ||||||||||||||||||
| local indent="${2:-}" | ||||||||||||||||||
|
|
||||||||||||||||||
| local data | ||||||||||||||||||
| data=$(curl -sS --connect-timeout 10 --max-time 30 \ | ||||||||||||||||||
| -u "$JIRA_EMAIL:$JIRA_TOKEN" \ | ||||||||||||||||||
| "$JIRA_INSTANCE/rest/api/3/issue/$key?fields=summary,status,issuetype,description,issuelinks,subtasks,parent" 2>/dev/null) | ||||||||||||||||||
|
|
||||||||||||||||||
|
Comment on lines
+45
to
+49
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial Silent failure on curl errors may hide connectivity issues. Redirecting stderr to Proposed fix to preserve error visibility local data
- data=$(curl -sS --connect-timeout 10 --max-time 30 \
+ data=$(curl -sS --fail-with-body --connect-timeout 10 --max-time 30 \
-u "$JIRA_EMAIL:$JIRA_TOKEN" \
- "$JIRA_INSTANCE/rest/api/3/issue/$key?fields=summary,status,issuetype,description,issuelinks,subtasks,parent" 2>/dev/null)
+ "$JIRA_INSTANCE/rest/api/3/issue/$key?fields=summary,status,issuetype,description,issuelinks,subtasks,parent" 2>&1) || trueThis preserves error messages while still allowing the script to continue and report the error on lines 153-156. 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||
| if echo "$data" | python3 -c "import sys,json; json.load(sys.stdin)['key']" >/dev/null 2>&1; then | ||||||||||||||||||
| python3 -c " | ||||||||||||||||||
| import json, sys, textwrap | ||||||||||||||||||
|
|
||||||||||||||||||
| data = json.loads(sys.argv[1]) | ||||||||||||||||||
| indent = sys.argv[2] | ||||||||||||||||||
| key = data['key'] | ||||||||||||||||||
| fields = data['fields'] | ||||||||||||||||||
| summary = fields['summary'] | ||||||||||||||||||
| status = fields['status']['name'] | ||||||||||||||||||
| issue_type = fields['issuetype']['name'] | ||||||||||||||||||
| parent = fields.get('parent', {}) | ||||||||||||||||||
| parent_key = parent.get('key', '') if parent else '' | ||||||||||||||||||
|
|
||||||||||||||||||
| print(f'{indent}=== {key}: {summary} ===') | ||||||||||||||||||
| print(f'{indent}Type: {issue_type} | Status: {status}') | ||||||||||||||||||
| if parent_key: | ||||||||||||||||||
| print(f'{indent}Parent: {parent_key}') | ||||||||||||||||||
| print() | ||||||||||||||||||
|
|
||||||||||||||||||
| # Description | ||||||||||||||||||
| desc = fields.get('description') | ||||||||||||||||||
| if desc and isinstance(desc, dict): | ||||||||||||||||||
| # ADF format — extract text | ||||||||||||||||||
| def extract_text(node, depth=0): | ||||||||||||||||||
| lines = [] | ||||||||||||||||||
| if isinstance(node, dict): | ||||||||||||||||||
| ntype = node.get('type', '') | ||||||||||||||||||
| if ntype == 'text': | ||||||||||||||||||
| text = node.get('text', '') | ||||||||||||||||||
| marks = node.get('marks', []) | ||||||||||||||||||
| for m in marks: | ||||||||||||||||||
| if m.get('type') == 'strong': | ||||||||||||||||||
| text = f'**{text}**' | ||||||||||||||||||
| elif m.get('type') == 'code': | ||||||||||||||||||
| text = f'\`{text}\`' | ||||||||||||||||||
| return [text] | ||||||||||||||||||
| if ntype == 'hardBreak': | ||||||||||||||||||
| return ['\n'] | ||||||||||||||||||
| if ntype == 'listItem': | ||||||||||||||||||
| child_text = [] | ||||||||||||||||||
| for c in node.get('content', []): | ||||||||||||||||||
| child_text.extend(extract_text(c, depth)) | ||||||||||||||||||
| return [' ' * depth + '- ' + ''.join(child_text).strip()] | ||||||||||||||||||
| if ntype in ('bulletList', 'orderedList'): | ||||||||||||||||||
| for c in node.get('content', []): | ||||||||||||||||||
| lines.extend(extract_text(c, depth + 1)) | ||||||||||||||||||
| return lines | ||||||||||||||||||
| if ntype == 'heading': | ||||||||||||||||||
| level = node.get('attrs', {}).get('level', 1) | ||||||||||||||||||
| child_text = [] | ||||||||||||||||||
| for c in node.get('content', []): | ||||||||||||||||||
| child_text.extend(extract_text(c, depth)) | ||||||||||||||||||
| return ['#' * level + ' ' + ''.join(child_text).strip()] | ||||||||||||||||||
| if ntype == 'codeBlock': | ||||||||||||||||||
| child_text = [] | ||||||||||||||||||
| for c in node.get('content', []): | ||||||||||||||||||
| child_text.extend(extract_text(c, depth)) | ||||||||||||||||||
| return ['\`\`\`\n' + ''.join(child_text) + '\n\`\`\`'] | ||||||||||||||||||
| for c in node.get('content', []): | ||||||||||||||||||
| lines.extend(extract_text(c, depth)) | ||||||||||||||||||
| if ntype == 'paragraph' and lines: | ||||||||||||||||||
| lines.append('') | ||||||||||||||||||
| return lines | ||||||||||||||||||
|
|
||||||||||||||||||
| text_lines = extract_text(desc) | ||||||||||||||||||
| desc_text = '\n'.join(text_lines).strip() | ||||||||||||||||||
| if desc_text: | ||||||||||||||||||
| for line in desc_text.split('\n'): | ||||||||||||||||||
| print(f'{indent}{line}') | ||||||||||||||||||
| print() | ||||||||||||||||||
|
|
||||||||||||||||||
| # Links | ||||||||||||||||||
| links = fields.get('issuelinks', []) | ||||||||||||||||||
| if links: | ||||||||||||||||||
| print(f'{indent}Linked issues:') | ||||||||||||||||||
| for link in links: | ||||||||||||||||||
| link_type = link.get('type', {}).get('name', '?') | ||||||||||||||||||
| if 'outwardIssue' in link: | ||||||||||||||||||
| linked = link['outwardIssue'] | ||||||||||||||||||
| direction = link.get('type', {}).get('outward', 'relates to') | ||||||||||||||||||
| elif 'inwardIssue' in link: | ||||||||||||||||||
| linked = link['inwardIssue'] | ||||||||||||||||||
| direction = link.get('type', {}).get('inward', 'relates to') | ||||||||||||||||||
| else: | ||||||||||||||||||
| continue | ||||||||||||||||||
| lkey = linked['key'] | ||||||||||||||||||
| lsummary = linked['fields']['summary'] | ||||||||||||||||||
| lstatus = linked['fields']['status']['name'] | ||||||||||||||||||
| print(f'{indent} {direction}: {lkey} — {lsummary} [{lstatus}]') | ||||||||||||||||||
| print() | ||||||||||||||||||
|
|
||||||||||||||||||
| # Subtasks | ||||||||||||||||||
| subtasks = fields.get('subtasks', []) | ||||||||||||||||||
| if subtasks: | ||||||||||||||||||
| print(f'{indent}Child issues:') | ||||||||||||||||||
| for st in subtasks: | ||||||||||||||||||
| skey = st['key'] | ||||||||||||||||||
| ssummary = st['fields']['summary'] | ||||||||||||||||||
| sstatus = st['fields']['status']['name'] | ||||||||||||||||||
| print(f'{indent} {skey} — {ssummary} [{sstatus}]') | ||||||||||||||||||
| print() | ||||||||||||||||||
| " "$data" "$indent" | ||||||||||||||||||
| else | ||||||||||||||||||
| echo "${indent}Error fetching $key" | ||||||||||||||||||
| echo "$data" | head -3 | ||||||||||||||||||
| fi | ||||||||||||||||||
| } | ||||||||||||||||||
|
Comment on lines
+41
to
+157
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial ADF parsing is comprehensive but Deeply nested Jira descriptions (e.g., 1000+ nested list levels) would cause The error handling on line 153-156 provides a reasonable fallback when fetch fails. 🤖 Prompt for AI Agents |
||||||||||||||||||
|
|
||||||||||||||||||
| # Fetch main ticket | ||||||||||||||||||
| fetch_ticket "$TICKET" | ||||||||||||||||||
|
|
||||||||||||||||||
| # Search for child issues via parent= JQL (Jira Cloud hierarchy) | ||||||||||||||||||
| CHILD_KEYS=$(curl -sS --connect-timeout 10 --max-time 30 \ | ||||||||||||||||||
| -u "$JIRA_EMAIL:$JIRA_TOKEN" \ | ||||||||||||||||||
| "$JIRA_INSTANCE/rest/api/3/search/jql?jql=parent%3D${TICKET}&fields=key,summary,status,issuetype&maxResults=20" 2>/dev/null | \ | ||||||||||||||||||
| python3 -c " | ||||||||||||||||||
| import json, sys | ||||||||||||||||||
| try: | ||||||||||||||||||
| data = json.load(sys.stdin) | ||||||||||||||||||
| for issue in data.get('issues', []): | ||||||||||||||||||
| key = issue['key'] | ||||||||||||||||||
| summary = issue['fields']['summary'] | ||||||||||||||||||
| status = issue['fields']['status']['name'] | ||||||||||||||||||
| itype = issue['fields']['issuetype']['name'] | ||||||||||||||||||
| print(f'{key} ({itype}) [{status}]: {summary}') | ||||||||||||||||||
| except Exception: | ||||||||||||||||||
| pass | ||||||||||||||||||
| " 2>/dev/null) | ||||||||||||||||||
|
Comment on lines
+162
to
+178
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial Child issues query silently swallows all errors. The Proposed improvement CHILD_KEYS=$(curl -sS --connect-timeout 10 --max-time 30 \
-u "$JIRA_EMAIL:$JIRA_TOKEN" \
- "$JIRA_INSTANCE/rest/api/3/search/jql?jql=parent%3D${TICKET}&fields=key,summary,status,issuetype&maxResults=20" 2>/dev/null | \
+ "$JIRA_INSTANCE/rest/api/3/search/jql?jql=parent%3D${TICKET}&fields=key,summary,status,issuetype&maxResults=20" | \
python3 -c "
import json, sys
try:
data = json.load(sys.stdin)
for issue in data.get('issues', []):
key = issue['key']
summary = issue['fields']['summary']
status = issue['fields']['status']['name']
itype = issue['fields']['issuetype']['name']
print(f'{key} ({itype}) [{status}]: {summary}')
-except Exception:
- pass
+except Exception as e:
+ print(f'Warning: Could not fetch child issues: {e}', file=sys.stderr)
" 2>/dev/null)🤖 Prompt for AI Agents |
||||||||||||||||||
|
|
||||||||||||||||||
| if [ -n "$CHILD_KEYS" ]; then | ||||||||||||||||||
| echo "Child issues:" | ||||||||||||||||||
| echo "$CHILD_KEYS" | while read -r line; do | ||||||||||||||||||
| echo " $line" | ||||||||||||||||||
| done | ||||||||||||||||||
| echo "" | ||||||||||||||||||
| fi | ||||||||||||||||||
|
|
||||||||||||||||||
| # If additional ticket keys are passed as arguments, fetch those too | ||||||||||||||||||
| shift | ||||||||||||||||||
| for extra in "$@"; do | ||||||||||||||||||
| if echo "$extra" | grep -qE '^[0-9]+$'; then | ||||||||||||||||||
| extra="LCORE-$extra" | ||||||||||||||||||
| fi | ||||||||||||||||||
| echo "────────────────────────────────────────────────────────" | ||||||||||||||||||
| echo "" | ||||||||||||||||||
| fetch_ticket "$extra" | ||||||||||||||||||
| done | ||||||||||||||||||
Uh oh!
There was an error while loading. Please reload this page.