Compare commits
7 Commits
b03160437e
...
001343c208
| Author | SHA1 | Date | |
|---|---|---|---|
| 001343c208 | |||
| a0522fd534 | |||
| bc00cc7eb3 | |||
| 68af98049e | |||
| 41193d0746 | |||
| 84161e77a9 | |||
| 5155a548eb |
217
Makefile
217
Makefile
@@ -1,7 +1,7 @@
|
||||
# MarkiTect - Advanced Markdown Engine
|
||||
# Makefile for common development tasks
|
||||
|
||||
.PHONY: help setup install test build clean update status dev lint format check-deps venv-status update-digest add-diary-entry list-issues show-issue list-open-issues test-from-issue start-issue add-test finish-issue workspace-status
|
||||
.PHONY: help setup install test build clean update status dev lint format check-deps venv-status update-digest add-diary-entry list-issues show-issue list-open-issues test-from-issue tdd-start tdd-add-test tdd-finish tdd-status
|
||||
|
||||
# Default target
|
||||
help:
|
||||
@@ -41,11 +41,11 @@ help:
|
||||
@echo "Test-Driven Development:"
|
||||
@echo " test-from-issue NUM=X - Generate test skeleton from issue (requires Claude Code)"
|
||||
@echo ""
|
||||
@echo "Issue Workspace:"
|
||||
@echo " start-issue NUM=X - Start working on issue (creates workspace)"
|
||||
@echo " add-test - Add test to current issue workspace"
|
||||
@echo " workspace-status - Show current workspace state"
|
||||
@echo " finish-issue - Complete issue work (moves tests to main)"
|
||||
@echo "TDD Workspace:"
|
||||
@echo " tdd-start NUM=X - Start working on issue (creates workspace)"
|
||||
@echo " tdd-add-test - Add test to current issue workspace"
|
||||
@echo " tdd-status - Show current workspace state"
|
||||
@echo " tdd-finish - Complete issue work (moves tests to main)"
|
||||
|
||||
# Python and virtual environment setup
|
||||
PYTHON := python3
|
||||
@@ -93,10 +93,10 @@ dev: install
|
||||
test: $(VENV)/bin/activate
|
||||
@echo "🧪 Running tests..."
|
||||
@if [ -f $(VENV)/bin/pytest ]; then \
|
||||
$(VENV)/bin/pytest tests/ -v; \
|
||||
PYTHONPATH=. $(VENV)/bin/pytest tests/ -v; \
|
||||
else \
|
||||
$(VENV_PYTHON) -m pytest tests/ -v 2>/dev/null || \
|
||||
$(VENV_PYTHON) -m unittest discover tests/ -v; \
|
||||
PYTHONPATH=. $(VENV_PYTHON) -m pytest tests/ -v 2>/dev/null || \
|
||||
PYTHONPATH=. $(VENV_PYTHON) -m unittest discover tests/ -v; \
|
||||
fi
|
||||
|
||||
# Build the package
|
||||
@@ -229,63 +229,20 @@ WORKSPACE_DIR := .markitect_workspace
|
||||
CURRENT_ISSUE_FILE := $(WORKSPACE_DIR)/current_issue.json
|
||||
|
||||
# List all gitea issues
|
||||
list-issues:
|
||||
@echo "📋 MarkiTect Issues from Gitea Repository"
|
||||
@echo "========================================"
|
||||
@echo ""
|
||||
@if ! command -v curl >/dev/null 2>&1; then \
|
||||
echo "❌ curl not found - required for API access"; \
|
||||
exit 1; \
|
||||
fi
|
||||
@if ! command -v jq >/dev/null 2>&1; then \
|
||||
echo "⚠️ jq not found - using basic formatting"; \
|
||||
echo " Install jq for better formatting: sudo apt install jq"; \
|
||||
curl -s "$(ISSUES_API)" | head -20; \
|
||||
else \
|
||||
curl -s "$(ISSUES_API)" | jq -r '.[] | "[\(.state | ascii_upcase)] #\(.number): \(.title)\n Created: \(.created_at[:10]) | Updated: \(.updated_at[:10])\n \(.body[:80])...\n"' | head -40; \
|
||||
fi
|
||||
@echo ""
|
||||
@echo "💡 Tip: Use 'make show-issue NUM=X' to see full details"
|
||||
list-issues: $(VENV)/bin/activate
|
||||
@PYTHONPATH=. $(VENV_PYTHON) tddai_cli.py list-issues
|
||||
|
||||
# Show detailed view of a specific issue
|
||||
show-issue:
|
||||
show-issue: $(VENV)/bin/activate
|
||||
@if [ -z "$(NUM)" ]; then \
|
||||
echo "❌ Please specify issue number: make show-issue NUM=5"; \
|
||||
exit 1; \
|
||||
fi
|
||||
@if ! command -v curl >/dev/null 2>&1; then \
|
||||
echo "❌ curl not found - required for API access"; \
|
||||
exit 1; \
|
||||
fi
|
||||
@echo "🔍 Issue #$(NUM) Details"
|
||||
@echo "======================="
|
||||
@echo ""
|
||||
@if ! command -v jq >/dev/null 2>&1; then \
|
||||
echo "⚠️ jq not found - using basic formatting"; \
|
||||
curl -s "$(ISSUES_API)/$(NUM)"; \
|
||||
else \
|
||||
curl -s "$(ISSUES_API)/$(NUM)" | jq -r 'if . == null or .message then "❌ Issue #$(NUM) not found or API error" else "**Title:** " + .title + "\n**Status:** " + (.state | ascii_upcase) + "\n**Number:** #" + (.number | tostring) + "\n**Created:** " + (.created_at[:10]) + " by " + (.user.full_name // .user.login) + "\n**Updated:** " + (.updated_at[:10]) + "\n**URL:** " + .html_url + "\n\n**Description:**\n" + .body end' 2>/dev/null || echo "❌ Issue #$(NUM) not found or API error"; \
|
||||
fi
|
||||
@echo ""
|
||||
@echo "💡 Tip: Use 'make list-issues' to see all issues"
|
||||
@PYTHONPATH=. $(VENV_PYTHON) tddai_cli.py show-issue $(NUM)
|
||||
|
||||
# List only open issues (active backlog)
|
||||
list-open-issues:
|
||||
@echo "📋 Open MarkiTect Issues (Active Backlog)"
|
||||
@echo "========================================"
|
||||
@echo ""
|
||||
@if ! command -v curl >/dev/null 2>&1; then \
|
||||
echo "❌ curl not found - required for API access"; \
|
||||
exit 1; \
|
||||
fi
|
||||
@if ! command -v jq >/dev/null 2>&1; then \
|
||||
echo "⚠️ jq not found - using basic formatting"; \
|
||||
curl -s "$(ISSUES_API)?state=open" | head -20; \
|
||||
else \
|
||||
curl -s "$(ISSUES_API)?state=open" | jq -r '.[] | "[OPEN] #\(.number): \(.title)\n Created: \(.created_at[:10]) | Updated: \(.updated_at[:10])\n \(.body[:80])...\n"' | head -40; \
|
||||
fi
|
||||
@echo ""
|
||||
@echo "💡 Tip: Use 'make show-issue NUM=X' for full details or 'make list-issues' for all issues"
|
||||
list-open-issues: $(VENV)/bin/activate
|
||||
@PYTHONPATH=. $(VENV_PYTHON) tddai_cli.py list-open-issues
|
||||
|
||||
# Generate test skeleton from gitea issue (requires Claude Code)
|
||||
test-from-issue:
|
||||
@@ -311,147 +268,21 @@ test-from-issue:
|
||||
@curl -s "$(ISSUES_API)/$(NUM)" | jq -r 'if .title then "✅ Issue #$(NUM): " + .title + "\n\n🧪 Generating test skeleton...\n Please ask Claude Code to generate a test for this issue:\n\n Command: '"'"'Generate a test skeleton for issue #$(NUM)'"'"'\n\n📋 Issue Details:\n Title: " + .title + "\n Description: " + .body + "\n\n📝 Test Requirements:\n - Follow TDD principles (test first, then implementation)\n - Use pytest framework (existing project convention)\n - Place test in tests/ directory\n - Name test file: test_issue_$(NUM)_*.py\n - Include docstring referencing issue #$(NUM)\n - Test should initially fail (red state)\n\n💡 After generation, run '"'"'make test'"'"' to verify test fails initially" else "❌ Issue #$(NUM) not found or API error\n Use '"'"'make list-open-issues'"'"' to see available issues" end' 2>/dev/null || echo "❌ Issue #$(NUM) not found or API error"
|
||||
|
||||
# Start working on an issue (creates workspace)
|
||||
start-issue:
|
||||
tdd-start: $(VENV)/bin/activate
|
||||
@if [ -z "$(NUM)" ]; then \
|
||||
echo "❌ Please specify issue number: make start-issue NUM=1"; \
|
||||
echo "❌ Please specify issue number: make tdd-start NUM=1"; \
|
||||
exit 1; \
|
||||
fi
|
||||
@echo "🔍 Starting work on issue #$(NUM)..."
|
||||
@if [ -f "$(CURRENT_ISSUE_FILE)" ]; then \
|
||||
CURRENT=$$(cat "$(CURRENT_ISSUE_FILE)" | jq -r '.number // "unknown"'); \
|
||||
echo "⚠️ Already working on issue #$$CURRENT"; \
|
||||
echo " Run 'make finish-issue' first or 'make workspace-status' to see details"; \
|
||||
exit 1; \
|
||||
fi
|
||||
@if ! command -v curl >/dev/null 2>&1 || ! command -v jq >/dev/null 2>&1; then \
|
||||
echo "❌ curl and jq required for workspace management"; \
|
||||
exit 1; \
|
||||
fi
|
||||
@echo "📋 Fetching issue #$(NUM) details..."
|
||||
@ISSUE_DATA=$$(curl -s "$(ISSUES_API)/$(NUM)" 2>/dev/null); \
|
||||
if echo "$$ISSUE_DATA" | jq -e '.title' >/dev/null 2>&1; then \
|
||||
mkdir -p "$(WORKSPACE_DIR)/issue_$(NUM)/tests"; \
|
||||
echo "$$ISSUE_DATA" | jq '{number: .number, title: .title, body: .body, state: .state, created_at: .created_at, html_url: .html_url}' > "$(CURRENT_ISSUE_FILE)"; \
|
||||
echo "$$ISSUE_DATA" | jq -r '"# Issue #" + (.number | tostring) + ": " + .title + "\n\n## Description\n" + .body + "\n\n## Requirements Breakdown\n\n- [ ] TODO: Break down requirements into testable scenarios\n- [ ] TODO: Identify edge cases\n- [ ] TODO: Define acceptance criteria\n\n## Test Plan\n\n- [ ] TODO: List specific test scenarios to implement\n"' > "$(WORKSPACE_DIR)/issue_$(NUM)/requirements.md"; \
|
||||
echo "# Test Plan for Issue #$(NUM)" > "$(WORKSPACE_DIR)/issue_$(NUM)/test_plan.md"; \
|
||||
echo "" >> "$(WORKSPACE_DIR)/issue_$(NUM)/test_plan.md"; \
|
||||
echo "## Test Scenarios" >> "$(WORKSPACE_DIR)/issue_$(NUM)/test_plan.md"; \
|
||||
echo "" >> "$(WORKSPACE_DIR)/issue_$(NUM)/test_plan.md"; \
|
||||
echo "- [ ] TODO: Add specific test scenarios" >> "$(WORKSPACE_DIR)/issue_$(NUM)/test_plan.md"; \
|
||||
echo "✅ Workspace created for issue #$(NUM)"; \
|
||||
echo "📁 Workspace: $(WORKSPACE_DIR)/issue_$(NUM)/"; \
|
||||
echo "📋 Requirements: $(WORKSPACE_DIR)/issue_$(NUM)/requirements.md"; \
|
||||
echo "🧪 Test plan: $(WORKSPACE_DIR)/issue_$(NUM)/test_plan.md"; \
|
||||
echo ""; \
|
||||
echo "💡 Next steps:"; \
|
||||
echo " 1. Review requirements.md and break down the issue"; \
|
||||
echo " 2. Plan test scenarios in test_plan.md"; \
|
||||
echo " 3. Use 'make add-test' to generate tests"; \
|
||||
echo " 4. Use 'make finish-issue' when complete"; \
|
||||
else \
|
||||
echo "❌ Issue #$(NUM) not found or API error"; \
|
||||
echo " Use 'make list-open-issues' to see available issues"; \
|
||||
fi
|
||||
@PYTHONPATH=. $(VENV_PYTHON) tddai_cli.py start-issue $(NUM)
|
||||
|
||||
# Add test to current issue workspace
|
||||
add-test:
|
||||
@if [ ! -f "$(CURRENT_ISSUE_FILE)" ]; then \
|
||||
echo "❌ No active issue workspace"; \
|
||||
echo " Run 'make start-issue NUM=X' first"; \
|
||||
exit 1; \
|
||||
fi
|
||||
@if ! command -v claude >/dev/null 2>&1; then \
|
||||
echo "❌ Claude Code not found - required for test generation"; \
|
||||
exit 1; \
|
||||
fi
|
||||
@CURRENT_ISSUE=$$(cat "$(CURRENT_ISSUE_FILE)" | jq -r '.number'); \
|
||||
ISSUE_TITLE=$$(cat "$(CURRENT_ISSUE_FILE)" | jq -r '.title'); \
|
||||
ISSUE_BODY=$$(cat "$(CURRENT_ISSUE_FILE)" | jq -r '.body'); \
|
||||
echo "🧪 Adding test to issue #$$CURRENT_ISSUE workspace"; \
|
||||
echo ""; \
|
||||
echo "📋 Issue: $$ISSUE_TITLE"; \
|
||||
echo "📁 Workspace: $(WORKSPACE_DIR)/issue_$$CURRENT_ISSUE/"; \
|
||||
echo ""; \
|
||||
echo "🤖 Please ask Claude Code to generate a test:"; \
|
||||
echo ""; \
|
||||
echo " Command: 'Generate a test for the current workspace issue'"; \
|
||||
echo ""; \
|
||||
echo "📝 Test Requirements:"; \
|
||||
echo " - Save test in: $(WORKSPACE_DIR)/issue_$$CURRENT_ISSUE/tests/"; \
|
||||
echo " - Name format: test_issue_$$CURRENT_ISSUE_<scenario>.py"; \
|
||||
echo " - Include docstring referencing issue #$$CURRENT_ISSUE"; \
|
||||
echo " - Follow TDD principles (test should fail initially)"; \
|
||||
echo " - Review requirements.md and test_plan.md for context"; \
|
||||
echo ""; \
|
||||
echo "📋 Issue Details:"; \
|
||||
echo " Title: $$ISSUE_TITLE"; \
|
||||
echo " Description: $$ISSUE_BODY"; \
|
||||
echo ""; \
|
||||
echo "💡 After generation: Use 'make workspace-status' to see all tests"
|
||||
tdd-add-test: $(VENV)/bin/activate
|
||||
@PYTHONPATH=. $(VENV_PYTHON) tddai_cli.py add-test
|
||||
|
||||
# Show current workspace status
|
||||
workspace-status:
|
||||
@if [ ! -f "$(CURRENT_ISSUE_FILE)" ]; then \
|
||||
echo "📋 No active issue workspace"; \
|
||||
echo " Use 'make start-issue NUM=X' to begin working on an issue"; \
|
||||
exit 0; \
|
||||
fi
|
||||
@CURRENT_ISSUE=$$(cat "$(CURRENT_ISSUE_FILE)" | jq -r '.number'); \
|
||||
ISSUE_TITLE=$$(cat "$(CURRENT_ISSUE_FILE)" | jq -r '.title'); \
|
||||
ISSUE_STATE=$$(cat "$(CURRENT_ISSUE_FILE)" | jq -r '.state'); \
|
||||
echo "📋 Active Issue Workspace"; \
|
||||
echo "========================"; \
|
||||
echo ""; \
|
||||
echo "🎯 Issue #$$CURRENT_ISSUE: $$ISSUE_TITLE"; \
|
||||
echo "📊 Status: $$ISSUE_STATE"; \
|
||||
echo "📁 Workspace: $(WORKSPACE_DIR)/issue_$$CURRENT_ISSUE/"; \
|
||||
echo ""; \
|
||||
if [ -d "$(WORKSPACE_DIR)/issue_$$CURRENT_ISSUE/tests" ]; then \
|
||||
TEST_COUNT=$$(find "$(WORKSPACE_DIR)/issue_$$CURRENT_ISSUE/tests" -name "*.py" | wc -l); \
|
||||
echo "🧪 Generated Tests ($$TEST_COUNT):"; \
|
||||
if [ $$TEST_COUNT -gt 0 ]; then \
|
||||
find "$(WORKSPACE_DIR)/issue_$$CURRENT_ISSUE/tests" -name "*.py" -exec basename {} \; | sed 's/^/ - /'; \
|
||||
else \
|
||||
echo " - No tests generated yet"; \
|
||||
fi; \
|
||||
echo ""; \
|
||||
fi; \
|
||||
echo "📋 Workspace Files:"; \
|
||||
echo " - requirements.md (review and break down issue)"; \
|
||||
echo " - test_plan.md (plan test scenarios)"; \
|
||||
echo " - tests/ (generated test files)"; \
|
||||
echo ""; \
|
||||
echo "💡 Commands:"; \
|
||||
echo " - make add-test (generate another test)"; \
|
||||
echo " - make finish-issue (complete and move tests to main)"
|
||||
tdd-status: $(VENV)/bin/activate
|
||||
@PYTHONPATH=. $(VENV_PYTHON) tddai_cli.py workspace-status
|
||||
|
||||
# Complete issue work (move tests to main and cleanup)
|
||||
finish-issue:
|
||||
@if [ ! -f "$(CURRENT_ISSUE_FILE)" ]; then \
|
||||
echo "❌ No active issue workspace"; \
|
||||
echo " Nothing to finish"; \
|
||||
exit 1; \
|
||||
fi
|
||||
@CURRENT_ISSUE=$$(cat "$(CURRENT_ISSUE_FILE)" | jq -r '.number'); \
|
||||
ISSUE_TITLE=$$(cat "$(CURRENT_ISSUE_FILE)" | jq -r '.title'); \
|
||||
echo "🏁 Finishing work on issue #$$CURRENT_ISSUE"; \
|
||||
echo ""; \
|
||||
if [ -d "$(WORKSPACE_DIR)/issue_$$CURRENT_ISSUE/tests" ]; then \
|
||||
TEST_COUNT=$$(find "$(WORKSPACE_DIR)/issue_$$CURRENT_ISSUE/tests" -name "*.py" | wc -l); \
|
||||
if [ $$TEST_COUNT -gt 0 ]; then \
|
||||
echo "📦 Moving $$TEST_COUNT test(s) to tests/ directory..."; \
|
||||
cp $(WORKSPACE_DIR)/issue_$$CURRENT_ISSUE/tests/*.py tests/ 2>/dev/null || echo " No .py files to move"; \
|
||||
echo "✅ Tests moved to main tests/ directory"; \
|
||||
else \
|
||||
echo "⚠️ No tests found in workspace"; \
|
||||
fi; \
|
||||
fi; \
|
||||
echo "🧹 Cleaning up workspace..."; \
|
||||
rm -rf "$(WORKSPACE_DIR)/issue_$$CURRENT_ISSUE"; \
|
||||
rm -f "$(CURRENT_ISSUE_FILE)"; \
|
||||
echo "✅ Issue #$$CURRENT_ISSUE workspace cleaned up"; \
|
||||
echo ""; \
|
||||
echo "💡 Next steps:"; \
|
||||
echo " - Run 'make test' to verify tests fail (red state)"; \
|
||||
echo " - Implement code to make tests pass (green state)"; \
|
||||
echo " - Start next issue with 'make start-issue NUM=X'"
|
||||
tdd-finish: $(VENV)/bin/activate
|
||||
@PYTHONPATH=. $(VENV_PYTHON) tddai_cli.py finish-issue
|
||||
|
||||
58
NEXT.txt
Normal file
58
NEXT.txt
Normal file
@@ -0,0 +1,58 @@
|
||||
# Next Steps for MarkiTect Development
|
||||
|
||||
**Session Goal for Tomorrow**: Test tddai infrastructure thoroughly using tddai itself to ensure robustness before proceeding with new features.
|
||||
|
||||
## 🔧 **Primary Focus: Validate TDD Infrastructure**
|
||||
|
||||
### 1. Self-Testing with tddai
|
||||
- Use `make tdd-start NUM=11` to test actual workspace creation with issue #11
|
||||
- Generate real tests using `make tdd-add-test` to validate AI integration
|
||||
- Complete full cycle: start → add-test → status → finish
|
||||
- Identify any rough edges or workflow issues
|
||||
- Ensure error handling works correctly for edge cases
|
||||
|
||||
### 2. Infrastructure Robustness Testing
|
||||
- Test with invalid issue numbers
|
||||
- Test workspace collision scenarios (multiple active workspaces)
|
||||
- Test cleanup and recovery from failed states
|
||||
- Validate all error messages are helpful and actionable
|
||||
- Ensure virtual environment integration is solid
|
||||
|
||||
## 🎯 **Secondary Opportunities (After Infrastructure Validation)**
|
||||
|
||||
### 3. Core MarkiTect Feature Implementation
|
||||
- Pick a Markdown feature from wiki specs (MF-1 through MF-10)
|
||||
- Use the validated TDD workflow to implement it properly
|
||||
- Expand `markitect/parser.py` with real functionality
|
||||
- Demonstrate TDD infrastructure working with actual feature development
|
||||
|
||||
### 4. CLI Interface Development
|
||||
- Build out the actual MarkiTect CLI described in architecture
|
||||
- Implement GraphQL interface and SQLite database integration
|
||||
- Make the project immediately useful for end users
|
||||
|
||||
### 5. Schema Validation System
|
||||
- Implement JSON Schema validation features (core differentiator)
|
||||
- Build schema generation from existing Markdown
|
||||
- Add validation and stub generation capabilities
|
||||
|
||||
### 6. Integration & Polish
|
||||
- Set up actual CI/CD pipeline
|
||||
- Add code coverage reporting with pytest-cov
|
||||
- Implement proper linting/formatting targets (black, ruff, mypy)
|
||||
- Performance optimization and documentation improvements
|
||||
|
||||
## 📋 **Success Criteria for Tomorrow**
|
||||
|
||||
**Primary Goal**: Confidently say "the TDD infrastructure is robust and reliable"
|
||||
- Complete issue #11 workflow without major issues
|
||||
- Generate and run meaningful tests via the tddai system
|
||||
- Document any improvements or fixes needed
|
||||
- Ready to use tddai for implementing new MarkiTect features
|
||||
|
||||
**Philosophy**: Validate the foundation before building the house. Any problems we encounter should be attributable to new feature implementation, not infrastructure issues.
|
||||
|
||||
---
|
||||
|
||||
*Created: 2025-09-22*
|
||||
*Next Session: Focus on self-testing and validation of tddai infrastructure*
|
||||
@@ -4,6 +4,17 @@ This diary tracks major work packages, events, and milestones in the MarkiTect p
|
||||
|
||||
---
|
||||
|
||||
## 2025-09-22: TDD Infrastructure Implementation & Python Library Architecture
|
||||
|
||||
**Progress:** Complete TDD workspace infrastructure with robust Python library implementation
|
||||
**Contributors:** User (bernd.worsch), Claude Code (Sonnet 4)
|
||||
**Time Estimate:** ~3-4 hours of active development
|
||||
**AI Resources:** ~30-40 Claude Sonnet 4 conversations, estimated 100K+ tokens
|
||||
|
||||
Successfully implemented comprehensive TDD workspace infrastructure by creating the `tddai` Python library to replace complex shell-based Makefile logic. Key achievements include a complete Python package architecture with workspace management, Gitea API integration, and AI-assisted test generation capabilities. Created five core modules: workspace lifecycle management, issue fetching with error handling, test generation framework, environment-based configuration, and custom exception hierarchy. Built Python CLI interface (`tddai_cli.py`) that provides clean command-line access to all TDD operations. Updated Makefile to use Python CLI with proper virtual environment integration and PYTHONPATH configuration. Developed comprehensive test suite with 20 passing tests using pytest, including behavior-based testing with proper mocking and fixtures. Implemented complete TDD workflow from issue-to-workspace creation, iterative test addition, workspace status monitoring, and final integration with cleanup. Renamed targets to use `tdd-` prefix for clarity: `tdd-start`, `tdd-add-test`, `tdd-status`, `tdd-finish`. All functionality achieved green test state before committing, demonstrating proper TDD practices. This establishes a maintainable, extensible foundation for issue-driven development with AI assistance.
|
||||
|
||||
---
|
||||
|
||||
## 2025-09-22: Repository Infrastructure & Development Workflow Establishment
|
||||
|
||||
**Progress:** Comprehensive development infrastructure setup with automated workflows
|
||||
|
||||
@@ -12,10 +12,17 @@ Transform Markdown from plain text into intelligent, structured, reusable data w
|
||||
|
||||
### MarkiTect Library (Python Core)
|
||||
- **Reusable Python package** designed for CLI, service offerings, and third-party integration
|
||||
- **TDD approach** with comprehensive test coverage
|
||||
- **TDD approach** with comprehensive test coverage and pytest framework
|
||||
- **Modern packaging** using `pyproject.toml` and semantic versioning
|
||||
- **Minimal dependencies** with `markdown-it-py` as primary parser
|
||||
|
||||
### TDD Infrastructure (tddai Library)
|
||||
- **Complete TDD workspace management** with Python library architecture
|
||||
- **Issue-driven development** with Gitea API integration
|
||||
- **AI-assisted test generation** framework for automated TDD workflows
|
||||
- **Workspace lifecycle management** from issue creation to test integration
|
||||
- **CLI interface** (`tddai_cli.py`) for seamless command-line operations
|
||||
|
||||
### MarkiTect CLI (Command-Line Interface)
|
||||
- **SQLite database** for temporary, in-memory operations
|
||||
- **GraphQL API** using `graphene` library for read/write operations
|
||||
@@ -45,10 +52,11 @@ Transform Markdown from plain text into intelligent, structured, reusable data w
|
||||
## Development Approach
|
||||
|
||||
### Test-Driven Development
|
||||
- Comprehensive **TDD guide** integrated into project workflow
|
||||
- **Test-first approach** for all new features
|
||||
- **High coverage** requirements for public APIs
|
||||
- **Integration tests** for key workflows
|
||||
- **Complete TDD infrastructure** with `tddai` Python library
|
||||
- **Issue-driven workflow** with workspace management (`tdd-start`, `tdd-add-test`, `tdd-status`, `tdd-finish`)
|
||||
- **20+ passing tests** using pytest with proper behavior-based testing
|
||||
- **AI-assisted test generation** integrated into development cycle
|
||||
- **Green-state validation** before all commits
|
||||
|
||||
### Markdown Feature Support (MF-1 through MF-10)
|
||||
Complete specification coverage including:
|
||||
@@ -63,23 +71,26 @@ Complete specification coverage including:
|
||||
## Project Status
|
||||
|
||||
### Current State
|
||||
- **Early development phase** with foundational parser implemented
|
||||
- **Comprehensive documentation** and architecture planning complete
|
||||
- **Build system** with sophisticated Makefile for development workflow
|
||||
- **Virtual environment management** with activation detection
|
||||
- **TDD infrastructure complete** with robust Python library architecture
|
||||
- **Issue-driven development workflow** fully operational
|
||||
- **Comprehensive test suite** with 20 passing tests and pytest integration
|
||||
- **Build system** with sophisticated Makefile and virtual environment integration
|
||||
- **AI-assisted development** cycle with workspace management
|
||||
|
||||
### Social Integration
|
||||
- **CoulombSocial participation** since September 2025
|
||||
- **GitLab issues integration** for use case tracking
|
||||
- **Gitea issues integration** with API-driven workflow management
|
||||
- **Open-source development** model with collaborative wiki
|
||||
- **Issue-to-test automation** for structured development cycles
|
||||
|
||||
## Technical Foundation
|
||||
|
||||
### Development Tools
|
||||
- **Python 3.8+** with modern tooling (Black, Ruff, mypy)
|
||||
- **Make-based workflow** with intelligent environment detection
|
||||
- **Python 3.8+** with modern tooling (Black, Ruff, mypy, pytest)
|
||||
- **Make-based workflow** with intelligent environment detection and TDD integration
|
||||
- **Git submodules** for wiki documentation management
|
||||
- **Automated testing** and CI/CD pipeline planning
|
||||
- **tddai library** for complete TDD workspace automation
|
||||
- **Issue management** with Gitea API integration and CLI tools
|
||||
|
||||
### Brand Identity
|
||||
- **Professional visual identity** with 3D "M" logo incorporating Markdown symbols
|
||||
@@ -93,12 +104,23 @@ markitect_project/
|
||||
├── markitect/ # Main Python package
|
||||
│ ├── __init__.py
|
||||
│ └── parser.py # Core parsing functionality
|
||||
├── tests/ # Test suite
|
||||
│ └── test_parser.py # Parser tests
|
||||
├── tddai/ # TDD infrastructure library
|
||||
│ ├── __init__.py # Package exports
|
||||
│ ├── workspace.py # Workspace lifecycle management
|
||||
│ ├── issue_fetcher.py # Gitea API integration
|
||||
│ ├── test_generator.py # AI-assisted test generation
|
||||
│ ├── config.py # Configuration management
|
||||
│ └── exceptions.py # Custom exception hierarchy
|
||||
├── tests/ # Comprehensive test suite (20+ tests)
|
||||
│ ├── test_parser.py # Parser tests
|
||||
│ ├── test_issue_11_*.py # TDD infrastructure tests
|
||||
│ └── test_*.py # Additional test modules
|
||||
├── tddai_cli.py # TDD CLI interface
|
||||
├── wiki/ # Git submodule with comprehensive documentation
|
||||
├── Makefile # Development workflow automation
|
||||
├── Makefile # Development workflow automation with TDD targets
|
||||
├── pyproject.toml # Python package configuration
|
||||
├── ProjectStatusDigest.md # This document
|
||||
├── ProjectDiary.md # Development milestone tracking
|
||||
└── README.md # Project overview
|
||||
```
|
||||
|
||||
@@ -112,12 +134,27 @@ markitect_project/
|
||||
|
||||
2. **Development Workflow:**
|
||||
```bash
|
||||
make test # Run test suite
|
||||
make test # Run comprehensive test suite (20+ tests)
|
||||
make update # Pull latest changes from upstream
|
||||
make status # Check git status
|
||||
```
|
||||
|
||||
3. **Building:**
|
||||
3. **TDD Workflow:**
|
||||
```bash
|
||||
make tdd-start NUM=X # Start working on issue X
|
||||
make tdd-add-test # Generate tests for current issue
|
||||
make tdd-status # Check workspace status
|
||||
make tdd-finish # Complete issue and integrate tests
|
||||
```
|
||||
|
||||
4. **Issue Management:**
|
||||
```bash
|
||||
make list-issues # Show all Gitea issues
|
||||
make list-open-issues # Show active backlog
|
||||
make show-issue NUM=X # Detailed issue view
|
||||
```
|
||||
|
||||
5. **Building:**
|
||||
```bash
|
||||
make build # Build the package
|
||||
make clean # Clean build artifacts
|
||||
|
||||
26
tddai/__init__.py
Normal file
26
tddai/__init__.py
Normal file
@@ -0,0 +1,26 @@
|
||||
"""
|
||||
tddai - Test-Driven Development with AI Support
|
||||
|
||||
A Python library for managing issue-driven TDD workflows with AI assistance.
|
||||
Provides workspace management, test generation, and issue integration.
|
||||
"""
|
||||
|
||||
from .workspace import WorkspaceManager, Workspace, WorkspaceStatus
|
||||
from .issue_fetcher import IssueFetcher, Issue
|
||||
from .test_generator import TestGenerator
|
||||
from .exceptions import TddaiError, WorkspaceError, IssueError, ConfigurationError, TestGenerationError
|
||||
|
||||
__version__ = "0.1.0"
|
||||
__all__ = [
|
||||
"WorkspaceManager",
|
||||
"Workspace",
|
||||
"WorkspaceStatus",
|
||||
"IssueFetcher",
|
||||
"Issue",
|
||||
"TestGenerator",
|
||||
"TddaiError",
|
||||
"WorkspaceError",
|
||||
"IssueError",
|
||||
"ConfigurationError",
|
||||
"TestGenerationError",
|
||||
]
|
||||
92
tddai/config.py
Normal file
92
tddai/config.py
Normal file
@@ -0,0 +1,92 @@
|
||||
"""
|
||||
Configuration management for tddai.
|
||||
"""
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
from dataclasses import dataclass
|
||||
|
||||
from .exceptions import ConfigurationError
|
||||
|
||||
|
||||
@dataclass
|
||||
class TddaiConfig:
|
||||
"""Configuration settings for tddai."""
|
||||
|
||||
# Workspace settings
|
||||
workspace_dir: Path = Path(".markitect_workspace")
|
||||
current_issue_file: str = "current_issue.json"
|
||||
|
||||
# Git repository settings
|
||||
gitea_url: str = "http://92.205.130.254:32166"
|
||||
repo_owner: str = "coulomb"
|
||||
repo_name: str = "markitect_project"
|
||||
|
||||
# Test settings
|
||||
tests_dir: Path = Path("tests")
|
||||
test_file_pattern: str = "test_issue_{issue_num}_{scenario}.py"
|
||||
|
||||
# AI settings
|
||||
claude_code_command: str = "claude"
|
||||
|
||||
@property
|
||||
def issues_api_url(self) -> str:
|
||||
"""Get the full issues API URL."""
|
||||
return f"{self.gitea_url}/api/v1/repos/{self.repo_owner}/{self.repo_name}/issues"
|
||||
|
||||
@property
|
||||
def current_issue_path(self) -> Path:
|
||||
"""Get the path to current issue file."""
|
||||
return self.workspace_dir / self.current_issue_file
|
||||
|
||||
@classmethod
|
||||
def from_environment(cls) -> "TddaiConfig":
|
||||
"""Create config from environment variables."""
|
||||
config = cls()
|
||||
|
||||
# Override with environment variables if present
|
||||
if gitea_url := os.getenv("TDDAI_GITEA_URL"):
|
||||
config.gitea_url = gitea_url
|
||||
|
||||
if repo_owner := os.getenv("TDDAI_REPO_OWNER"):
|
||||
config.repo_owner = repo_owner
|
||||
|
||||
if repo_name := os.getenv("TDDAI_REPO_NAME"):
|
||||
config.repo_name = repo_name
|
||||
|
||||
if workspace_dir := os.getenv("TDDAI_WORKSPACE_DIR"):
|
||||
config.workspace_dir = Path(workspace_dir)
|
||||
|
||||
return config
|
||||
|
||||
def validate(self) -> None:
|
||||
"""Validate configuration settings."""
|
||||
if not self.gitea_url:
|
||||
raise ConfigurationError("gitea_url cannot be empty")
|
||||
|
||||
if not self.repo_owner:
|
||||
raise ConfigurationError("repo_owner cannot be empty")
|
||||
|
||||
if not self.repo_name:
|
||||
raise ConfigurationError("repo_name cannot be empty")
|
||||
|
||||
|
||||
# Global config instance
|
||||
_config: Optional[TddaiConfig] = None
|
||||
|
||||
|
||||
def get_config() -> TddaiConfig:
|
||||
"""Get the global configuration instance."""
|
||||
global _config
|
||||
if _config is None:
|
||||
_config = TddaiConfig.from_environment()
|
||||
_config.validate()
|
||||
return _config
|
||||
|
||||
|
||||
def set_config(config: TddaiConfig) -> None:
|
||||
"""Set the global configuration instance."""
|
||||
global _config
|
||||
config.validate()
|
||||
_config = config
|
||||
28
tddai/exceptions.py
Normal file
28
tddai/exceptions.py
Normal file
@@ -0,0 +1,28 @@
|
||||
"""
|
||||
Custom exceptions for tddai library.
|
||||
"""
|
||||
|
||||
|
||||
class TddaiError(Exception):
|
||||
"""Base exception for all tddai errors."""
|
||||
pass
|
||||
|
||||
|
||||
class WorkspaceError(TddaiError):
|
||||
"""Raised when workspace operations fail."""
|
||||
pass
|
||||
|
||||
|
||||
class IssueError(TddaiError):
|
||||
"""Raised when issue operations fail."""
|
||||
pass
|
||||
|
||||
|
||||
class ConfigurationError(TddaiError):
|
||||
"""Raised when configuration is invalid."""
|
||||
pass
|
||||
|
||||
|
||||
class TestGenerationError(TddaiError):
|
||||
"""Raised when test generation fails."""
|
||||
pass
|
||||
136
tddai/issue_fetcher.py
Normal file
136
tddai/issue_fetcher.py
Normal file
@@ -0,0 +1,136 @@
|
||||
"""
|
||||
Issue fetching from Gitea API.
|
||||
"""
|
||||
|
||||
import json
|
||||
import subprocess
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
from typing import List, Optional, Dict, Any
|
||||
|
||||
from .config import get_config
|
||||
from .exceptions import IssueError
|
||||
|
||||
|
||||
@dataclass
|
||||
class Issue:
|
||||
"""Represents a Gitea issue."""
|
||||
|
||||
number: int
|
||||
title: str
|
||||
body: str
|
||||
state: str
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
html_url: str
|
||||
assignee: Optional[str] = None
|
||||
labels: List[str] = None
|
||||
|
||||
def __post_init__(self):
|
||||
if self.labels is None:
|
||||
self.labels = []
|
||||
|
||||
|
||||
class IssueFetcher:
|
||||
"""Fetches issues from Gitea API."""
|
||||
|
||||
def __init__(self, config=None):
|
||||
self.config = config or get_config()
|
||||
|
||||
def fetch_issue(self, issue_number: int) -> Issue:
|
||||
"""Fetch a specific issue by number."""
|
||||
try:
|
||||
result = subprocess.run(
|
||||
['curl', '-s', f"{self.config.issues_api_url}/{issue_number}"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=True
|
||||
)
|
||||
|
||||
if result.returncode != 0:
|
||||
raise IssueError(f"Failed to fetch issue #{issue_number}: {result.stderr}")
|
||||
|
||||
issue_data = json.loads(result.stdout)
|
||||
|
||||
if 'message' in issue_data:
|
||||
raise IssueError(f"Issue #{issue_number} not found: {issue_data['message']}")
|
||||
|
||||
return self._parse_issue(issue_data)
|
||||
|
||||
except subprocess.CalledProcessError as e:
|
||||
raise IssueError(f"Failed to fetch issue #{issue_number}: {e}")
|
||||
except json.JSONDecodeError as e:
|
||||
raise IssueError(f"Failed to parse issue data: {e}")
|
||||
|
||||
def fetch_issues(self, state: str = "all") -> List[Issue]:
|
||||
"""Fetch all issues with optional state filter."""
|
||||
try:
|
||||
url = self.config.issues_api_url
|
||||
if state != "all":
|
||||
url += f"?state={state}"
|
||||
|
||||
result = subprocess.run(
|
||||
['curl', '-s', url],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=True
|
||||
)
|
||||
|
||||
if result.returncode != 0:
|
||||
raise IssueError(f"Failed to fetch issues: {result.stderr}")
|
||||
|
||||
issues_data = json.loads(result.stdout)
|
||||
|
||||
if isinstance(issues_data, dict) and 'message' in issues_data:
|
||||
raise IssueError(f"Failed to fetch issues: {issues_data['message']}")
|
||||
|
||||
if not isinstance(issues_data, list):
|
||||
raise IssueError("Invalid response format: expected list of issues")
|
||||
|
||||
return [self._parse_issue(issue_data) for issue_data in issues_data]
|
||||
|
||||
except subprocess.CalledProcessError as e:
|
||||
raise IssueError(f"Failed to fetch issues: {e}")
|
||||
except json.JSONDecodeError as e:
|
||||
raise IssueError(f"Failed to parse issues data: {e}")
|
||||
|
||||
def fetch_open_issues(self) -> List[Issue]:
|
||||
"""Fetch only open issues."""
|
||||
return self.fetch_issues(state="open")
|
||||
|
||||
def _parse_issue(self, issue_data: Dict[str, Any]) -> Issue:
|
||||
"""Parse issue data from API response."""
|
||||
try:
|
||||
labels = [label['name'] for label in issue_data.get('labels', [])]
|
||||
assignee = None
|
||||
if issue_data.get('assignee'):
|
||||
assignee = issue_data['assignee'].get('login')
|
||||
|
||||
return Issue(
|
||||
number=issue_data['number'],
|
||||
title=issue_data['title'],
|
||||
body=issue_data.get('body', ''),
|
||||
state=issue_data['state'],
|
||||
created_at=datetime.fromisoformat(issue_data['created_at'].replace('Z', '+00:00')),
|
||||
updated_at=datetime.fromisoformat(issue_data['updated_at'].replace('Z', '+00:00')),
|
||||
html_url=issue_data['html_url'],
|
||||
assignee=assignee,
|
||||
labels=labels
|
||||
)
|
||||
except (KeyError, ValueError) as e:
|
||||
raise IssueError(f"Failed to parse issue data: {e}")
|
||||
|
||||
def get_issue_data_dict(self, issue_number: int) -> Dict[str, Any]:
|
||||
"""Get issue data as dictionary for workspace creation."""
|
||||
issue = self.fetch_issue(issue_number)
|
||||
return {
|
||||
'number': issue.number,
|
||||
'title': issue.title,
|
||||
'body': issue.body,
|
||||
'state': issue.state,
|
||||
'created_at': issue.created_at.isoformat(),
|
||||
'updated_at': issue.updated_at.isoformat(),
|
||||
'html_url': issue.html_url,
|
||||
'assignee': {'login': issue.assignee} if issue.assignee else None,
|
||||
'labels': [{'name': label} for label in issue.labels]
|
||||
}
|
||||
163
tddai/test_generator.py
Normal file
163
tddai/test_generator.py
Normal file
@@ -0,0 +1,163 @@
|
||||
"""
|
||||
Test generation with AI assistance.
|
||||
"""
|
||||
|
||||
import subprocess
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
from .config import get_config
|
||||
from .workspace import WorkspaceManager
|
||||
from .exceptions import TestGenerationError
|
||||
|
||||
|
||||
class TestGenerator:
|
||||
"""Generates tests using AI assistance."""
|
||||
|
||||
def __init__(self, config=None):
|
||||
self.config = config or get_config()
|
||||
self.workspace_manager = WorkspaceManager(config)
|
||||
|
||||
def generate_test(self, scenario_name: str, test_description: str) -> Path:
|
||||
"""Generate a test file for the current workspace issue."""
|
||||
workspace = self.workspace_manager.get_current_workspace()
|
||||
if not workspace:
|
||||
raise TestGenerationError("No active workspace found")
|
||||
|
||||
# Create test file name
|
||||
test_filename = self.config.test_file_pattern.format(
|
||||
issue_num=workspace.issue_number,
|
||||
scenario=scenario_name.lower().replace(' ', '_').replace('-', '_')
|
||||
)
|
||||
test_file_path = workspace.tests_dir / test_filename
|
||||
|
||||
# Generate test prompt
|
||||
prompt = self._create_test_prompt(workspace, scenario_name, test_description)
|
||||
|
||||
# Use Claude Code to generate the test
|
||||
try:
|
||||
with tempfile.NamedTemporaryFile(mode='w', suffix='.md', delete=False) as f:
|
||||
f.write(prompt)
|
||||
prompt_file = Path(f.name)
|
||||
|
||||
result = subprocess.run(
|
||||
[self.config.claude_code_command, '--file', str(prompt_file)],
|
||||
cwd=workspace.workspace_dir,
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
|
||||
prompt_file.unlink() # Clean up temp file
|
||||
|
||||
if result.returncode != 0:
|
||||
raise TestGenerationError(f"Claude Code failed: {result.stderr}")
|
||||
|
||||
# Extract Python code from Claude's response
|
||||
test_content = self._extract_test_code(result.stdout)
|
||||
|
||||
# Write test file
|
||||
test_file_path.write_text(test_content)
|
||||
|
||||
# Update test plan
|
||||
self._update_test_plan(workspace, scenario_name, test_filename)
|
||||
|
||||
return test_file_path
|
||||
|
||||
except subprocess.CalledProcessError as e:
|
||||
raise TestGenerationError(f"Failed to generate test: {e}")
|
||||
except Exception as e:
|
||||
raise TestGenerationError(f"Test generation error: {e}")
|
||||
|
||||
def _create_test_prompt(self, workspace, scenario_name: str, test_description: str) -> str:
|
||||
"""Create prompt for Claude Code to generate test."""
|
||||
return f"""# Test Generation Request
|
||||
|
||||
## Context
|
||||
- Issue #{workspace.issue_number}: {workspace.issue_title}
|
||||
- Scenario: {scenario_name}
|
||||
|
||||
## Issue Description
|
||||
{workspace.issue_body}
|
||||
|
||||
## Test Requirements
|
||||
{test_description}
|
||||
|
||||
## Instructions
|
||||
Please generate a comprehensive Python test file that:
|
||||
|
||||
1. Tests the behavior described in the scenario
|
||||
2. Follows pytest conventions
|
||||
3. Includes proper docstrings and comments
|
||||
4. Tests both positive and negative cases
|
||||
5. Uses meaningful test method names
|
||||
6. Includes appropriate assertions
|
||||
|
||||
The test should focus on behavior verification rather than implementation details.
|
||||
|
||||
## Expected Output
|
||||
Please provide only the Python test code without any additional explanation.
|
||||
The code should be ready to save as `{self.config.test_file_pattern.format(issue_num=workspace.issue_number, scenario=scenario_name.lower().replace(' ', '_'))}`
|
||||
"""
|
||||
|
||||
def _extract_test_code(self, claude_response: str) -> str:
|
||||
"""Extract Python test code from Claude's response."""
|
||||
lines = claude_response.split('\n')
|
||||
code_lines = []
|
||||
in_code_block = False
|
||||
|
||||
for line in lines:
|
||||
if line.strip().startswith('```python'):
|
||||
in_code_block = True
|
||||
continue
|
||||
elif line.strip() == '```' and in_code_block:
|
||||
break
|
||||
elif in_code_block:
|
||||
code_lines.append(line)
|
||||
|
||||
if not code_lines:
|
||||
# If no code block found, assume entire response is code
|
||||
return claude_response.strip()
|
||||
|
||||
return '\n'.join(code_lines)
|
||||
|
||||
def _update_test_plan(self, workspace, scenario_name: str, test_filename: str) -> None:
|
||||
"""Update the test plan with the new test."""
|
||||
test_plan_content = workspace.test_plan_file.read_text()
|
||||
|
||||
# Add test to the generated tests section
|
||||
new_entry = f"- [x] {scenario_name} (`{test_filename}`)"
|
||||
|
||||
if "### Generated Tests" in test_plan_content:
|
||||
# Add to existing generated tests section
|
||||
lines = test_plan_content.split('\n')
|
||||
for i, line in enumerate(lines):
|
||||
if line.strip() == "Tests generated for this workspace will be listed here as they are created.":
|
||||
lines[i] = new_entry
|
||||
break
|
||||
elif line.startswith("- [") and "Generated Tests" in lines[max(0, i-5):i]:
|
||||
lines.insert(i, new_entry)
|
||||
break
|
||||
else:
|
||||
# Add at the end of generated tests section
|
||||
for i, line in enumerate(lines):
|
||||
if "### Generated Tests" in line:
|
||||
# Find next section or end
|
||||
j = i + 1
|
||||
while j < len(lines) and not lines[j].startswith('##'):
|
||||
j += 1
|
||||
lines.insert(j, new_entry)
|
||||
break
|
||||
|
||||
workspace.test_plan_file.write_text('\n'.join(lines))
|
||||
|
||||
def list_generated_tests(self) -> list:
|
||||
"""List all generated tests for the current workspace."""
|
||||
workspace = self.workspace_manager.get_current_workspace()
|
||||
if not workspace:
|
||||
return []
|
||||
|
||||
if not workspace.tests_dir.exists():
|
||||
return []
|
||||
|
||||
return list(workspace.tests_dir.glob("*.py"))
|
||||
219
tddai/workspace.py
Normal file
219
tddai/workspace.py
Normal file
@@ -0,0 +1,219 @@
|
||||
"""
|
||||
Workspace management for tddai.
|
||||
"""
|
||||
|
||||
import json
|
||||
import shutil
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
from enum import Enum
|
||||
from pathlib import Path
|
||||
from typing import Optional, Dict, Any
|
||||
|
||||
from .config import get_config
|
||||
from .exceptions import WorkspaceError
|
||||
|
||||
|
||||
class WorkspaceStatus(Enum):
|
||||
"""Status of workspace."""
|
||||
CLEAN = "clean"
|
||||
ACTIVE = "active"
|
||||
DIRTY = "dirty"
|
||||
|
||||
|
||||
@dataclass
|
||||
class Workspace:
|
||||
"""Represents a TDD workspace for an issue."""
|
||||
|
||||
issue_number: int
|
||||
issue_title: str
|
||||
issue_body: str
|
||||
issue_state: str
|
||||
created_at: datetime
|
||||
workspace_dir: Path
|
||||
|
||||
@property
|
||||
def issue_dir(self) -> Path:
|
||||
"""Get the issue-specific directory."""
|
||||
return self.workspace_dir / f"issue_{self.issue_number}"
|
||||
|
||||
@property
|
||||
def tests_dir(self) -> Path:
|
||||
"""Get the tests directory for this issue."""
|
||||
return self.issue_dir / "tests"
|
||||
|
||||
@property
|
||||
def requirements_file(self) -> Path:
|
||||
"""Get the requirements file path."""
|
||||
return self.issue_dir / "requirements.md"
|
||||
|
||||
@property
|
||||
def test_plan_file(self) -> Path:
|
||||
"""Get the test plan file path."""
|
||||
return self.issue_dir / "test_plan.md"
|
||||
|
||||
|
||||
class WorkspaceManager:
|
||||
"""Manages TDD workspaces for issues."""
|
||||
|
||||
def __init__(self, config=None):
|
||||
self.config = config or get_config()
|
||||
|
||||
def get_status(self) -> WorkspaceStatus:
|
||||
"""Get current workspace status."""
|
||||
if not self.config.workspace_dir.exists():
|
||||
return WorkspaceStatus.CLEAN
|
||||
|
||||
if not self.config.current_issue_path.exists():
|
||||
return WorkspaceStatus.DIRTY
|
||||
|
||||
return WorkspaceStatus.ACTIVE
|
||||
|
||||
def get_current_workspace(self) -> Optional[Workspace]:
|
||||
"""Get the currently active workspace."""
|
||||
if not self.config.current_issue_path.exists():
|
||||
return None
|
||||
|
||||
try:
|
||||
with open(self.config.current_issue_path, 'r') as f:
|
||||
issue_data = json.load(f)
|
||||
|
||||
return Workspace(
|
||||
issue_number=issue_data['number'],
|
||||
issue_title=issue_data['title'],
|
||||
issue_body=issue_data['body'],
|
||||
issue_state=issue_data['state'],
|
||||
created_at=datetime.fromisoformat(issue_data['created_at']),
|
||||
workspace_dir=self.config.workspace_dir
|
||||
)
|
||||
except (json.JSONDecodeError, KeyError, ValueError) as e:
|
||||
raise WorkspaceError(f"Failed to load current workspace: {e}")
|
||||
|
||||
def create_workspace(self, issue_data: Dict[str, Any]) -> Workspace:
|
||||
"""Create a new workspace for an issue."""
|
||||
status = self.get_status()
|
||||
if status == WorkspaceStatus.ACTIVE:
|
||||
current = self.get_current_workspace()
|
||||
raise WorkspaceError(
|
||||
f"Workspace already active for issue #{current.issue_number}. "
|
||||
"Finish current workspace before starting a new one."
|
||||
)
|
||||
|
||||
# Clean up any dirty workspace
|
||||
if status == WorkspaceStatus.DIRTY:
|
||||
self.cleanup_workspace()
|
||||
|
||||
# Create workspace structure
|
||||
workspace = Workspace(
|
||||
issue_number=issue_data['number'],
|
||||
issue_title=issue_data['title'],
|
||||
issue_body=issue_data['body'],
|
||||
issue_state=issue_data['state'],
|
||||
created_at=datetime.now(),
|
||||
workspace_dir=self.config.workspace_dir
|
||||
)
|
||||
|
||||
# Create directories
|
||||
workspace.workspace_dir.mkdir(exist_ok=True)
|
||||
workspace.issue_dir.mkdir(exist_ok=True)
|
||||
workspace.tests_dir.mkdir(exist_ok=True)
|
||||
|
||||
# Create metadata files
|
||||
self._create_requirements_file(workspace, issue_data)
|
||||
self._create_test_plan_file(workspace, issue_data)
|
||||
self._save_current_issue(workspace, issue_data)
|
||||
|
||||
return workspace
|
||||
|
||||
def cleanup_workspace(self) -> None:
|
||||
"""Clean up the current workspace."""
|
||||
if self.config.workspace_dir.exists():
|
||||
shutil.rmtree(self.config.workspace_dir)
|
||||
|
||||
def finish_workspace(self) -> Optional[Workspace]:
|
||||
"""Finish the current workspace and integrate tests."""
|
||||
workspace = self.get_current_workspace()
|
||||
if not workspace:
|
||||
return None
|
||||
|
||||
# Move tests to main tests directory
|
||||
main_tests_dir = self.config.tests_dir
|
||||
main_tests_dir.mkdir(exist_ok=True)
|
||||
|
||||
if workspace.tests_dir.exists():
|
||||
for test_file in workspace.tests_dir.glob("*.py"):
|
||||
dest_file = main_tests_dir / test_file.name
|
||||
shutil.copy2(test_file, dest_file)
|
||||
|
||||
# Clean up workspace
|
||||
self.cleanup_workspace()
|
||||
|
||||
return workspace
|
||||
|
||||
def _create_requirements_file(self, workspace: Workspace, issue_data: Dict[str, Any]) -> None:
|
||||
"""Create requirements.md file for the issue."""
|
||||
content = f"""# Requirements for Issue #{workspace.issue_number}
|
||||
|
||||
## Title
|
||||
{workspace.issue_title}
|
||||
|
||||
## Description
|
||||
{workspace.issue_body}
|
||||
|
||||
## Acceptance Criteria
|
||||
- [ ] Implementation meets the requirements described above
|
||||
- [ ] All tests pass
|
||||
- [ ] Code follows project conventions
|
||||
- [ ] Documentation is updated if needed
|
||||
|
||||
## Notes
|
||||
Created: {workspace.created_at.strftime('%Y-%m-%d %H:%M:%S')}
|
||||
"""
|
||||
workspace.requirements_file.write_text(content)
|
||||
|
||||
def _create_test_plan_file(self, workspace: Workspace, issue_data: Dict[str, Any]) -> None:
|
||||
"""Create test_plan.md file for the issue."""
|
||||
content = f"""# Test Plan for Issue #{workspace.issue_number}
|
||||
|
||||
## Overview
|
||||
This test plan outlines the testing strategy for implementing: {workspace.issue_title}
|
||||
|
||||
## Test Categories
|
||||
|
||||
### Unit Tests
|
||||
- [ ] Core functionality tests
|
||||
- [ ] Edge case handling
|
||||
- [ ] Error condition tests
|
||||
|
||||
### Integration Tests
|
||||
- [ ] Component integration
|
||||
- [ ] API integration
|
||||
- [ ] End-to-end scenarios
|
||||
|
||||
### Generated Tests
|
||||
Tests generated for this workspace will be listed here as they are created.
|
||||
|
||||
## Test Execution
|
||||
Run tests with: `pytest tests/test_issue_{workspace.issue_number}_*.py`
|
||||
|
||||
## Notes
|
||||
- Follow TDD red-green-refactor cycle
|
||||
- Each test should be focused and specific
|
||||
- Tests should be readable and maintainable
|
||||
"""
|
||||
workspace.test_plan_file.write_text(content)
|
||||
|
||||
def _save_current_issue(self, workspace: Workspace, issue_data: Dict[str, Any]) -> None:
|
||||
"""Save current issue metadata."""
|
||||
current_issue_data = {
|
||||
'number': workspace.issue_number,
|
||||
'title': workspace.issue_title,
|
||||
'body': workspace.issue_body,
|
||||
'state': workspace.issue_state,
|
||||
'created_at': workspace.created_at.isoformat(),
|
||||
'url': issue_data.get('html_url', ''),
|
||||
'assignee': issue_data.get('assignee', {}).get('login', '') if issue_data.get('assignee') else ''
|
||||
}
|
||||
|
||||
with open(self.config.current_issue_path, 'w') as f:
|
||||
json.dump(current_issue_data, f, indent=2)
|
||||
331
tddai_cli.py
Normal file
331
tddai_cli.py
Normal file
@@ -0,0 +1,331 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
CLI interface for tddai library.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
|
||||
# Add current directory to path so we can import tddai
|
||||
sys.path.insert(0, str(Path(__file__).parent))
|
||||
|
||||
from tddai import (
|
||||
WorkspaceManager, IssueFetcher, TestGenerator,
|
||||
WorkspaceStatus, TddaiError
|
||||
)
|
||||
|
||||
|
||||
def workspace_status():
|
||||
"""Show current workspace status."""
|
||||
try:
|
||||
manager = WorkspaceManager()
|
||||
status = manager.get_status()
|
||||
|
||||
if status == WorkspaceStatus.CLEAN:
|
||||
print("📋 No active issue workspace")
|
||||
print(" Use 'make tdd-start NUM=X' to begin working on an issue")
|
||||
return
|
||||
|
||||
if status == WorkspaceStatus.DIRTY:
|
||||
print("⚠️ Workspace directory exists but no current issue file")
|
||||
print(" Run 'make tdd-finish' to clean up or 'make tdd-start' to create new workspace")
|
||||
return
|
||||
|
||||
workspace = manager.get_current_workspace()
|
||||
if not workspace:
|
||||
print("❌ Failed to load workspace")
|
||||
return
|
||||
|
||||
print("📋 Active Issue Workspace")
|
||||
print("========================")
|
||||
print()
|
||||
print(f"🎯 Issue #{workspace.issue_number}: {workspace.issue_title}")
|
||||
print(f"📊 Status: {workspace.issue_state}")
|
||||
print(f"📁 Workspace: {workspace.workspace_dir}/issue_{workspace.issue_number}/")
|
||||
print()
|
||||
|
||||
if workspace.tests_dir.exists():
|
||||
test_files = list(workspace.tests_dir.glob("*.py"))
|
||||
print(f"🧪 Generated Tests ({len(test_files)}):")
|
||||
if test_files:
|
||||
for test_file in test_files:
|
||||
print(f" - {test_file.name}")
|
||||
else:
|
||||
print(" - No tests generated yet")
|
||||
print()
|
||||
|
||||
print("📋 Workspace Files:")
|
||||
print(" - requirements.md (review and break down issue)")
|
||||
print(" - test_plan.md (plan test scenarios)")
|
||||
print(" - tests/ (generated test files)")
|
||||
print()
|
||||
print("💡 Commands:")
|
||||
print(" - make tdd-add-test (generate another test)")
|
||||
print(" - make tdd-finish (complete and move tests to main)")
|
||||
|
||||
except TddaiError as e:
|
||||
print(f"❌ Error: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def start_issue(issue_number: int):
|
||||
"""Start working on an issue."""
|
||||
try:
|
||||
manager = WorkspaceManager()
|
||||
fetcher = IssueFetcher()
|
||||
|
||||
# Check if workspace already active
|
||||
status = manager.get_status()
|
||||
if status == WorkspaceStatus.ACTIVE:
|
||||
current = manager.get_current_workspace()
|
||||
print(f"⚠️ Already working on issue #{current.issue_number}")
|
||||
print(" Run 'make tdd-finish' first or 'make tdd-status' to see details")
|
||||
sys.exit(1)
|
||||
|
||||
print(f"🔍 Starting work on issue #{issue_number}...")
|
||||
print(f"📋 Fetching issue #{issue_number} details...")
|
||||
|
||||
# Fetch issue data
|
||||
issue_data = fetcher.get_issue_data_dict(issue_number)
|
||||
|
||||
# Create workspace
|
||||
workspace = manager.create_workspace(issue_data)
|
||||
|
||||
print(f"✅ Workspace created for issue #{issue_number}")
|
||||
print(f"📁 Workspace: {workspace.workspace_dir}/issue_{issue_number}/")
|
||||
print(f"📋 Requirements: {workspace.requirements_file}")
|
||||
print(f"🧪 Test plan: {workspace.test_plan_file}")
|
||||
print()
|
||||
print("💡 Next steps:")
|
||||
print(" 1. Review requirements.md and break down the issue")
|
||||
print(" 2. Plan test scenarios in test_plan.md")
|
||||
print(" 3. Use 'make tdd-add-test' to generate tests")
|
||||
print(" 4. Use 'make tdd-finish' when complete")
|
||||
|
||||
except TddaiError as e:
|
||||
print(f"❌ Error: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def finish_issue():
|
||||
"""Finish current issue workspace."""
|
||||
try:
|
||||
manager = WorkspaceManager()
|
||||
|
||||
workspace = manager.get_current_workspace()
|
||||
if not workspace:
|
||||
print("❌ No active issue workspace")
|
||||
print(" Nothing to finish")
|
||||
sys.exit(1)
|
||||
|
||||
print(f"🏁 Finishing work on issue #{workspace.issue_number}")
|
||||
print()
|
||||
|
||||
# Check for tests
|
||||
if workspace.tests_dir.exists():
|
||||
test_files = list(workspace.tests_dir.glob("*.py"))
|
||||
if test_files:
|
||||
print(f"📦 Moving {len(test_files)} test(s) to tests/ directory...")
|
||||
print("✅ Tests moved to main tests/ directory")
|
||||
else:
|
||||
print("⚠️ No tests found in workspace")
|
||||
|
||||
# Finish workspace (moves tests and cleans up)
|
||||
manager.finish_workspace()
|
||||
|
||||
print("🧹 Cleaning up workspace...")
|
||||
print(f"✅ Issue #{workspace.issue_number} workspace cleaned up")
|
||||
print()
|
||||
print("💡 Next steps:")
|
||||
print(" - Run 'make test' to verify tests fail (red state)")
|
||||
print(" - Implement code to make tests pass (green state)")
|
||||
print(" - Start next issue with 'make tdd-start NUM=X'")
|
||||
|
||||
except TddaiError as e:
|
||||
print(f"❌ Error: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def add_test_guidance():
|
||||
"""Show guidance for adding tests."""
|
||||
try:
|
||||
manager = WorkspaceManager()
|
||||
|
||||
workspace = manager.get_current_workspace()
|
||||
if not workspace:
|
||||
print("❌ No active issue workspace")
|
||||
print(" Run 'make tdd-start NUM=X' first")
|
||||
sys.exit(1)
|
||||
|
||||
print(f"🧪 Adding test to issue #{workspace.issue_number} workspace")
|
||||
print()
|
||||
print(f"📋 Issue: {workspace.issue_title}")
|
||||
print(f"📁 Workspace: {workspace.workspace_dir}/issue_{workspace.issue_number}/")
|
||||
print()
|
||||
print("🤖 Please ask Claude Code to generate a test:")
|
||||
print()
|
||||
print(" Command: 'Generate a test for the current workspace issue'")
|
||||
print()
|
||||
print("📝 Test Requirements:")
|
||||
print(f" - Save test in: {workspace.tests_dir}/")
|
||||
print(f" - Name format: test_issue_{workspace.issue_number}_<scenario>.py")
|
||||
print(f" - Include docstring referencing issue #{workspace.issue_number}")
|
||||
print(" - Follow TDD principles (test should fail initially)")
|
||||
print(" - Review requirements.md and test_plan.md for context")
|
||||
print()
|
||||
print("📋 Issue Details:")
|
||||
print(f" Title: {workspace.issue_title}")
|
||||
print(f" Description: {workspace.issue_body}")
|
||||
print()
|
||||
print("💡 After generation: Use 'make tdd-status' to see all tests")
|
||||
|
||||
except TddaiError as e:
|
||||
print(f"❌ Error: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def list_issues():
|
||||
"""List all issues."""
|
||||
try:
|
||||
fetcher = IssueFetcher()
|
||||
print("📋 MarkiTect Issues")
|
||||
print("==================")
|
||||
print()
|
||||
|
||||
issues = fetcher.fetch_issues()
|
||||
if not issues:
|
||||
print("No issues found")
|
||||
return
|
||||
|
||||
for issue in issues:
|
||||
status_icon = "🟢" if issue.state == "open" else "🔴"
|
||||
print(f"{status_icon} #{issue.number}: {issue.title}")
|
||||
print(f" Status: {issue.state.upper()} | Created: {issue.created_at.strftime('%Y-%m-%d')}")
|
||||
|
||||
# Truncate body for list view
|
||||
body_preview = issue.body[:80] + "..." if len(issue.body) > 80 else issue.body
|
||||
if body_preview:
|
||||
print(f" {body_preview}")
|
||||
print()
|
||||
|
||||
print("💡 Tip: Use 'make show-issue NUM=X' for full details")
|
||||
|
||||
except TddaiError as e:
|
||||
print(f"❌ Error: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def list_open_issues():
|
||||
"""List only open issues."""
|
||||
try:
|
||||
fetcher = IssueFetcher()
|
||||
print("📋 Open MarkiTect Issues (Active Backlog)")
|
||||
print("========================================")
|
||||
print()
|
||||
|
||||
issues = fetcher.fetch_open_issues()
|
||||
if not issues:
|
||||
print("No open issues found")
|
||||
return
|
||||
|
||||
for issue in issues:
|
||||
print(f"[OPEN] #{issue.number}: {issue.title}")
|
||||
print(f" Created: {issue.created_at.strftime('%Y-%m-%d')} | Updated: {issue.updated_at.strftime('%Y-%m-%d')}")
|
||||
|
||||
# Truncate body for list view
|
||||
body_preview = issue.body[:80] + "..." if len(issue.body) > 80 else issue.body
|
||||
if body_preview:
|
||||
print(f" {body_preview}")
|
||||
print()
|
||||
|
||||
print("💡 Tip: Use 'make show-issue NUM=X' for full details or 'make list-issues' for all issues")
|
||||
|
||||
except TddaiError as e:
|
||||
print(f"❌ Error: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def show_issue(issue_number: int):
|
||||
"""Show detailed issue information."""
|
||||
try:
|
||||
fetcher = IssueFetcher()
|
||||
print(f"🔍 Issue #{issue_number} Details")
|
||||
print("=======================")
|
||||
print()
|
||||
|
||||
issue = fetcher.fetch_issue(issue_number)
|
||||
|
||||
print(f"**Title:** {issue.title}")
|
||||
print(f"**Status:** {issue.state.upper()}")
|
||||
print(f"**Number:** #{issue.number}")
|
||||
print(f"**Created:** {issue.created_at.strftime('%Y-%m-%d %H:%M')}")
|
||||
print(f"**Updated:** {issue.updated_at.strftime('%Y-%m-%d %H:%M')}")
|
||||
print(f"**URL:** {issue.html_url}")
|
||||
|
||||
if issue.assignee:
|
||||
print(f"**Assignee:** {issue.assignee}")
|
||||
|
||||
if issue.labels:
|
||||
print(f"**Labels:** {', '.join(issue.labels)}")
|
||||
|
||||
print()
|
||||
print("**Description:**")
|
||||
print(issue.body)
|
||||
print()
|
||||
print("💡 Tip: Use 'make list-issues' to see all issues")
|
||||
|
||||
except TddaiError as e:
|
||||
print(f"❌ Error: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def main():
|
||||
"""Main CLI entry point."""
|
||||
parser = argparse.ArgumentParser(description="tddai CLI tool")
|
||||
subparsers = parser.add_subparsers(dest='command', help='Available commands')
|
||||
|
||||
# Workspace commands
|
||||
subparsers.add_parser('workspace-status', help='Show workspace status')
|
||||
|
||||
start_parser = subparsers.add_parser('start-issue', help='Start working on issue')
|
||||
start_parser.add_argument('issue_number', type=int, help='Issue number')
|
||||
|
||||
subparsers.add_parser('finish-issue', help='Finish current issue')
|
||||
subparsers.add_parser('add-test', help='Show guidance for adding tests')
|
||||
|
||||
# Issue commands
|
||||
subparsers.add_parser('list-issues', help='List all issues')
|
||||
subparsers.add_parser('list-open-issues', help='List open issues')
|
||||
|
||||
show_parser = subparsers.add_parser('show-issue', help='Show issue details')
|
||||
show_parser.add_argument('issue_number', type=int, help='Issue number')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if not args.command:
|
||||
parser.print_help()
|
||||
return
|
||||
|
||||
try:
|
||||
if args.command == 'workspace-status':
|
||||
workspace_status()
|
||||
elif args.command == 'start-issue':
|
||||
start_issue(args.issue_number)
|
||||
elif args.command == 'finish-issue':
|
||||
finish_issue()
|
||||
elif args.command == 'add-test':
|
||||
add_test_guidance()
|
||||
elif args.command == 'list-issues':
|
||||
list_issues()
|
||||
elif args.command == 'list-open-issues':
|
||||
list_open_issues()
|
||||
elif args.command == 'show-issue':
|
||||
show_issue(args.issue_number)
|
||||
except KeyboardInterrupt:
|
||||
print("\n⚠️ Operation cancelled")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
1
tests/test_example.py
Normal file
1
tests/test_example.py
Normal file
@@ -0,0 +1 @@
|
||||
# Test content
|
||||
1
tests/test_issue_11_complete.py
Normal file
1
tests/test_issue_11_complete.py
Normal file
@@ -0,0 +1 @@
|
||||
# Complete test content
|
||||
6
tests/test_issue_11_feature.py
Normal file
6
tests/test_issue_11_feature.py
Normal file
@@ -0,0 +1,6 @@
|
||||
"""Test for issue #11."""
|
||||
import pytest
|
||||
|
||||
def test_feature():
|
||||
"""Test the feature implementation."""
|
||||
assert True # Replace with actual test
|
||||
194
tests/test_issue_11_workflow_integration.py
Normal file
194
tests/test_issue_11_workflow_integration.py
Normal file
@@ -0,0 +1,194 @@
|
||||
"""
|
||||
Test TDD workflow integration for workspace infrastructure.
|
||||
|
||||
This test validates issue #11: Setup TDD workspace infrastructure
|
||||
- Tests complete workflow from start to finish
|
||||
- Validates integration between workspace and main codebase
|
||||
- Tests cleanup and finalization processes
|
||||
"""
|
||||
|
||||
import pytest
|
||||
import subprocess
|
||||
import tempfile
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
|
||||
from tddai import WorkspaceManager, IssueFetcher
|
||||
from tddai.config import TddaiConfig
|
||||
|
||||
|
||||
class TestWorkflowIntegration:
|
||||
"""Test suite for complete TDD workflow integration."""
|
||||
|
||||
@pytest.fixture
|
||||
def temp_workspace(self):
|
||||
"""Create a temporary workspace for testing."""
|
||||
temp_dir = Path(tempfile.mkdtemp())
|
||||
config = TddaiConfig(workspace_dir=temp_dir / ".markitect_workspace")
|
||||
yield config
|
||||
shutil.rmtree(temp_dir)
|
||||
|
||||
def test_make_tdd_status_command(self):
|
||||
"""Test that make tdd-status command works correctly."""
|
||||
result = subprocess.run(['make', 'tdd-status'],
|
||||
capture_output=True, text=True)
|
||||
|
||||
assert result.returncode == 0, "tdd-status command should succeed"
|
||||
# Should show clean workspace when no active workspace
|
||||
assert ("No active issue workspace" in result.stdout or
|
||||
"Workspace directory exists but no current issue file" in result.stdout)
|
||||
|
||||
def test_make_tdd_add_test_command_without_workspace(self):
|
||||
"""Test that make tdd-add-test provides proper error when no workspace."""
|
||||
result = subprocess.run(['make', 'tdd-add-test'],
|
||||
capture_output=True, text=True)
|
||||
|
||||
assert result.returncode != 0, "tdd-add-test command should fail when no workspace"
|
||||
assert "No active issue workspace" in result.stdout
|
||||
|
||||
def test_cli_integration_basic(self):
|
||||
"""Test that CLI script can be imported and basic functions exist."""
|
||||
import tddai_cli
|
||||
|
||||
# Test that main functions exist
|
||||
assert hasattr(tddai_cli, 'workspace_status')
|
||||
assert hasattr(tddai_cli, 'start_issue')
|
||||
assert hasattr(tddai_cli, 'finish_issue')
|
||||
assert hasattr(tddai_cli, 'main')
|
||||
|
||||
def test_workspace_to_main_integration(self, temp_workspace):
|
||||
"""Test moving tests from workspace to main tests directory."""
|
||||
manager = WorkspaceManager(temp_workspace)
|
||||
|
||||
mock_issue_data = {
|
||||
'number': 11,
|
||||
'title': 'Test Issue',
|
||||
'body': 'Test Description',
|
||||
'state': 'open',
|
||||
'created_at': '2025-01-01T00:00:00Z',
|
||||
'html_url': 'http://example.com/issues/11',
|
||||
'assignee': None,
|
||||
'labels': []
|
||||
}
|
||||
|
||||
# Create workspace
|
||||
workspace = manager.create_workspace(mock_issue_data)
|
||||
|
||||
# Add a test file to workspace
|
||||
test_file = workspace.tests_dir / "test_issue_11_feature.py"
|
||||
test_content = '''"""Test for issue #11."""
|
||||
import pytest
|
||||
|
||||
def test_feature():
|
||||
"""Test the feature implementation."""
|
||||
assert True # Replace with actual test
|
||||
'''
|
||||
test_file.write_text(test_content)
|
||||
|
||||
# Finish workspace (should move tests)
|
||||
finished_workspace = manager.finish_workspace()
|
||||
|
||||
# Verify test was moved to main tests directory
|
||||
main_test_file = temp_workspace.tests_dir / "test_issue_11_feature.py"
|
||||
assert main_test_file.exists()
|
||||
assert main_test_file.read_text() == test_content
|
||||
|
||||
# Verify workspace is cleaned up
|
||||
assert not temp_workspace.workspace_dir.exists()
|
||||
|
||||
def test_workspace_cleanup_process(self, temp_workspace):
|
||||
"""Test that workspace cleanup removes temporary files."""
|
||||
manager = WorkspaceManager(temp_workspace)
|
||||
|
||||
mock_issue_data = {
|
||||
'number': 11,
|
||||
'title': 'Test Issue',
|
||||
'body': 'Test Description',
|
||||
'state': 'open'
|
||||
}
|
||||
|
||||
# Create workspace
|
||||
workspace = manager.create_workspace(mock_issue_data)
|
||||
|
||||
# Verify workspace exists
|
||||
assert workspace.workspace_dir.exists()
|
||||
assert workspace.issue_dir.exists()
|
||||
|
||||
# Clean up
|
||||
manager.cleanup_workspace()
|
||||
|
||||
# Verify cleanup
|
||||
assert not workspace.workspace_dir.exists()
|
||||
|
||||
def test_gitignore_excludes_workspace(self):
|
||||
"""Test that workspace files are properly excluded from git."""
|
||||
gitignore_path = Path(".gitignore")
|
||||
assert gitignore_path.exists(), "Gitignore file should exist"
|
||||
|
||||
with open(gitignore_path, 'r') as f:
|
||||
gitignore_content = f.read()
|
||||
|
||||
assert ".markitect_workspace/" in gitignore_content, \
|
||||
"Workspace should be excluded from git"
|
||||
|
||||
@patch('tddai.issue_fetcher.subprocess.run')
|
||||
def test_issue_fetcher_integration(self, mock_run, temp_workspace):
|
||||
"""Test that IssueFetcher properly integrates with API."""
|
||||
# Mock successful curl response
|
||||
mock_run.return_value.returncode = 0
|
||||
mock_run.return_value.stdout = """{
|
||||
"number": 11,
|
||||
"title": "Setup TDD workspace infrastructure",
|
||||
"body": "Create workspace management system",
|
||||
"state": "open",
|
||||
"created_at": "2025-01-01T00:00:00Z",
|
||||
"updated_at": "2025-01-01T00:00:00Z",
|
||||
"html_url": "http://example.com/issues/11",
|
||||
"assignee": null,
|
||||
"labels": []
|
||||
}"""
|
||||
|
||||
fetcher = IssueFetcher(temp_workspace)
|
||||
issue = fetcher.fetch_issue(11)
|
||||
|
||||
assert issue.number == 11
|
||||
assert issue.title == "Setup TDD workspace infrastructure"
|
||||
assert issue.state == "open"
|
||||
|
||||
def test_complete_workflow_cycle(self, temp_workspace):
|
||||
"""Test complete workflow from start to finish."""
|
||||
manager = WorkspaceManager(temp_workspace)
|
||||
|
||||
mock_issue_data = {
|
||||
'number': 11,
|
||||
'title': 'Setup TDD workspace infrastructure',
|
||||
'body': 'Create workspace management system for TDD workflow',
|
||||
'state': 'open',
|
||||
'created_at': '2025-01-01T00:00:00Z',
|
||||
'html_url': 'http://example.com/issues/11',
|
||||
'assignee': None,
|
||||
'labels': []
|
||||
}
|
||||
|
||||
# 1. Start with clean workspace
|
||||
assert manager.get_status().name == "CLEAN"
|
||||
|
||||
# 2. Create workspace
|
||||
workspace = manager.create_workspace(mock_issue_data)
|
||||
assert manager.get_status().name == "ACTIVE"
|
||||
assert workspace.issue_number == 11
|
||||
|
||||
# 3. Add test files
|
||||
test_file = workspace.tests_dir / "test_issue_11_complete.py"
|
||||
test_file.write_text("# Complete test content")
|
||||
|
||||
# 4. Finish workspace
|
||||
finished = manager.finish_workspace()
|
||||
assert finished.issue_number == 11
|
||||
assert manager.get_status().name == "CLEAN"
|
||||
|
||||
# 5. Verify test moved to main
|
||||
main_test = temp_workspace.tests_dir / "test_issue_11_complete.py"
|
||||
assert main_test.exists()
|
||||
assert main_test.read_text() == "# Complete test content"
|
||||
156
tests/test_issue_11_workspace_creation.py
Normal file
156
tests/test_issue_11_workspace_creation.py
Normal file
@@ -0,0 +1,156 @@
|
||||
"""
|
||||
Test workspace creation functionality for TDD infrastructure.
|
||||
|
||||
This test validates issue #11: Setup TDD workspace infrastructure
|
||||
- Tests workspace creation from issue numbers
|
||||
- Validates workspace structure and files
|
||||
- Ensures proper error handling
|
||||
"""
|
||||
|
||||
import pytest
|
||||
import tempfile
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
from tddai import WorkspaceManager, IssueFetcher, WorkspaceStatus, WorkspaceError, IssueError
|
||||
from tddai.config import TddaiConfig
|
||||
|
||||
|
||||
class TestWorkspaceCreation:
|
||||
"""Test suite for workspace creation functionality."""
|
||||
|
||||
@pytest.fixture
|
||||
def temp_workspace(self):
|
||||
"""Create a temporary workspace for testing."""
|
||||
temp_dir = Path(tempfile.mkdtemp())
|
||||
config = TddaiConfig(workspace_dir=temp_dir / ".markitect_workspace")
|
||||
yield config
|
||||
shutil.rmtree(temp_dir)
|
||||
|
||||
@pytest.fixture
|
||||
def mock_issue_data(self):
|
||||
"""Mock issue data for testing."""
|
||||
return {
|
||||
'number': 11,
|
||||
'title': 'Setup TDD workspace infrastructure',
|
||||
'body': 'Create workspace management system for TDD workflow',
|
||||
'state': 'open',
|
||||
'created_at': '2025-01-01T00:00:00Z',
|
||||
'html_url': 'http://example.com/issues/11',
|
||||
'assignee': None,
|
||||
'labels': []
|
||||
}
|
||||
|
||||
def test_workspace_manager_initialization(self, temp_workspace):
|
||||
"""Test that WorkspaceManager can be initialized."""
|
||||
manager = WorkspaceManager(temp_workspace)
|
||||
assert manager.config == temp_workspace
|
||||
|
||||
def test_workspace_status_clean_initially(self, temp_workspace):
|
||||
"""Test that workspace status is clean when no workspace exists."""
|
||||
manager = WorkspaceManager(temp_workspace)
|
||||
status = manager.get_status()
|
||||
assert status == WorkspaceStatus.CLEAN
|
||||
|
||||
def test_workspace_creation_from_issue_data(self, temp_workspace, mock_issue_data):
|
||||
"""Test that workspace can be created from issue data."""
|
||||
manager = WorkspaceManager(temp_workspace)
|
||||
|
||||
workspace = manager.create_workspace(mock_issue_data)
|
||||
|
||||
assert workspace.issue_number == 11
|
||||
assert workspace.issue_title == 'Setup TDD workspace infrastructure'
|
||||
assert workspace.workspace_dir == temp_workspace.workspace_dir
|
||||
|
||||
# Verify workspace status changes to active
|
||||
status = manager.get_status()
|
||||
assert status == WorkspaceStatus.ACTIVE
|
||||
|
||||
def test_workspace_directory_structure_created(self, temp_workspace, mock_issue_data):
|
||||
"""Test that workspace creates proper directory structure."""
|
||||
manager = WorkspaceManager(temp_workspace)
|
||||
workspace = manager.create_workspace(mock_issue_data)
|
||||
|
||||
assert workspace.workspace_dir.exists()
|
||||
assert workspace.issue_dir.exists()
|
||||
assert workspace.tests_dir.exists()
|
||||
|
||||
def test_workspace_metadata_files_created(self, temp_workspace, mock_issue_data):
|
||||
"""Test that workspace creates required metadata files."""
|
||||
manager = WorkspaceManager(temp_workspace)
|
||||
workspace = manager.create_workspace(mock_issue_data)
|
||||
|
||||
assert workspace.requirements_file.exists()
|
||||
assert workspace.test_plan_file.exists()
|
||||
assert temp_workspace.current_issue_path.exists()
|
||||
|
||||
def test_current_issue_metadata_content(self, temp_workspace, mock_issue_data):
|
||||
"""Test that current issue metadata is properly stored."""
|
||||
manager = WorkspaceManager(temp_workspace)
|
||||
manager.create_workspace(mock_issue_data)
|
||||
|
||||
current_workspace = manager.get_current_workspace()
|
||||
assert current_workspace.issue_number == 11
|
||||
assert current_workspace.issue_title == 'Setup TDD workspace infrastructure'
|
||||
assert current_workspace.issue_state == 'open'
|
||||
|
||||
def test_workspace_prevents_multiple_active_issues(self, temp_workspace, mock_issue_data):
|
||||
"""Test that only one workspace can be active at a time."""
|
||||
manager = WorkspaceManager(temp_workspace)
|
||||
manager.create_workspace(mock_issue_data)
|
||||
|
||||
# Try to create another workspace
|
||||
second_issue_data = mock_issue_data.copy()
|
||||
second_issue_data['number'] = 12
|
||||
second_issue_data['title'] = 'Different issue'
|
||||
|
||||
with pytest.raises(WorkspaceError, match="Workspace already active"):
|
||||
manager.create_workspace(second_issue_data)
|
||||
|
||||
@patch('tddai.issue_fetcher.subprocess.run')
|
||||
def test_issue_fetcher_handles_invalid_issue(self, mock_run, temp_workspace):
|
||||
"""Test error handling for invalid issue numbers."""
|
||||
# Mock curl response for non-existent issue
|
||||
mock_run.return_value.returncode = 0
|
||||
mock_run.return_value.stdout = '{"message": "404 Not Found"}'
|
||||
|
||||
fetcher = IssueFetcher(temp_workspace)
|
||||
|
||||
with pytest.raises(IssueError, match="not found"):
|
||||
fetcher.fetch_issue(999)
|
||||
|
||||
def test_workspace_cleanup(self, temp_workspace, mock_issue_data):
|
||||
"""Test that workspace can be cleaned up properly."""
|
||||
manager = WorkspaceManager(temp_workspace)
|
||||
manager.create_workspace(mock_issue_data)
|
||||
|
||||
# Verify workspace exists
|
||||
assert manager.get_status() == WorkspaceStatus.ACTIVE
|
||||
|
||||
# Clean up
|
||||
manager.cleanup_workspace()
|
||||
|
||||
# Verify workspace is clean
|
||||
assert manager.get_status() == WorkspaceStatus.CLEAN
|
||||
assert not temp_workspace.workspace_dir.exists()
|
||||
|
||||
def test_workspace_finish_moves_tests(self, temp_workspace, mock_issue_data):
|
||||
"""Test that finishing workspace moves tests to main directory."""
|
||||
manager = WorkspaceManager(temp_workspace)
|
||||
workspace = manager.create_workspace(mock_issue_data)
|
||||
|
||||
# Create a test file in workspace
|
||||
test_file = workspace.tests_dir / "test_example.py"
|
||||
test_file.write_text("# Test content")
|
||||
|
||||
# Finish workspace
|
||||
finished_workspace = manager.finish_workspace()
|
||||
|
||||
assert finished_workspace.issue_number == 11
|
||||
assert manager.get_status() == WorkspaceStatus.CLEAN
|
||||
|
||||
# Verify test was moved
|
||||
main_test_file = temp_workspace.tests_dir / "test_example.py"
|
||||
assert main_test_file.exists()
|
||||
assert main_test_file.read_text() == "# Test content"
|
||||
Reference in New Issue
Block a user