feat: implement markitect installer with version/release commands (issue #80)

- Add comprehensive version information system with git integration
- Add `markitect version` and `markitect release` commands with multiple output formats
- Add global `--version` flag for quick version checking
- Create Python installer script with advanced options (install.py)
- Create shell installer wrapper for easy installation (install.sh)
- Add comprehensive installation documentation (INSTALL.md)
- Support user and system-wide installations with virtual environments
- Include development mode installation with test dependencies
- Add installation status checking and uninstall functionality

Commands added:
- `markitect --version` - Quick version display
- `markitect version [--short]` - Detailed version information
- `markitect release [--format text|json|yaml]` - Release information

Installer features:
- Automatic virtual environment creation
- Symbolic link management for global access
- Custom installation paths and prefixes
- Development mode with test dependencies
- Installation validation and troubleshooting

Resolves #80

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-10-03 05:47:02 +02:00
parent 3231bd291a
commit 8e6ba272ca
6 changed files with 1237 additions and 0 deletions

279
.clinerules Normal file
View File

@@ -0,0 +1,279 @@
# MarkiTect Project - Claude Code Rules
# =====================================
# Guidelines for Claude Code when working with the MarkiTect project
# This project follows TDD8 methodology with Clean Architecture
## Project Overview
This is a high-performance markdown processing engine with database integration,
AST-based parsing, and sophisticated caching. The project follows Clean Architecture
principles with strict separation of concerns.
## Directory Structure & Clean Architecture
```
markitect_project/
├── domain/ # Business logic (innermost layer)
├── application/ # Use cases and workflows
├── infrastructure/ # External interfaces (database, file system)
├── cli/ # Presentation layer (CLI interface)
├── markitect/ # Core markdown processing engine
├── tests/ # Comprehensive test suite (TDD8 methodology)
├── docs/ # Architecture and user documentation
└── tddai/ # TDD workflow tools and utilities
```
## Core Principles
### 1. TDD8 Methodology - ALWAYS FOLLOW
1. **ISSUE**: Analyze GitHub issue and extract requirements
2. **TEST**: Write comprehensive tests BEFORE implementation
3. **RED**: Ensure tests fail initially (validate test correctness)
4. **GREEN**: Implement minimum viable solution to pass tests
5. **REFACTOR**: Improve code quality and design
6. **DOCUMENT**: Update documentation and examples
7. **REFINE**: Performance optimization and edge cases
8. **PUBLISH**: Integration validation and delivery
### 2. Clean Architecture Dependency Rules
- **NEVER violate dependency inversion**: Outer layers depend on inner layers, never reverse
- **Domain layer**: Pure business logic, no external dependencies
- **Application layer**: Use cases, may depend only on domain
- **Infrastructure layer**: External concerns (database, CLI, API)
- **Presentation layer**: User interfaces (CLI commands)
### 3. Testing Requirements
- **Minimum 80% test coverage** - Use `pytest --cov=markitect --cov-report=html`
- **Test naming**: `test_issue_{issue_num}_{scenario}.py` pattern
- **Architectural testing**: Run tests by layer (`make test-domain`, `make test-infrastructure`)
- **Performance validation**: All cache operations must be <50% of parsing time
- **TDD workspace**: Use `.tddai_workspace/` for issue-specific development
## Development Workflow
### Starting Work on an Issue
```bash
# Always start with TDD workspace
make tdd-start NUM=<issue_number>
# Analyze requirements first
make validate-requirements
# Create tests before implementation
make tdd-add-test
```
### Code Quality Gates
```bash
# Run before any commit
make test # All tests must pass
make lint # Code style compliance
make test-coverage NUM=X # Verify coverage targets
make validate-mocks # Mock compatibility
```
### Performance Requirements
- **Cache operations**: <50% of initial parsing time (enforced by tests)
- **Memory usage**: <50MB baseline for normal operations
- **Database queries**: Sub-millisecond metadata retrieval
- **Bulk operations**: Linear scaling with document count
## Technology Stack & Dependencies
### Core Technologies
- **Python 3.8+** with type hints (gradual mypy adoption)
- **SQLite** for database operations (ACID compliance required)
- **markdown-it-py** for AST processing
- **pytest** for testing with comprehensive fixtures
- **Click** for CLI framework
### Key Libraries
- `PyYAML` - Front matter processing
- `jsonpath-ng` - AST querying
- `tabulate` - Output formatting
- `aiohttp` - Async HTTP operations
## Coding Standards
### Python Code Style
- **Type hints**: Use where possible (gradual mypy adoption)
- **Docstrings**: Required for all public methods
- **Error handling**: Comprehensive exception handling and validation
- **Security**: Never log secrets, validate all inputs, prevent SQL injection
### File Organization
- **One concept per file**: Clear separation of responsibilities
- **Interface segregation**: Clean interfaces between layers
- **Plugin architecture**: Support modular extensions
### Database Operations
- **Read-only queries**: Default to safe operations
- **Transaction safety**: Use ACID compliance for batch operations
- **Performance optimization**: Leverage SQLite capabilities
- **Migration support**: Schema versioning and updates
## Common Patterns
### CLI Command Structure
```python
@click.command()
@click.option('--format', type=click.Choice(['table', 'json', 'yaml']))
def command_name(format):
"""Command description with clear purpose."""
try:
# Implementation with proper error handling
pass
except SpecificException as e:
# Provide helpful error messages
pass
```
### Test Structure (TDD8 Pattern)
```python
class TestIssue{N}_{Description}:
"""Test suite for issue #{N}: {description}"""
def test_{scenario}_success(self):
"""Test successful operation scenario."""
# Arrange
# Act
# Assert
def test_{scenario}_error_handling(self):
"""Test error handling scenario."""
# Test edge cases and error conditions
```
### Domain Model Pattern
```python
from dataclasses import dataclass
from typing import Optional
@dataclass
class DomainEntity:
"""Domain entity with business logic."""
id: str
name: str
def business_method(self) -> bool:
"""Business logic belongs in domain layer."""
return True
```
## Performance Guidelines
### AST Caching System
- **Cache validation**: Automatic timestamp-based invalidation
- **Serialization**: Optimized JSON format for AST storage
- **Memory management**: Careful resource cleanup
- **Performance contracts**: <50% of parsing time (tested)
### Database Optimization
- **Query optimization**: Use appropriate indexes
- **Batch operations**: Minimize database round trips
- **Connection management**: Proper connection lifecycle
- **Read-only defaults**: Safety-first approach
## Security Requirements
### Input Validation
- **SQL injection prevention**: Use parameterized queries
- **Path traversal protection**: Validate file paths
- **Command injection**: Sanitize shell command inputs
- **YAML safety**: Safe loading of front matter
### Secrets Management
- **Never log secrets**: Authentication tokens, passwords
- **Environment variables**: Use for sensitive configuration
- **Git repository**: Never commit credentials
- **Error messages**: Don't expose sensitive information
## Documentation Standards
### Code Documentation
- **API documentation**: Clear method signatures and purposes
- **Architecture decisions**: Document in docs/architecture/
- **Usage examples**: Include practical examples
- **Performance notes**: Document performance characteristics
### User Documentation
- **CLI help**: Comprehensive command documentation
- **Configuration**: Clear setup instructions
- **Troubleshooting**: Common issues and solutions
- **Performance**: Usage optimization guidelines
## Integration Points
### Git Platform Integration
- **Gitea API**: Primary integration for issue management
- **GitHub compatibility**: Support multiple platforms
- **Authentication**: Token-based with multiple sources
- **Error handling**: Robust network failure handling
### Development Tools
- **Makefile integration**: Standard development commands
- **pytest integration**: Comprehensive test framework
- **mypy integration**: Gradual type checking adoption
- **CLI tools**: Complete command-line interface
## Common Mistakes to Avoid
### Architecture Violations
- ❌ **Domain depending on infrastructure**: Never import database in domain
- ❌ **Skipping tests**: Never implement without tests first (TDD8)
- ❌ **Performance assumptions**: Always validate cache performance
- ❌ **Direct database access**: Use repository pattern
### Security Issues
- ❌ **SQL injection**: Always use parameterized queries
- ❌ **Logging secrets**: Never log authentication tokens
- ❌ **Unsafe YAML**: Use yaml.safe_load() not yaml.load()
- ❌ **Path injection**: Validate and sanitize file paths
### Testing Issues
- ❌ **Insufficient coverage**: Maintain >80% test coverage
- ❌ **Missing edge cases**: Test error conditions thoroughly
- ❌ **Test dependencies**: Tests must be independent
- ❌ **Performance tests**: Validate cache performance contracts
## When Making Changes
### Before Implementation
1. **Read the issue**: Understand requirements completely
2. **TDD workspace**: Use `make tdd-start NUM=X`
3. **Write tests first**: Follow TDD8 methodology strictly
4. **Validate architecture**: Ensure clean dependency flow
### During Implementation
1. **Red-Green-Refactor**: Follow TDD cycle religiously
2. **Performance validation**: Test cache performance contracts
3. **Security review**: Validate input handling and safety
4. **Documentation updates**: Keep docs current with changes
### Before Completion
1. **Full test suite**: `make test` must pass completely
2. **Performance benchmarks**: Validate performance requirements
3. **Code quality**: `make lint` and type checking
4. **Integration tests**: Verify end-to-end functionality
## Emergency Procedures
### If Tests Fail
1. **Don't ignore**: Never commit with failing tests
2. **Isolate issue**: Use `make test-module MODULE=name`
3. **Check dependencies**: Verify layer boundary violations
4. **Performance regression**: Check cache performance contracts
### If Performance Degrades
1. **Run benchmarks**: Use performance test suite
2. **Cache validation**: Verify cache hit rates and timing
3. **Memory profiling**: Check for memory leaks
4. **Database optimization**: Review query performance
### If Security Issues Found
1. **Immediate assessment**: Evaluate impact and scope
2. **Input validation**: Review all user input handling
3. **Secrets audit**: Check for credential exposure
4. **Dependency updates**: Update vulnerable dependencies
Remember: This project's success depends on maintaining architectural discipline,
comprehensive testing, and performance contracts. When in doubt, ask for clarification
and always prioritize correctness over speed of implementation.

219
INSTALL.md Normal file
View File

@@ -0,0 +1,219 @@
# MarkiTect Installation Guide
This document describes how to install MarkiTect and make it available system-wide.
## Quick Installation
For most users, the quick installer is the easiest option:
```bash
# Install for current user
./install.sh
# Install system-wide (requires sudo)
./install.sh --system
# Install in development mode with test dependencies
./install.sh --dev
```
## Advanced Installation
For more control over the installation process, use the Python installer:
```bash
# Install with custom prefix
python install.py --prefix /opt/markitect
# Install with custom virtual environment location
python install.py --venv-dir /path/to/custom/venv
# Install without creating symbolic links (manual PATH setup)
python install.py --no-symlinks
# Force reinstallation over existing installation
python install.py --force
```
## Installation Options
### Installation Types
- **User Installation** (default): Installs to `~/.local/`
- **System Installation** (`--system`): Installs to `/usr/local/` (requires sudo)
- **Development Installation** (`--dev`): Installs in editable mode with test dependencies
### Installation Paths
By default, MarkiTect is installed to:
- **User installation**: `~/.local/lib/markitect/` (virtual environment)
- **System installation**: `/usr/local/lib/markitect/` (virtual environment)
- **Binaries**: `~/.local/bin/` or `/usr/local/bin/`
### Available Commands
After installation, these commands will be available:
- `markitect` - Main MarkiTect CLI
- `tddai` - TDD workflow management
- `issue` - Issue management
## Checking Installation
Check if MarkiTect is already installed:
```bash
./install.sh --check
# or
python install.py --check
```
Check version after installation:
```bash
markitect version
markitect version --short
markitect release
```
## Uninstallation
To remove MarkiTect:
```bash
./install.sh --uninstall
# or
python install.py --uninstall
```
## Manual Installation
If you prefer to install manually:
1. **Create virtual environment:**
```bash
python -m venv ~/.local/lib/markitect
```
2. **Activate virtual environment:**
```bash
source ~/.local/lib/markitect/bin/activate
```
3. **Install MarkiTect:**
```bash
pip install -e .
```
4. **Create symbolic links:**
```bash
mkdir -p ~/.local/bin
ln -sf ~/.local/lib/markitect/bin/markitect ~/.local/bin/markitect
ln -sf ~/.local/lib/markitect/bin/tddai ~/.local/bin/tddai
ln -sf ~/.local/lib/markitect/bin/issue ~/.local/bin/issue
```
5. **Add to PATH** (add to `~/.bashrc` or `~/.zshrc`):
```bash
export PATH="$HOME/.local/bin:$PATH"
```
## Development Installation
For development work:
```bash
# Install in development mode
./install.sh --dev
# This includes:
# - Editable installation (changes reflect immediately)
# - Test dependencies (pytest, black, flake8, mypy)
# - All development tools
```
## Troubleshooting
### Common Issues
1. **Command not found after installation:**
- Make sure `~/.local/bin` is in your PATH
- Run: `export PATH="$HOME/.local/bin:$PATH"`
- Add the export to your shell profile
2. **Permission denied on system installation:**
- Use `sudo ./install.sh --system`
- Or install to user directory instead
3. **Python version error:**
- MarkiTect requires Python 3.8 or higher
- Check version: `python3 --version`
4. **Installation already exists:**
- Use `--force` to overwrite: `./install.sh --force`
- Or uninstall first: `./install.sh --uninstall`
### Manual PATH Setup
If symbolic links don't work, add the virtual environment bin directory to your PATH:
```bash
# For bash/zsh (add to ~/.bashrc or ~/.zshrc)
export PATH="$HOME/.local/lib/markitect/bin:$PATH"
# For fish (add to ~/.config/fish/config.fish)
set -gx PATH $HOME/.local/lib/markitect/bin $PATH
```
### Testing Installation
After installation, verify everything works:
```bash
# Test basic functionality
markitect --help
markitect version
# Test TDD tools
tddai --help
# Test issue management
issue --help
```
## Dependencies
MarkiTect automatically installs these dependencies:
### Production Dependencies
- markdown-it-py - Markdown parsing
- PyYAML - YAML processing
- click>=8.0.0 - CLI framework
- tabulate>=0.9.0 - Table formatting
- jsonpath-ng>=1.5.0 - JSONPath queries
- aiohttp>=3.8.0 - Async HTTP client
- toml - TOML file parsing
### Development Dependencies (with --dev)
- pytest - Testing framework
- pytest-cov - Test coverage
- black - Code formatting
- flake8 - Code linting
- mypy - Type checking
## System Requirements
- Python 3.8 or higher
- pip (Python package installer)
- git (optional, for version info)
- Unix-like system (Linux, macOS) or Windows with Python support
## Support
For installation issues:
1. Check this guide first
2. Run `./install.sh --check` to diagnose problems
3. See the main project documentation
4. Report issues on the project issue tracker

388
install.py Normal file
View File

@@ -0,0 +1,388 @@
#!/usr/bin/env python3
"""
MarkiTect Installer
This script provides an easy way to install MarkiTect and make it available
system-wide. It handles virtual environment creation, dependency installation,
and creates symbolic links to make the commands available from anywhere.
Usage:
python install.py [options]
Options:
--prefix PATH Installation prefix (default: ~/.local)
--system Install system-wide (requires sudo, uses /usr/local)
--venv-dir PATH Custom virtual environment directory
--no-symlinks Don't create symbolic links (manual PATH setup required)
--force Force reinstallation over existing installation
--dev Install in development mode with test dependencies
--check Check if MarkiTect is already installed
--uninstall Uninstall MarkiTect
--help Show this help message
"""
import os
import sys
import subprocess
import shutil
import argparse
from pathlib import Path
import tempfile
class MarkiTectInstaller:
"""MarkiTect installation manager."""
def __init__(self, prefix=None, system=False, venv_dir=None, force=False, dev=False):
self.system = system
self.force = force
self.dev = dev
# Determine installation paths
if system:
self.prefix = Path("/usr/local")
self.bin_dir = self.prefix / "bin"
self.venv_dir = Path(venv_dir) if venv_dir else self.prefix / "lib" / "markitect"
else:
self.prefix = Path(prefix) if prefix else Path.home() / ".local"
self.bin_dir = self.prefix / "bin"
self.venv_dir = Path(venv_dir) if venv_dir else self.prefix / "lib" / "markitect"
self.project_dir = Path(__file__).parent.absolute()
def check_requirements(self):
"""Check system requirements."""
print("🔍 Checking system requirements...")
# Check Python version
if sys.version_info < (3, 8):
print("❌ Python 3.8 or higher is required")
sys.exit(1)
print(f"✅ Python {sys.version.split()[0]} found")
# Check if pip is available
try:
subprocess.run([sys.executable, "-m", "pip", "--version"],
check=True, capture_output=True)
print("✅ pip is available")
except subprocess.CalledProcessError:
print("❌ pip is not available. Please install pip first.")
sys.exit(1)
# Check if git is available (optional)
try:
subprocess.run(["git", "--version"], check=True, capture_output=True)
print("✅ git is available")
except (subprocess.CalledProcessError, FileNotFoundError):
print("⚠️ git is not available (optional for version info)")
def check_existing_installation(self):
"""Check if MarkiTect is already installed."""
# Check for existing venv
if self.venv_dir.exists():
print(f"📁 Existing installation found at {self.venv_dir}")
return True
# Check for existing binaries
markitect_bin = self.bin_dir / "markitect"
if markitect_bin.exists():
print(f"📁 Existing binary found at {markitect_bin}")
return True
return False
def create_directories(self):
"""Create necessary directories."""
print(f"📁 Creating directories...")
if self.system and not os.access(self.prefix, os.W_OK):
print("❌ System installation requires sudo privileges")
print(" Please run with sudo or choose a different installation prefix")
sys.exit(1)
self.prefix.mkdir(parents=True, exist_ok=True)
self.bin_dir.mkdir(parents=True, exist_ok=True)
print(f"✅ Created directories in {self.prefix}")
def create_virtual_environment(self):
"""Create and set up virtual environment."""
print(f"🐍 Creating virtual environment at {self.venv_dir}")
if self.venv_dir.exists():
if self.force:
print(f"🗑️ Removing existing installation...")
shutil.rmtree(self.venv_dir)
else:
print("❌ Virtual environment already exists. Use --force to overwrite.")
sys.exit(1)
# Create virtual environment
subprocess.run([
sys.executable, "-m", "venv", str(self.venv_dir)
], check=True)
# Get paths to venv executables
if sys.platform == "win32":
venv_python = self.venv_dir / "Scripts" / "python.exe"
venv_pip = self.venv_dir / "Scripts" / "pip.exe"
else:
venv_python = self.venv_dir / "bin" / "python"
venv_pip = self.venv_dir / "bin" / "pip"
# Upgrade pip
print("📦 Upgrading pip...")
subprocess.run([
str(venv_pip), "install", "--upgrade", "pip", "setuptools", "wheel"
], check=True)
return venv_python, venv_pip
def install_markitect(self, venv_python, venv_pip):
"""Install MarkiTect in the virtual environment."""
print("📦 Installing MarkiTect...")
install_cmd = [str(venv_pip), "install"]
if self.dev:
print("🛠️ Installing in development mode with test dependencies...")
# Install in editable mode from current directory
install_cmd.extend(["-e", str(self.project_dir)])
# Install test dependencies
subprocess.run(install_cmd, check=True)
subprocess.run([
str(venv_pip), "install", "pytest", "pytest-cov", "black", "flake8", "mypy"
], check=True)
else:
# Install from current directory
install_cmd.append(str(self.project_dir))
subprocess.run(install_cmd, check=True)
print("✅ MarkiTect installed successfully")
def create_symlinks(self, no_symlinks=False):
"""Create symbolic links for global access."""
if no_symlinks:
print("⚠️ Skipping symbolic link creation")
self.show_manual_setup()
return
print("🔗 Creating symbolic links...")
# Get venv bin directory
if sys.platform == "win32":
venv_bin = self.venv_dir / "Scripts"
exe_suffix = ".exe"
else:
venv_bin = self.venv_dir / "bin"
exe_suffix = ""
# Commands to link
commands = ["markitect", "tddai", "issue"]
for cmd in commands:
src = venv_bin / f"{cmd}{exe_suffix}"
dst = self.bin_dir / cmd
if src.exists():
# Remove existing symlink/file
if dst.exists() or dst.is_symlink():
dst.unlink()
# Create symlink
try:
dst.symlink_to(src)
print(f"✅ Created symlink: {dst} -> {src}")
except OSError:
# Fallback: create wrapper script
self.create_wrapper_script(dst, src)
else:
print(f"⚠️ Command {cmd} not found in virtual environment")
def create_wrapper_script(self, dst, src):
"""Create a wrapper script when symlinks aren't available."""
print(f"🔧 Creating wrapper script: {dst}")
if sys.platform == "win32":
# Windows batch file
dst = dst.with_suffix(".bat")
content = f'@echo off\n"{src}" %*\n'
else:
# Unix shell script
content = f'#!/bin/bash\nexec "{src}" "$@"\n'
dst.write_text(content)
if sys.platform != "win32":
os.chmod(dst, 0o755)
def show_manual_setup(self):
"""Show manual PATH setup instructions."""
print("\n📋 Manual Setup Instructions:")
print("=" * 50)
print(f"Add the following to your PATH environment variable:")
print(f" {self.venv_dir / 'bin'}")
print()
print("For bash/zsh, add this line to ~/.bashrc or ~/.zshrc:")
print(f' export PATH="{self.venv_dir / "bin"}:$PATH"')
print()
def test_installation(self):
"""Test the installation."""
print("🧪 Testing installation...")
# Test markitect command
try:
markitect_bin = self.bin_dir / "markitect"
if not markitect_bin.exists():
# Try direct venv path
if sys.platform == "win32":
markitect_bin = self.venv_dir / "Scripts" / "markitect.exe"
else:
markitect_bin = self.venv_dir / "bin" / "markitect"
result = subprocess.run([
str(markitect_bin), "version", "--short"
], capture_output=True, text=True, check=True)
version = result.stdout.strip()
print(f"✅ MarkiTect installed successfully - version {version}")
return True
except (subprocess.CalledProcessError, FileNotFoundError) as e:
print(f"❌ Installation test failed: {e}")
return False
def uninstall(self):
"""Uninstall MarkiTect."""
print("🗑️ Uninstalling MarkiTect...")
removed_something = False
# Remove virtual environment
if self.venv_dir.exists():
print(f"🗑️ Removing virtual environment: {self.venv_dir}")
shutil.rmtree(self.venv_dir)
removed_something = True
# Remove symlinks
commands = ["markitect", "tddai", "issue"]
for cmd in commands:
for bin_path in [self.bin_dir / cmd, self.bin_dir / f"{cmd}.bat"]:
if bin_path.exists() or bin_path.is_symlink():
print(f"🗑️ Removing: {bin_path}")
bin_path.unlink()
removed_something = True
if removed_something:
print("✅ MarkiTect uninstalled successfully")
else:
print("⚠️ No MarkiTect installation found")
def install(self, no_symlinks=False):
"""Perform the complete installation."""
print("🚀 Installing MarkiTect")
print("=" * 50)
self.check_requirements()
if not self.force and self.check_existing_installation():
print("❌ MarkiTect is already installed. Use --force to reinstall.")
sys.exit(1)
self.create_directories()
venv_python, venv_pip = self.create_virtual_environment()
self.install_markitect(venv_python, venv_pip)
self.create_symlinks(no_symlinks)
print()
if self.test_installation():
print("🎉 Installation completed successfully!")
print()
print("You can now use MarkiTect from anywhere:")
print(" markitect --help")
print(" markitect version")
print(" tddai --help")
print(" issue --help")
else:
print("⚠️ Installation completed but tests failed")
self.show_manual_setup()
def check_installation_status(self):
"""Check current installation status."""
print("🔍 MarkiTect Installation Status")
print("=" * 50)
# Check virtual environment
if self.venv_dir.exists():
print(f"✅ Virtual environment: {self.venv_dir}")
else:
print(f"❌ Virtual environment: Not found at {self.venv_dir}")
# Check binaries
commands = ["markitect", "tddai", "issue"]
for cmd in commands:
bin_path = self.bin_dir / cmd
if bin_path.exists():
print(f"{cmd}: {bin_path}")
else:
print(f"{cmd}: Not found at {bin_path}")
# Try to get version
try:
result = subprocess.run([
"markitect", "version", "--short"
], capture_output=True, text=True)
if result.returncode == 0:
version = result.stdout.strip()
print(f"✅ Working installation: version {version}")
else:
print("❌ Installation found but not working")
except FileNotFoundError:
print("❌ markitect command not available in PATH")
def main():
parser = argparse.ArgumentParser(
description="MarkiTect Installer",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog=__doc__.split('\n\n')[1] # Show usage from docstring
)
parser.add_argument("--prefix", type=Path,
help="Installation prefix (default: ~/.local)")
parser.add_argument("--system", action="store_true",
help="Install system-wide (requires sudo)")
parser.add_argument("--venv-dir", type=Path,
help="Custom virtual environment directory")
parser.add_argument("--no-symlinks", action="store_true",
help="Don't create symbolic links")
parser.add_argument("--force", action="store_true",
help="Force reinstallation")
parser.add_argument("--dev", action="store_true",
help="Install in development mode")
parser.add_argument("--check", action="store_true",
help="Check installation status")
parser.add_argument("--uninstall", action="store_true",
help="Uninstall MarkiTect")
args = parser.parse_args()
# Create installer instance
installer = MarkiTectInstaller(
prefix=args.prefix,
system=args.system,
venv_dir=args.venv_dir,
force=args.force,
dev=args.dev
)
# Handle different actions
if args.check:
installer.check_installation_status()
elif args.uninstall:
installer.uninstall()
else:
installer.install(no_symlinks=args.no_symlinks)
if __name__ == "__main__":
main()

160
install.sh Executable file
View File

@@ -0,0 +1,160 @@
#!/bin/bash
#
# MarkiTect Quick Installer
#
# This script provides a simple way to install MarkiTect.
# It's a wrapper around the Python installer script.
#
# Usage:
# ./install.sh [options]
# curl -sSL https://raw.githubusercontent.com/example/markitect/main/install.sh | bash
#
# Options:
# --system Install system-wide (requires sudo)
# --dev Install in development mode
# --check Check installation status
# --uninstall Uninstall MarkiTect
# --help Show help
set -e
# Default options
SYSTEM=""
DEV=""
CHECK=""
UNINSTALL=""
HELP=""
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Function to print colored output
print_info() {
echo -e "${BLUE} $1${NC}"
}
print_success() {
echo -e "${GREEN}$1${NC}"
}
print_warning() {
echo -e "${YELLOW}⚠️ $1${NC}"
}
print_error() {
echo -e "${RED}$1${NC}"
}
# Parse command line arguments
while [[ $# -gt 0 ]]; do
case $1 in
--system)
SYSTEM="--system"
shift
;;
--dev)
DEV="--dev"
shift
;;
--check)
CHECK="--check"
shift
;;
--uninstall)
UNINSTALL="--uninstall"
shift
;;
--help|-h)
HELP="--help"
shift
;;
*)
print_error "Unknown option: $1"
exit 1
;;
esac
done
# Show help if requested
if [[ -n "$HELP" ]]; then
cat << EOF
MarkiTect Quick Installer
Usage: $0 [options]
Options:
--system Install system-wide (requires sudo)
--dev Install in development mode with test dependencies
--check Check current installation status
--uninstall Uninstall MarkiTect
--help Show this help message
Examples:
$0 # Install for current user
$0 --system # Install system-wide
$0 --dev # Install in development mode
$0 --check # Check installation status
$0 --uninstall # Uninstall MarkiTect
For more advanced options, use the Python installer directly:
python install.py --help
EOF
exit 0
fi
# Check if Python is available
if ! command -v python3 &> /dev/null; then
print_error "Python 3 is required but not found"
print_info "Please install Python 3.8 or higher and try again"
exit 1
fi
# Check Python version
python_version=$(python3 -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')")
required_version="3.8"
if ! python3 -c "import sys; sys.exit(0 if sys.version_info >= (3, 8) else 1)"; then
print_error "Python $required_version or higher is required (found: $python_version)"
exit 1
fi
print_success "Python $python_version found"
# Determine script directory
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
INSTALLER_SCRIPT="$SCRIPT_DIR/install.py"
# Check if installer script exists
if [[ ! -f "$INSTALLER_SCRIPT" ]]; then
print_error "Installer script not found: $INSTALLER_SCRIPT"
print_info "Make sure you're running this from the MarkiTect project directory"
exit 1
fi
# Build command
cmd="python3 $INSTALLER_SCRIPT"
if [[ -n "$SYSTEM" ]]; then
cmd="$cmd $SYSTEM"
print_warning "System installation requires sudo privileges"
fi
if [[ -n "$DEV" ]]; then
cmd="$cmd $DEV"
fi
if [[ -n "$CHECK" ]]; then
cmd="$cmd $CHECK"
fi
if [[ -n "$UNINSTALL" ]]; then
cmd="$cmd $UNINSTALL"
fi
# Run the installer
print_info "Running: $cmd"
exec $cmd

123
markitect/__version__.py Normal file
View File

@@ -0,0 +1,123 @@
"""
Version information for MarkiTect.
This module provides version and release information for the MarkiTect package.
Version information is sourced from pyproject.toml and git metadata when available.
"""
import os
import subprocess
from pathlib import Path
from typing import Optional
# Base version from pyproject.toml
__version__ = "0.1.0"
def get_git_commit_hash() -> Optional[str]:
"""Get the current git commit hash if available."""
try:
result = subprocess.run(
['git', 'rev-parse', '--short', 'HEAD'],
capture_output=True,
text=True,
check=True,
cwd=Path(__file__).parent.parent
)
return result.stdout.strip()
except (subprocess.CalledProcessError, FileNotFoundError):
return None
def get_git_branch() -> Optional[str]:
"""Get the current git branch if available."""
try:
result = subprocess.run(
['git', 'branch', '--show-current'],
capture_output=True,
text=True,
check=True,
cwd=Path(__file__).parent.parent
)
return result.stdout.strip()
except (subprocess.CalledProcessError, FileNotFoundError):
return None
def get_git_tag() -> Optional[str]:
"""Get the current git tag if available."""
try:
result = subprocess.run(
['git', 'describe', '--tags', '--exact-match'],
capture_output=True,
text=True,
check=True,
cwd=Path(__file__).parent.parent
)
return result.stdout.strip()
except (subprocess.CalledProcessError, FileNotFoundError):
return None
def is_development_version() -> bool:
"""Check if this is a development version (has uncommitted changes)."""
try:
result = subprocess.run(
['git', 'status', '--porcelain'],
capture_output=True,
text=True,
check=True,
cwd=Path(__file__).parent.parent
)
return bool(result.stdout.strip())
except (subprocess.CalledProcessError, FileNotFoundError):
return False
def get_version_info() -> dict:
"""Get comprehensive version information."""
git_commit = get_git_commit_hash()
git_branch = get_git_branch()
git_tag = get_git_tag()
is_dev = is_development_version()
# Build version string
version_parts = [__version__]
if git_tag and git_tag != f"v{__version__}":
# If we have a different tag, use it
version_parts = [git_tag.lstrip('v')]
if git_commit:
if is_dev:
version_parts.append(f"dev+{git_commit}")
elif not git_tag:
version_parts.append(f"+{git_commit}")
if is_dev and not git_commit:
version_parts.append("dev")
full_version = ".".join(version_parts)
return {
"version": __version__,
"full_version": full_version,
"git_commit": git_commit,
"git_branch": git_branch,
"git_tag": git_tag,
"is_development": is_dev,
"is_git_repo": git_commit is not None
}
def get_release_info() -> dict:
"""Get release information."""
version_info = get_version_info()
release_type = "development" if version_info["is_development"] else "release"
if version_info["git_tag"]:
release_type = "tagged-release"
elif version_info["git_commit"] and not version_info["is_development"]:
release_type = "commit-build"
return {
"release_type": release_type,
"build_from": version_info["git_branch"] or "unknown",
"commit": version_info["git_commit"] or "unknown",
"clean_build": not version_info["is_development"],
**version_info
}

View File

@@ -27,6 +27,7 @@ import builtins
from .database import DatabaseManager from .database import DatabaseManager
from .legacy_compat import LegacyMode, emit_deprecation_warning, legacy_switch_option from .legacy_compat import LegacyMode, emit_deprecation_warning, legacy_switch_option
from .__version__ import get_version_info, get_release_info
# Import legacy system components for advanced management # Import legacy system components for advanced management
try: try:
@@ -175,10 +176,20 @@ def format_output(data, output_format):
return format_output(data, 'table') return format_output(data, 'table')
def print_version(ctx, param, value):
"""Callback to print version and exit."""
if not value or ctx.resilient_parsing:
return
version_info = get_version_info()
click.echo(version_info['full_version'])
ctx.exit()
@click.group() @click.group()
@click.option('--verbose', '-v', is_flag=True, help='Enable verbose output') @click.option('--verbose', '-v', is_flag=True, help='Enable verbose output')
@click.option('--config', 'config_file', type=click.Path(exists=True), help='Configuration file path') @click.option('--config', 'config_file', type=click.Path(exists=True), help='Configuration file path')
@click.option('--database', type=click.Path(), help='Database file path') @click.option('--database', type=click.Path(), help='Database file path')
@click.option('--version', is_flag=True, expose_value=False, is_eager=True,
callback=print_version, help='Show version and exit')
@pass_config @pass_config
def cli(config, verbose, database, config_file): def cli(config, verbose, database, config_file):
""" """
@@ -218,6 +229,63 @@ def cli(config, verbose, database, config_file):
# Issue management commands removed - use dedicated 'issue' CLI or 'tddai' CLI instead # Issue management commands removed - use dedicated 'issue' CLI or 'tddai' CLI instead
# Version and release information commands
@cli.command()
@click.option('--short', is_flag=True, help='Show only version number')
def version(short):
"""Show MarkiTect version information."""
version_info = get_version_info()
if short:
click.echo(version_info['full_version'])
else:
click.echo("MarkiTect Version Information")
click.echo("============================")
click.echo(f"Version: {version_info['full_version']}")
click.echo(f"Base Version: {version_info['version']}")
if version_info['is_git_repo']:
click.echo(f"Git Commit: {version_info['git_commit'] or 'N/A'}")
click.echo(f"Git Branch: {version_info['git_branch'] or 'N/A'}")
if version_info['git_tag']:
click.echo(f"Git Tag: {version_info['git_tag']}")
click.echo(f"Development Build: {'Yes' if version_info['is_development'] else 'No'}")
else:
click.echo("Git Repository: Not available")
@cli.command()
@click.option('--format', 'output_format', default='text',
type=click.Choice(['text', 'json', 'yaml']),
help='Output format (text, json, yaml)')
def release(output_format):
"""Show MarkiTect release information."""
release_info = get_release_info()
if output_format == 'json':
import json
click.echo(json.dumps(release_info, indent=2))
elif output_format == 'yaml':
import yaml
click.echo(yaml.dump(release_info, default_flow_style=False))
else:
# Text format
click.echo("MarkiTect Release Information")
click.echo("============================")
click.echo(f"Version: {release_info['full_version']}")
click.echo(f"Release Type: {release_info['release_type']}")
click.echo(f"Build From: {release_info['build_from']}")
click.echo(f"Commit: {release_info['commit']}")
click.echo(f"Clean Build: {'Yes' if release_info['clean_build'] else 'No'}")
if release_info['is_git_repo']:
click.echo(f"Git Repository: Available")
if release_info['git_tag']:
click.echo(f"Tagged Release: {release_info['git_tag']}")
else:
click.echo("Git Repository: Not available")
@cli.command() @cli.command()
@click.argument('file_path', type=click.Path(exists=True)) @click.argument('file_path', type=click.Path(exists=True))