Files
journal_organizer/.kiro/specs/code-quality-improvements/design.md
T
windyboy f7e54692a9 Initial project setup: Obsidian intelligent journal organizer
- Add core agent architecture with Command + Skill pattern
- Implement Claude API integration for content analysis
- Add Obsidian REST API integration for vault operations
- Create conversational interface (v2.0) with natural language processing
- Add comprehensive configuration management and validation
- Include project documentation and developer guides
- Set up testing framework with unit, integration, and property tests
- Add Kiro specs for Claude API configuration and code quality improvements
- Configure project steering files for development guidelines
2025-12-31 17:55:10 +08:00

9.2 KiB

Design Document

Overview

This design addresses critical code quality issues in the Obsidian journal organizer project. The improvements focus on fixing syntax errors, adding comprehensive type hints, modernizing Python patterns, and establishing consistent error handling throughout the codebase. The design maintains backward compatibility while significantly improving maintainability and developer experience.

Architecture

The existing Command + Skill architecture will be preserved, but enhanced with:

  1. Type Safety Layer: Comprehensive type hints using Python's typing module
  2. Error Handling Framework: Consistent error patterns across all components
  3. Configuration Validation: Robust validation and sanitization of user inputs
  4. Modern Python Patterns: Updated code to follow current best practices

Components and Interfaces

Enhanced Agent Core

The agent_core.py module will be updated with:

from typing import Dict, Any, List, Optional, Union, Protocol, TypeVar
from dataclasses import dataclass, field
from pathlib import Path
from abc import ABC, abstractmethod

T = TypeVar('T')

class SkillProtocol(Protocol):
    async def execute(self, context: 'CommandContext', **kwargs: Any) -> 'SkillResult':
        ...

@dataclass
class SkillResult:
    success: bool
    data: Optional[Dict[str, Any]] = None
    error: Optional[str] = None
    message: str = ""
    timestamp: str = field(default_factory=lambda: datetime.now().isoformat())
    
    def to_dict(self) -> Dict[str, Any]:
        return asdict(self)

Type-Safe Configuration Management

from typing import TypedDict, Optional
from pathlib import Path

class ObsidianConfig(TypedDict):
    vault_path: str
    rest_api: Dict[str, Union[str, bool]]

class ClaudeConfig(TypedDict):
    api_key: str
    model: str
    max_tokens: int

class SystemConfig(TypedDict):
    obsidian: ObsidianConfig
    claude: ClaudeConfig
    output: Dict[str, str]

Enhanced Error Handling

class JournalOrganizerError(Exception):
    """Base exception for journal organizer errors"""
    pass

class ConfigurationError(JournalOrganizerError):
    """Raised when configuration is invalid or missing"""
    pass

class APIError(JournalOrganizerError):
    """Raised when external API calls fail"""
    pass

class ValidationError(JournalOrganizerError):
    """Raised when input validation fails"""
    pass

Data Models

Enhanced SkillResult with Validation

@dataclass
class SkillResult:
    success: bool
    data: Optional[Dict[str, Any]] = None
    error: Optional[str] = None
    message: str = ""
    timestamp: str = field(default_factory=lambda: datetime.now().isoformat())
    
    def __post_init__(self) -> None:
        if not self.success and not self.error:
            raise ValueError("Failed results must include error message")
        if self.success and self.error:
            raise ValueError("Successful results should not include error message")

Configuration Validation Models

from pydantic import BaseModel, validator, Field
from typing import Optional

class ObsidianRestAPIConfig(BaseModel):
    url: str = Field(..., regex=r'^https?://')
    api_key: str = Field(..., min_length=1)
    verify_ssl: bool = False

class ObsidianConfig(BaseModel):
    vault_path: str = Field(..., min_length=1)
    rest_api: ObsidianRestAPIConfig

    @validator('vault_path')
    def validate_vault_path(cls, v: str) -> str:
        path = Path(v)
        if not path.exists():
            raise ValueError(f"Vault path does not exist: {v}")
        return str(path.resolve())

Error Handling

Centralized Error Management

class ErrorHandler:
    """Centralized error handling and logging"""
    
    def __init__(self, logger: logging.Logger):
        self.logger = logger
    
    def handle_api_error(self, error: Exception, context: str) -> SkillResult:
        """Handle API-related errors consistently"""
        self.logger.error(f"API error in {context}: {str(error)}")
        return SkillResult(
            success=False,
            error=f"API call failed: {str(error)}",
            message=f"Failed to complete {context}"
        )
    
    def handle_validation_error(self, error: Exception, field: str) -> SkillResult:
        """Handle validation errors with user-friendly messages"""
        self.logger.warning(f"Validation error for {field}: {str(error)}")
        return SkillResult(
            success=False,
            error=f"Invalid {field}: {str(error)}",
            message="Please check your input and try again"
        )

