feat: transform to agent coordination platform with comprehensive documentation

Transform Issue Facade from a universal CLI tool into an agent coordination platform with comprehensive documentation and enhanced capabilities for autonomous coding agents.

Major Changes:
- Complete README rewrite focusing on agent-driven coordination
- New comprehensive documentation (AGENT_INTEGRATION.md, CLAUDE.md, ROADMAP.md)
- Capability integration setup with CAPABILITY.yaml and integration scripts
- Enhanced Makefile with local development targets for easier workflows

Bug Fixes:
- Fix schema initialization using executescript() for multi-line SQL support
- Disable FTS5 triggers due to compatibility issues (documented for future re-enablement)

Features:
- Enhanced CLI list command with full parameter passthrough
- New examples directory with agent integration patterns
- New comprehensive test suite (test_core_models.py, test_local_backend.py)

Code Quality:
- Remove @cached_property decorators for Label properties (simplification)
- Clean up test organization (removed old test_gitea_integration.py)

This milestone establishes Issue Facade as a production-ready coordination layer for multi-agent software development, with clear integration paths and comprehensive developer documentation.

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-17 19:32:37 +01:00
parent 2dfe5130a3
commit 324453bd8d
22 changed files with 6489 additions and 835 deletions

View File

@@ -81,12 +81,8 @@ class LocalSQLiteBackend(LocalBackend, SyncableBackend):
with open(schema_path, 'r') as f:
schema_sql = f.read()
# Execute schema in parts (SQLite doesn't like multiple statements)
for statement in schema_sql.split(';'):
statement = statement.strip()
if statement:
self.connection.execute(statement)
self.connection.commit()
# Use executescript to handle multi-line statements (triggers, views, etc.)
self.connection.executescript(schema_sql)
def _get_next_issue_number(self) -> int:
"""Get the next available issue number."""

View File

@@ -160,30 +160,10 @@ LEFT JOIN issue_assignees ia ON i.id = ia.issue_id
LEFT JOIN users u ON ia.user_id = u.id
GROUP BY i.id, i.number, i.title, i.state, i.created_at, i.updated_at, i.closed_at, m.title;
-- Full-text search setup (if SQLite supports FTS)
CREATE VIRTUAL TABLE IF NOT EXISTS issue_search USING fts5(
issue_id,
title,
description,
labels,
content='issues'
);
-- Trigger to keep FTS index updated
CREATE TRIGGER IF NOT EXISTS issue_search_insert AFTER INSERT ON issues
BEGIN
INSERT INTO issue_search(issue_id, title, description)
VALUES (NEW.id, NEW.title, NEW.description);
END;
CREATE TRIGGER IF NOT EXISTS issue_search_update AFTER UPDATE ON issues
BEGIN
UPDATE issue_search
SET title = NEW.title, description = NEW.description
WHERE issue_id = NEW.id;
END;
CREATE TRIGGER IF NOT EXISTS issue_search_delete AFTER DELETE ON issues
BEGIN
DELETE FROM issue_search WHERE issue_id = OLD.id;
END;
-- Full-text search setup (optional - disabled for now due to compatibility issues)
-- Can be enabled later by creating FTS5 virtual table manually
-- CREATE VIRTUAL TABLE IF NOT EXISTS issue_search USING fts5(
-- issue_id UNINDEXED,
-- title,
-- description
-- );

View File

@@ -56,10 +56,26 @@ cli.add_command(sync_group, name='sync')
# Convenience aliases - direct issue commands
@cli.command('list')
@click.option('--state', type=click.Choice(['open', 'closed', 'all']), default='open', help='Issue state filter')
@click.option('--assignee', help='Filter by assignee')
@click.option('--label', multiple=True, help='Filter by labels')
@click.option('--milestone', help='Filter by milestone')
@click.option('--search', help='Search in title and description')
@click.option('--limit', type=int, default=30, help='Maximum number of issues to show')
@click.option('--format', 'output_format', type=click.Choice(['table', 'json', 'compact']), default='table', help='Output format')
@click.pass_context
def list_issues(ctx):
def list_issues(ctx, state, assignee, label, milestone, search, limit, output_format):
"""List all issues (alias for 'issue list')."""
ctx.invoke(issue_group.get_command(ctx, 'list'))
ctx.invoke(
issue_group.get_command(ctx, 'list'),
state=state,
assignee=assignee,
label=label,
milestone=milestone,
search=search,
limit=limit,
output_format=output_format
)
@cli.command('show')

View File

@@ -10,7 +10,6 @@ from dataclasses import dataclass, field
from datetime import datetime, timezone
from enum import Enum
from typing import List, Optional, Dict, Any
from functools import cached_property
class IssueState(Enum):
@@ -88,7 +87,7 @@ class Label:
description: Optional[str] = None
backend_id: Optional[str] = None # Backend-specific ID for sync
@cached_property
@property
def category(self) -> str:
"""Categorize label for efficient filtering."""
if self.name.startswith('priority:'):
@@ -102,12 +101,12 @@ class Label:
else:
return 'other'
@cached_property
@property
def priority(self) -> Optional[Priority]:
"""Extract priority if this is a priority label."""
return Priority.from_label(self.name)
@cached_property
@property
def issue_type(self) -> Optional[IssueType]:
"""Extract issue type if this is a type label."""
return IssueType.from_label(self.name)
@@ -121,7 +120,7 @@ class LabelCategories:
status_labels: List[Label]
other_labels: List[Label]
@cached_property
@property
def priority(self) -> Optional[Priority]:
"""Get the issue priority."""
for label in self.priority_labels:
@@ -129,7 +128,7 @@ class LabelCategories:
return label.priority
return None
@cached_property
@property
def issue_type(self) -> Optional[IssueType]:
"""Get the issue type."""
for label in self.type_labels:
@@ -205,9 +204,9 @@ class Issue:
sync_metadata: Dict[str, Any] = field(default_factory=dict)
# Performance Optimization
_label_categories: Optional[LabelCategories] = field(default=None, init=False)
_label_categories: Optional[LabelCategories] = field(default=None, init=False, repr=False)
@cached_property
@property
def label_categories(self) -> LabelCategories:
"""Efficiently categorize labels with caching."""
if self._label_categories is None:
@@ -227,12 +226,12 @@ class Issue:
else:
other_labels.append(label)
self._label_categories = LabelCategories(
object.__setattr__(self, '_label_categories', LabelCategories(
priority_labels=priority_labels,
type_labels=type_labels,
status_labels=status_labels,
other_labels=other_labels
)
))
return self._label_categories
@property