@techwavedev/agi-agent-kit 1.1.7 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of @techwavedev/agi-agent-kit might be problematic. Click here for more details.
- package/CHANGELOG.md +82 -1
- package/README.md +190 -12
- package/bin/init.js +30 -2
- package/package.json +6 -3
- package/templates/base/AGENTS.md +54 -23
- package/templates/base/README.md +325 -0
- package/templates/base/directives/memory_integration.md +95 -0
- package/templates/base/execution/memory_manager.py +309 -0
- package/templates/base/execution/session_boot.py +218 -0
- package/templates/base/execution/session_init.py +320 -0
- package/templates/base/skill-creator/SKILL_skillcreator.md +23 -36
- package/templates/base/skill-creator/scripts/init_skill.py +18 -135
- package/templates/skills/ec/README.md +31 -0
- package/templates/skills/ec/aws/SKILL.md +1020 -0
- package/templates/skills/ec/aws/defaults.yaml +13 -0
- package/templates/skills/ec/aws/references/common_patterns.md +80 -0
- package/templates/skills/ec/aws/references/mcp_servers.md +98 -0
- package/templates/skills/ec/aws-terraform/SKILL.md +349 -0
- package/templates/skills/ec/aws-terraform/references/best_practices.md +394 -0
- package/templates/skills/ec/aws-terraform/references/checkov_reference.md +337 -0
- package/templates/skills/ec/aws-terraform/scripts/configure_mcp.py +150 -0
- package/templates/skills/ec/confluent-kafka/SKILL.md +655 -0
- package/templates/skills/ec/confluent-kafka/references/ansible_playbooks.md +792 -0
- package/templates/skills/ec/confluent-kafka/references/ec_deployment.md +579 -0
- package/templates/skills/ec/confluent-kafka/references/kraft_migration.md +490 -0
- package/templates/skills/ec/confluent-kafka/references/troubleshooting.md +778 -0
- package/templates/skills/ec/confluent-kafka/references/upgrade_7x_to_8x.md +488 -0
- package/templates/skills/ec/confluent-kafka/scripts/kafka_health_check.py +435 -0
- package/templates/skills/ec/confluent-kafka/scripts/upgrade_preflight.py +568 -0
- package/templates/skills/ec/confluent-kafka/scripts/validate_config.py +455 -0
- package/templates/skills/ec/consul/SKILL.md +427 -0
- package/templates/skills/ec/consul/references/acl_setup.md +168 -0
- package/templates/skills/ec/consul/references/ha_config.md +196 -0
- package/templates/skills/ec/consul/references/troubleshooting.md +267 -0
- package/templates/skills/ec/consul/references/upgrades.md +213 -0
- package/templates/skills/ec/consul/scripts/consul_health_report.py +530 -0
- package/templates/skills/ec/consul/scripts/consul_status.py +264 -0
- package/templates/skills/ec/consul/scripts/generate_values.py +170 -0
- package/templates/skills/ec/documentation/SKILL.md +351 -0
- package/templates/skills/ec/documentation/references/best_practices.md +201 -0
- package/templates/skills/ec/documentation/scripts/analyze_code.py +307 -0
- package/templates/skills/ec/documentation/scripts/detect_changes.py +460 -0
- package/templates/skills/ec/documentation/scripts/generate_changelog.py +312 -0
- package/templates/skills/ec/documentation/scripts/sync_docs.py +272 -0
- package/templates/skills/ec/documentation/scripts/update_skill_docs.py +366 -0
- package/templates/skills/ec/gitlab/SKILL.md +529 -0
- package/templates/skills/ec/gitlab/references/agent_installation.md +416 -0
- package/templates/skills/ec/gitlab/references/api_reference.md +508 -0
- package/templates/skills/ec/gitlab/references/gitops_flux.md +465 -0
- package/templates/skills/ec/gitlab/references/troubleshooting.md +518 -0
- package/templates/skills/ec/gitlab/scripts/generate_agent_values.py +329 -0
- package/templates/skills/ec/gitlab/scripts/gitlab_agent_status.py +414 -0
- package/templates/skills/ec/jira/SKILL.md +484 -0
- package/templates/skills/ec/jira/references/jql_reference.md +148 -0
- package/templates/skills/ec/jira/scripts/add_comment.py +91 -0
- package/templates/skills/ec/jira/scripts/bulk_log_work.py +124 -0
- package/templates/skills/ec/jira/scripts/create_ticket.py +162 -0
- package/templates/skills/ec/jira/scripts/get_ticket.py +191 -0
- package/templates/skills/ec/jira/scripts/jira_client.py +383 -0
- package/templates/skills/ec/jira/scripts/log_work.py +154 -0
- package/templates/skills/ec/jira/scripts/search_tickets.py +104 -0
- package/templates/skills/ec/jira/scripts/update_comment.py +67 -0
- package/templates/skills/ec/jira/scripts/update_ticket.py +161 -0
- package/templates/skills/ec/karpenter/SKILL.md +301 -0
- package/templates/skills/ec/karpenter/references/ec2nodeclasses.md +421 -0
- package/templates/skills/ec/karpenter/references/migration.md +396 -0
- package/templates/skills/ec/karpenter/references/nodepools.md +400 -0
- package/templates/skills/ec/karpenter/references/troubleshooting.md +359 -0
- package/templates/skills/ec/karpenter/scripts/generate_ec2nodeclass.py +187 -0
- package/templates/skills/ec/karpenter/scripts/generate_nodepool.py +245 -0
- package/templates/skills/ec/karpenter/scripts/karpenter_status.py +359 -0
- package/templates/skills/ec/opensearch/SKILL.md +720 -0
- package/templates/skills/ec/opensearch/references/ml_neural_search.md +576 -0
- package/templates/skills/ec/opensearch/references/operator.md +532 -0
- package/templates/skills/ec/opensearch/references/query_dsl.md +532 -0
- package/templates/skills/ec/opensearch/scripts/configure_mcp.py +148 -0
- package/templates/skills/ec/victoriametrics/SKILL.md +598 -0
- package/templates/skills/ec/victoriametrics/references/kubernetes.md +531 -0
- package/templates/skills/ec/victoriametrics/references/prometheus_migration.md +333 -0
- package/templates/skills/ec/victoriametrics/references/troubleshooting.md +442 -0
- package/templates/skills/knowledge/SKILLS_CATALOG.md +274 -4
- package/templates/skills/knowledge/intelligent-routing/SKILL.md +237 -164
- package/templates/skills/knowledge/parallel-agents/SKILL.md +345 -73
- package/templates/skills/knowledge/plugin-discovery/SKILL.md +582 -0
- package/templates/skills/knowledge/plugin-discovery/scripts/platform_setup.py +1083 -0
- package/templates/skills/knowledge/design-md/README.md +0 -34
- package/templates/skills/knowledge/design-md/SKILL.md +0 -193
- package/templates/skills/knowledge/design-md/examples/DESIGN.md +0 -154
- package/templates/skills/knowledge/notebooklm-mcp/SKILL.md +0 -71
- package/templates/skills/knowledge/notebooklm-mcp/assets/example_asset.txt +0 -24
- package/templates/skills/knowledge/notebooklm-mcp/references/api_reference.md +0 -34
- package/templates/skills/knowledge/notebooklm-mcp/scripts/example.py +0 -19
- package/templates/skills/knowledge/react-components/README.md +0 -36
- package/templates/skills/knowledge/react-components/SKILL.md +0 -53
- package/templates/skills/knowledge/react-components/examples/gold-standard-card.tsx +0 -80
- package/templates/skills/knowledge/react-components/package-lock.json +0 -231
- package/templates/skills/knowledge/react-components/package.json +0 -16
- package/templates/skills/knowledge/react-components/resources/architecture-checklist.md +0 -15
- package/templates/skills/knowledge/react-components/resources/component-template.tsx +0 -37
- package/templates/skills/knowledge/react-components/resources/stitch-api-reference.md +0 -14
- package/templates/skills/knowledge/react-components/resources/style-guide.json +0 -27
- package/templates/skills/knowledge/react-components/scripts/fetch-stitch.sh +0 -30
- package/templates/skills/knowledge/react-components/scripts/validate.js +0 -68
- package/templates/skills/knowledge/self-update/SKILL.md +0 -60
- package/templates/skills/knowledge/self-update/scripts/update_kit.py +0 -103
- package/templates/skills/knowledge/stitch-loop/README.md +0 -54
- package/templates/skills/knowledge/stitch-loop/SKILL.md +0 -235
- package/templates/skills/knowledge/stitch-loop/examples/SITE.md +0 -73
- package/templates/skills/knowledge/stitch-loop/examples/next-prompt.md +0 -25
- package/templates/skills/knowledge/stitch-loop/resources/baton-schema.md +0 -61
- package/templates/skills/knowledge/stitch-loop/resources/site-template.md +0 -104
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Bulk Log Work Utility
|
|
4
|
+
|
|
5
|
+
A reusable script to log work across a date range for a specific ticket.
|
|
6
|
+
Automatically handles weekends and European Commission holidays.
|
|
7
|
+
|
|
8
|
+
Usage:
|
|
9
|
+
python bulk_log_work.py --ticket <key> --start <YYYY-MM-DD> --end <YYYY-MM-DD> --time <duration> --comment <text>
|
|
10
|
+
|
|
11
|
+
Arguments:
|
|
12
|
+
--ticket Ticket key (required)
|
|
13
|
+
--start Start date YYYY-MM-DD (required)
|
|
14
|
+
--end End date YYYY-MM-DD (default: today)
|
|
15
|
+
--time Time per working day (e.g., "3h", "1h 30m")
|
|
16
|
+
--comment Comment for worklogs. If multiple, separate with '|' to rotate.
|
|
17
|
+
--dry-run Show what would be logged without making API calls
|
|
18
|
+
|
|
19
|
+
Example:
|
|
20
|
+
python bulk_log_work.py --ticket KASP-1241 --start 2026-01-01 --time 2h --comment "Infra check|Alert check"
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
import argparse
|
|
24
|
+
import sys
|
|
25
|
+
from datetime import datetime, timedelta
|
|
26
|
+
from pathlib import Path
|
|
27
|
+
from typing import List
|
|
28
|
+
|
|
29
|
+
sys.path.insert(0, str(Path(__file__).parent))
|
|
30
|
+
from jira_client import get_client
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def get_ec_holidays(year: int) -> List[str]:
|
|
34
|
+
"""Return a list of EC holidays for a given year (approximate)."""
|
|
35
|
+
# Standard EC holidays
|
|
36
|
+
holidays = [
|
|
37
|
+
f"{year}-01-01", f"{year}-01-02", # New Year
|
|
38
|
+
f"{year}-05-01", # Labor Day
|
|
39
|
+
f"{year}-05-09", # Europe Day
|
|
40
|
+
f"{year}-07-21", # National Day
|
|
41
|
+
f"{year}-08-15", # Assumption
|
|
42
|
+
f"{year}-11-01", # All Saints
|
|
43
|
+
f"{year}-11-02", # All Souls
|
|
44
|
+
f"{year}-12-24", f"{year}-12-25", f"{year}-12-26",
|
|
45
|
+
f"{year}-12-27", f"{year}-12-28", f"{year}-12-29",
|
|
46
|
+
f"{year}-12-30", f"{year}-12-31" # Winter break
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
# Easter-related (approximate for demonstration, usually fixed per year)
|
|
50
|
+
if year == 2026:
|
|
51
|
+
holidays.extend(["2026-04-02", "2026-04-03", "2026-04-06", "2026-05-14", "2026-05-25"])
|
|
52
|
+
|
|
53
|
+
return holidays
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def main():
|
|
57
|
+
parser = argparse.ArgumentParser(description="Bulk log work to Jira tickets")
|
|
58
|
+
parser.add_argument("--ticket", required=True, help="Ticket key")
|
|
59
|
+
parser.add_argument("--start", required=True, help="Start date (YYYY-MM-DD)")
|
|
60
|
+
parser.add_argument("--end", help="End date (YYYY-MM-DD, defaults to today)")
|
|
61
|
+
parser.add_argument("--time", required=True, help="Time per working day (e.g., 2h)")
|
|
62
|
+
parser.add_argument("--comment", required=True, help="Comment(s) - use | to rotate multiple")
|
|
63
|
+
parser.add_argument("--dry-run", action="store_true", help="Don't actually log work")
|
|
64
|
+
args = parser.parse_args()
|
|
65
|
+
|
|
66
|
+
client = get_client()
|
|
67
|
+
ticket = args.ticket.upper()
|
|
68
|
+
|
|
69
|
+
# Parse dates
|
|
70
|
+
start_date = datetime.strptime(args.start, "%Y-%m-%d")
|
|
71
|
+
end_date = datetime.strptime(args.end, "%Y-%m-%d") if args.end else datetime.now()
|
|
72
|
+
|
|
73
|
+
# Parse comments
|
|
74
|
+
comments = [c.strip() for c in args.comment.split('|')]
|
|
75
|
+
|
|
76
|
+
print(f"🚀 {'[DRY RUN] ' if args.dry_run else ''}Bulk logging for {ticket}")
|
|
77
|
+
print(f" Period: {start_date.strftime('%Y-%m-%d')} to {end_date.strftime('%Y-%m-%d')}")
|
|
78
|
+
print(f" Work: {args.time} per day")
|
|
79
|
+
print("-" * 50)
|
|
80
|
+
|
|
81
|
+
current_date = start_date
|
|
82
|
+
working_days_count = 0
|
|
83
|
+
|
|
84
|
+
while current_date <= end_date:
|
|
85
|
+
date_str = current_date.strftime("%Y-%m-%d")
|
|
86
|
+
holidays = get_ec_holidays(current_date.year)
|
|
87
|
+
|
|
88
|
+
# Skip weekends and holidays
|
|
89
|
+
if current_date.weekday() >= 5:
|
|
90
|
+
# print(f"🏠 {date_str}: Weekend (Skipping)")
|
|
91
|
+
pass
|
|
92
|
+
elif date_str in holidays:
|
|
93
|
+
print(f"🎉 {date_str}: Holiday (Skipping)")
|
|
94
|
+
else:
|
|
95
|
+
comment = comments[working_days_count % len(comments)]
|
|
96
|
+
# 14:00 (2 PM) as a default work check time
|
|
97
|
+
started = current_date.strftime("%Y-%m-%dT14:00:00.000+0100")
|
|
98
|
+
|
|
99
|
+
print(f"⏱️ {date_str}: Logging {args.time} - {comment}...", end=" ", flush=True)
|
|
100
|
+
|
|
101
|
+
if not args.dry_run:
|
|
102
|
+
success, result = client.add_worklog(
|
|
103
|
+
ticket,
|
|
104
|
+
time_spent=args.time,
|
|
105
|
+
comment=comment,
|
|
106
|
+
started=started
|
|
107
|
+
)
|
|
108
|
+
if success:
|
|
109
|
+
print("✅ Done")
|
|
110
|
+
else:
|
|
111
|
+
print(f"❌ Failed: {result}")
|
|
112
|
+
else:
|
|
113
|
+
print("📝 (Skipped)")
|
|
114
|
+
|
|
115
|
+
working_days_count += 1
|
|
116
|
+
|
|
117
|
+
current_date += timedelta(days=1)
|
|
118
|
+
|
|
119
|
+
print("-" * 50)
|
|
120
|
+
print(f"🏁 Finished! Processed {working_days_count} working day(s).")
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
if __name__ == "__main__":
|
|
124
|
+
main()
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Create Jira Ticket
|
|
4
|
+
|
|
5
|
+
Creates a new issue in Jira with specified fields.
|
|
6
|
+
|
|
7
|
+
Usage:
|
|
8
|
+
python create_ticket.py --project <key> --summary <text> [options]
|
|
9
|
+
|
|
10
|
+
Arguments:
|
|
11
|
+
--project Project key (required)
|
|
12
|
+
--summary Issue summary (required)
|
|
13
|
+
--type Issue type: Bug, Task, Story, Epic (default: Task)
|
|
14
|
+
--priority Priority: Highest, High, Medium, Low, Lowest
|
|
15
|
+
--description Description text
|
|
16
|
+
--assignee Assignee username or email
|
|
17
|
+
--labels Comma-separated labels
|
|
18
|
+
--components Comma-separated component names
|
|
19
|
+
--epic Epic link key
|
|
20
|
+
--parent Parent ticket key (for sub-tasks)
|
|
21
|
+
--custom-fields Custom fields as JSON string
|
|
22
|
+
--output Output format: json, key, url (default: json)
|
|
23
|
+
|
|
24
|
+
Exit Codes:
|
|
25
|
+
0 - Success
|
|
26
|
+
1 - Invalid arguments
|
|
27
|
+
2 - API error
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
import argparse
|
|
31
|
+
import json
|
|
32
|
+
import sys
|
|
33
|
+
from pathlib import Path
|
|
34
|
+
|
|
35
|
+
# Add scripts directory to path for imports
|
|
36
|
+
sys.path.insert(0, str(Path(__file__).parent))
|
|
37
|
+
from jira_client import get_client
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def parse_labels(labels_str: str) -> list:
|
|
41
|
+
"""Parse comma-separated labels."""
|
|
42
|
+
if not labels_str:
|
|
43
|
+
return []
|
|
44
|
+
return [l.strip() for l in labels_str.split(',') if l.strip()]
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def build_fields(args, client) -> dict:
|
|
48
|
+
"""Build the fields dictionary for issue creation."""
|
|
49
|
+
fields = {
|
|
50
|
+
'project': {'key': args.project},
|
|
51
|
+
'summary': args.summary,
|
|
52
|
+
'issuetype': {'name': args.type}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if args.priority:
|
|
56
|
+
fields['priority'] = {'name': args.priority}
|
|
57
|
+
|
|
58
|
+
if args.description:
|
|
59
|
+
fields['description'] = client._format_body(args.description)
|
|
60
|
+
|
|
61
|
+
if args.assignee:
|
|
62
|
+
# Try to resolve assignee
|
|
63
|
+
user_id_key = 'name' if client.is_server else 'accountId'
|
|
64
|
+
|
|
65
|
+
if args.assignee.lower() == 'me':
|
|
66
|
+
success, me = client.get_myself()
|
|
67
|
+
if success:
|
|
68
|
+
fields['assignee'] = {user_id_key: me.get(user_id_key)}
|
|
69
|
+
else:
|
|
70
|
+
# Search for user
|
|
71
|
+
success, users = client.search_users(args.assignee, max_results=1)
|
|
72
|
+
if success and users:
|
|
73
|
+
fields['assignee'] = {user_id_key: users[0].get(user_id_key)}
|
|
74
|
+
else:
|
|
75
|
+
# Fallback to literal name if search fails but we have a candidate
|
|
76
|
+
fields['assignee'] = {user_id_key: args.assignee}
|
|
77
|
+
|
|
78
|
+
if args.parent:
|
|
79
|
+
fields['parent'] = {'key': args.parent}
|
|
80
|
+
|
|
81
|
+
if args.epic:
|
|
82
|
+
# Epic link field varies by project, but 'parent' is often used in newer Jira
|
|
83
|
+
fields['parent'] = {'key': args.epic}
|
|
84
|
+
|
|
85
|
+
if args.labels:
|
|
86
|
+
fields['labels'] = parse_labels(args.labels)
|
|
87
|
+
|
|
88
|
+
if args.components:
|
|
89
|
+
fields['components'] = [{'name': c.strip()} for c in args.components.split(',')]
|
|
90
|
+
|
|
91
|
+
# Custom fields
|
|
92
|
+
if args.custom_fields:
|
|
93
|
+
try:
|
|
94
|
+
custom = json.loads(args.custom_fields)
|
|
95
|
+
fields.update(custom)
|
|
96
|
+
except json.JSONDecodeError as e:
|
|
97
|
+
print(f"⚠️ Warning: Invalid custom-fields JSON: {e}", file=sys.stderr)
|
|
98
|
+
|
|
99
|
+
return fields
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def main():
|
|
103
|
+
parser = argparse.ArgumentParser(
|
|
104
|
+
description='Create a Jira ticket',
|
|
105
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
106
|
+
epilog=__doc__
|
|
107
|
+
)
|
|
108
|
+
parser.add_argument('--project', required=True, help='Project key')
|
|
109
|
+
parser.add_argument('--summary', required=True, help='Issue summary')
|
|
110
|
+
parser.add_argument('--type', default='Task', help='Issue type')
|
|
111
|
+
parser.add_argument('--priority', help='Priority level')
|
|
112
|
+
parser.add_argument('--description', help='Description text')
|
|
113
|
+
parser.add_argument('--assignee', help='Assignee username/email')
|
|
114
|
+
parser.add_argument('--labels', help='Comma-separated labels')
|
|
115
|
+
parser.add_argument('--components', help='Comma-separated components')
|
|
116
|
+
parser.add_argument('--epic', help='Epic link key')
|
|
117
|
+
parser.add_argument('--parent', help='Parent ticket key (for sub-tasks)')
|
|
118
|
+
parser.add_argument('--custom-fields', help='Custom fields as JSON')
|
|
119
|
+
parser.add_argument('--output', choices=['json', 'key', 'url'], default='json',
|
|
120
|
+
help='Output format')
|
|
121
|
+
args = parser.parse_args()
|
|
122
|
+
|
|
123
|
+
client = get_client()
|
|
124
|
+
|
|
125
|
+
print(f"📝 Creating ticket in project {args.project}...", file=sys.stderr)
|
|
126
|
+
|
|
127
|
+
# Build fields
|
|
128
|
+
fields = build_fields(args, client)
|
|
129
|
+
|
|
130
|
+
# Create issue
|
|
131
|
+
success, result = client.create_issue(fields)
|
|
132
|
+
|
|
133
|
+
if not success:
|
|
134
|
+
print(f"❌ Error creating ticket: {result}", file=sys.stderr)
|
|
135
|
+
sys.exit(2)
|
|
136
|
+
|
|
137
|
+
issue_key = result.get('key', 'Unknown')
|
|
138
|
+
issue_url = f"{client.base_url}/browse/{issue_key}"
|
|
139
|
+
|
|
140
|
+
# Output based on format
|
|
141
|
+
if args.output == 'key':
|
|
142
|
+
print(issue_key)
|
|
143
|
+
elif args.output == 'url':
|
|
144
|
+
print(issue_url)
|
|
145
|
+
else:
|
|
146
|
+
output = {
|
|
147
|
+
'success': True,
|
|
148
|
+
'key': issue_key,
|
|
149
|
+
'id': result.get('id'),
|
|
150
|
+
'url': issue_url,
|
|
151
|
+
'self': result.get('self')
|
|
152
|
+
}
|
|
153
|
+
print(json.dumps(output, indent=2))
|
|
154
|
+
|
|
155
|
+
print(f"✅ Created ticket: {issue_key}", file=sys.stderr)
|
|
156
|
+
print(f" URL: {issue_url}", file=sys.stderr)
|
|
157
|
+
|
|
158
|
+
sys.exit(0)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
if __name__ == '__main__':
|
|
162
|
+
main()
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Get Jira Ticket Details
|
|
4
|
+
|
|
5
|
+
Retrieve full details for a specific Jira issue.
|
|
6
|
+
|
|
7
|
+
Usage:
|
|
8
|
+
python get_ticket.py --ticket <key> [options]
|
|
9
|
+
|
|
10
|
+
Arguments:
|
|
11
|
+
--ticket Ticket key (required)
|
|
12
|
+
--include-comments Include comments
|
|
13
|
+
--include-worklog Include work logs
|
|
14
|
+
--include-transitions Show available transitions
|
|
15
|
+
--output Output format: json, summary (default: summary)
|
|
16
|
+
|
|
17
|
+
Exit Codes:
|
|
18
|
+
0 - Success
|
|
19
|
+
1 - Invalid arguments
|
|
20
|
+
2 - Ticket not found
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
import argparse
|
|
24
|
+
import json
|
|
25
|
+
import sys
|
|
26
|
+
from pathlib import Path
|
|
27
|
+
|
|
28
|
+
sys.path.insert(0, str(Path(__file__).parent))
|
|
29
|
+
from jira_client import get_client
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def format_summary(issue: dict, transitions: list = None, comments: list = None,
|
|
33
|
+
worklogs: list = None, base_url: str = '') -> str:
|
|
34
|
+
"""Format issue as human-readable summary."""
|
|
35
|
+
key = issue.get('key', 'Unknown')
|
|
36
|
+
fields = issue.get('fields', {})
|
|
37
|
+
|
|
38
|
+
lines = [
|
|
39
|
+
f"# {key}: {fields.get('summary', 'No summary')}",
|
|
40
|
+
"",
|
|
41
|
+
f"**URL:** {base_url}/browse/{key}",
|
|
42
|
+
"",
|
|
43
|
+
"## Details",
|
|
44
|
+
"",
|
|
45
|
+
f"| Field | Value |",
|
|
46
|
+
f"|-------|-------|",
|
|
47
|
+
f"| Type | {fields.get('issuetype', {}).get('name', '-')} |",
|
|
48
|
+
f"| Status | {fields.get('status', {}).get('name', '-')} |",
|
|
49
|
+
f"| Priority | {fields.get('priority', {}).get('name', '-') if fields.get('priority') else '-'} |",
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
assignee = fields.get('assignee')
|
|
53
|
+
lines.append(f"| Assignee | {assignee.get('displayName', '-') if assignee else 'Unassigned'} |")
|
|
54
|
+
|
|
55
|
+
reporter = fields.get('reporter')
|
|
56
|
+
lines.append(f"| Reporter | {reporter.get('displayName', '-') if reporter else '-'} |")
|
|
57
|
+
|
|
58
|
+
lines.append(f"| Created | {fields.get('created', '-')[:10] if fields.get('created') else '-'} |")
|
|
59
|
+
lines.append(f"| Updated | {fields.get('updated', '-')[:10] if fields.get('updated') else '-'} |")
|
|
60
|
+
|
|
61
|
+
if fields.get('labels'):
|
|
62
|
+
lines.append(f"| Labels | {', '.join(fields['labels'])} |")
|
|
63
|
+
|
|
64
|
+
lines.append("")
|
|
65
|
+
|
|
66
|
+
# Description
|
|
67
|
+
description = fields.get('description')
|
|
68
|
+
if description:
|
|
69
|
+
lines.append("## Description")
|
|
70
|
+
lines.append("")
|
|
71
|
+
if isinstance(description, dict):
|
|
72
|
+
# ADF format - extract text
|
|
73
|
+
content = description.get('content', [])
|
|
74
|
+
for block in content:
|
|
75
|
+
if block.get('type') == 'paragraph':
|
|
76
|
+
text_parts = [c.get('text', '') for c in block.get('content', [])]
|
|
77
|
+
lines.append(''.join(text_parts))
|
|
78
|
+
else:
|
|
79
|
+
lines.append(str(description))
|
|
80
|
+
lines.append("")
|
|
81
|
+
|
|
82
|
+
# Transitions
|
|
83
|
+
if transitions:
|
|
84
|
+
lines.append("## Available Transitions")
|
|
85
|
+
lines.append("")
|
|
86
|
+
for t in transitions:
|
|
87
|
+
lines.append(f"- {t.get('name')} → {t.get('to', {}).get('name', '?')}")
|
|
88
|
+
lines.append("")
|
|
89
|
+
|
|
90
|
+
# Comments
|
|
91
|
+
if comments:
|
|
92
|
+
lines.append(f"## Comments ({len(comments)})")
|
|
93
|
+
lines.append("")
|
|
94
|
+
for c in comments[-5:]: # Last 5 comments
|
|
95
|
+
author = c.get('author', {}).get('displayName', 'Unknown')
|
|
96
|
+
created = c.get('created', '')[:10]
|
|
97
|
+
body = c.get('body', '')
|
|
98
|
+
if isinstance(body, dict):
|
|
99
|
+
# ADF format
|
|
100
|
+
text_parts = []
|
|
101
|
+
for block in body.get('content', []):
|
|
102
|
+
if block.get('type') == 'paragraph':
|
|
103
|
+
text_parts.extend([p.get('text', '') for p in block.get('content', [])])
|
|
104
|
+
body = ' '.join(text_parts)
|
|
105
|
+
lines.append(f"**{author}** ({created}):")
|
|
106
|
+
lines.append(f"> {body[:200]}...")
|
|
107
|
+
lines.append("")
|
|
108
|
+
|
|
109
|
+
# Worklogs
|
|
110
|
+
if worklogs:
|
|
111
|
+
lines.append(f"## Work Logs ({len(worklogs)})")
|
|
112
|
+
lines.append("")
|
|
113
|
+
total_seconds = sum(w.get('timeSpentSeconds', 0) for w in worklogs)
|
|
114
|
+
total_hours = total_seconds / 3600
|
|
115
|
+
lines.append(f"**Total Time Logged:** {total_hours:.1f}h")
|
|
116
|
+
lines.append("")
|
|
117
|
+
for w in worklogs[-5:]: # Last 5 entries
|
|
118
|
+
author = w.get('author', {}).get('displayName', 'Unknown')
|
|
119
|
+
time_spent = w.get('timeSpent', '?')
|
|
120
|
+
lines.append(f"- {author}: {time_spent}")
|
|
121
|
+
lines.append("")
|
|
122
|
+
|
|
123
|
+
return '\n'.join(lines)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def main():
|
|
127
|
+
parser = argparse.ArgumentParser(
|
|
128
|
+
description='Get Jira ticket details',
|
|
129
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
130
|
+
epilog=__doc__
|
|
131
|
+
)
|
|
132
|
+
parser.add_argument('--ticket', required=True, help='Ticket key')
|
|
133
|
+
parser.add_argument('--include-comments', action='store_true', help='Include comments')
|
|
134
|
+
parser.add_argument('--include-worklog', action='store_true', help='Include work logs')
|
|
135
|
+
parser.add_argument('--include-transitions', action='store_true', help='Show transitions')
|
|
136
|
+
parser.add_argument('--output', choices=['json', 'summary'], default='summary',
|
|
137
|
+
help='Output format')
|
|
138
|
+
args = parser.parse_args()
|
|
139
|
+
|
|
140
|
+
client = get_client()
|
|
141
|
+
ticket = args.ticket.upper()
|
|
142
|
+
|
|
143
|
+
print(f"📋 Getting details for {ticket}...", file=sys.stderr)
|
|
144
|
+
|
|
145
|
+
# Get issue
|
|
146
|
+
expand = []
|
|
147
|
+
if args.include_comments:
|
|
148
|
+
expand.append('renderedFields')
|
|
149
|
+
|
|
150
|
+
success, issue = client.get_issue(ticket, expand=expand if expand else None)
|
|
151
|
+
if not success:
|
|
152
|
+
print(f"❌ Error: Could not find ticket {ticket}: {issue}", file=sys.stderr)
|
|
153
|
+
sys.exit(2)
|
|
154
|
+
|
|
155
|
+
# Get optional data
|
|
156
|
+
transitions = None
|
|
157
|
+
comments = None
|
|
158
|
+
worklogs = None
|
|
159
|
+
|
|
160
|
+
if args.include_transitions:
|
|
161
|
+
success, trans_data = client.get_transitions(ticket)
|
|
162
|
+
if success:
|
|
163
|
+
transitions = trans_data.get('transitions', [])
|
|
164
|
+
|
|
165
|
+
if args.include_comments:
|
|
166
|
+
success, comment_data = client.get_comments(ticket)
|
|
167
|
+
if success:
|
|
168
|
+
comments = comment_data.get('comments', [])
|
|
169
|
+
|
|
170
|
+
if args.include_worklog:
|
|
171
|
+
success, worklog_data = client.get_worklogs(ticket)
|
|
172
|
+
if success:
|
|
173
|
+
worklogs = worklog_data.get('worklogs', [])
|
|
174
|
+
|
|
175
|
+
# Output
|
|
176
|
+
if args.output == 'json':
|
|
177
|
+
output = {
|
|
178
|
+
'issue': issue,
|
|
179
|
+
'transitions': transitions,
|
|
180
|
+
'comments': comments,
|
|
181
|
+
'worklogs': worklogs
|
|
182
|
+
}
|
|
183
|
+
print(json.dumps(output, indent=2))
|
|
184
|
+
else:
|
|
185
|
+
print(format_summary(issue, transitions, comments, worklogs, client.base_url))
|
|
186
|
+
|
|
187
|
+
sys.exit(0)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
if __name__ == '__main__':
|
|
191
|
+
main()
|