Async Context Managers for Resources

from contextlib import asynccontextmanager
from typing import AsyncGenerator

@asynccontextmanager
async def obsidian_client(config: ObsidianConfig) -> AsyncGenerator[aiohttp.ClientSession, None]:
    """Async context manager for Obsidian API client"""
    ssl_context = ssl.create_default_context()
    ssl_context.check_hostname = False
    ssl_context.verify_mode = ssl.CERT_NONE
    
    connector = aiohttp.TCPConnector(ssl=ssl_context)
    headers = {
        'Authorization': f'Bearer {config.rest_api.api_key}',
        'Content-Type': 'application/json'
    }
    
    async with aiohttp.ClientSession(connector=connector, headers=headers) as session:
        try:
            yield session
        except Exception as e:
            logger.error(f"Error in Obsidian client: {str(e)}")
            raise APIError(f"Obsidian API error: {str(e)}") from e

Testing Strategy

Dual Testing Approach

The testing strategy combines unit tests for specific functionality with property-based tests for comprehensive validation:

Unit Tests:

  • Test specific error conditions and edge cases
  • Validate configuration parsing and validation
  • Test individual Skill and Command functionality
  • Mock external API calls for isolated testing

Property-Based Tests:

  • Validate that all SkillResult objects maintain consistency
  • Test configuration validation across various input combinations
  • Verify error handling patterns work for all error types
  • Ensure type hints are correctly applied

Testing Framework:

  • Use pytest for unit testing framework
  • Use hypothesis for property-based testing
  • Configure tests to run minimum 100 iterations per property test
  • Tag each property test with: Feature: code-quality-improvements, Property {number}: {property_text}

Correctness Properties

A property is a characteristic or behavior that should hold true across all valid executions of a system-essentially, a formal statement about what the system should do. Properties serve as the bridge between human-readable specifications and machine-verifiable correctness guarantees.

Property 1: Syntax Validity Across All Modules

For any Python module in the project, parsing the module with Python's AST parser should succeed without raising SyntaxError exceptions Validates: Requirements 1.1, 1.2, 1.3

Property 2: Comprehensive Type Hint Coverage

For any public method or function in Agent_Core, Skills, Commands, or Chat_Interface, the method should have complete type annotations for parameters and return values Validates: Requirements 2.1, 2.2, 2.3, 2.4, 2.5

Property 3: Consistent Error Handling Structure

For any error condition in Skills or Commands, the returned SkillResult should have a consistent structure with appropriate error messages and no sensitive information exposure Validates: Requirements 3.1, 3.2, 3.3, 3.4, 3.5

Property 4: Modern Python Code Patterns

For any source file in the project, the code should use f-strings for formatting, pathlib.Path for file operations, proper dataclass definitions, and follow PEP 8 style guidelines Validates: Requirements 4.1, 4.2, 4.3, 4.4, 4.5

Property 5: Configuration Validation Completeness

For any configuration input (valid or invalid), the system should validate all required fields, provide specific error messages for invalid values, and handle missing API keys with clear guidance Validates: Requirements 5.1, 5.2, 5.4, 7.4

Property 6: Import Organization and Style

For any Python module in the project, imports should be organized according to PEP 8 standards, use absolute imports consistently, and handle missing dependencies gracefully Validates: Requirements 6.1, 6.2, 6.3, 6.4, 6.5

Property 7: Input Validation and Sanitization

For any user input (file paths, dates, configuration values), the system should validate format and constraints, sanitize potentially dangerous inputs, and reject malformed data Validates: Requirements 7.1, 7.2, 7.3, 7.5

Property 8: Environment Variable Expansion

For any configuration value containing environment variable references, the system should correctly expand the variables or provide clear error messages when variables are undefined Validates: Requirements 5.3

Property 9: String Formatting Consistency

For any string formatting operation in the codebase, the system should use consistent and appropriate formatting patterns (f-strings, proper escaping, etc.) Validates: Requirements 1.4

Property-Based Testing Configuration

Each property test will be tagged with comments referencing the design document property and run with sufficient iterations to catch edge cases through randomization.