Comprehensive developer toolkit providing reusable skills for Java/Spring Boot, TypeScript/NestJS/React/Next.js, Python, PHP, AWS CloudFormation, AI/RAG, DevOps, and more.
90
90%
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Risky
Do not use without reviewing
"""
Task Frontmatter Parser and Validator
Provides utilities for reading, validating, modifying, and writing
task frontmatter with consistent formatting and schema compliance.
"""
import re
import yaml
from pathlib import Path
from datetime import datetime
from typing import Optional, List, Dict, Any, Tuple
from dataclasses import dataclass, field, asdict
from task_schema import (
TaskStatus, FIELD_SCHEMA, FIELD_ORDER, STATUS_WORKFLOW,
validate_status_transition, get_expected_dates_for_status,
ProvidedItem, ExpectedItem
)
class FrontmatterError(Exception):
"""Exception for frontmatter parsing/validation errors."""
pass
@dataclass
class TaskFrontmatter:
"""Represents a task frontmatter with all fields."""
# Required fields
id: str
title: str
spec: str
lang: str = "general"
# Status and lifecycle
status: str = "pending"
started_date: Optional[str] = None
implemented_date: Optional[str] = None
reviewed_date: Optional[str] = None
completed_date: Optional[str] = None
cleanup_date: Optional[str] = None
# Relationships
dependencies: List[str] = field(default_factory=list)
provides: List[Dict[str, Any]] = field(default_factory=list)
expects: List[Dict[str, Any]] = field(default_factory=list)
# Optional fields
complexity: Optional[int] = None
optional: bool = False
parent_task: Optional[str] = None
supersedes: List[str] = field(default_factory=list)
def __post_init__(self):
"""Normalize status value after initialization."""
self.status = self._normalize_status(self.status)
@staticmethod
def _normalize_status(status: str) -> str:
"""Normalize status to standard values."""
status_map = {
"done": "completed",
"in-progress": "in_progress",
"in progress": "in_progress",
}
normalized = status.lower().strip()
return status_map.get(normalized, normalized)
def to_dict(self, include_null: bool = False) -> Dict[str, Any]:
"""Convert to dictionary, optionally including null values."""
result = {}
for key in FIELD_ORDER:
if hasattr(self, key):
value = getattr(self, key)
if value is not None or include_null:
if key == "status":
result[key] = value
elif isinstance(value, list) and not value:
# Skip empty lists unless required
if FIELD_SCHEMA.get(key, {}).get("required", False):
result[key] = value
elif include_null:
result[key] = value
else:
result[key] = value
return result
def set_status(self, new_status: str, date: Optional[str] = None) -> Tuple[bool, Optional[str]]:
"""
Set task status with validation.
Args:
new_status: The new status value
date: Optional date string (defaults to today)
Returns:
Tuple of (success, error_message)
"""
normalized_new = self._normalize_status(new_status)
try:
current = TaskStatus(self.status)
new = TaskStatus(normalized_new)
except ValueError as e:
return False, f"Invalid status: {e}"
# Validate transition
is_valid, error = validate_status_transition(current, new)
if not is_valid:
return False, error
# Set the date for the new status
today = date or datetime.now().strftime("%Y-%m-%d")
date_field_map = {
TaskStatus.IN_PROGRESS: "started_date",
TaskStatus.IMPLEMENTED: "implemented_date",
TaskStatus.REVIEWED: "reviewed_date",
TaskStatus.COMPLETED: "completed_date",
}
if new in date_field_map:
setattr(self, date_field_map[new], today)
if new == TaskStatus.COMPLETED:
self.cleanup_date = today
self.status = normalized_new
return True, None
def validate(self) -> List[str]:
"""
Validate the frontmatter against the schema.
Returns:
List of validation error messages
"""
errors = []
# Check required fields
for field_name, schema in FIELD_SCHEMA.items():
if schema.get("required", False):
value = getattr(self, field_name, None)
if value is None or (isinstance(value, str) and not value.strip()):
errors.append(f"Required field '{field_name}' is missing or empty")
# Validate ID format
if self.id and not re.match(r"^TASK-\d+$", self.id):
errors.append(f"Invalid ID format '{self.id}': must be TASK-XXX")
# Validate status
if self.status not in [s.value for s in TaskStatus]:
errors.append(f"Invalid status '{self.status}': must be one of {[s.value for s in TaskStatus]}")
# Validate date formats
date_fields = ["started_date", "implemented_date", "reviewed_date", "completed_date", "cleanup_date"]
for field_name in date_fields:
value = getattr(self, field_name, None)
if value and not re.match(r"^\d{4}-\d{2}-\d{2}$", str(value)):
errors.append(f"Invalid date format in '{field_name}': '{value}' (expected YYYY-MM-DD)")
# Note: We don't strictly require dates for validation
# Dates are suggested based on status workflow but not enforced
# This allows flexibility for tasks that skip intermediate steps
# Validate dependency format
for dep in self.dependencies:
if not re.match(r"^TASK-\d+$", dep):
errors.append(f"Invalid dependency format '{dep}': must be TASK-XXX")
return errors
def parse_frontmatter(content: str) -> Tuple[Optional[TaskFrontmatter], Optional[str], str]:
"""
Parse frontmatter from markdown content.
Args:
content: Full markdown content with YAML frontmatter
Returns:
Tuple of (frontmatter_object, error_message, body_content)
"""
# Match YAML frontmatter between --- delimiters
pattern = r'^---\s*\n(.*?)\n---\s*\n(.*)$'
match = re.match(pattern, content, re.DOTALL)
if not match:
return None, "No YAML frontmatter found", content
yaml_content = match.group(1)
body_content = match.group(2)
try:
data = yaml.safe_load(yaml_content)
if not isinstance(data, dict):
return None, "Frontmatter is not a valid YAML dictionary", body_content
frontmatter = TaskFrontmatter(
id=data.get("id", ""),
title=data.get("title", ""),
spec=data.get("spec", ""),
lang=data.get("lang", "general"),
status=data.get("status", "pending"),
started_date=data.get("started_date"),
implemented_date=data.get("implemented_date"),
reviewed_date=data.get("reviewed_date"),
completed_date=data.get("completed_date"),
cleanup_date=data.get("cleanup_date"),
dependencies=data.get("dependencies", []),
provides=data.get("provides", []),
expects=data.get("expects", []),
complexity=data.get("complexity"),
optional=data.get("optional", False),
parent_task=data.get("parent_task"),
supersedes=data.get("supersedes", []),
)
return frontmatter, None, body_content
except yaml.YAMLError as e:
return None, f"YAML parsing error: {e}", body_content
def serialize_frontmatter(frontmatter: TaskFrontmatter, body_content: str) -> str:
"""
Serialize frontmatter and body to markdown.
Args:
frontmatter: The frontmatter object
body_content: The markdown body content
Returns:
Complete markdown content with YAML frontmatter
"""
data = frontmatter.to_dict(include_null=False)
# Custom YAML serialization for consistent formatting
yaml_lines = []
for key in FIELD_ORDER:
if key in data:
value = data[key]
if value is None:
continue
elif isinstance(value, list):
if not value:
yaml_lines.append(f"{key}: []")
else:
yaml_lines.append(f"{key}:")
for item in value:
if isinstance(item, dict):
yaml_lines.append(f" - file: \"{item.get('file', '')}\"")
if 'symbols' in item and item['symbols']:
yaml_lines.append(f" symbols: {item['symbols']}")
if 'type' in item:
yaml_lines.append(f" type: \"{item['type']}\"")
else:
yaml_lines.append(f" - \"{item}\"")
elif isinstance(value, str):
# Quote strings that might be interpreted as other types
if value in ['true', 'false', 'yes', 'no', 'null', ''] or ':' in value:
yaml_lines.append(f'{key}: "{value}"')
else:
yaml_lines.append(f"{key}: {value}")
elif isinstance(value, bool):
yaml_lines.append(f"{key}: {str(value).lower()}")
else:
yaml_lines.append(f"{key}: {value}")
yaml_str = "\n".join(yaml_lines)
return f"---\n{yaml_str}\n---\n{body_content}"
def read_task_file(filepath: str | Path) -> Tuple[Optional[TaskFrontmatter], Optional[str], str]:
"""
Read and parse a task file.
Args:
filepath: Path to the task markdown file
Returns:
Tuple of (frontmatter_object, error_message, body_content)
"""
path = Path(filepath)
if not path.exists():
return None, f"File not found: {filepath}", ""
content = path.read_text(encoding="utf-8")
return parse_frontmatter(content)
def write_task_file(filepath: str | Path, frontmatter: TaskFrontmatter, body_content: str) -> Optional[str]:
"""
Write a task file with frontmatter.
Args:
filepath: Path to the task markdown file
frontmatter: The frontmatter object
body_content: The markdown body content
Returns:
Error message if failed, None if successful
"""
try:
path = Path(filepath)
content = serialize_frontmatter(frontmatter, body_content)
path.write_text(content, encoding="utf-8")
return None
except Exception as e:
return f"Failed to write file: {e}"
def update_task_status(
filepath: str | Path,
new_status: str,
date: Optional[str] = None,
validate: bool = True
) -> Tuple[bool, Optional[str]]:
"""
Update the status of a task file.
Args:
filepath: Path to the task markdown file
new_status: New status value
date: Optional date (defaults to today)
validate: Whether to validate the transition
Returns:
Tuple of (success, error_message)
"""
frontmatter, error, body = read_task_file(filepath)
if error:
return False, error
if frontmatter is None:
return False, "Could not parse frontmatter"
success, error = frontmatter.set_status(new_status, date)
if not success:
return False, error
if validate:
errors = frontmatter.validate()
if errors:
return False, f"Validation failed: {'; '.join(errors)}"
error = write_task_file(filepath, frontmatter, body)
if error:
return False, error
return True, None
def validate_task_file(filepath: str | Path) -> Tuple[bool, List[str]]:
"""
Validate a task file against the schema.
Args:
filepath: Path to the task markdown file
Returns:
Tuple of (is_valid, list_of_errors)
"""
frontmatter, error, _ = read_task_file(filepath)
if error and frontmatter is None:
return False, [error]
if frontmatter is None:
return False, ["Could not parse frontmatter"]
errors = frontmatter.validate()
return len(errors) == 0, errors
def normalize_task_file(filepath: str | Path) -> Tuple[bool, Optional[str]]:
"""
Normalize a task file - fix inconsistencies and standardize format.
Args:
filepath: Path to the task markdown file
Returns:
Tuple of (success, error_message)
"""
frontmatter, error, body = read_task_file(filepath)
if error and frontmatter is None:
return False, error
if frontmatter is None:
return False, "Could not parse frontmatter"
# Re-serialize to normalize format
error = write_task_file(filepath, frontmatter, body)
if error:
return False, error
return True, Nonedocs
plugins
developer-kit-ai
developer-kit-aws
agents
docs
skills
aws
aws-cli-beast
aws-cost-optimization
aws-drawio-architecture-diagrams
aws-sam-bootstrap
aws-cloudformation
aws-cloudformation-auto-scaling
aws-cloudformation-bedrock
aws-cloudformation-cloudfront
aws-cloudformation-cloudwatch
aws-cloudformation-dynamodb
aws-cloudformation-ec2
aws-cloudformation-ecs
aws-cloudformation-elasticache
references
aws-cloudformation-iam
references
aws-cloudformation-lambda
aws-cloudformation-rds
aws-cloudformation-s3
aws-cloudformation-security
aws-cloudformation-task-ecs-deploy-gh
aws-cloudformation-vpc
references
developer-kit-core
agents
commands
skills
developer-kit-devops
developer-kit-java
agents
commands
docs
skills
aws-lambda-java-integration
aws-rds-spring-boot-integration
aws-sdk-java-v2-bedrock
aws-sdk-java-v2-core
aws-sdk-java-v2-dynamodb
aws-sdk-java-v2-kms
aws-sdk-java-v2-lambda
aws-sdk-java-v2-messaging
aws-sdk-java-v2-rds
aws-sdk-java-v2-s3
aws-sdk-java-v2-secrets-manager
clean-architecture
graalvm-native-image
langchain4j-ai-services-patterns
references
langchain4j-mcp-server-patterns
references
langchain4j-rag-implementation-patterns
references
langchain4j-spring-boot-integration
langchain4j-testing-strategies
langchain4j-tool-function-calling-patterns
langchain4j-vector-stores-configuration
references
qdrant
references
spring-ai-mcp-server-patterns
spring-boot-actuator
spring-boot-cache
spring-boot-crud-patterns
spring-boot-dependency-injection
spring-boot-event-driven-patterns
spring-boot-openapi-documentation
spring-boot-project-creator
spring-boot-resilience4j
spring-boot-rest-api-standards
spring-boot-saga-pattern
spring-boot-security-jwt
assets
references
scripts
spring-boot-test-patterns
spring-data-jpa
references
spring-data-neo4j
references
unit-test-application-events
unit-test-bean-validation
unit-test-boundary-conditions
unit-test-caching
unit-test-config-properties
references
unit-test-controller-layer
unit-test-exception-handler
references
unit-test-json-serialization
unit-test-mapper-converter
references
unit-test-parameterized
unit-test-scheduled-async
references
unit-test-service-layer
references
unit-test-utility-methods
unit-test-wiremock-rest-api
references
developer-kit-php
developer-kit-project-management
developer-kit-python
developer-kit-specs
commands
docs
hooks
test-templates
tests
skills
developer-kit-tools
developer-kit-typescript
agents
docs
hooks
rules
skills
aws-cdk
aws-lambda-typescript-integration
better-auth
clean-architecture
drizzle-orm-patterns
dynamodb-toolbox-patterns
references
nestjs
nestjs-best-practices
nestjs-code-review
nestjs-drizzle-crud-generator
nextjs-app-router
nextjs-authentication
nextjs-code-review
nextjs-data-fetching
nextjs-deployment
nextjs-performance
nx-monorepo
react-code-review
react-patterns
shadcn-ui
tailwind-css-patterns
tailwind-design-system
references
turborepo-monorepo
typescript-docs
typescript-security-review
zod-validation-utilities
references
github-spec-kit