feat: Add Unix-friendly issue index with multiple output formats

- Add issue-index command with TSV, CSV, JSON, and fields output formats
- Support sorting by number, title, priority, state, created, updated
- Add filtering by state (open/closed) and priority level
- Include proper data cleaning for Unix pipeline processing
- Add make targets: issues-get, issues-csv, issues-json, issues-high
- Optimize for awk, cut, grep, and other Unix text processing tools

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-09-26 11:24:25 +02:00
parent c05dd855a9
commit b20b7003f5
3 changed files with 179 additions and 3 deletions

1
.gitignore vendored
View File

@@ -89,3 +89,4 @@ debug_*.py
# Claude Code local settings (user-specific permissions)
.claude/settings.local.json
.aider*

View File

@@ -37,9 +37,13 @@ help:
@echo " add-diary-entry - Add new entry to ProjectDiary.md (requires Claude Code)"
@echo ""
@echo "Issue Management:"
@echo " list-issues - Show all gitea issues with status and priority"
@echo " list-open-issues - Show only open issues (active backlog)"
@echo " show-issue NUM=X - Show detailed view of specific issue"
@echo " list-issues - Show all gitea issues with status and priority"
@echo " list-open-issues - Show only open issues (active backlog)"
@echo " show-issue NUM=X - Show detailed view of specific issue"
@echo " issues-get - Export compact issue index to ISSUES.index"
@echo " issues-csv - Export issues as CSV for spreadsheet processing"
@echo " issues-json - Export issues as JSON for programmatic processing"
@echo " issues-high - Export only high/critical priority issues"
@echo ""
@echo "Test-Driven Development:"
@echo " test-from-issue NUM=X - Generate test skeleton from issue (requires Claude Code)"
@@ -247,6 +251,40 @@ show-issue: $(VENV)/bin/activate
list-open-issues: $(VENV)/bin/activate
@PYTHONPATH=. $(VENV_PYTHON) tddai_cli.py list-open-issues
# Export compact issue index to ISSUES.index file (TSV format)
issues-get: $(VENV)/bin/activate
@echo "📋 Fetching issue index from gitea..."
@PYTHONPATH=. $(VENV_PYTHON) tddai_cli.py issue-index --format tsv --sort number > ISSUES.index
@echo "✅ Issue index exported to ISSUES.index (TSV format)"
@echo "📄 File contents:"
@cat ISSUES.index
# Export issues as CSV for spreadsheet processing
issues-csv: $(VENV)/bin/activate
@echo "📊 Exporting issues as CSV..."
@PYTHONPATH=. $(VENV_PYTHON) tddai_cli.py issue-index --format csv --sort priority --include-state > ISSUES.csv
@echo "✅ Issues exported to ISSUES.csv"
@wc -l ISSUES.csv | awk '{print "📄 Total entries:", $$1-1, "(excluding header)"}'
# Export issues as JSON for programmatic processing
issues-json: $(VENV)/bin/activate
@echo "🔧 Exporting issues as JSON..."
@PYTHONPATH=. $(VENV_PYTHON) tddai_cli.py issue-index --format json --sort priority > ISSUES.json
@echo "✅ Issues exported to ISSUES.json"
@echo "📄 Sample entry:"
@head -20 ISSUES.json
# Export only high and critical priority issues
issues-high: $(VENV)/bin/activate
@echo "🚨 Exporting high priority issues..."
@echo "High priority issues:" > ISSUES.high.txt
@PYTHONPATH=. $(VENV_PYTHON) tddai_cli.py issue-index --format tsv --filter-priority high --sort number >> ISSUES.high.txt
@echo "" >> ISSUES.high.txt
@echo "Critical priority issues:" >> ISSUES.high.txt
@PYTHONPATH=. $(VENV_PYTHON) tddai_cli.py issue-index --format tsv --filter-priority critical --sort number >> ISSUES.high.txt
@echo "✅ High priority issues exported to ISSUES.high.txt"
@cat ISSUES.high.txt
# Generate test skeleton from gitea issue (requires Claude Code)
test-from-issue:
@if [ -z "$(NUM)" ]; then \

View File

