generated from coulomb/repo-seed
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:
@@ -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."""
|
||||
|
||||
@@ -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
|
||||
-- );
|
||||
@@ -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')
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user