@@ -679,6 +679,123 @@ def assign_issue_to_milestone(issue_number: int, milestone_id: int):
sys.exit(1)
def issue_index(format_type="tsv", sort_by="number", filter_state=None, filter_priority=None, include_state=False):
"""Output compact index of all issues with ID, title, and priority for efficient parsing.
Args:
format_type: Output format (tsv, csv, json, fields)
sort_by: Sort by field (number, title, priority, state, created, updated)
filter_state: Filter by state (open, closed, all) - None means all
filter_priority: Filter by priority (low, medium, high, critical, none) - None means all
include_state: Include state column in output
"""
try:
fetcher = IssueFetcher()
project_mgr = ProjectManager()
from tddai.config import get_config
import json
config = get_config()
issues = fetcher.fetch_issues()
if not issues:
return
# Collect full issue data with additional fields
issue_data = []
for issue in issues:
# Get priority and state from labels
priority = "none"
state = "none"
try:
# Use ProjectManager's API call method
issue_url = f"{config.issues_api_url}/{issue.number}"
detailed_issue = project_mgr._make_api_call('GET', issue_url)
labels = detailed_issue.get('labels', [])
priority_labels = [l['name'] for l in labels if l['name'].startswith('priority:')]
state_labels = [l['name'] for l in labels if l['name'].startswith('status:')]
if priority_labels:
priority = priority_labels[0].replace('priority:', '')
if state_labels:
state = state_labels[0].replace('status:', '')
except:
pass # Keep defaults if API call fails
issue_info = {
'number': issue.number,
'title': issue.title.replace('\t', ' ').replace('\n', ' '), # Clean for TSV
'priority': priority,
'state': issue.state, # open/closed from basic data
'status': state, # detailed status from labels
'created': issue.created_at.strftime('%Y-%m-%d'),
'updated': issue.updated_at.strftime('%Y-%m-%d')
}
issue_data.append(issue_info)
# Apply filters
if filter_state:
if filter_state == "open":
issue_data = [i for i in issue_data if i['state'] == 'open']
elif filter_state == "closed":
issue_data = [i for i in issue_data if i['state'] == 'closed']
if filter_priority:
issue_data = [i for i in issue_data if i['priority'] == filter_priority]
# Sort issues
sort_key_map = {
'number': lambda x: x['number'],
'title': lambda x: x['title'].lower(),
'priority': lambda x: {'critical': 4, 'high': 3, 'medium': 2, 'low': 1, 'none': 0}[x['priority']],
'state': lambda x: x['state'],
'created': lambda x: x['created'],
'updated': lambda x: x['updated']
}
if sort_by in sort_key_map:
issue_data.sort(key=sort_key_map[sort_by], reverse=(sort_by in ['number', 'priority', 'created', 'updated']))
# Output in requested format
if format_type == "json":
print(json.dumps(issue_data, indent=2))
elif format_type == "csv":
# CSV header
if include_state:
print("number,title,priority,state,created,updated")
for issue in issue_data:
title = issue['title'].replace('"', '""') # Escape quotes
print(f'{issue["number"]},"{title}",{issue["priority"]},{issue["state"]},{issue["created"]},{issue["updated"]}')
else:
print("number,title,priority,created,updated")
for issue in issue_data:
title = issue['title'].replace('"', '""')
print(f'{issue["number"]},"{title}",{issue["priority"]},{issue["created"]},{issue["updated"]}')
elif format_type == "fields":
# Space-separated fields for easy awk processing
if include_state:
print("NUMBER TITLE PRIORITY STATE CREATED UPDATED")
for issue in issue_data:
title = issue['title'].replace(' ', '_')
print(f'{issue["number"]} {title} {issue["priority"]} {issue["state"]} {issue["created"]} {issue["updated"]}')
else:
print("NUMBER TITLE PRIORITY CREATED UPDATED")
for issue in issue_data:
title = issue['title'].replace(' ', '_')
print(f'{issue["number"]} {title} {issue["priority"]} {issue["created"]} {issue["updated"]}')
else: # Default TSV
if include_state:
for issue in issue_data:
print(f'{issue["number"]}\t{issue["title"]}\t{issue["priority"]}\t{issue["state"]}\t{issue["created"]}\t{issue["updated"]}')
else:
for issue in issue_data:
print(f'{issue["number"]}\t{issue["title"]}\t{issue["priority"]}\t{issue["created"]}\t{issue["updated"]}')
except TddaiError as e:
print(f"❌ Error: {e}", file=sys.stderr)
sys.exit(1)
def project_overview():
"""Show project management overview."""
try:
@@ -718,6 +835,18 @@ def main():
subparsers.add_parser('list-issues', help='List all issues')
subparsers.add_parser('list-open-issues', help='List open issues')
index_parser = subparsers.add_parser('issue-index', help='Output compact issue index for Unix processing')
index_parser.add_argument('--format', choices=['tsv', 'csv', 'json', 'fields'], default='tsv',
help='Output format (default: tsv)')
index_parser.add_argument('--sort', choices=['number', 'title', 'priority', 'state', 'created', 'updated'],
default='number', help='Sort by field (default: number)')
index_parser.add_argument('--filter-state', choices=['open', 'closed'],
help='Filter by issue state')
index_parser.add_argument('--filter-priority', choices=['low', 'medium', 'high', 'critical', 'none'],
help='Filter by priority level')
index_parser.add_argument('--include-state', action='store_true',
help='Include state column in output')
show_parser = subparsers.add_parser('show-issue', help='Show issue details')
show_parser.add_argument('issue_number', type=int, help='Issue number')
@@ -783,6 +912,14 @@ def main():
list_issues()
elif args.command == 'list-open-issues':
list_open_issues()
elif args.command == 'issue-index':
issue_index(
format_type=args.format,
sort_by=args.sort,
filter_state=args.filter_state,
filter_priority=args.filter_priority,
include_state=args.include_state
)
elif args.command == 'show-issue':
show_issue(args.issue_number)
elif args.command == 'analyze-coverage':