Completed progress tracking system with multiple features
[feature:track_progress] Implemented main progress tracking system [feature:git_integration] Added Git history analysis with commit tag parsing [feature:template_engine] Created flexible template engine for documentation generation [feature:feature_tracking] Implemented feature status tracking and updates [feature:changelog_generation] Added automatic changelog generation from commits [feature:code_stats] Added code statistics and metrics collection [feature:roadmap_generation] Implemented roadmap generation [new-feature:launcher_scripts:Created cross-platform launcher scripts for easy execution] [new-feature:executable_build:Added Nuitka-based executable build system] [new-feature:project_installation:Created setup script for easy installation to projects] [status:track_progress_and_feature_statuses] Completed automatic status document generation [status:git_integration] Completed parsing Git logs for status updates [status:doc_generation] Completed auto-generation of status docs and changelog [changelog:Added comprehensive progress tracking system] [changelog:Added feature status tracking with Git integration] [changelog:Added customizable template system for documentation] [changelog:Added automatic changelog generation from commit messages] [changelog:Added code statistics and metrics collection] [changelog:Added cross-platform launcher scripts] [changelog:Added executable build system using Nuitka] [changelog:Added project installation script] [milestone:v1.0] Completed initial release of progress tracking system
This commit is contained in:
parent
5352e907b9
commit
1b7601120c
7
.gitignore
vendored
7
.gitignore
vendored
@ -7,3 +7,10 @@ src/bindings/python/__pycache__/
|
|||||||
/src/AdvChkSys.Benchmarks/bin
|
/src/AdvChkSys.Benchmarks/bin
|
||||||
/src/AdvChkSys.Benchmarks/obj
|
/src/AdvChkSys.Benchmarks/obj
|
||||||
build/
|
build/
|
||||||
|
docs/features.md.*
|
||||||
|
scripts/__pycache__/
|
||||||
|
snyk.exe
|
||||||
|
scripts/track_progress/__pycache__/
|
||||||
|
scripts/track_progress/dist/
|
||||||
|
scripts/track_progress/scripts/__pycache__/
|
||||||
|
scripts/track_progress/docs/features.md.bak.*
|
||||||
|
|||||||
11
CHANGELOG.md
11
CHANGELOG.md
@ -1,14 +1,3 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
|
||||||
- 2025-05-11: removed unnecessary ignores in gitignore (79d6716)
|
|
||||||
- 2025-05-11: XML comments added Builds cleanly (0ae815c)
|
|
||||||
- 2025-05-11: updated track_progress restored accidentally deleted source file (8a6f914)
|
|
||||||
- 2025-05-10: fixed a bug of not building (09ee0d8)
|
|
||||||
- 2025-05-10: added a build (e59a909)
|
|
||||||
- 2025-05-10: updated ChunkMark (f35c612)
|
|
||||||
- 2025-05-10: updated memory memory handling to be more safe (becedcc)
|
|
||||||
- 2025-05-09: first commit (f1b3868)
|
|
||||||
|
|
||||||
- 2025-05-10: added a build (e59a909)
|
|
||||||
- 2025-05-10: updated memory memory handling to be more safe (becedcc)
|
|
||||||
118
docs/features.md
118
docs/features.md
@ -1,118 +0,0 @@
|
|||||||
# Feature Tracking
|
|
||||||
|
|
||||||
## chunk_management
|
|
||||||
- Status: completed
|
|
||||||
- Description: 2D/3D chunk management with memory efficiency
|
|
||||||
- Last Update: 2025-05-10
|
|
||||||
|
|
||||||
## memory_efficiency
|
|
||||||
- Status: completed
|
|
||||||
- Description: LRU caching, array pooling, and air-singleton patterns
|
|
||||||
- Last Update: 2025-05-10
|
|
||||||
|
|
||||||
## resource_tracking
|
|
||||||
- Status: completed
|
|
||||||
- Description: Track and manage chunk resources
|
|
||||||
- Last Update: 2025-05-10
|
|
||||||
|
|
||||||
## async_tasks
|
|
||||||
- Status: completed
|
|
||||||
- Description: Asynchronous chunk loading and processing
|
|
||||||
- Last Update: 2025-05-10
|
|
||||||
|
|
||||||
## serialization
|
|
||||||
- Status: completed
|
|
||||||
- Description: Chunk serialization and deserialization
|
|
||||||
- Last Update: 2025-05-10
|
|
||||||
|
|
||||||
## event_system
|
|
||||||
- Status: completed
|
|
||||||
- Description: Events for load/unload/save operations
|
|
||||||
- Last Update: 2025-05-10
|
|
||||||
|
|
||||||
## interop
|
|
||||||
- Status: completed
|
|
||||||
- Description: Python and .NET interoperability
|
|
||||||
- Last Update: 2025-05-10
|
|
||||||
|
|
||||||
## spatial_queries
|
|
||||||
- Status: completed
|
|
||||||
- Description: Spatial indexing and region queries with 2D/3D support and quadtree optimization
|
|
||||||
- Last Update: 2025-05-11
|
|
||||||
|
|
||||||
## thread_sync
|
|
||||||
- Status: completed
|
|
||||||
- Description: Explicit synchronization and concurrent collections
|
|
||||||
- Last Update: 2025-05-11
|
|
||||||
- Updated By: Stan44
|
|
||||||
|
|
||||||
## spatial_interfaces
|
|
||||||
- Status: completed
|
|
||||||
- Description: Interface-level spatial query methods with support for custom filters
|
|
||||||
- Last Update: 2025-05-11
|
|
||||||
|
|
||||||
## parallel_processing
|
|
||||||
- Status: completed
|
|
||||||
- Description: Parallel processing of chunks based on spatial queries and regions
|
|
||||||
- Last Update: 2025-05-11
|
|
||||||
|
|
||||||
## feature_tracking
|
|
||||||
- Status: completed
|
|
||||||
- Description: Track feature statuses
|
|
||||||
- Last Update: 2025-05-11
|
|
||||||
|
|
||||||
## priority_loading
|
|
||||||
- Status: in_progress
|
|
||||||
- Description: Prioritized chunk loading system (partially implemented)
|
|
||||||
- Last Update: 2025-05-10
|
|
||||||
|
|
||||||
## dependency_tracking
|
|
||||||
- Status: in_progress
|
|
||||||
- Description: Dependency-aware disposal logic (Partially Implemented)
|
|
||||||
- Last Update: 2025-05-10
|
|
||||||
|
|
||||||
## dispose_pattern
|
|
||||||
- Status: in_progress
|
|
||||||
- Description: Full dispose pattern with finalizers for unmanaged resources (Partially Implemented)
|
|
||||||
- Last Update: 2025-05-10
|
|
||||||
|
|
||||||
## exception_handling
|
|
||||||
- Status: in_progress
|
|
||||||
- Description: Better async exception handling with specific types and logging
|
|
||||||
- Last Update: 2025-05-10
|
|
||||||
|
|
||||||
## performance_metrics
|
|
||||||
- Status: in_progress
|
|
||||||
- Description: Track load times, cache hit rates, and memory usage (partially implemented)
|
|
||||||
- Last Update: 2025-05-10
|
|
||||||
|
|
||||||
## progress_tracking
|
|
||||||
- Status: in_progress
|
|
||||||
- Description: Track progress and auto-update status documents (mostly implemented kinda still manual...)
|
|
||||||
- Last Update: 2025-05-11
|
|
||||||
|
|
||||||
## git_integration
|
|
||||||
- Status: in_progress
|
|
||||||
- Description: Parse Git logs for status updates (partially implemented testing with this push.)
|
|
||||||
- Last Update: 2025-05-11
|
|
||||||
|
|
||||||
## doc_generation
|
|
||||||
- Status: in_progress
|
|
||||||
- Description: Auto-generate status docs and changelog (partially implemented)
|
|
||||||
- Last Update: 2025-05-10
|
|
||||||
|
|
||||||
## dependency_interfaces
|
|
||||||
- Status: planned
|
|
||||||
- Description: Interface-level dependency methods
|
|
||||||
- Last Update: 2025-05-10
|
|
||||||
|
|
||||||
## runtime_config
|
|
||||||
- Status: planned
|
|
||||||
- Description: Runtime-adjustable configuration options
|
|
||||||
- Last Update: 2025-05-10
|
|
||||||
|
|
||||||
## known_issues
|
|
||||||
- Status: in_progress
|
|
||||||
- Description: Edge chunk unload delay under high concurrency
|
|
||||||
- Last Update: 2025-05-10
|
|
||||||
|
|
||||||
@ -1,87 +0,0 @@
|
|||||||
# AdvChkSys Development Status
|
|
||||||
|
|
||||||
Last updated: 2025-05-11
|
|
||||||
|
|
||||||
## Code Statistics
|
|
||||||
|
|
||||||
Total lines of code: **7414**
|
|
||||||
|
|
||||||
Number of source files: **34**
|
|
||||||
|
|
||||||
### All Files by Line Count
|
|
||||||
|
|
||||||
| File | Lines | Path |
|
|
||||||
|------|------:|------|
|
|
||||||
| SpatialChunkIndex.cs | 1228 | src/AdvChkSys\Spatial\SpatialChunkIndex.cs |
|
|
||||||
| ChunkLoadingPriority.cs | 696 | src/AdvChkSys\Loading\ChunkLoadingPriority.cs |
|
|
||||||
| ChunkThreadingExtensions.cs | 380 | src/AdvChkSys\Threading\ChunkThreadingExtensions.cs |
|
|
||||||
| ChunkDependencyTracker.cs | 325 | src/AdvChkSys\Dependencies\ChunkDependencyTracker.cs |
|
|
||||||
| ChunkManager2D.cs | 313 | src/AdvChkSys\Manager\ChunkManager2D.cs |
|
|
||||||
| ChunkTaskScheduler.cs | 310 | src/AdvChkSys\Threading\ChunkTaskScheduler.cs |
|
|
||||||
| ChunkThreadingManager.cs | 280 | src/AdvChkSys\Threading\ChunkThreadingManager.cs |
|
|
||||||
| ChunkOperationQueue.cs | 274 | src/AdvChkSys\Threading\ChunkOperationQueue.cs |
|
|
||||||
| ChunkThreadingDiagnostics.cs | 273 | src/AdvChkSys\Threading\ChunkThreadingDiagnostics.cs |
|
|
||||||
| ChunkThreadingPerformanceMonitor.cs | 265 | src/AdvChkSys\Threading\ChunkThreadingPerformanceMonitor.cs |
|
|
||||||
| ChunkParallelProcessor.cs | 252 | src/AdvChkSys\Threading\ChunkParallelProcessor.cs |
|
|
||||||
| ChunkManager3D.cs | 244 | src/AdvChkSys\Manager\ChunkManager3D.cs |
|
|
||||||
| ChunkThreadSafetyManager.cs | 237 | src/AdvChkSys\Threading\ChunkThreadSafetyManager.cs |
|
|
||||||
| ChunkThreadingExtensions2.cs | 223 | src/AdvChkSys\Threading\ChunkThreadingExtensions2.cs |
|
|
||||||
| Chunk3D.cs | 215 | src/AdvChkSys\Chunk\Chunk3D.cs |
|
|
||||||
| ChunkSerializer.cs | 194 | src/AdvChkSys\Serialization\ChunkSerializer.cs |
|
|
||||||
| Chunk2D.cs | 174 | src/AdvChkSys\Chunk\Chunk2D.cs |
|
|
||||||
| LimitedConcurrencyTaskScheduler.cs | 170 | src/AdvChkSys\Threading\LimitedConcurrencyTaskScheduler.cs |
|
|
||||||
| ChunkAsyncLock.cs | 167 | src/AdvChkSys\Threading\ChunkAsyncLock.cs |
|
|
||||||
| AdvChkSys.cs | 162 | src/AdvChkSys\AdvChkSys.cs |
|
|
||||||
| MemoryHelper.cs | 143 | src/AdvChkSys\Util\MemoryHelper.cs |
|
|
||||||
| MemoryUsageReporter.cs | 140 | src/AdvChkSys\Diagnostics\MemoryUsageReporter.cs |
|
|
||||||
| LRUCache.cs | 135 | src/AdvChkSys\Util\LRUCache.cs |
|
|
||||||
| ChunkEvents.cs | 126 | src/AdvChkSys\Events\ChunkEvents.cs |
|
|
||||||
| ChunkThreadingConfiguration.cs | 98 | src/AdvChkSys\Threading\ChunkThreadingConfiguration.cs |
|
|
||||||
| ChunkResourceManager.cs | 72 | src/AdvChkSys\Resources\ChunkResourceManager.cs |
|
|
||||||
| WorldConstraints.cs | 67 | src/AdvChkSys\Constraints\WorldConstraints.cs |
|
|
||||||
| ChunkTaskSchedulerExtensions.cs | 63 | src/AdvChkSys\Threading\ChunkTaskSchedulerExtensions.cs |
|
|
||||||
| ChunkExtensions.cs | 40 | src/AdvChkSys\Spatial\ChunkExtensions.cs |
|
|
||||||
| IChunkManager.cs | 38 | src/AdvChkSys\Interfaces\IChunkManager.cs |
|
|
||||||
| IChunk.cs | 36 | src/AdvChkSys\Interfaces\IChunk.cs |
|
|
||||||
| CacheCapacityHelper.cs | 30 | src/AdvChkSys\Util\CacheCapacityHelper.cs |
|
|
||||||
| AdvChkSys.AssemblyInfo.cs | 22 | src/AdvChkSys\obj\Debug\netstandard2.1\AdvChkSys.AssemblyInfo.cs |
|
|
||||||
| AdvChkSys.AssemblyInfo.cs | 22 | src/AdvChkSys\obj\Release\netstandard2.1\AdvChkSys.AssemblyInfo.cs |
|
|
||||||
|
|
||||||
## Feature Status
|
|
||||||
|
|
||||||
| Feature | Status | Description | Last Update |
|
|
||||||
|---------|--------|-------------|-------------|
|
|
||||||
| Chunk Management | [COMPLETED] | 2D/3D chunk management with memory efficiency | 2025-05-10 |
|
|
||||||
| Memory Efficiency | [COMPLETED] | LRU caching, array pooling, and air-singleton patterns | 2025-05-10 |
|
|
||||||
| Resource Tracking | [COMPLETED] | Track and manage chunk resources | 2025-05-10 |
|
|
||||||
| Async Tasks | [COMPLETED] | Asynchronous chunk loading and processing | 2025-05-10 |
|
|
||||||
| Serialization | [COMPLETED] | Chunk serialization and deserialization | 2025-05-10 |
|
|
||||||
| Event System | [COMPLETED] | Events for load/unload/save operations | 2025-05-10 |
|
|
||||||
| Interop | [COMPLETED] | Python and .NET interoperability | 2025-05-10 |
|
|
||||||
| Spatial Queries | [COMPLETED] | Spatial indexing and region queries with 2D/3D support and quadtree optimization | 2025-05-11 |
|
|
||||||
| Thread Sync | [COMPLETED] | Explicit synchronization and concurrent collections | 2025-05-11 |
|
|
||||||
| Spatial Interfaces | [COMPLETED] | Interface-level spatial query methods with support for custom filters | 2025-05-11 |
|
|
||||||
| Parallel Processing | [COMPLETED] | Parallel processing of chunks based on spatial queries and regions | 2025-05-11 |
|
|
||||||
| Feature Tracking | [COMPLETED] | Track feature statuses | 2025-05-11 |
|
|
||||||
| Priority Loading | [IN PROGRESS] | Prioritized chunk loading system (partially implemented) | 2025-05-10 |
|
|
||||||
| Dependency Tracking | [IN PROGRESS] | Dependency-aware disposal logic (Partially Implemented) | 2025-05-10 |
|
|
||||||
| Dispose Pattern | [IN PROGRESS] | Full dispose pattern with finalizers for unmanaged resources (Partially Implemented) | 2025-05-10 |
|
|
||||||
| Exception Handling | [IN PROGRESS] | Better async exception handling with specific types and logging | 2025-05-10 |
|
|
||||||
| Performance Metrics | [IN PROGRESS] | Track load times, cache hit rates, and memory usage (partially implemented) | 2025-05-10 |
|
|
||||||
| Progress Tracking | [IN PROGRESS] | Track progress and auto-update status documents (mostly implemented kinda still manual...) | 2025-05-11 |
|
|
||||||
| Git Integration | [IN PROGRESS] | Parse Git logs for status updates (partially implemented testing with this push.) | 2025-05-11 |
|
|
||||||
| Doc Generation | [IN PROGRESS] | Auto-generate status docs and changelog (partially implemented) | 2025-05-10 |
|
|
||||||
| Dependency Interfaces | [PLANNED] | Interface-level dependency methods | 2025-05-10 |
|
|
||||||
| Runtime Config | [PLANNED] | Runtime-adjustable configuration options | 2025-05-10 |
|
|
||||||
| Known Issues | [IN PROGRESS] | Edge chunk unload delay under high concurrency | 2025-05-10 |
|
|
||||||
|
|
||||||
## Recent Updates
|
|
||||||
|
|
||||||
- 2025-05-11: removed unnecessary ignores in gitignore (79d6716)
|
|
||||||
- 2025-05-11: XML comments added Builds cleanly (0ae815c)
|
|
||||||
- 2025-05-11: updated track_progress restored accidentally deleted source file (8a6f914)
|
|
||||||
- 2025-05-10: fixed a bug of not building (09ee0d8)
|
|
||||||
- 2025-05-10: added a build (e59a909)
|
|
||||||
- 2025-05-10: updated ChunkMark (f35c612)
|
|
||||||
- 2025-05-10: updated memory memory handling to be more safe (becedcc)
|
|
||||||
- 2025-05-09: first commit (f1b3868)
|
|
||||||
@ -1,81 +0,0 @@
|
|||||||
# AdvChkSys Development Scripts
|
|
||||||
|
|
||||||
## Track Progress Script
|
|
||||||
|
|
||||||
The `track_progress.py` script automates feature tracking, status updates, and changelog management for the AdvChkSys project.
|
|
||||||
|
|
||||||
### Overview
|
|
||||||
|
|
||||||
This script:
|
|
||||||
- Tracks features and their status in a human-readable Markdown file
|
|
||||||
- Generates a status document with current development progress
|
|
||||||
- Updates the changelog with tagged entries from commit messages
|
|
||||||
- Automatically detects new features from specially formatted commit messages
|
|
||||||
|
|
||||||
### Usage
|
|
||||||
|
|
||||||
Run the script periodically to update documentation:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
python scripts/track_progress.py
|
|
||||||
```
|
|
||||||
|
|
||||||
### Commit Message Tags
|
|
||||||
|
|
||||||
The script recognizes special tags in commit messages:
|
|
||||||
|
|
||||||
#### 1. Feature Status Updates
|
|
||||||
|
|
||||||
```
|
|
||||||
git commit -m "Implemented spatial queries [feature:spatial_queries]"
|
|
||||||
```
|
|
||||||
|
|
||||||
This marks the `spatial_queries` feature as completed and records the date and author.
|
|
||||||
|
|
||||||
#### 2. New Features
|
|
||||||
|
|
||||||
```
|
|
||||||
git commit -m "Initial setup [new-feature:custom_serialization:Support for custom serialization formats]"
|
|
||||||
```
|
|
||||||
|
|
||||||
This adds a new feature called `custom_serialization` with the provided description and sets its status to "planned".
|
|
||||||
|
|
||||||
#### 3. Changelog Entries
|
|
||||||
|
|
||||||
```
|
|
||||||
git commit -m "Fixed bug in chunk loading [changelog:Fixed race condition in async chunk loading]"
|
|
||||||
```
|
|
||||||
|
|
||||||
This adds an entry to the changelog with the date, message, and commit hash.
|
|
||||||
|
|
||||||
#### 4. Status Updates
|
|
||||||
|
|
||||||
```
|
|
||||||
git commit -m "Completed milestone 1 [status:milestone1]"
|
|
||||||
```
|
|
||||||
|
|
||||||
This records a general status update.
|
|
||||||
|
|
||||||
### Output Files
|
|
||||||
|
|
||||||
The script generates and updates the following files:
|
|
||||||
|
|
||||||
- `docs/features.md`: Human-readable feature tracking in Markdown format
|
|
||||||
- `docs/status/ChunkManager-Status.md`: Status document with feature progress and recent updates
|
|
||||||
- `CHANGELOG.md`: Project changelog with tagged entries from commits
|
|
||||||
|
|
||||||
### Feature Statuses
|
|
||||||
|
|
||||||
Features can have the following statuses:
|
|
||||||
- `planned`: Feature is planned but not yet started
|
|
||||||
- `in_progress`: Feature is currently being implemented
|
|
||||||
- `completed`: Feature has been implemented
|
|
||||||
|
|
||||||
### Manual Editing
|
|
||||||
|
|
||||||
You can manually edit the `docs/features.md` file to update feature descriptions, statuses, or add new features. The script will preserve your changes when it runs.
|
|
||||||
|
|
||||||
### Requirements
|
|
||||||
|
|
||||||
- Python 3.6+
|
|
||||||
- Git repository
|
|
||||||
@ -1,291 +0,0 @@
|
|||||||
import os
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
from datetime import datetime
|
|
||||||
from subprocess import check_output
|
|
||||||
|
|
||||||
# Try to set UTF-8 encoding for better emoji support
|
|
||||||
sys.stderr = open(
|
|
||||||
sys.stderr.fileno(), mode="w", encoding="utf-8", errors="replace"
|
|
||||||
) # Configuration
|
|
||||||
PROGRESS_FILE = "docs/progress.json"
|
|
||||||
STATUS_DOC = "docs/status/ChunkManager-Status.md"
|
|
||||||
CHANGELOG_FILE = "CHANGELOG.md"
|
|
||||||
FEATURES_FILE = "docs/features.md"
|
|
||||||
GIT_LOG_LIMIT = 100 # number of commits to parse
|
|
||||||
UNTAGGED_COMMIT_LIMIT = 100 # number of untagged commits to include
|
|
||||||
|
|
||||||
# Regular expression to match tags in commit messages
|
|
||||||
STATUS_TAG_RE = re.compile(r"\[status:(\w+)\]")
|
|
||||||
CHANGELOG_TAG_RE = re.compile(r"\[changelog:(.+?)\]")
|
|
||||||
FEATURE_TAG_RE = re.compile(r"\[feature:(\w+)\]")
|
|
||||||
NEW_FEATURE_RE = re.compile(r"\[new-feature:(\w+):(.+?)\]")
|
|
||||||
|
|
||||||
|
|
||||||
# Ensure directories exist
|
|
||||||
os.makedirs(os.path.dirname(STATUS_DOC), exist_ok=True)
|
|
||||||
os.makedirs(os.path.dirname(FEATURES_FILE), exist_ok=True)
|
|
||||||
|
|
||||||
|
|
||||||
def count_lines_of_code():
|
|
||||||
"""Count lines of code in the project and return statistics."""
|
|
||||||
import os
|
|
||||||
import glob
|
|
||||||
|
|
||||||
stats = {}
|
|
||||||
total_lines = 0
|
|
||||||
file_stats = []
|
|
||||||
|
|
||||||
# Find all .cs files in the src/AdvChkSys directory and subdirectories
|
|
||||||
cs_files = glob.glob("src/AdvChkSys/**/*.cs", recursive=True)
|
|
||||||
|
|
||||||
for file_path in cs_files:
|
|
||||||
with open(file_path, "r", encoding="utf-8", errors="ignore") as f:
|
|
||||||
lines = len(f.readlines())
|
|
||||||
total_lines += lines
|
|
||||||
file_name = os.path.basename(file_path)
|
|
||||||
file_stats.append((file_name, lines, file_path))
|
|
||||||
|
|
||||||
# Sort by line count in descending order
|
|
||||||
file_stats.sort(key=lambda x: x[1], reverse=True)
|
|
||||||
|
|
||||||
stats["total_lines"] = total_lines
|
|
||||||
stats["file_count"] = len(cs_files)
|
|
||||||
stats["files"] = file_stats
|
|
||||||
|
|
||||||
return stats
|
|
||||||
|
|
||||||
|
|
||||||
# Load features from Markdown file if it exists
|
|
||||||
features = {}
|
|
||||||
if os.path.exists(FEATURES_FILE):
|
|
||||||
with open(FEATURES_FILE, "r", encoding="utf-8") as f:
|
|
||||||
content = f.read()
|
|
||||||
# Parse the markdown file
|
|
||||||
sections = re.split(r"## (\w+)", content)[1:] # Skip the header
|
|
||||||
for i in range(0, len(sections), 2):
|
|
||||||
if i + 1 < len(sections):
|
|
||||||
feature_key = sections[i]
|
|
||||||
feature_content = sections[i + 1]
|
|
||||||
|
|
||||||
# Extract status, description, and date
|
|
||||||
status_match = re.search(r"- Status: (\w+)", feature_content)
|
|
||||||
desc_match = re.search(
|
|
||||||
r"- Description: (.+?)$", feature_content, re.MULTILINE
|
|
||||||
)
|
|
||||||
date_match = re.search(
|
|
||||||
r"- Last Update: (.+?)$", feature_content, re.MULTILINE
|
|
||||||
)
|
|
||||||
|
|
||||||
status = status_match.group(1) if status_match else "planned"
|
|
||||||
description = desc_match.group(1) if desc_match else ""
|
|
||||||
date = date_match.group(1) if date_match else ""
|
|
||||||
|
|
||||||
features[feature_key] = {
|
|
||||||
"status": status,
|
|
||||||
"description": description,
|
|
||||||
"date": date,
|
|
||||||
}
|
|
||||||
else:
|
|
||||||
# Default features
|
|
||||||
features = {
|
|
||||||
"spatial_queries": {
|
|
||||||
"status": "planned",
|
|
||||||
"description": "Methods to efficiently find chunks within regions or distances", # noqa: E501
|
|
||||||
},
|
|
||||||
"priority_loading": {
|
|
||||||
"status": "planned",
|
|
||||||
"description": "API to specify which chunks should be loaded first", # noqa: E501
|
|
||||||
},
|
|
||||||
"serialization_optimization": {
|
|
||||||
"status": "in_progress",
|
|
||||||
"description": "Further improvements to chunk saving/loading",
|
|
||||||
},
|
|
||||||
"chunk_dependency": {
|
|
||||||
"status": "planned",
|
|
||||||
"description": "For cases where chunks need to reference neighbors", # noqa: E501
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
# Get recent git log entries
|
|
||||||
git_log = check_output(
|
|
||||||
[
|
|
||||||
"git",
|
|
||||||
"log",
|
|
||||||
f"-n{GIT_LOG_LIMIT}",
|
|
||||||
"--pretty=format:%h|%s|%ad|%an",
|
|
||||||
"--date=short",
|
|
||||||
]
|
|
||||||
).decode()
|
|
||||||
|
|
||||||
# Parse and collect updates
|
|
||||||
status_updates = {}
|
|
||||||
changelog_entries = []
|
|
||||||
feature_updates = {}
|
|
||||||
new_features = {}
|
|
||||||
untagged_count = 0 # Initialize the counter for untagged commits
|
|
||||||
|
|
||||||
for line in git_log.splitlines():
|
|
||||||
parts = line.split("|")
|
|
||||||
if len(parts) < 4:
|
|
||||||
continue
|
|
||||||
|
|
||||||
commit_hash, subject, date, author = parts
|
|
||||||
|
|
||||||
status_match = STATUS_TAG_RE.search(subject)
|
|
||||||
changelog_match = CHANGELOG_TAG_RE.search(subject)
|
|
||||||
feature_match = FEATURE_TAG_RE.search(subject)
|
|
||||||
new_feature_match = NEW_FEATURE_RE.search(subject)
|
|
||||||
|
|
||||||
if status_match:
|
|
||||||
status_key = status_match.group(1)
|
|
||||||
status_updates[status_key] = {
|
|
||||||
"status": "done",
|
|
||||||
"commit": commit_hash,
|
|
||||||
"date": date,
|
|
||||||
"message": subject,
|
|
||||||
}
|
|
||||||
|
|
||||||
if changelog_match:
|
|
||||||
changelog_entries.append(
|
|
||||||
f"- {date}: {changelog_match.group(1)} ({commit_hash})"
|
|
||||||
)
|
|
||||||
|
|
||||||
if feature_match:
|
|
||||||
feature_key = feature_match.group(1)
|
|
||||||
if feature_key in features:
|
|
||||||
feature_updates[feature_key] = {
|
|
||||||
"status": "completed",
|
|
||||||
"date": date,
|
|
||||||
"author": author,
|
|
||||||
}
|
|
||||||
|
|
||||||
if new_feature_match:
|
|
||||||
feature_key = new_feature_match.group(1)
|
|
||||||
feature_desc = new_feature_match.group(2)
|
|
||||||
if feature_key not in features:
|
|
||||||
new_features[feature_key] = {
|
|
||||||
"status": "planned",
|
|
||||||
"description": feature_desc,
|
|
||||||
"date": date,
|
|
||||||
"author": author,
|
|
||||||
}
|
|
||||||
|
|
||||||
# Also add regular commits without tags to changelog entries
|
|
||||||
if not (status_match or changelog_match or feature_match or new_feature_match):
|
|
||||||
# Skip if we've reached the limit for untagged commits
|
|
||||||
if untagged_count >= UNTAGGED_COMMIT_LIMIT:
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Skip merge commits and very short messages
|
|
||||||
if not subject.startswith('Merge ') and len(subject) > 5:
|
|
||||||
# Extract the first sentence or up to 100 chars
|
|
||||||
commit_desc = subject.split('.')[0]
|
|
||||||
if len(commit_desc) > 100:
|
|
||||||
commit_desc = commit_desc[:97] + "..."
|
|
||||||
|
|
||||||
# Add to changelog entries with a different format to distinguish from tagged entries
|
|
||||||
changelog_entries.append(
|
|
||||||
f"- {date}: {commit_desc} ({commit_hash})"
|
|
||||||
)
|
|
||||||
untagged_count += 1
|
|
||||||
|
|
||||||
# Update features with new information
|
|
||||||
for feature_key, update in feature_updates.items():
|
|
||||||
features[feature_key].update(update)
|
|
||||||
|
|
||||||
# Add new features
|
|
||||||
for feature_key, feature_data in new_features.items():
|
|
||||||
features[feature_key] = feature_data
|
|
||||||
|
|
||||||
# Save features to Markdown file
|
|
||||||
with open(FEATURES_FILE, "w", encoding="utf-8") as f:
|
|
||||||
f.write("# Feature Tracking\n\n")
|
|
||||||
|
|
||||||
for feature_key, feature_data in features.items():
|
|
||||||
status = feature_data.get("status", "planned")
|
|
||||||
description = feature_data.get("description", "")
|
|
||||||
date = feature_data.get("date", "")
|
|
||||||
author = feature_data.get("author", "")
|
|
||||||
|
|
||||||
f.write(f"## {feature_key}\n")
|
|
||||||
f.write(f"- Status: {status}\n")
|
|
||||||
f.write(f"- Description: {description}\n")
|
|
||||||
f.write(f"- Last Update: {date}\n")
|
|
||||||
if author:
|
|
||||||
f.write(f"- Updated By: {author}\n")
|
|
||||||
f.write("\n")
|
|
||||||
|
|
||||||
# Generate status document
|
|
||||||
with open(STATUS_DOC, "w", encoding="utf-8") as f:
|
|
||||||
f.write("# AdvChkSys Development Status\n\n")
|
|
||||||
f.write(f"Last updated: {datetime.now().strftime('%Y-%m-%d')}\n\n")
|
|
||||||
|
|
||||||
# Add code statistics section
|
|
||||||
f.write("## Code Statistics\n\n")
|
|
||||||
code_stats = count_lines_of_code()
|
|
||||||
f.write(f"Total lines of code: **{code_stats['total_lines']}**\n\n")
|
|
||||||
f.write(f"Number of source files: **{code_stats['file_count']}**\n\n")
|
|
||||||
|
|
||||||
f.write("### All Files by Line Count\n\n")
|
|
||||||
f.write("| File | Lines | Path |\n")
|
|
||||||
f.write("|------|------:|------|\n")
|
|
||||||
|
|
||||||
for file_name, lines, file_path in code_stats["files"]:
|
|
||||||
f.write(f"| {file_name} | {lines} | {file_path} |\n")
|
|
||||||
|
|
||||||
f.write("\n")
|
|
||||||
|
|
||||||
f.write("## Feature Status\n\n")
|
|
||||||
f.write("| Feature | Status | Description | Last Update |\n")
|
|
||||||
f.write("|---------|--------|-------------|-------------|\n")
|
|
||||||
|
|
||||||
for feature_key, feature_data in features.items():
|
|
||||||
status = feature_data.get("status", "unknown")
|
|
||||||
description = feature_data.get("description", "")
|
|
||||||
date = feature_data.get("date", "")
|
|
||||||
|
|
||||||
# Use text indicators instead of emojis
|
|
||||||
status_indicator = {
|
|
||||||
"planned": "[PLANNED]",
|
|
||||||
"in_progress": "[IN PROGRESS]",
|
|
||||||
"completed": "[COMPLETED]",
|
|
||||||
"done": "[DONE]",
|
|
||||||
}.get(status, "[UNKNOWN]")
|
|
||||||
|
|
||||||
f.write(
|
|
||||||
f"| {feature_key.replace('_', ' ').title()} | {status_indicator} | {description} | {date} |\n" # noqa: E501
|
|
||||||
)
|
|
||||||
|
|
||||||
f.write("\n## Recent Updates\n\n")
|
|
||||||
for entry in changelog_entries[
|
|
||||||
:100
|
|
||||||
]:
|
|
||||||
f.write(f"{entry}\n")
|
|
||||||
|
|
||||||
# Update changelog if there are new entries
|
|
||||||
if changelog_entries and os.path.exists(CHANGELOG_FILE):
|
|
||||||
with open(CHANGELOG_FILE, "r", encoding="utf-8") as f:
|
|
||||||
existing_changelog = f.read()
|
|
||||||
|
|
||||||
with open(CHANGELOG_FILE, "w", encoding="utf-8") as f:
|
|
||||||
# Add new entries at the top, under the first heading
|
|
||||||
lines = existing_changelog.splitlines()
|
|
||||||
insertion_point = next(
|
|
||||||
(i for i, line in enumerate(lines) if line.startswith("##")), 2
|
|
||||||
)
|
|
||||||
|
|
||||||
updated_changelog = "\n".join(lines[:insertion_point]) + "\n\n"
|
|
||||||
updated_changelog += "\n".join(changelog_entries) + "\n\n"
|
|
||||||
updated_changelog += "\n".join(lines[insertion_point:])
|
|
||||||
|
|
||||||
f.write(updated_changelog)
|
|
||||||
elif changelog_entries:
|
|
||||||
# Create new changelog file
|
|
||||||
with open(CHANGELOG_FILE, "w", encoding="utf-8") as f:
|
|
||||||
f.write("# Changelog\n\n")
|
|
||||||
f.write("\n".join(changelog_entries))
|
|
||||||
f.write("\n")
|
|
||||||
|
|
||||||
print(f"Progress tracking updated. Status document generated at {STATUS_DOC}")
|
|
||||||
print(f"Features updated at {FEATURES_FILE}")
|
|
||||||
2
scripts/track_progress/CHANGELOG.md
Normal file
2
scripts/track_progress/CHANGELOG.md
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
207
scripts/track_progress/README.md
Normal file
207
scripts/track_progress/README.md
Normal file
@ -0,0 +1,207 @@
|
|||||||
|
# Progress Tracker
|
||||||
|
|
||||||
|
A comprehensive tool for tracking project progress, feature status, and generating documentation from Git history.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The Progress Tracker analyzes your Git repository and codebase to automatically generate:
|
||||||
|
|
||||||
|
- Feature tracking documentation
|
||||||
|
- Project status reports
|
||||||
|
- Changelog based on commit messages
|
||||||
|
- Code statistics and metrics
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
- Python 3.8 or higher
|
||||||
|
- Git
|
||||||
|
|
||||||
|
### Dependencies
|
||||||
|
|
||||||
|
Install the required dependencies:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install -r scripts/track_progress/requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
For development or building the executable, install all dependencies:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install -r scripts/track_progress/requirements.txt[all]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Quick Install
|
||||||
|
|
||||||
|
Run the setup script to install the tracker to your project:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python scripts/track_progress/scripts/setup_install.py --project "Your Project Name" --dest path/to/destination
|
||||||
|
```
|
||||||
|
|
||||||
|
Options:
|
||||||
|
- `--project name`: Set your project name
|
||||||
|
- `--dest folder`: Set destination directory (default: "scripts")
|
||||||
|
- `--verbose` or `-v`: Enable verbose output
|
||||||
|
- `--force` or `-f`: Force overwrite existing files
|
||||||
|
|
||||||
|
### Manual Installation
|
||||||
|
|
||||||
|
1. Copy the `track_progress` directory to your project
|
||||||
|
2. Create a `docs/status` directory in your project root
|
||||||
|
3. Create a `docs/features.md` file if it doesn't exist
|
||||||
|
4. Create a `CHANGELOG.md` file in your project root
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Basic Usage
|
||||||
|
|
||||||
|
Run the tracker with default settings:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Using the Python script
|
||||||
|
python scripts/track_progress/track_progress.py
|
||||||
|
|
||||||
|
# Using the executable (if built)
|
||||||
|
scripts/track_progress/track_progress
|
||||||
|
```
|
||||||
|
|
||||||
|
### Command-Line Options
|
||||||
|
|
||||||
|
The tracker supports the following command-line options:
|
||||||
|
|
||||||
|
```
|
||||||
|
--config PATH Path to configuration file (default: scripts/track_progress/progress_config.json)
|
||||||
|
--project NAME Project name (overrides config)
|
||||||
|
--output-dir DIR Output directory for status document (overrides config)
|
||||||
|
--repo PATH Repository path to analyze (default: current directory)
|
||||||
|
--verbose, -v Enable verbose output
|
||||||
|
```
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Specify a custom configuration file
|
||||||
|
python scripts/track_progress/track_progress.py --config path/to/config.json
|
||||||
|
|
||||||
|
# Override project name
|
||||||
|
python scripts/track_progress/track_progress.py --project "My Project"
|
||||||
|
|
||||||
|
# Specify output directory
|
||||||
|
python scripts/track_progress/track_progress.py --output-dir docs/my-status
|
||||||
|
|
||||||
|
# Analyze a different repository
|
||||||
|
python scripts/track_progress/track_progress.py --repo path/to/repo
|
||||||
|
|
||||||
|
# Enable verbose output
|
||||||
|
python scripts/track_progress/track_progress.py --verbose
|
||||||
|
```
|
||||||
|
|
||||||
|
## Commit Message Tags
|
||||||
|
|
||||||
|
The tracker recognizes special tags in commit messages to update feature status and documentation:
|
||||||
|
|
||||||
|
| Tag | Description | Example |
|
||||||
|
|-----|-------------|---------|
|
||||||
|
| `[status:key]` | Update status of a component | `[status:database]` |
|
||||||
|
| `[feature:key]` | Mark a feature as completed | `[feature:login]` |
|
||||||
|
| `[new-feature:key:description]` | Add a new feature | `[new-feature:search:Implement search functionality]` |
|
||||||
|
| `[changelog:message]` | Add an entry to the changelog | `[changelog:Added dark mode]` |
|
||||||
|
| `[fix:description]` | Document a bug fix | `[fix:Fixed login issue]` |
|
||||||
|
| `[issue:id]` | Reference an issue | `[issue:42]` |
|
||||||
|
| `[breaking]` | Mark a breaking change | `[breaking]` |
|
||||||
|
| `[roadmap:milestone:item]` | Add an item to the roadmap | `[roadmap:v2:Add OAuth support]` |
|
||||||
|
| `[milestone:key]` | Reference a milestone | `[milestone:v1.0]` |
|
||||||
|
| `[priority:level]` | Set priority level | `[priority:high]` |
|
||||||
|
| `[owner:name]` | Assign an owner | `[owner:John]` |
|
||||||
|
| `[tag:name]` | Add a custom tag | `[tag:security]` |
|
||||||
|
|
||||||
|
Example commit message:
|
||||||
|
```
|
||||||
|
Implement search functionality [feature:search] [changelog:Added search with filtering]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
The tracker uses a JSON configuration file (`progress_config.json`) with the following settings:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"project_name": "Your Project",
|
||||||
|
"output_dir": "docs/status",
|
||||||
|
"status_doc": "status.md",
|
||||||
|
"features_file": "docs/features.md",
|
||||||
|
"changelog_file": "CHANGELOG.md",
|
||||||
|
"git_log_limit": 100,
|
||||||
|
"untagged_commit_limit": 50,
|
||||||
|
"top_files_limit": 20,
|
||||||
|
"exclude_dirs": ["node_modules", "venv", ".git"],
|
||||||
|
"source_extensions": [".py", ".cs", ".js"],
|
||||||
|
"templates": {
|
||||||
|
"status": "status_template.md",
|
||||||
|
"features": "feature_template.md",
|
||||||
|
"changelog": "changelog_template.md"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Output Files
|
||||||
|
|
||||||
|
The tracker generates the following files:
|
||||||
|
|
||||||
|
- `docs/features.md`: Feature tracking document
|
||||||
|
- `docs/status/status.md`: Project status document (or path specified in config)
|
||||||
|
- `CHANGELOG.md`: Project changelog
|
||||||
|
|
||||||
|
## Components
|
||||||
|
|
||||||
|
The tracker consists of the following components:
|
||||||
|
|
||||||
|
- `track_progress.py`: Main script that orchestrates the tracking process
|
||||||
|
- `config.py`: Configuration management
|
||||||
|
- `git_analyzer.py`: Git history analysis
|
||||||
|
- `code_stats.py`: Code statistics and metrics
|
||||||
|
- `feature_markdown.py`: Feature tracking utilities
|
||||||
|
- `template_engine.py`: Template rendering engine
|
||||||
|
- `changelog_generator.py`: Changelog generation
|
||||||
|
- `roadmap_generator.py`: Roadmap generation
|
||||||
|
|
||||||
|
## Building an Executable
|
||||||
|
|
||||||
|
You can build a standalone executable using Nuitka:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python scripts/track_progress/scripts/build_with_nuitka.py
|
||||||
|
```
|
||||||
|
|
||||||
|
This creates an executable in the `scripts/track_progress/dist` directory.
|
||||||
|
|
||||||
|
## Customizing Templates
|
||||||
|
|
||||||
|
The tracker uses Markdown templates to generate documentation. You can customize these templates in the `scripts/track_progress/templates` directory:
|
||||||
|
|
||||||
|
- `status_template.md`: Template for the status document
|
||||||
|
- `feature_template.md`: Template for the feature tracking document
|
||||||
|
- `changelog_template.md`: Template for the changelog
|
||||||
|
|
||||||
|
The templates use a simple syntax:
|
||||||
|
- `{{ variable }}`: Insert a variable
|
||||||
|
- `{% if condition %}...{% endif %}`: Conditional block
|
||||||
|
- `{% for item in items %}...{% endfor %}`: Loop over items
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Common Issues
|
||||||
|
|
||||||
|
1. **Missing Git Repository**: Ensure you're running the tracker in a Git repository.
|
||||||
|
2. **No Features Found**: Create a basic `docs/features.md` file with at least one feature.
|
||||||
|
3. **Template Not Found**: Check that templates exist in the templates directory.
|
||||||
|
|
||||||
|
### Debug Mode
|
||||||
|
|
||||||
|
Run with `--verbose` to see detailed output:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python scripts/track_progress/track_progress.py --verbose
|
||||||
|
```
|
||||||
1
scripts/track_progress/__init__.py
Normal file
1
scripts/track_progress/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
# This file makes the directory a Python package
|
||||||
103
scripts/track_progress/changelog_generator.py
Normal file
103
scripts/track_progress/changelog_generator.py
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
"""
|
||||||
|
Changelog generator for project documentation.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
from datetime import datetime
|
||||||
|
from typing import List, Dict, Any
|
||||||
|
|
||||||
|
|
||||||
|
def update_changelog_file(
|
||||||
|
changelog_entries: List[Dict[str, Any]],
|
||||||
|
changelog_file: str,
|
||||||
|
max_entries: int = 100,
|
||||||
|
verbose: bool = False,
|
||||||
|
) -> bool:
|
||||||
|
"""Update the changelog file with new entries."""
|
||||||
|
if not changelog_file:
|
||||||
|
if verbose:
|
||||||
|
print("No changelog file specified, skipping update")
|
||||||
|
return False
|
||||||
|
|
||||||
|
if verbose:
|
||||||
|
print(f"Updating changelog file: {changelog_file}")
|
||||||
|
print(f"Found {len(changelog_entries)} entries to add")
|
||||||
|
|
||||||
|
# Ensure the directory exists if the file is not in the current directory
|
||||||
|
changelog_dir = os.path.dirname(changelog_file)
|
||||||
|
if (
|
||||||
|
changelog_dir
|
||||||
|
): # Only create directory if there is one (not empty string)
|
||||||
|
os.makedirs(changelog_dir, exist_ok=True)
|
||||||
|
|
||||||
|
# Create the file if it doesn't exist
|
||||||
|
if not os.path.exists(changelog_file):
|
||||||
|
with open(changelog_file, "w", encoding="utf-8") as f:
|
||||||
|
f.write("# Changelog\n\n")
|
||||||
|
if verbose:
|
||||||
|
print(f"Created new changelog file: {changelog_file}")
|
||||||
|
|
||||||
|
# Read existing content
|
||||||
|
with open(changelog_file, "r", encoding="utf-8") as f:
|
||||||
|
content = f.read()
|
||||||
|
|
||||||
|
# Extract header (everything before the first entry)
|
||||||
|
header_end = content.find("## ")
|
||||||
|
if header_end == -1:
|
||||||
|
header = "# Changelog\n\n"
|
||||||
|
else:
|
||||||
|
header = content[:header_end]
|
||||||
|
|
||||||
|
# Format new entries
|
||||||
|
new_entries = []
|
||||||
|
|
||||||
|
# Group entries by date
|
||||||
|
entries_by_date = {}
|
||||||
|
for entry in changelog_entries:
|
||||||
|
date = entry.get("date", "")
|
||||||
|
if not date:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if date not in entries_by_date:
|
||||||
|
entries_by_date[date] = []
|
||||||
|
|
||||||
|
entries_by_date[date].append(entry)
|
||||||
|
|
||||||
|
# Sort dates in reverse chronological order
|
||||||
|
for date in sorted(entries_by_date.keys(), reverse=True):
|
||||||
|
entries = entries_by_date[date]
|
||||||
|
|
||||||
|
# Format date as a section header
|
||||||
|
new_entries.append(f"## {date}\n")
|
||||||
|
|
||||||
|
# Add each entry
|
||||||
|
for entry in entries:
|
||||||
|
message = entry.get("message", "").strip()
|
||||||
|
commit = entry.get("commit", "")
|
||||||
|
|
||||||
|
if message:
|
||||||
|
# Clean up the message
|
||||||
|
if message.startswith("- "):
|
||||||
|
message = message[2:]
|
||||||
|
|
||||||
|
new_entries.append(f"- {message} ({commit})")
|
||||||
|
|
||||||
|
new_entries.append("") # Add an empty line after each date section
|
||||||
|
|
||||||
|
# If no new entries, just return
|
||||||
|
if not new_entries:
|
||||||
|
if verbose:
|
||||||
|
print("No new entries to add to changelog")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Combine header and new entries
|
||||||
|
new_content = header + "\n".join(new_entries)
|
||||||
|
|
||||||
|
# Write the updated content
|
||||||
|
with open(changelog_file, "w", encoding="utf-8") as f:
|
||||||
|
f.write(new_content)
|
||||||
|
|
||||||
|
if verbose:
|
||||||
|
print(f"Updated changelog with {len(changelog_entries)} new entries")
|
||||||
|
|
||||||
|
return True
|
||||||
121
scripts/track_progress/code_stats.py
Normal file
121
scripts/track_progress/code_stats.py
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
import os
|
||||||
|
import glob
|
||||||
|
from typing import Dict, Any, List, Optional
|
||||||
|
|
||||||
|
|
||||||
|
def count_lines(file_path: str) -> int:
|
||||||
|
"""Count lines in a file."""
|
||||||
|
try:
|
||||||
|
with open(file_path, "r", encoding="utf-8", errors="ignore") as f:
|
||||||
|
return len(f.readlines())
|
||||||
|
except Exception:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def get_language(file_path: str) -> str:
|
||||||
|
"""Determine language from file extension."""
|
||||||
|
ext = os.path.splitext(file_path)[1].lower()
|
||||||
|
language_map = {
|
||||||
|
".py": "Python",
|
||||||
|
".cs": "C#",
|
||||||
|
".js": "JavaScript",
|
||||||
|
".ts": "TypeScript",
|
||||||
|
".html": "HTML",
|
||||||
|
".css": "CSS",
|
||||||
|
".md": "Markdown",
|
||||||
|
".json": "JSON",
|
||||||
|
".xml": "XML",
|
||||||
|
".java": "Java",
|
||||||
|
".cpp": "C++",
|
||||||
|
".c": "C",
|
||||||
|
".h": "C/C++ Header",
|
||||||
|
".go": "Go",
|
||||||
|
".rs": "Rust",
|
||||||
|
".php": "PHP",
|
||||||
|
".rb": "Ruby",
|
||||||
|
".sh": "Shell",
|
||||||
|
".bat": "Batch",
|
||||||
|
".ps1": "PowerShell",
|
||||||
|
".sql": "SQL",
|
||||||
|
".yaml": "YAML",
|
||||||
|
".yml": "YAML",
|
||||||
|
}
|
||||||
|
return language_map.get(ext, "Other")
|
||||||
|
|
||||||
|
|
||||||
|
def analyze_code_stats(
|
||||||
|
root_dir: str = ".",
|
||||||
|
exclude_dirs: List[str] = None,
|
||||||
|
source_extensions: List[str] = None,
|
||||||
|
top_files_limit: int = 20,
|
||||||
|
) -> Dict[str, Any]:
|
||||||
|
"""Analyze code statistics for the project."""
|
||||||
|
if exclude_dirs is None:
|
||||||
|
exclude_dirs = ["node_modules", "venv", ".git", ".vs", "bin", "obj"]
|
||||||
|
|
||||||
|
if source_extensions is None:
|
||||||
|
source_extensions = [
|
||||||
|
".py",
|
||||||
|
".cs",
|
||||||
|
".js",
|
||||||
|
".ts",
|
||||||
|
".html",
|
||||||
|
".css",
|
||||||
|
".md",
|
||||||
|
]
|
||||||
|
|
||||||
|
stats = {
|
||||||
|
"total_lines": 0,
|
||||||
|
"file_count": 0,
|
||||||
|
"languages": {},
|
||||||
|
"top_files": [],
|
||||||
|
}
|
||||||
|
|
||||||
|
file_stats = []
|
||||||
|
|
||||||
|
# Convert exclude_dirs to absolute paths
|
||||||
|
exclude_paths = [os.path.join(root_dir, d) for d in exclude_dirs]
|
||||||
|
|
||||||
|
# Find all source files
|
||||||
|
for ext in source_extensions:
|
||||||
|
pattern = os.path.join(root_dir, "**", f"*{ext}")
|
||||||
|
for file_path in glob.glob(pattern, recursive=True):
|
||||||
|
# Skip excluded directories
|
||||||
|
if any(
|
||||||
|
file_path.startswith(exclude_path)
|
||||||
|
for exclude_path in exclude_paths
|
||||||
|
):
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Count lines
|
||||||
|
lines = count_lines(file_path)
|
||||||
|
|
||||||
|
# Get language
|
||||||
|
language = get_language(file_path)
|
||||||
|
|
||||||
|
# Update statistics
|
||||||
|
stats["total_lines"] += lines
|
||||||
|
stats["file_count"] += 1
|
||||||
|
|
||||||
|
# Update language count
|
||||||
|
if language not in stats["languages"]:
|
||||||
|
stats["languages"][language] = 0
|
||||||
|
stats["languages"][language] += 1
|
||||||
|
|
||||||
|
# Add to file stats
|
||||||
|
file_stats.append(
|
||||||
|
{
|
||||||
|
"path": file_path,
|
||||||
|
"name": os.path.basename(file_path),
|
||||||
|
"lines": lines,
|
||||||
|
"language": language,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Sort file stats by line count
|
||||||
|
file_stats.sort(key=lambda x: x["lines"], reverse=True)
|
||||||
|
|
||||||
|
# Get top files
|
||||||
|
stats["top_files"] = file_stats[:top_files_limit]
|
||||||
|
|
||||||
|
return stats
|
||||||
87
scripts/track_progress/config.py
Normal file
87
scripts/track_progress/config.py
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
"""
|
||||||
|
Configuration management for project status documentation.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import json
|
||||||
|
from typing import Dict, Any, List, Optional
|
||||||
|
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
"""Configuration manager for project status documentation."""
|
||||||
|
|
||||||
|
def __init__(self, config_file: str = "scripts/progress_config.json"):
|
||||||
|
"""Initialize with configuration file."""
|
||||||
|
self.config_file = config_file
|
||||||
|
self.config = self._load_config()
|
||||||
|
|
||||||
|
def _load_config(self) -> Dict[str, Any]:
|
||||||
|
"""Load configuration from file."""
|
||||||
|
default_config = {
|
||||||
|
"project_name": "Project",
|
||||||
|
"output_dir": "docs/status",
|
||||||
|
"status_doc": "status.md",
|
||||||
|
"features_file": "docs/features.md",
|
||||||
|
"changelog_file": "CHANGELOG.md",
|
||||||
|
"git_log_limit": 100,
|
||||||
|
"untagged_commit_limit": 50,
|
||||||
|
"top_files_limit": 20,
|
||||||
|
"exclude_dirs": [
|
||||||
|
"node_modules",
|
||||||
|
"venv",
|
||||||
|
".git",
|
||||||
|
".vs",
|
||||||
|
"bin",
|
||||||
|
"obj",
|
||||||
|
],
|
||||||
|
"source_extensions": [".py", ".cs", ".js", ".ts", ".html", ".css"],
|
||||||
|
"templates": {
|
||||||
|
"status": "status_template.md",
|
||||||
|
"features": "feature_template.md",
|
||||||
|
"changelog": "changelog_template.md",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if not os.path.exists(self.config_file):
|
||||||
|
return default_config
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(self.config_file, "r", encoding="utf-8") as f:
|
||||||
|
config = json.load(f)
|
||||||
|
|
||||||
|
# Merge with default config to ensure all keys exist
|
||||||
|
merged_config = default_config.copy()
|
||||||
|
merged_config.update(config)
|
||||||
|
|
||||||
|
return merged_config
|
||||||
|
except Exception:
|
||||||
|
return default_config
|
||||||
|
|
||||||
|
def get(self, key: str, default: Any = None) -> Any:
|
||||||
|
"""Get a configuration value."""
|
||||||
|
if "." in key:
|
||||||
|
# Handle nested keys like "templates.status"
|
||||||
|
parts = key.split(".")
|
||||||
|
value = self.config
|
||||||
|
for part in parts:
|
||||||
|
if isinstance(value, dict) and part in value:
|
||||||
|
value = value[part]
|
||||||
|
else:
|
||||||
|
return default
|
||||||
|
return value
|
||||||
|
|
||||||
|
return self.config.get(key, default)
|
||||||
|
|
||||||
|
def set(self, key: str, value: Any) -> None:
|
||||||
|
"""Set a configuration value."""
|
||||||
|
if "." in key:
|
||||||
|
# Handle nested keys
|
||||||
|
parts = key.split(".")
|
||||||
|
config = self.config
|
||||||
|
for part in parts[:-1]:
|
||||||
|
if part not in config:
|
||||||
|
config[part] = {}
|
||||||
|
config = config[part]
|
||||||
|
config[parts[-1]] = value
|
||||||
|
else:
|
||||||
|
self.config[key] = value
|
||||||
218
scripts/track_progress/docs/features.md
Normal file
218
scripts/track_progress/docs/features.md
Normal file
@ -0,0 +1,218 @@
|
|||||||
|
# Feature Tracking
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This document tracks the implementation status of all features in the Project project.
|
||||||
|
|
||||||
|
## Feature Status Summary
|
||||||
|
|
||||||
|
| Status | Description |
|
||||||
|
|--------|-------------|
|
||||||
|
| ✅ Completed | Features that are fully implemented and tested |
|
||||||
|
| 🔄 In Progress | Features currently being implemented |
|
||||||
|
| 📝 Planned | Features planned for future implementation |
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
### 2d3d_chunk_management
|
||||||
|
|
||||||
|
**Status:** ✅ completed
|
||||||
|
**Description:** 2D/3D chunk management with memory efficiency
|
||||||
|
**Last Update:** 2025-05-10
|
||||||
|
|
||||||
|
### asynchronous_chunk_loading
|
||||||
|
|
||||||
|
**Status:** ✅ completed
|
||||||
|
**Description:** Asynchronous chunk loading and processing
|
||||||
|
**Last Update:** 2025-05-10
|
||||||
|
|
||||||
|
### autogenerate_status_docs
|
||||||
|
|
||||||
|
**Status:** 🔄 in_progress
|
||||||
|
**Description:** Auto-generate status docs and changelog (partially implemented)
|
||||||
|
**Last Update:** 2025-05-10
|
||||||
|
|
||||||
|
### better_async_exception
|
||||||
|
|
||||||
|
**Status:** 🔄 in_progress
|
||||||
|
**Description:** Better async exception handling with specific types and logging
|
||||||
|
**Last Update:** 2025-05-10
|
||||||
|
|
||||||
|
### chunk_serialization_and
|
||||||
|
|
||||||
|
**Status:** ✅ completed
|
||||||
|
**Description:** Chunk serialization and deserialization
|
||||||
|
**Last Update:** 2025-05-10
|
||||||
|
|
||||||
|
### dependencyaware_disposal_logic
|
||||||
|
|
||||||
|
**Status:** 🔄 in_progress
|
||||||
|
**Description:** Dependency-aware disposal logic (Partially Implemented)
|
||||||
|
**Last Update:** 2025-05-10
|
||||||
|
|
||||||
|
### edge_chunk_unload
|
||||||
|
|
||||||
|
**Status:** 🔄 in_progress
|
||||||
|
**Description:** Edge chunk unload delay under high concurrency
|
||||||
|
**Last Update:** 2025-05-10
|
||||||
|
|
||||||
|
### events_for_loadunloadsave
|
||||||
|
|
||||||
|
**Status:** ✅ completed
|
||||||
|
**Description:** Events for load/unload/save operations
|
||||||
|
**Last Update:** 2025-05-10
|
||||||
|
|
||||||
|
### explicit_synchronization_and
|
||||||
|
|
||||||
|
**Status:** ✅ completed
|
||||||
|
**Description:** Explicit synchronization and concurrent collections
|
||||||
|
**Last Update:** 2025-05-11
|
||||||
|
|
||||||
|
### full_dispose_pattern
|
||||||
|
|
||||||
|
**Status:** 🔄 in_progress
|
||||||
|
**Description:** Full dispose pattern with finalizers for unmanaged resources (Partially Implemented)
|
||||||
|
**Last Update:** 2025-05-10
|
||||||
|
|
||||||
|
### interfacelevel_dependency_methods
|
||||||
|
|
||||||
|
**Status:** 📝 planned
|
||||||
|
**Description:** Interface-level dependency methods
|
||||||
|
**Last Update:** 2025-05-10
|
||||||
|
|
||||||
|
### interfacelevel_spatial_query
|
||||||
|
|
||||||
|
**Status:** ✅ completed
|
||||||
|
**Description:** Interface-level spatial query methods with support for custom filters
|
||||||
|
**Last Update:** 2025-05-11
|
||||||
|
|
||||||
|
### lru_caching_array
|
||||||
|
|
||||||
|
**Status:** ✅ completed
|
||||||
|
**Description:** LRU caching, array pooling, and air-singleton patterns
|
||||||
|
**Last Update:** 2025-05-10
|
||||||
|
|
||||||
|
### parallel_processing_of
|
||||||
|
|
||||||
|
**Status:** ✅ completed
|
||||||
|
**Description:** Parallel processing of chunks based on spatial queries and regions
|
||||||
|
**Last Update:** 2025-05-11
|
||||||
|
|
||||||
|
### parse_git_logs
|
||||||
|
|
||||||
|
**Status:** ✅ completed
|
||||||
|
**Description:** Parse Git logs for status updates (partially implemented testing with this push.)
|
||||||
|
**Last Update:** 2025-05-12
|
||||||
|
|
||||||
|
### prioritized_chunk_loading
|
||||||
|
|
||||||
|
**Status:** 🔄 in_progress
|
||||||
|
**Description:** Prioritized chunk loading system (partially implemented)
|
||||||
|
**Last Update:** 2025-05-10
|
||||||
|
|
||||||
|
### python_and_net
|
||||||
|
|
||||||
|
**Status:** ✅ completed
|
||||||
|
**Description:** Python and .NET interoperability
|
||||||
|
**Last Update:** 2025-05-10
|
||||||
|
|
||||||
|
### runtimeadjustable_configuration_options
|
||||||
|
|
||||||
|
**Status:** 📝 planned
|
||||||
|
**Description:** Runtime-adjustable configuration options
|
||||||
|
**Last Update:** 2025-05-10
|
||||||
|
|
||||||
|
### spatial_indexing_and
|
||||||
|
|
||||||
|
**Status:** ✅ completed
|
||||||
|
**Description:** Spatial indexing and region queries with 2D/3D support and quadtree optimization
|
||||||
|
**Last Update:** 2025-05-11
|
||||||
|
|
||||||
|
### track_and_manage
|
||||||
|
|
||||||
|
**Status:** ✅ completed
|
||||||
|
**Description:** Track and manage chunk resources
|
||||||
|
**Last Update:** 2025-05-10
|
||||||
|
|
||||||
|
### track_load_times
|
||||||
|
|
||||||
|
**Status:** 🔄 in_progress
|
||||||
|
**Description:** Track load times, cache hit rates, and memory usage (partially implemented)
|
||||||
|
**Last Update:** 2025-05-10
|
||||||
|
|
||||||
|
### track_progress_and feature_statuses
|
||||||
|
|
||||||
|
**Status:** ✅ completed
|
||||||
|
**Description:** Track progress and auto-update status documents (you can use cron or something or a vs commit script that requires running this after committing)
|
||||||
|
**Last Update:** 2025-05-12
|
||||||
|
|
||||||
|
## Feature Categories
|
||||||
|
|
||||||
|
### Completed Features
|
||||||
|
|
||||||
|
- **2d3d_chunk_management**: 2D/3D chunk management with memory efficiency
|
||||||
|
- **asynchronous_chunk_loading**: Asynchronous chunk loading and processing
|
||||||
|
- **chunk_serialization_and**: Chunk serialization and deserialization
|
||||||
|
- **events_for_loadunloadsave**: Events for load/unload/save operations
|
||||||
|
- **explicit_synchronization_and**: Explicit synchronization and concurrent collections
|
||||||
|
- **interfacelevel_spatial_query**: Interface-level spatial query methods with support for custom filters
|
||||||
|
- **lru_caching_array**: LRU caching, array pooling, and air-singleton patterns
|
||||||
|
- **parallel_processing_of**: Parallel processing of chunks based on spatial queries and regions
|
||||||
|
- **python_and_net**: Python and .NET interoperability
|
||||||
|
- **spatial_indexing_and**: Spatial indexing and region queries with 2D/3D support and quadtree optimization
|
||||||
|
|
||||||
|
### In Progress Features
|
||||||
|
|
||||||
|
- **autogenerate_status_docs**: Auto-generate status docs and changelog (partially implemented)
|
||||||
|
- **better_async_exception**: Better async exception handling with specific types and logging
|
||||||
|
- **dependencyaware_disposal_logic**: Dependency-aware disposal logic (Partially Implemented)
|
||||||
|
- **edge_chunk_unload**: Edge chunk unload delay under high concurrency
|
||||||
|
- **full_dispose_pattern**: Full dispose pattern with finalizers for unmanaged resources (Partially Implemented)
|
||||||
|
- **parse_git_logs**: Parse Git logs for status updates (partially implemented testing with this push.)
|
||||||
|
- **prioritized_chunk_loading**: Prioritized chunk loading system (partially implemented)
|
||||||
|
- **track_load_times**: Track load times, cache hit rates, and memory usage (partially implemented)
|
||||||
|
|
||||||
|
### Planned Features
|
||||||
|
|
||||||
|
- **interfacelevel_dependency_methods**: Interface-level dependency methods
|
||||||
|
- **runtimeadjustable_configuration_options**: Runtime-adjustable configuration options
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.feature-card {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 5px;
|
||||||
|
border-left: 5px solid #ccc;
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-card.completed {
|
||||||
|
border-left-color: #28a745;
|
||||||
|
background-color: #f0fff0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-card.in_progress {
|
||||||
|
border-left-color: #007bff;
|
||||||
|
background-color: #f0f8ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-card.planned {
|
||||||
|
border-left-color: #6c757d;
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-status {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-date {
|
||||||
|
color: #6c757d;
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
100
scripts/track_progress/docs/status/status.md
Normal file
100
scripts/track_progress/docs/status/status.md
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
# Project Development Status
|
||||||
|
|
||||||
|
Last updated: 2025-05-12
|
||||||
|
|
||||||
|
## Code Statistics
|
||||||
|
|
||||||
|
|
||||||
|
Total lines of code: **2151**
|
||||||
|
|
||||||
|
Number of source files: **15**
|
||||||
|
|
||||||
|
### Files by Language
|
||||||
|
|
||||||
|
- **Python**: 15 files
|
||||||
|
|
||||||
|
|
||||||
|
### Top Files by Line Count
|
||||||
|
|
||||||
|
| File | Lines | Language | Path |
|
||||||
|
|------|------:|----------|------|
|
||||||
|
|
||||||
|
| 2D3D Chunk Management | ✅ completed | 2D/3D chunk management with memory efficiency | 2025-05-10 | |
|
||||||
|
|
||||||
|
| Asynchronous Chunk Loading | ✅ completed | Asynchronous chunk loading and processing | 2025-05-10 | |
|
||||||
|
|
||||||
|
| Autogenerate Status Docs | 🔄 in_progress | Auto-generate status docs and changelog (partially implemented) | 2025-05-10 | |
|
||||||
|
|
||||||
|
| Better Async Exception | 🔄 in_progress | Better async exception handling with specific types and logging | 2025-05-10 | |
|
||||||
|
|
||||||
|
| Chunk Serialization And | ✅ completed | Chunk serialization and deserialization | 2025-05-10 | |
|
||||||
|
|
||||||
|
| Dependencyaware Disposal Logic | 🔄 in_progress | Dependency-aware disposal logic (Partially Implemented) | 2025-05-10 | |
|
||||||
|
|
||||||
|
| Edge Chunk Unload | 🔄 in_progress | Edge chunk unload delay under high concurrency | 2025-05-10 | |
|
||||||
|
|
||||||
|
| Events For Loadunloadsave | ✅ completed | Events for load/unload/save operations | 2025-05-10 | |
|
||||||
|
|
||||||
|
| Explicit Synchronization And | ✅ completed | Explicit synchronization and concurrent collections | 2025-05-11 | |
|
||||||
|
|
||||||
|
| Full Dispose Pattern | 🔄 in_progress | Full dispose pattern with finalizers for unmanaged resources (Partially Implemented) | 2025-05-10 | |
|
||||||
|
|
||||||
|
| Interfacelevel Dependency Methods | 📝 planned | Interface-level dependency methods | 2025-05-10 | |
|
||||||
|
|
||||||
|
| Interfacelevel Spatial Query | ✅ completed | Interface-level spatial query methods with support for custom filters | 2025-05-11 | |
|
||||||
|
|
||||||
|
| Lru Caching Array | ✅ completed | LRU caching, array pooling, and air-singleton patterns | 2025-05-10 | |
|
||||||
|
|
||||||
|
| Parallel Processing Of | ✅ completed | Parallel processing of chunks based on spatial queries and regions | 2025-05-11 | |
|
||||||
|
|
||||||
|
| Parse Git Logs | 🔄 in_progress | Parse Git logs for status updates (partially implemented testing with this push.) | 2025-05-11 | |
|
||||||
|
|
||||||
|
| Prioritized Chunk Loading | 🔄 in_progress | Prioritized chunk loading system (partially implemented) | 2025-05-10 | |
|
||||||
|
|
||||||
|
| Python And Net | ✅ completed | Python and .NET interoperability | 2025-05-10 | |
|
||||||
|
|
||||||
|
| Runtimeadjustable Configuration Options | 📝 planned | Runtime-adjustable configuration options | 2025-05-10 | |
|
||||||
|
|
||||||
|
| Spatial Indexing And | ✅ completed | Spatial indexing and region queries with 2D/3D support and quadtree optimization | 2025-05-11 | |
|
||||||
|
|
||||||
|
| Track And Manage | ✅ completed | Track and manage chunk resources | 2025-05-10 | |
|
||||||
|
|
||||||
|
| Track Feature Statuses | ✅ completed | Track feature statuses | 2025-05-11 | |
|
||||||
|
|
||||||
|
| Track Load Times | 🔄 in_progress | Track load times, cache hit rates, and memory usage (partially implemented) | 2025-05-10 | |
|
||||||
|
|
||||||
|
| Track Progress And | 🔄 in_progress | Track progress and auto-update status documents (mostly implemented kinda still manual...) | 2025-05-11 | |
|
||||||
|
|
||||||
|
|
||||||
|
## Recent Updates
|
||||||
|
|
||||||
|
|
||||||
|
- 2025-05-11: part of the code formatter fix and minor bug fixes (5352e90)
|
||||||
|
|
||||||
|
- 2025-05-11: fixed code formatter (01d2cbf)
|
||||||
|
|
||||||
|
- 2025-05-11: fixed track_progress not getting git history it was not getting the data correctly (657e19f)
|
||||||
|
|
||||||
|
- 2025-05-11: removed unnecessary ignores in gitignore (79d6716)
|
||||||
|
|
||||||
|
- 2025-05-11: XML comments added Builds cleanly (0ae815c)
|
||||||
|
|
||||||
|
- 2025-05-11: updated track_progress restored accidentally deleted source file (8a6f914)
|
||||||
|
|
||||||
|
- 2025-05-10: fixed a bug of not building (09ee0d8)
|
||||||
|
|
||||||
|
- 2025-05-10: added a build (e59a909)
|
||||||
|
|
||||||
|
- 2025-05-10: updated ChunkMark (f35c612)
|
||||||
|
|
||||||
|
- 2025-05-10: updated memory memory handling to be more safe (becedcc)
|
||||||
|
|
||||||
|
- 2025-05-09: first commit (f1b3868)
|
||||||
|
|
||||||
|
|
||||||
|
{% if known_issues %}
|
||||||
|
## Known Issues
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
317
scripts/track_progress/feature_markdown.py
Normal file
317
scripts/track_progress/feature_markdown.py
Normal file
@ -0,0 +1,317 @@
|
|||||||
|
"""
|
||||||
|
Feature markdown utilities for project status documentation.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
from typing import Dict, Any, List, Optional
|
||||||
|
import re
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
# Mapping of status to emojis
|
||||||
|
STATUS_EMOJIS = {
|
||||||
|
"completed": "✅",
|
||||||
|
"in_progress": "🔄",
|
||||||
|
"planned": "📝",
|
||||||
|
"error": "☣️",
|
||||||
|
"blocked": "⛔",
|
||||||
|
"failed": "❌",
|
||||||
|
"warning": "⚠️",
|
||||||
|
"unknown": "❓",
|
||||||
|
"done": "✅",
|
||||||
|
"testing": "🧪",
|
||||||
|
"review": "👀",
|
||||||
|
"design": "🎨",
|
||||||
|
"research": "🔍",
|
||||||
|
"deprecated": "🗑️",
|
||||||
|
"postponed": "⏳",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def emoji_status(status: str) -> str:
|
||||||
|
"""Return emoji for a status."""
|
||||||
|
return STATUS_EMOJIS.get(status.lower(), STATUS_EMOJIS["unknown"])
|
||||||
|
|
||||||
|
|
||||||
|
def format_feature_md(feature_name: str, data: dict) -> str:
|
||||||
|
"""Format a single feature into Markdown."""
|
||||||
|
status = data.get("status", "unknown")
|
||||||
|
status_emoji = emoji_status(status)
|
||||||
|
|
||||||
|
lines = [f"## {feature_name}"]
|
||||||
|
lines.append(f"- Status: {status_emoji} {status}")
|
||||||
|
|
||||||
|
if "description" in data:
|
||||||
|
lines.append(f"- Description: {data['description']}")
|
||||||
|
|
||||||
|
if "date" in data:
|
||||||
|
lines.append(f"- Last Update: {data['date']}")
|
||||||
|
|
||||||
|
if "author" in data:
|
||||||
|
lines.append(f"- Updated By: {data['author']}")
|
||||||
|
|
||||||
|
if "details" in data:
|
||||||
|
lines.append(f"- Details: {data['details']}")
|
||||||
|
|
||||||
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
|
||||||
|
def build_feature_md(features: dict, title: str = "# Feature Tracking") -> str:
|
||||||
|
"""Build the full Markdown document from features dictionary."""
|
||||||
|
lines = [title, ""]
|
||||||
|
|
||||||
|
for feature_name, data in features.items():
|
||||||
|
lines.append(format_feature_md(feature_name, data))
|
||||||
|
lines.append("") # Add an empty line between features
|
||||||
|
|
||||||
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
|
||||||
|
def extract_features(
|
||||||
|
features_file: str, verbose: bool = False
|
||||||
|
) -> Dict[str, Dict[str, Any]]:
|
||||||
|
"""Extract all valid features from the file, focusing on descriptions."""
|
||||||
|
features = {}
|
||||||
|
seen_descriptions = set()
|
||||||
|
|
||||||
|
if not os.path.exists(features_file):
|
||||||
|
if verbose:
|
||||||
|
print(f"File not found: {features_file}")
|
||||||
|
return features
|
||||||
|
|
||||||
|
with open(features_file, "r", encoding="utf-8") as f:
|
||||||
|
content = f.read()
|
||||||
|
|
||||||
|
# First, try to extract features from the specific format
|
||||||
|
# This format has ### feature_name followed by status, description, date
|
||||||
|
status_blocks = re.findall(
|
||||||
|
r"### ([^\n]+)\s+\*\*Status:\*\* ([^\n]+)\s+\*\*Description:\*\* ([^\n]+)\s+\*\*Last Update:\*\* ([^\n]+)",
|
||||||
|
content,
|
||||||
|
)
|
||||||
|
|
||||||
|
for feature_key, status_line, desc, date in status_blocks:
|
||||||
|
desc = desc.strip()
|
||||||
|
date = date.strip()
|
||||||
|
|
||||||
|
if desc and len(desc) > 2 and desc not in seen_descriptions:
|
||||||
|
seen_descriptions.add(desc)
|
||||||
|
|
||||||
|
# Extract status without emoji
|
||||||
|
status_match = re.search(
|
||||||
|
r"[^a-zA-Z]*([a-zA-Z_]+)", status_line
|
||||||
|
)
|
||||||
|
status = (
|
||||||
|
status_match.group(1).lower()
|
||||||
|
if status_match
|
||||||
|
else "planned"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Clean up the feature key
|
||||||
|
clean_key = re.sub(
|
||||||
|
r"[^a-zA-Z0-9_]", "", feature_key.replace(" ", "_").lower()
|
||||||
|
)
|
||||||
|
if not clean_key or len(clean_key) < 2:
|
||||||
|
# Generate key from description
|
||||||
|
words = desc.split()[:3]
|
||||||
|
clean_key = "_".join(words).lower()
|
||||||
|
clean_key = re.sub(r"[^a-zA-Z0-9_]", "", clean_key)
|
||||||
|
|
||||||
|
features[clean_key] = {
|
||||||
|
"status": status,
|
||||||
|
"description": desc,
|
||||||
|
"date": date,
|
||||||
|
"author": "",
|
||||||
|
"details": "",
|
||||||
|
}
|
||||||
|
|
||||||
|
# Also try the format: ### **Status:** status \n **Description:** desc \n **Last Update:** date
|
||||||
|
alt_blocks = re.findall(
|
||||||
|
r"### \*\*Status:\*\* ([^\n]+)\s+\*\*Description:\*\* ([^\n]+)\s+\*\*Last Update:\*\* ([^\n]+)",
|
||||||
|
content,
|
||||||
|
)
|
||||||
|
|
||||||
|
for status_line, desc, date in alt_blocks:
|
||||||
|
desc = desc.strip()
|
||||||
|
date = date.strip()
|
||||||
|
|
||||||
|
if desc and len(desc) > 2 and desc not in seen_descriptions:
|
||||||
|
seen_descriptions.add(desc)
|
||||||
|
|
||||||
|
# Extract status without emoji
|
||||||
|
status_match = re.search(
|
||||||
|
r"[^a-zA-Z]*([a-zA-Z_]+)", status_line
|
||||||
|
)
|
||||||
|
status = (
|
||||||
|
status_match.group(1).lower()
|
||||||
|
if status_match
|
||||||
|
else "planned"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Generate key from description
|
||||||
|
words = desc.split()[:3]
|
||||||
|
clean_key = "_".join(words).lower()
|
||||||
|
clean_key = re.sub(r"[^a-zA-Z0-9_]", "", clean_key)
|
||||||
|
|
||||||
|
features[clean_key] = {
|
||||||
|
"status": status,
|
||||||
|
"description": desc,
|
||||||
|
"date": date,
|
||||||
|
"author": "",
|
||||||
|
"details": "",
|
||||||
|
}
|
||||||
|
|
||||||
|
return features
|
||||||
|
|
||||||
|
|
||||||
|
def enrich_features(
|
||||||
|
features: Dict[str, Dict[str, Any]]
|
||||||
|
) -> Dict[str, Dict[str, Any]]:
|
||||||
|
"""Add additional information to features for display purposes."""
|
||||||
|
enriched = {}
|
||||||
|
|
||||||
|
for key, data in features.items():
|
||||||
|
enriched[key] = data.copy()
|
||||||
|
status = data.get("status", "unknown")
|
||||||
|
enriched[key]["status_emoji"] = emoji_status(status)
|
||||||
|
|
||||||
|
# Add title-cased key for display
|
||||||
|
enriched[key]["display_name"] = key.replace("_", " ").title()
|
||||||
|
|
||||||
|
return enriched
|
||||||
|
|
||||||
|
|
||||||
|
def generate_features_md(
|
||||||
|
features: Dict[str, Dict[str, Any]], project_name: str
|
||||||
|
) -> str:
|
||||||
|
"""Generate a clean features markdown from the features dictionary."""
|
||||||
|
lines = [
|
||||||
|
"# Feature Tracking",
|
||||||
|
"",
|
||||||
|
"## Overview",
|
||||||
|
"",
|
||||||
|
f"This document tracks the implementation status of all features in the {project_name} project.",
|
||||||
|
"",
|
||||||
|
"## Feature Status Summary",
|
||||||
|
"",
|
||||||
|
"| Status | Description |",
|
||||||
|
"|--------|-------------|",
|
||||||
|
"| ✅ Completed | Features that are fully implemented and tested |",
|
||||||
|
"| 🔄 In Progress | Features currently being implemented |",
|
||||||
|
"| 📝 Planned | Features planned for future implementation |",
|
||||||
|
"",
|
||||||
|
"## Features",
|
||||||
|
"",
|
||||||
|
]
|
||||||
|
|
||||||
|
# Add each feature
|
||||||
|
for feature_key, feature in sorted(features.items()):
|
||||||
|
status = feature.get("status", "planned").lower()
|
||||||
|
status_emoji = emoji_status(status)
|
||||||
|
description = feature.get("description", "")
|
||||||
|
date = feature.get("date", "") or datetime.now().strftime("%Y-%m-%d")
|
||||||
|
author = feature.get("author", "")
|
||||||
|
details = feature.get("details", "")
|
||||||
|
|
||||||
|
lines.append(f"### {feature_key}")
|
||||||
|
lines.append("")
|
||||||
|
lines.append(f"**Status:** {status_emoji} {status}")
|
||||||
|
lines.append(f"**Description:** {description}")
|
||||||
|
lines.append(f"**Last Update:** {date}")
|
||||||
|
|
||||||
|
if author:
|
||||||
|
lines.append(f"**Owner:** {author}")
|
||||||
|
|
||||||
|
if details:
|
||||||
|
lines.append(f"**Details:** {details}")
|
||||||
|
|
||||||
|
lines.append("")
|
||||||
|
|
||||||
|
# Add categorized lists
|
||||||
|
lines.append("## Feature Categories")
|
||||||
|
lines.append("")
|
||||||
|
|
||||||
|
# Completed features
|
||||||
|
lines.append("### Completed Features")
|
||||||
|
lines.append("")
|
||||||
|
completed_count = 0
|
||||||
|
for feature_key, feature in sorted(features.items()):
|
||||||
|
if feature.get("status", "").lower() in ["completed", "done"]:
|
||||||
|
lines.append(
|
||||||
|
f"- **{feature_key}**: {feature.get('description', '')}"
|
||||||
|
)
|
||||||
|
completed_count += 1
|
||||||
|
if completed_count == 0:
|
||||||
|
lines.append("*No completed features yet.*")
|
||||||
|
lines.append("")
|
||||||
|
|
||||||
|
# In progress features
|
||||||
|
lines.append("### In Progress Features")
|
||||||
|
lines.append("")
|
||||||
|
in_progress_count = 0
|
||||||
|
for feature_key, feature in sorted(features.items()):
|
||||||
|
if feature.get("status", "").lower() == "in_progress":
|
||||||
|
lines.append(
|
||||||
|
f"- **{feature_key}**: {feature.get('description', '')}"
|
||||||
|
)
|
||||||
|
in_progress_count += 1
|
||||||
|
if in_progress_count == 0:
|
||||||
|
lines.append("*No features currently in progress.*")
|
||||||
|
lines.append("")
|
||||||
|
|
||||||
|
# Planned features
|
||||||
|
lines.append("### Planned Features")
|
||||||
|
lines.append("")
|
||||||
|
planned_count = 0
|
||||||
|
for feature_key, feature in sorted(features.items()):
|
||||||
|
if feature.get("status", "").lower() == "planned":
|
||||||
|
lines.append(
|
||||||
|
f"- **{feature_key}**: {feature.get('description', '')}"
|
||||||
|
)
|
||||||
|
planned_count += 1
|
||||||
|
if planned_count == 0:
|
||||||
|
lines.append("*No planned features yet.*")
|
||||||
|
lines.append("")
|
||||||
|
|
||||||
|
# Add CSS styling
|
||||||
|
lines.append(
|
||||||
|
"""<style>
|
||||||
|
.feature-card {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 5px;
|
||||||
|
border-left: 5px solid #ccc;
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-card.completed {
|
||||||
|
border-left-color: #28a745;
|
||||||
|
background-color: #f0fff0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-card.in_progress {
|
||||||
|
border-left-color: #007bff;
|
||||||
|
background-color: #f0f8ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-card.planned {
|
||||||
|
border-left-color: #6c757d;
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-status {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-date {
|
||||||
|
color: #6c757d;
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
</style>"""
|
||||||
|
)
|
||||||
|
|
||||||
|
return "\n".join(lines)
|
||||||
238
scripts/track_progress/git_analyzer.py
Normal file
238
scripts/track_progress/git_analyzer.py
Normal file
@ -0,0 +1,238 @@
|
|||||||
|
import os
|
||||||
|
import re
|
||||||
|
import subprocess
|
||||||
|
from datetime import datetime
|
||||||
|
from typing import Dict, Any, List, Optional, Tuple
|
||||||
|
|
||||||
|
|
||||||
|
class GitAnalyzer:
|
||||||
|
"""Analyze Git repository for project status information."""
|
||||||
|
|
||||||
|
def __init__(self, repo_path: str = "."):
|
||||||
|
"""Initialize with repository path."""
|
||||||
|
self.repo_path = repo_path
|
||||||
|
|
||||||
|
def _run_git_command(self, command: List[str]) -> str:
|
||||||
|
"""Run a Git command and return the output."""
|
||||||
|
try:
|
||||||
|
result = subprocess.run(
|
||||||
|
["git"] + command,
|
||||||
|
cwd=self.repo_path,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
check=True,
|
||||||
|
)
|
||||||
|
return result.stdout.strip()
|
||||||
|
except subprocess.CalledProcessError:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def get_repo_info(self) -> Dict[str, Any]:
|
||||||
|
"""Get basic repository information."""
|
||||||
|
info = {}
|
||||||
|
|
||||||
|
# Get remote URL
|
||||||
|
remote_url = self._run_git_command(["remote", "get-url", "origin"])
|
||||||
|
info["remote_url"] = remote_url
|
||||||
|
|
||||||
|
# Get current branch
|
||||||
|
branch = self._run_git_command(["branch", "--show-current"])
|
||||||
|
info["branch"] = branch
|
||||||
|
|
||||||
|
# Get last commit
|
||||||
|
last_commit = self._run_git_command(
|
||||||
|
["log", "-1", "--pretty=format:%h|%s|%ad|%an", "--date=short"]
|
||||||
|
)
|
||||||
|
if last_commit:
|
||||||
|
parts = last_commit.split("|")
|
||||||
|
if len(parts) >= 4:
|
||||||
|
info["last_commit"] = {
|
||||||
|
"hash": parts[0],
|
||||||
|
"message": parts[1],
|
||||||
|
"date": parts[2],
|
||||||
|
"author": parts[3],
|
||||||
|
}
|
||||||
|
|
||||||
|
return info
|
||||||
|
|
||||||
|
def analyze_commits(
|
||||||
|
self, limit: int = 100, untagged_limit: int = 50
|
||||||
|
) -> Dict[str, Any]:
|
||||||
|
"""Analyze Git commits for feature updates, changelog entries, etc."""
|
||||||
|
result = {
|
||||||
|
"feature_updates": {},
|
||||||
|
"new_features": {},
|
||||||
|
"changelog_entries": [],
|
||||||
|
"fixes": [],
|
||||||
|
"issues": [],
|
||||||
|
"untagged_commits": [],
|
||||||
|
"milestones": {},
|
||||||
|
"roadmap_items": {},
|
||||||
|
}
|
||||||
|
|
||||||
|
# Regular expressions for parsing commit messages
|
||||||
|
status_re = re.compile(r"\[status:(\w+)\]")
|
||||||
|
feature_re = re.compile(r"\[feature:(\w+)\]")
|
||||||
|
new_feature_re = re.compile(r"\[new-feature:(\w+):(.+?)\]")
|
||||||
|
changelog_re = re.compile(r"\[changelog:(.+?)\]")
|
||||||
|
fix_re = re.compile(r"\[fix:(.+?)\]")
|
||||||
|
issue_re = re.compile(r"\[issue:(.+?)\]")
|
||||||
|
milestone_re = re.compile(r"\[milestone:(\w+)\]")
|
||||||
|
roadmap_re = re.compile(r"\[roadmap:(\w+):(.+?)\]")
|
||||||
|
|
||||||
|
# Get Git log
|
||||||
|
git_log = self._run_git_command(
|
||||||
|
[
|
||||||
|
"log",
|
||||||
|
f"-n{limit}",
|
||||||
|
"--pretty=format:%h|%s|%ad|%an",
|
||||||
|
"--date=short",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
untagged_count = 0
|
||||||
|
|
||||||
|
# Parse commits
|
||||||
|
for line in git_log.splitlines():
|
||||||
|
parts = line.split("|")
|
||||||
|
if len(parts) < 4:
|
||||||
|
continue
|
||||||
|
|
||||||
|
commit_hash, subject, date, author = parts
|
||||||
|
|
||||||
|
# Check for tags
|
||||||
|
status_match = status_re.search(subject)
|
||||||
|
feature_match = feature_re.search(subject)
|
||||||
|
new_feature_match = new_feature_re.search(subject)
|
||||||
|
changelog_match = changelog_re.search(subject)
|
||||||
|
fix_match = fix_re.search(subject)
|
||||||
|
issue_match = issue_re.search(subject)
|
||||||
|
milestone_match = milestone_re.search(subject)
|
||||||
|
roadmap_match = roadmap_re.search(subject)
|
||||||
|
|
||||||
|
# Process status updates
|
||||||
|
if status_match:
|
||||||
|
status_key = status_match.group(1)
|
||||||
|
result["feature_updates"][status_key] = {
|
||||||
|
"status": "completed",
|
||||||
|
"date": date,
|
||||||
|
"author": author,
|
||||||
|
}
|
||||||
|
|
||||||
|
# Process feature updates
|
||||||
|
if feature_match:
|
||||||
|
feature_key = feature_match.group(1)
|
||||||
|
result["feature_updates"][feature_key] = {
|
||||||
|
"status": "completed",
|
||||||
|
"date": date,
|
||||||
|
"author": author,
|
||||||
|
}
|
||||||
|
|
||||||
|
# Process new features
|
||||||
|
if new_feature_match:
|
||||||
|
feature_key = new_feature_match.group(1)
|
||||||
|
feature_desc = new_feature_match.group(2)
|
||||||
|
result["new_features"][feature_key] = {
|
||||||
|
"status": "planned",
|
||||||
|
"description": feature_desc,
|
||||||
|
"date": date,
|
||||||
|
"author": author,
|
||||||
|
}
|
||||||
|
|
||||||
|
# Process changelog entries
|
||||||
|
if changelog_match:
|
||||||
|
result["changelog_entries"].append(
|
||||||
|
{
|
||||||
|
"message": changelog_match.group(1),
|
||||||
|
"date": date,
|
||||||
|
"commit": commit_hash,
|
||||||
|
"author": author,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Process fixes
|
||||||
|
if fix_match:
|
||||||
|
result["fixes"].append(
|
||||||
|
{
|
||||||
|
"message": fix_match.group(1),
|
||||||
|
"date": date,
|
||||||
|
"commit": commit_hash,
|
||||||
|
"author": author,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Process issues
|
||||||
|
if issue_match:
|
||||||
|
result["issues"].append(
|
||||||
|
{
|
||||||
|
"message": issue_match.group(1),
|
||||||
|
"date": date,
|
||||||
|
"commit": commit_hash,
|
||||||
|
"author": author,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Process milestones
|
||||||
|
if milestone_match:
|
||||||
|
milestone_key = milestone_match.group(1)
|
||||||
|
if milestone_key not in result["milestones"]:
|
||||||
|
result["milestones"][milestone_key] = {
|
||||||
|
"first_date": date,
|
||||||
|
"last_date": date,
|
||||||
|
"commits": [],
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
result["milestones"][milestone_key]["last_date"] = date
|
||||||
|
|
||||||
|
result["milestones"][milestone_key]["commits"].append(
|
||||||
|
commit_hash
|
||||||
|
)
|
||||||
|
|
||||||
|
# Process roadmap items
|
||||||
|
if roadmap_match:
|
||||||
|
milestone_key = roadmap_match.group(1)
|
||||||
|
item_desc = roadmap_match.group(2)
|
||||||
|
|
||||||
|
if milestone_key not in result["roadmap_items"]:
|
||||||
|
result["roadmap_items"][milestone_key] = []
|
||||||
|
|
||||||
|
result["roadmap_items"][milestone_key].append(
|
||||||
|
{
|
||||||
|
"description": item_desc,
|
||||||
|
"date": date,
|
||||||
|
"commit": commit_hash,
|
||||||
|
"author": author,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Process untagged commits
|
||||||
|
if not any(
|
||||||
|
[
|
||||||
|
status_match,
|
||||||
|
feature_match,
|
||||||
|
new_feature_match,
|
||||||
|
changelog_match,
|
||||||
|
fix_match,
|
||||||
|
issue_match,
|
||||||
|
milestone_match,
|
||||||
|
roadmap_match,
|
||||||
|
]
|
||||||
|
):
|
||||||
|
if untagged_count < untagged_limit:
|
||||||
|
# Skip merge commits and very short messages
|
||||||
|
if not subject.startswith("Merge ") and len(subject) > 5:
|
||||||
|
# Extract the first sentence or up to 100 chars
|
||||||
|
commit_desc = subject.split(".")[0]
|
||||||
|
if len(commit_desc) > 100:
|
||||||
|
commit_desc = commit_desc[:97] + "..."
|
||||||
|
|
||||||
|
result["untagged_commits"].append(
|
||||||
|
{
|
||||||
|
"message": commit_desc,
|
||||||
|
"date": date,
|
||||||
|
"commit": commit_hash,
|
||||||
|
"author": author,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
untagged_count += 1
|
||||||
|
|
||||||
|
return result
|
||||||
37
scripts/track_progress/progress_config.json
Normal file
37
scripts/track_progress/progress_config.json
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
{
|
||||||
|
"project_name": "AdvChkSys",
|
||||||
|
"output_dir": "docs/status",
|
||||||
|
"status_doc": "ChunkManager-Status.md",
|
||||||
|
"features_file": "docs/features.md",
|
||||||
|
"changelog_file": "CHANGELOG.md",
|
||||||
|
"git_log_limit": 100,
|
||||||
|
"untagged_commit_limit": 50,
|
||||||
|
"top_files_limit": 20,
|
||||||
|
"exclude_dirs": [
|
||||||
|
"node_modules",
|
||||||
|
"venv",
|
||||||
|
".git",
|
||||||
|
".vs",
|
||||||
|
"bin",
|
||||||
|
"obj",
|
||||||
|
"dist",
|
||||||
|
"build"
|
||||||
|
],
|
||||||
|
"source_extensions": [
|
||||||
|
".py",
|
||||||
|
".cs",
|
||||||
|
".js",
|
||||||
|
".ts",
|
||||||
|
".html",
|
||||||
|
".css",
|
||||||
|
".rs",
|
||||||
|
".go",
|
||||||
|
".java",
|
||||||
|
".md"
|
||||||
|
],
|
||||||
|
"templates": {
|
||||||
|
"status": "status_template.md",
|
||||||
|
"features": "feature_template.md",
|
||||||
|
"changelog": "changelog_template.md"
|
||||||
|
}
|
||||||
|
}
|
||||||
16
scripts/track_progress/requirements.txt
Normal file
16
scripts/track_progress/requirements.txt
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
# Core dependencies
|
||||||
|
argparse # For parsing command-line arguments
|
||||||
|
shutil # For file and directory operations
|
||||||
|
GitPython # For Git repository analysis
|
||||||
|
Jinja2 # Alternative template engine (optional)
|
||||||
|
Markdown # For Markdown processing (optional)
|
||||||
|
|
||||||
|
# Build dependencies (only needed for building the executable)
|
||||||
|
nuitka # For compiling Python to executable
|
||||||
|
ordered-set # Required by Nuitka
|
||||||
|
zstandard # For better compression in Nuitka builds
|
||||||
|
|
||||||
|
# Development dependencies (optional)
|
||||||
|
black # Code formatting
|
||||||
|
pylint # Code linting
|
||||||
|
pytest # Testing
|
||||||
33
scripts/track_progress/roadmap_generator.py
Normal file
33
scripts/track_progress/roadmap_generator.py
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
"""
|
||||||
|
Roadmap generator for project status documentation.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from typing import Dict, Any, List, Optional
|
||||||
|
|
||||||
|
|
||||||
|
def generate_roadmap(
|
||||||
|
milestones: Dict[str, Dict[str, Any]],
|
||||||
|
roadmap_items: Dict[str, List[Dict[str, Any]]],
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
"""Generate roadmap from milestones and roadmap items."""
|
||||||
|
roadmap = []
|
||||||
|
|
||||||
|
# Process each milestone
|
||||||
|
for milestone_key, milestone_data in milestones.items():
|
||||||
|
# Create milestone entry
|
||||||
|
milestone = {
|
||||||
|
"name": milestone_key.replace("_", " ").title(),
|
||||||
|
"target_date": milestone_data.get("last_date", "TBD"),
|
||||||
|
"items": [],
|
||||||
|
}
|
||||||
|
|
||||||
|
# Add roadmap items for this milestone
|
||||||
|
if milestone_key in roadmap_items:
|
||||||
|
for item in roadmap_items[milestone_key]:
|
||||||
|
milestone["items"].append(
|
||||||
|
{"description": item["description"], "status": "planned"}
|
||||||
|
)
|
||||||
|
|
||||||
|
roadmap.append(milestone)
|
||||||
|
|
||||||
|
return roadmap
|
||||||
55
scripts/track_progress/scripts/SCRIPTS.md
Normal file
55
scripts/track_progress/scripts/SCRIPTS.md
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
# Progress Tracker Scripts
|
||||||
|
|
||||||
|
This directory contains utility scripts for building, installing, and running the progress tracker.
|
||||||
|
|
||||||
|
## Available Scripts
|
||||||
|
|
||||||
|
### setup_install.py
|
||||||
|
|
||||||
|
Installs the progress tracker to your project.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python setup_install.py --dest scripts --project "Your Project Name"
|
||||||
|
```
|
||||||
|
|
||||||
|
Options:
|
||||||
|
- `--dest folder`: Set destination directory (default: "scripts")
|
||||||
|
- `--project name`: Set project name (default: "Project")
|
||||||
|
- `--verbose` or `-v`: Enable verbose output
|
||||||
|
- `--force` or `-f`: Force overwrite existing files
|
||||||
|
|
||||||
|
### build_with_nuitka.py
|
||||||
|
|
||||||
|
Builds the progress tracker into a standalone executable using Nuitka.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python build_with_nuitka.py
|
||||||
|
```
|
||||||
|
|
||||||
|
Options:
|
||||||
|
- `--output-dir`: Output directory for the build (default: "dist")
|
||||||
|
- `--verbose` or `-v`: Enable verbose output
|
||||||
|
|
||||||
|
### create_launcher_scripts.py
|
||||||
|
|
||||||
|
Creates launcher scripts for running the progress tracker. This is usually called by setup_install.py.
|
||||||
|
|
||||||
|
## Launcher Scripts
|
||||||
|
|
||||||
|
The following launcher scripts are created during installation:
|
||||||
|
|
||||||
|
- `run_progress_tracker.py`: Python launcher script
|
||||||
|
- `run_progress_tracker.sh`: Shell launcher script (Linux/macOS)
|
||||||
|
- `run_progress_tracker.bat`: Batch launcher script (Windows)
|
||||||
|
|
||||||
|
## Usage Example
|
||||||
|
|
||||||
|
1. Install the tracker:
|
||||||
|
```bash
|
||||||
|
python setup_install.py --project "My Project" --verbose
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Run the tracker using one of the launcher scripts:
|
||||||
|
- Windows: `run_progress_tracker.bat`
|
||||||
|
- Linux/macOS: `./run_progress_tracker.sh`
|
||||||
|
- Any platform: `python run_progress_tracker.py`
|
||||||
133
scripts/track_progress/scripts/build_with_nuitka.py
Normal file
133
scripts/track_progress/scripts/build_with_nuitka.py
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Build the progress tracker with Nuitka.
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Build the progress tracker with Nuitka."""
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description="Build the progress tracker with Nuitka"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--output-dir", default="dist", help="Output directory for the build"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--verbose", "-v", action="store_true", help="Enable verbose output"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--move-exe",
|
||||||
|
action="store_true",
|
||||||
|
help="Move executable to parent directory after build",
|
||||||
|
)
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
verbose = args.verbose
|
||||||
|
output_dir = args.output_dir
|
||||||
|
move_exe = args.move_exe
|
||||||
|
|
||||||
|
# Get the directory of this script
|
||||||
|
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
parent_dir = os.path.dirname(script_dir) # scripts/track_progress
|
||||||
|
|
||||||
|
# Check if Nuitka is installed
|
||||||
|
try:
|
||||||
|
subprocess.check_call(
|
||||||
|
[sys.executable, "-m", "pip", "show", "nuitka"],
|
||||||
|
stdout=subprocess.DEVNULL,
|
||||||
|
stderr=subprocess.DEVNULL,
|
||||||
|
)
|
||||||
|
except subprocess.CalledProcessError:
|
||||||
|
print("Nuitka is not installed. Installing...")
|
||||||
|
try:
|
||||||
|
subprocess.check_call(
|
||||||
|
[sys.executable, "-m", "pip", "install", "nuitka"]
|
||||||
|
)
|
||||||
|
except subprocess.CalledProcessError:
|
||||||
|
print(
|
||||||
|
"Failed to install Nuitka. Please install it manually with 'pip install nuitka'."
|
||||||
|
)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
# Create output directory
|
||||||
|
dist_dir = os.path.join(parent_dir, output_dir)
|
||||||
|
os.makedirs(dist_dir, exist_ok=True)
|
||||||
|
|
||||||
|
# Path to the main script
|
||||||
|
main_script = os.path.join(parent_dir, "track_progress.py")
|
||||||
|
|
||||||
|
if not os.path.exists(main_script):
|
||||||
|
print(f"Error: Main script not found at {main_script}")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
# Build command
|
||||||
|
cmd = [
|
||||||
|
sys.executable,
|
||||||
|
"-m",
|
||||||
|
"nuitka",
|
||||||
|
"--standalone",
|
||||||
|
"--onefile",
|
||||||
|
"--lto=auto",
|
||||||
|
"--jobs=4",
|
||||||
|
"--include-package=track_progress",
|
||||||
|
"--include-data-dir="
|
||||||
|
+ os.path.join(parent_dir, "templates")
|
||||||
|
+ "=templates",
|
||||||
|
"--output-dir=" + dist_dir,
|
||||||
|
main_script,
|
||||||
|
]
|
||||||
|
|
||||||
|
# Try to enable UPX if available
|
||||||
|
try:
|
||||||
|
subprocess.check_call(
|
||||||
|
["upx", "--version"],
|
||||||
|
stdout=subprocess.DEVNULL,
|
||||||
|
stderr=subprocess.DEVNULL,
|
||||||
|
)
|
||||||
|
cmd.append("--enable-plugin=upx")
|
||||||
|
except (subprocess.CalledProcessError, FileNotFoundError):
|
||||||
|
if verbose:
|
||||||
|
print("UPX not found, continuing without compression")
|
||||||
|
|
||||||
|
if verbose:
|
||||||
|
print(f"Building with command: {' '.join(cmd)}")
|
||||||
|
else:
|
||||||
|
cmd.append("--quiet")
|
||||||
|
|
||||||
|
# Run Nuitka
|
||||||
|
try:
|
||||||
|
subprocess.check_call(cmd)
|
||||||
|
print(f"Build successful! Executable is in {dist_dir}")
|
||||||
|
|
||||||
|
# Move the executable to the parent directory if requested
|
||||||
|
if move_exe:
|
||||||
|
exe_ext = ".exe" if sys.platform == "win32" else ""
|
||||||
|
exe_name = f"track_progress{exe_ext}"
|
||||||
|
src_exe = os.path.join(dist_dir, exe_name)
|
||||||
|
dst_exe = os.path.join(parent_dir, exe_name)
|
||||||
|
|
||||||
|
if os.path.exists(src_exe):
|
||||||
|
# Remove existing executable if it exists
|
||||||
|
if os.path.exists(dst_exe):
|
||||||
|
os.remove(dst_exe)
|
||||||
|
|
||||||
|
# Move the executable
|
||||||
|
shutil.move(src_exe, dst_exe)
|
||||||
|
if verbose:
|
||||||
|
print(f"Moved executable from {src_exe} to {dst_exe}")
|
||||||
|
else:
|
||||||
|
print(f"Warning: Built executable not found at {src_exe}")
|
||||||
|
|
||||||
|
return 0
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
print(f"Build failed: {e}")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
217
scripts/track_progress/scripts/create_launcher_scripts.py
Normal file
217
scripts/track_progress/scripts/create_launcher_scripts.py
Normal file
@ -0,0 +1,217 @@
|
|||||||
|
"""
|
||||||
|
Create launcher scripts for the progress tracker.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from typing import List, Tuple
|
||||||
|
|
||||||
|
|
||||||
|
def create_python_launcher(
|
||||||
|
output_path: str, dest_dir: str, project_name: str
|
||||||
|
) -> bool:
|
||||||
|
"""Create a Python launcher script."""
|
||||||
|
content = f"""#!/usr/bin/env python3
|
||||||
|
\"\"\"
|
||||||
|
Launcher for the progress tracker.
|
||||||
|
Tries to use the executable version first, then falls back to the Python version.
|
||||||
|
\"\"\"
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
def main():
|
||||||
|
\"\"\"Main function to run the tracker.\"\"\"
|
||||||
|
# Get the directory of this script
|
||||||
|
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
tracker_dir = os.path.dirname(script_dir)
|
||||||
|
|
||||||
|
# Paths to executable and Python script
|
||||||
|
exe_ext = ".exe" if sys.platform == "win32" else ""
|
||||||
|
exe_path = os.path.join(tracker_dir, f"track_progress{{exe_ext}}")
|
||||||
|
py_path = os.path.join(tracker_dir, "track_progress.py")
|
||||||
|
|
||||||
|
# Command-line arguments to pass through
|
||||||
|
args = sys.argv[1:]
|
||||||
|
|
||||||
|
# Try executable first
|
||||||
|
if os.path.exists(exe_path) and (sys.platform == "win32" or os.access(exe_path, os.X_OK)):
|
||||||
|
print("Running Progress Tracker (executable version)...")
|
||||||
|
try:
|
||||||
|
return subprocess.call([exe_path] + args)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error running executable: {{e}}")
|
||||||
|
print("Falling back to Python version...")
|
||||||
|
else:
|
||||||
|
print("Executable not found, trying Python version...")
|
||||||
|
|
||||||
|
# Fall back to Python script
|
||||||
|
if os.path.exists(py_path):
|
||||||
|
print("Running Progress Tracker (Python version)...")
|
||||||
|
try:
|
||||||
|
# Add the tracker directory to the Python path
|
||||||
|
sys.path.insert(0, tracker_dir)
|
||||||
|
|
||||||
|
# Change to the tracker directory
|
||||||
|
os.chdir(tracker_dir)
|
||||||
|
|
||||||
|
# Run the Python script
|
||||||
|
return subprocess.call([sys.executable, py_path] + args)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error running Python script: {{e}}")
|
||||||
|
return 1
|
||||||
|
else:
|
||||||
|
print("Error: Neither executable nor Python script found at:")
|
||||||
|
print(f"- {{exe_path}}")
|
||||||
|
print(f"- {{py_path}}")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
exit_code = main()
|
||||||
|
if exit_code == 0:
|
||||||
|
print("Progress tracking completed successfully!")
|
||||||
|
else:
|
||||||
|
print(f"Error running progress tracker! (Exit code: {{exit_code}})")
|
||||||
|
|
||||||
|
# On Windows, pause to see the output
|
||||||
|
if sys.platform == "win32":
|
||||||
|
input("Press Enter to continue...")
|
||||||
|
|
||||||
|
sys.exit(exit_code)
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(output_path, "w", encoding="utf-8", newline="\n") as f:
|
||||||
|
f.write(content)
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error creating Python launcher: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def create_shell_launcher(output_path: str) -> bool:
|
||||||
|
"""Create a shell launcher script."""
|
||||||
|
content = """#!/bin/bash
|
||||||
|
|
||||||
|
# Get the directory of this script
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
TRACKER_DIR="$(dirname "$SCRIPT_DIR")"
|
||||||
|
EXE_PATH="$TRACKER_DIR/track_progress"
|
||||||
|
PY_PATH="$TRACKER_DIR/track_progress.py"
|
||||||
|
|
||||||
|
# Check if executable exists and is executable
|
||||||
|
if [ -x "$EXE_PATH" ]; then
|
||||||
|
echo "Running Progress Tracker (executable version)..."
|
||||||
|
"$EXE_PATH" "$@"
|
||||||
|
elif [ -f "$PY_PATH" ]; then
|
||||||
|
echo "Executable not found, trying Python version..."
|
||||||
|
echo "Running Progress Tracker (Python version)..."
|
||||||
|
python "$PY_PATH" "$@"
|
||||||
|
else
|
||||||
|
echo "Error: Neither executable nor Python script found at:"
|
||||||
|
echo "- $EXE_PATH"
|
||||||
|
echo "- $PY_PATH"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
EXIT_CODE=$?
|
||||||
|
if [ $EXIT_CODE -eq 0 ]; then
|
||||||
|
echo "Progress tracking completed successfully!"
|
||||||
|
else
|
||||||
|
echo "Error running progress tracker! (Exit code: $EXIT_CODE)"
|
||||||
|
fi
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(output_path, "w", encoding="utf-8", newline="\n") as f:
|
||||||
|
f.write(content)
|
||||||
|
|
||||||
|
# Make the script executable on Unix-like systems
|
||||||
|
if sys.platform != "win32":
|
||||||
|
os.chmod(output_path, 0o755)
|
||||||
|
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error creating shell launcher: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def create_batch_launcher(output_path: str) -> bool:
|
||||||
|
"""Create a batch launcher script."""
|
||||||
|
content = """@echo off
|
||||||
|
setlocal enabledelayedexpansion
|
||||||
|
|
||||||
|
REM Get the directory of this script
|
||||||
|
set "SCRIPT_DIR=%~dp0"
|
||||||
|
set "TRACKER_DIR=%SCRIPT_DIR%.."
|
||||||
|
set "EXE_PATH=%TRACKER_DIR%\\track_progress.exe"
|
||||||
|
set "PY_PATH=%TRACKER_DIR%\\track_progress.py"
|
||||||
|
|
||||||
|
REM Check if executable exists
|
||||||
|
if exist "%EXE_PATH%" (
|
||||||
|
echo Running Progress Tracker (executable version)...
|
||||||
|
"%EXE_PATH%" %*
|
||||||
|
) else (
|
||||||
|
echo Executable not found, trying Python version...
|
||||||
|
if exist "%PY_PATH%" (
|
||||||
|
echo Running Progress Tracker (Python version)...
|
||||||
|
python "%PY_PATH%" %*
|
||||||
|
) else (
|
||||||
|
echo Error: Neither executable nor Python script found at:
|
||||||
|
echo - %EXE_PATH%
|
||||||
|
echo - %PY_PATH%
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if %ERRORLEVEL% EQU 0 (
|
||||||
|
echo Progress tracking completed successfully!
|
||||||
|
) else (
|
||||||
|
echo Error running progress tracker! (Exit code: %ERRORLEVEL%)
|
||||||
|
)
|
||||||
|
pause
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(output_path, "w", encoding="utf-8", newline="\n") as f:
|
||||||
|
f.write(content)
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error creating batch launcher: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def ensure_launcher_scripts(
|
||||||
|
script_dir: str,
|
||||||
|
dest_dir: str,
|
||||||
|
project_name: str,
|
||||||
|
verbose: bool = False,
|
||||||
|
force: bool = False,
|
||||||
|
) -> List[str]:
|
||||||
|
"""Ensure all launcher scripts exist in the script directory."""
|
||||||
|
created_scripts = []
|
||||||
|
|
||||||
|
# Define the scripts to check/create
|
||||||
|
scripts_to_ensure = [
|
||||||
|
("run_progress_tracker.py", create_python_launcher),
|
||||||
|
("run_progress_tracker.sh", create_shell_launcher),
|
||||||
|
("run_progress_tracker.bat", create_batch_launcher),
|
||||||
|
]
|
||||||
|
|
||||||
|
for script_name, create_func in scripts_to_ensure:
|
||||||
|
script_path = os.path.join(script_dir, script_name)
|
||||||
|
|
||||||
|
if not os.path.exists(script_path) or force:
|
||||||
|
if verbose:
|
||||||
|
print(f"Creating launcher script: {script_path}")
|
||||||
|
|
||||||
|
if create_func(script_path, dest_dir, project_name):
|
||||||
|
created_scripts.append(script_path)
|
||||||
|
if verbose:
|
||||||
|
print(f"Created {script_path}")
|
||||||
|
else:
|
||||||
|
print(f"Failed to create {script_path}")
|
||||||
|
elif verbose:
|
||||||
|
print(f"Launcher script already exists: {script_path}")
|
||||||
|
|
||||||
|
return created_scripts
|
||||||
32
scripts/track_progress/scripts/run_progress_tracker.bat
Normal file
32
scripts/track_progress/scripts/run_progress_tracker.bat
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
@echo off
|
||||||
|
setlocal enabledelayedexpansion
|
||||||
|
|
||||||
|
REM Get the directory of this script
|
||||||
|
set "SCRIPT_DIR=%~dp0"
|
||||||
|
set "TRACKER_DIR=%SCRIPT_DIR%.."
|
||||||
|
set "EXE_PATH=%TRACKER_DIR%\track_progress.exe"
|
||||||
|
set "PY_PATH=%TRACKER_DIR%\track_progress.py"
|
||||||
|
|
||||||
|
REM Check if executable exists
|
||||||
|
if exist "%EXE_PATH%" (
|
||||||
|
echo Running Progress Tracker (executable version)...
|
||||||
|
"%EXE_PATH%" %*
|
||||||
|
) else (
|
||||||
|
echo Executable not found, trying Python version...
|
||||||
|
if exist "%PY_PATH%" (
|
||||||
|
echo Running Progress Tracker (Python version)...
|
||||||
|
python "%PY_PATH%" %*
|
||||||
|
) else (
|
||||||
|
echo Error: Neither executable nor Python script found at:
|
||||||
|
echo - %EXE_PATH%
|
||||||
|
echo - %PY_PATH%
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if %ERRORLEVEL% EQU 0 (
|
||||||
|
echo Progress tracking completed successfully!
|
||||||
|
) else (
|
||||||
|
echo Error running progress tracker! (Exit code: %ERRORLEVEL%)
|
||||||
|
)
|
||||||
|
pause
|
||||||
71
scripts/track_progress/scripts/run_progress_tracker.py
Normal file
71
scripts/track_progress/scripts/run_progress_tracker.py
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Launcher for the progress tracker.
|
||||||
|
Tries to use the executable version first, then falls back to the Python version.
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Main function to run the tracker."""
|
||||||
|
# Get the directory of this script
|
||||||
|
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
tracker_dir = os.path.dirname(script_dir)
|
||||||
|
|
||||||
|
# Paths to executable and Python script
|
||||||
|
exe_ext = ".exe" if sys.platform == "win32" else ""
|
||||||
|
exe_path = os.path.join(tracker_dir, f"track_progress{exe_ext}")
|
||||||
|
py_path = os.path.join(tracker_dir, "track_progress.py")
|
||||||
|
|
||||||
|
# Command-line arguments to pass through
|
||||||
|
args = sys.argv[1:]
|
||||||
|
|
||||||
|
# Try executable first
|
||||||
|
if os.path.exists(exe_path) and (
|
||||||
|
sys.platform == "win32" or os.access(exe_path, os.X_OK)
|
||||||
|
):
|
||||||
|
print("Running Progress Tracker (executable version)...")
|
||||||
|
try:
|
||||||
|
return subprocess.call([exe_path] + args)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error running executable: {e}")
|
||||||
|
print("Falling back to Python version...")
|
||||||
|
else:
|
||||||
|
print("Executable not found, trying Python version...")
|
||||||
|
|
||||||
|
# Fall back to Python script
|
||||||
|
if os.path.exists(py_path):
|
||||||
|
print("Running Progress Tracker (Python version)...")
|
||||||
|
try:
|
||||||
|
# Add the tracker directory to the Python path
|
||||||
|
sys.path.insert(0, tracker_dir)
|
||||||
|
|
||||||
|
# Change to the tracker directory
|
||||||
|
os.chdir(tracker_dir)
|
||||||
|
|
||||||
|
# Run the Python script
|
||||||
|
return subprocess.call([sys.executable, py_path] + args)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error running Python script: {e}")
|
||||||
|
return 1
|
||||||
|
else:
|
||||||
|
print("Error: Neither executable nor Python script found at:")
|
||||||
|
print(f"- {exe_path}")
|
||||||
|
print(f"- {py_path}")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
exit_code = main()
|
||||||
|
if exit_code == 0:
|
||||||
|
print("Progress tracking completed successfully!")
|
||||||
|
else:
|
||||||
|
print(f"Error running progress tracker! (Exit code: {exit_code})")
|
||||||
|
|
||||||
|
# On Windows, pause to see the output
|
||||||
|
if sys.platform == "win32":
|
||||||
|
input("Press Enter to continue...")
|
||||||
|
|
||||||
|
sys.exit(exit_code)
|
||||||
29
scripts/track_progress/scripts/run_progress_tracker.sh
Normal file
29
scripts/track_progress/scripts/run_progress_tracker.sh
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Get the directory of this script
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
TRACKER_DIR="$(dirname "$SCRIPT_DIR")"
|
||||||
|
EXE_PATH="$TRACKER_DIR/track_progress"
|
||||||
|
PY_PATH="$TRACKER_DIR/track_progress.py"
|
||||||
|
|
||||||
|
# Check if executable exists and is executable
|
||||||
|
if [ -x "$EXE_PATH" ]; then
|
||||||
|
echo "Running Progress Tracker (executable version)..."
|
||||||
|
"$EXE_PATH" "$@"
|
||||||
|
elif [ -f "$PY_PATH" ]; then
|
||||||
|
echo "Executable not found, trying Python version..."
|
||||||
|
echo "Running Progress Tracker (Python version)..."
|
||||||
|
python "$PY_PATH" "$@"
|
||||||
|
else
|
||||||
|
echo "Error: Neither executable nor Python script found at:"
|
||||||
|
echo "- $EXE_PATH"
|
||||||
|
echo "- $PY_PATH"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
EXIT_CODE=$?
|
||||||
|
if [ $EXIT_CODE -eq 0 ]; then
|
||||||
|
echo "Progress tracking completed successfully!"
|
||||||
|
else
|
||||||
|
echo "Error running progress tracker! (Exit code: $EXIT_CODE)"
|
||||||
|
fi
|
||||||
285
scripts/track_progress/scripts/setup_install.py
Normal file
285
scripts/track_progress/scripts/setup_install.py
Normal file
@ -0,0 +1,285 @@
|
|||||||
|
""" Run this to install to your projects
|
||||||
|
| "--dest folder" |to set destination dir
|
||||||
|
| "--project name" |to set project name
|
||||||
|
| "--verbose" or "-v"|to enable verbose output
|
||||||
|
| "--force" or "-f"|to force overwrite existing files
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import shutil
|
||||||
|
import argparse
|
||||||
|
import subprocess
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
# Try to import our script creation module
|
||||||
|
try:
|
||||||
|
from create_launcher_scripts import ensure_launcher_scripts
|
||||||
|
except ImportError:
|
||||||
|
# If we can't import it, we'll define a simple version here
|
||||||
|
def ensure_launcher_scripts(
|
||||||
|
script_dir, dest_dir, project_name, verbose=False, force=False
|
||||||
|
):
|
||||||
|
"""Fallback function if the module can't be imported."""
|
||||||
|
print(
|
||||||
|
"Warning: create_launcher_scripts module not found. Using fallback."
|
||||||
|
)
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Main installation function."""
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description="Install the progress tracking system."
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--dest", default="scripts", help="Destination directory"
|
||||||
|
)
|
||||||
|
parser.add_argument("--project", default="Project", help="Project name")
|
||||||
|
parser.add_argument(
|
||||||
|
"--verbose", "-v", action="store_true", help="Enable verbose output"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--force",
|
||||||
|
"-f",
|
||||||
|
action="store_true",
|
||||||
|
help="Force overwrite existing files",
|
||||||
|
)
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
verbose = args.verbose
|
||||||
|
force = args.force
|
||||||
|
|
||||||
|
if verbose:
|
||||||
|
print(
|
||||||
|
f"Installing progress tracking system to {args.dest} for project {args.project}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create destination directories
|
||||||
|
os.makedirs(
|
||||||
|
os.path.join(args.dest, "track_progress", "templates"), exist_ok=True
|
||||||
|
)
|
||||||
|
os.makedirs("docs/status", exist_ok=True)
|
||||||
|
|
||||||
|
# Get the directory of this script
|
||||||
|
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
parent_dir = os.path.dirname(script_dir) # scripts/track_progress
|
||||||
|
|
||||||
|
# Look for compiled executable
|
||||||
|
exe_ext = ".exe" if sys.platform == "win32" else ""
|
||||||
|
compiled_exe = os.path.join(parent_dir, f"track_progress{exe_ext}")
|
||||||
|
dist_exe = os.path.join(parent_dir, "dist", f"track_progress{exe_ext}")
|
||||||
|
|
||||||
|
# Check if we have the executable in either location
|
||||||
|
if not os.path.exists(compiled_exe) and not os.path.exists(dist_exe):
|
||||||
|
# Try to build it
|
||||||
|
if verbose:
|
||||||
|
print(f"Compiled executable not found, trying to build it...")
|
||||||
|
|
||||||
|
build_script = os.path.join(script_dir, "build_with_nuitka.py")
|
||||||
|
if os.path.exists(build_script):
|
||||||
|
build_cmd = [sys.executable, build_script]
|
||||||
|
if verbose:
|
||||||
|
build_cmd.append("--verbose")
|
||||||
|
|
||||||
|
try:
|
||||||
|
subprocess.check_call(build_cmd)
|
||||||
|
# The executable should now be in the dist directory
|
||||||
|
|
||||||
|
# Move the executable from dist to parent directory
|
||||||
|
if os.path.exists(dist_exe):
|
||||||
|
if os.path.exists(compiled_exe):
|
||||||
|
os.remove(compiled_exe)
|
||||||
|
shutil.move(dist_exe, compiled_exe)
|
||||||
|
if verbose:
|
||||||
|
print(
|
||||||
|
f"Moved executable from {dist_exe} to {compiled_exe}"
|
||||||
|
)
|
||||||
|
except subprocess.CalledProcessError:
|
||||||
|
print(
|
||||||
|
"Failed to build the executable. Falling back to Python files."
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
print("Build script not found. Falling back to Python files.")
|
||||||
|
|
||||||
|
# Check if we have the executable now
|
||||||
|
have_executable = os.path.exists(compiled_exe)
|
||||||
|
|
||||||
|
if have_executable:
|
||||||
|
# Copy the compiled executable
|
||||||
|
dst_exe = os.path.join(
|
||||||
|
args.dest, "track_progress", f"track_progress{exe_ext}"
|
||||||
|
)
|
||||||
|
if not os.path.exists(dst_exe) or force:
|
||||||
|
shutil.copy2(compiled_exe, dst_exe)
|
||||||
|
if verbose:
|
||||||
|
print(
|
||||||
|
f"Copied compiled executable from {compiled_exe} to {dst_exe}"
|
||||||
|
)
|
||||||
|
elif verbose:
|
||||||
|
print(f"Skipping existing executable: {dst_exe}")
|
||||||
|
else:
|
||||||
|
# Copy individual Python files
|
||||||
|
python_files = [
|
||||||
|
"track_progress.py",
|
||||||
|
"config.py",
|
||||||
|
"git_analyzer.py",
|
||||||
|
"code_stats.py",
|
||||||
|
"feature_markdown.py",
|
||||||
|
"template_engine.py",
|
||||||
|
"changelog_generator.py",
|
||||||
|
"roadmap_generator.py",
|
||||||
|
"__init__.py", # Add this to make it a proper package
|
||||||
|
"README.md",
|
||||||
|
]
|
||||||
|
|
||||||
|
for file in python_files:
|
||||||
|
src = os.path.join(parent_dir, file)
|
||||||
|
dst = os.path.join(args.dest, "track_progress", file)
|
||||||
|
|
||||||
|
# Skip existing files unless force is specified
|
||||||
|
if os.path.exists(dst) and not force:
|
||||||
|
if verbose:
|
||||||
|
print(f"Skipping existing file: {dst}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
if os.path.exists(src):
|
||||||
|
shutil.copy2(src, dst)
|
||||||
|
if verbose:
|
||||||
|
print(f"Copied {file} to {dst}")
|
||||||
|
elif file == "__init__.py":
|
||||||
|
# Create empty __init__.py if it doesn't exist
|
||||||
|
with open(dst, "w", encoding="utf-8") as f:
|
||||||
|
f.write(
|
||||||
|
"# This file makes the directory a Python package\n"
|
||||||
|
)
|
||||||
|
if verbose:
|
||||||
|
print(f"Created {dst}")
|
||||||
|
|
||||||
|
# Copy templates (always copy templates, they should remain exposed)
|
||||||
|
template_files = [
|
||||||
|
"status_template.md",
|
||||||
|
"feature_template.md",
|
||||||
|
"changelog_template.md",
|
||||||
|
]
|
||||||
|
|
||||||
|
for file in template_files:
|
||||||
|
src = os.path.join(parent_dir, "templates", file)
|
||||||
|
dst = os.path.join(args.dest, "track_progress", "templates", file)
|
||||||
|
|
||||||
|
# Skip existing files unless force is specified
|
||||||
|
if os.path.exists(dst) and not force:
|
||||||
|
if verbose:
|
||||||
|
print(f"Skipping existing template: {dst}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
if os.path.exists(src):
|
||||||
|
shutil.copy2(src, dst)
|
||||||
|
if verbose:
|
||||||
|
print(f"Copied template {file} to {dst}")
|
||||||
|
|
||||||
|
# Copy and customize configuration
|
||||||
|
config_src = os.path.join(parent_dir, "progress_config.json")
|
||||||
|
config_dst = os.path.join(
|
||||||
|
args.dest, "track_progress", "progress_config.json"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Skip existing config unless force is specified
|
||||||
|
if not os.path.exists(config_dst) or force:
|
||||||
|
if os.path.exists(config_src):
|
||||||
|
# Read the config file
|
||||||
|
with open(config_src, "r", encoding="utf-8") as f:
|
||||||
|
config_content = f.read()
|
||||||
|
|
||||||
|
# Replace project name
|
||||||
|
config_content = config_content.replace(
|
||||||
|
'"project_name": "AdvChkSys"',
|
||||||
|
f'"project_name": "{args.project}"',
|
||||||
|
)
|
||||||
|
|
||||||
|
# Ensure paths are relative to the project root
|
||||||
|
config_content = config_content.replace(
|
||||||
|
'"features_file": "docs/features.md"',
|
||||||
|
'"features_file": "docs/features.md"',
|
||||||
|
)
|
||||||
|
config_content = config_content.replace(
|
||||||
|
'"output_dir": "docs/status"',
|
||||||
|
'"output_dir": "docs/status"',
|
||||||
|
)
|
||||||
|
config_content = config_content.replace(
|
||||||
|
'"changelog_file": "CHANGELOG.md"',
|
||||||
|
'"changelog_file": "CHANGELOG.md"',
|
||||||
|
)
|
||||||
|
|
||||||
|
# Write the customized config
|
||||||
|
with open(config_dst, "w", encoding="utf-8") as f:
|
||||||
|
f.write(config_content)
|
||||||
|
|
||||||
|
if verbose:
|
||||||
|
print(f"Created customized configuration at {config_dst}")
|
||||||
|
elif verbose:
|
||||||
|
print(f"Skipping existing configuration: {config_dst}")
|
||||||
|
|
||||||
|
# Ensure launcher scripts exist in the scripts directory
|
||||||
|
created_scripts = ensure_launcher_scripts(
|
||||||
|
script_dir, args.dest, args.project, verbose, force
|
||||||
|
)
|
||||||
|
if created_scripts and verbose:
|
||||||
|
print(
|
||||||
|
f"Created {len(created_scripts)} launcher scripts in {script_dir}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create an empty CHANGELOG.md if it doesn't exist
|
||||||
|
if not os.path.exists("CHANGELOG.md"):
|
||||||
|
with open("CHANGELOG.md", "w", encoding="utf-8") as f:
|
||||||
|
f.write("# Changelog\n\n")
|
||||||
|
if verbose:
|
||||||
|
print("Created empty CHANGELOG.md")
|
||||||
|
|
||||||
|
# Copy the scripts directory to the destination if it doesn't exist
|
||||||
|
scripts_dst = os.path.join(args.dest, "track_progress", "scripts")
|
||||||
|
if not os.path.exists(scripts_dst) or force:
|
||||||
|
os.makedirs(scripts_dst, exist_ok=True)
|
||||||
|
|
||||||
|
# Copy script files
|
||||||
|
script_files = [
|
||||||
|
"build_with_nuitka.py",
|
||||||
|
"create_launcher_scripts.py",
|
||||||
|
"setup_install.py",
|
||||||
|
]
|
||||||
|
|
||||||
|
for file in script_files:
|
||||||
|
src = os.path.join(script_dir, file)
|
||||||
|
dst = os.path.join(scripts_dst, file)
|
||||||
|
|
||||||
|
if os.path.exists(src) and (not os.path.exists(dst) or force):
|
||||||
|
shutil.copy2(src, dst)
|
||||||
|
if verbose:
|
||||||
|
print(f"Copied script {file} to {dst}")
|
||||||
|
|
||||||
|
print("\nInstallation complete!")
|
||||||
|
if have_executable:
|
||||||
|
print(
|
||||||
|
f"Compiled tracker installed to: {os.path.join(args.dest, 'track_progress')}"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
print(
|
||||||
|
f"Python tracker installed to: {os.path.join(args.dest, 'track_progress')}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Check which launcher scripts exist
|
||||||
|
py_launcher = os.path.join(script_dir, "run_progress_tracker.py")
|
||||||
|
sh_launcher = os.path.join(script_dir, "run_progress_tracker.sh")
|
||||||
|
bat_launcher = os.path.join(script_dir, "run_progress_tracker.bat")
|
||||||
|
|
||||||
|
print("\nTo run the tracker, use one of these commands:")
|
||||||
|
if os.path.exists(py_launcher):
|
||||||
|
print(f"- Python: python {py_launcher}")
|
||||||
|
if os.path.exists(sh_launcher) and sys.platform != "win32":
|
||||||
|
print(f"- Shell: {sh_launcher}")
|
||||||
|
if os.path.exists(bat_launcher) and sys.platform == "win32":
|
||||||
|
print(f"- Batch: {bat_launcher}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
239
scripts/track_progress/template_engine.py
Normal file
239
scripts/track_progress/template_engine.py
Normal file
@ -0,0 +1,239 @@
|
|||||||
|
"""
|
||||||
|
Template engine for generating Markdown documentation from templates.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from typing import Dict, Any, List, Optional
|
||||||
|
|
||||||
|
|
||||||
|
class TemplateEngine:
|
||||||
|
"""Simple template engine for generating Markdown files."""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self, templates_dir: str = "scripts/track_progress/templates"
|
||||||
|
):
|
||||||
|
"""Initialize the template engine with the templates directory."""
|
||||||
|
self.templates_dir = templates_dir
|
||||||
|
|
||||||
|
def _load_template(self, template_name: str) -> str:
|
||||||
|
"""Load a template file."""
|
||||||
|
# Try multiple possible template locations
|
||||||
|
possible_paths = [
|
||||||
|
# Original path
|
||||||
|
os.path.join(self.templates_dir, template_name),
|
||||||
|
# Path relative to current working directory
|
||||||
|
os.path.join("templates", template_name),
|
||||||
|
# Path relative to executable directory
|
||||||
|
os.path.join(
|
||||||
|
os.path.dirname(sys.executable), "templates", template_name
|
||||||
|
),
|
||||||
|
# Path relative to script directory
|
||||||
|
os.path.join(
|
||||||
|
os.path.dirname(os.path.abspath(__file__)),
|
||||||
|
"templates",
|
||||||
|
template_name,
|
||||||
|
),
|
||||||
|
# Path with 'scripts/track_progress' removed (for compiled version)
|
||||||
|
template_name.replace("scripts/track_progress/", ""),
|
||||||
|
# Just the filename
|
||||||
|
template_name,
|
||||||
|
]
|
||||||
|
|
||||||
|
# Try each path
|
||||||
|
for template_path in possible_paths:
|
||||||
|
if os.path.exists(template_path):
|
||||||
|
with open(template_path, "r", encoding="utf-8") as f:
|
||||||
|
return f.read()
|
||||||
|
|
||||||
|
# If we get here, none of the paths worked
|
||||||
|
error_message = f"Template not found: {template_name}\nTried paths: {possible_paths}"
|
||||||
|
raise FileNotFoundError(error_message)
|
||||||
|
|
||||||
|
def _replace_variables(
|
||||||
|
self, template: str, context: Dict[str, Any]
|
||||||
|
) -> str:
|
||||||
|
"""Replace {{ variable }} with values from context."""
|
||||||
|
|
||||||
|
def replace_var(match):
|
||||||
|
var_name = match.group(1).strip()
|
||||||
|
|
||||||
|
# Handle nested variables with dot notation
|
||||||
|
if "." in var_name:
|
||||||
|
parts = var_name.split(".")
|
||||||
|
value = context
|
||||||
|
for part in parts:
|
||||||
|
if isinstance(value, dict) and part in value:
|
||||||
|
value = value[part]
|
||||||
|
else:
|
||||||
|
return match.group(0) # Return original if not found
|
||||||
|
return str(value) if value is not None else ""
|
||||||
|
|
||||||
|
# Handle simple variables
|
||||||
|
if var_name in context:
|
||||||
|
return (
|
||||||
|
str(context[var_name])
|
||||||
|
if context[var_name] is not None
|
||||||
|
else ""
|
||||||
|
)
|
||||||
|
|
||||||
|
return match.group(0) # Return original if not found
|
||||||
|
|
||||||
|
# Replace {{ variable }}
|
||||||
|
pattern = r"{{(.*?)}}"
|
||||||
|
return re.sub(pattern, replace_var, template)
|
||||||
|
|
||||||
|
def _process_conditionals(
|
||||||
|
self, template: str, context: Dict[str, Any]
|
||||||
|
) -> str:
|
||||||
|
"""Process {% if condition %} ... {% endif %} blocks."""
|
||||||
|
|
||||||
|
def replace_conditional(match):
|
||||||
|
condition_var = match.group(1).strip()
|
||||||
|
content = match.group(2)
|
||||||
|
|
||||||
|
# Handle nested conditions with dot notation
|
||||||
|
if "." in condition_var:
|
||||||
|
parts = condition_var.split(".")
|
||||||
|
value = context
|
||||||
|
for part in parts:
|
||||||
|
if isinstance(value, dict) and part in value:
|
||||||
|
value = value[part]
|
||||||
|
else:
|
||||||
|
return "" # Condition not met
|
||||||
|
|
||||||
|
if value:
|
||||||
|
return content
|
||||||
|
return ""
|
||||||
|
|
||||||
|
# Handle simple conditions
|
||||||
|
if condition_var in context and context[condition_var]:
|
||||||
|
return content
|
||||||
|
return ""
|
||||||
|
|
||||||
|
# Process {% if condition %} ... {% endif %}
|
||||||
|
pattern = r"{%\s*if\s+(.*?)\s*%}(.*?){%\s*endif\s*%}"
|
||||||
|
return re.sub(pattern, replace_conditional, template, flags=re.DOTALL)
|
||||||
|
|
||||||
|
def _process_loops(self, template: str, context: Dict[str, Any]) -> str:
|
||||||
|
"""Process {% for item_var, item in items.items() %} ... {% endfor %} blocks."""
|
||||||
|
|
||||||
|
# First, process dictionary iteration with items()
|
||||||
|
def replace_dict_loop(match):
|
||||||
|
item_key_var = match.group(1).strip()
|
||||||
|
item_val_var = match.group(2).strip()
|
||||||
|
collection_var = match.group(3).strip()
|
||||||
|
content_template = match.group(4)
|
||||||
|
|
||||||
|
# Handle nested collections with dot notation
|
||||||
|
if "." in collection_var:
|
||||||
|
parts = collection_var.split(".")
|
||||||
|
collection = context
|
||||||
|
for part in parts:
|
||||||
|
if isinstance(collection, dict) and part in collection:
|
||||||
|
collection = collection[part]
|
||||||
|
else:
|
||||||
|
return "" # Collection not found
|
||||||
|
else:
|
||||||
|
collection = context.get(collection_var, {})
|
||||||
|
|
||||||
|
if not isinstance(collection, dict):
|
||||||
|
return "" # Not a dictionary
|
||||||
|
|
||||||
|
result = []
|
||||||
|
for key, value in collection.items():
|
||||||
|
# Create a new context for each iteration
|
||||||
|
loop_context = context.copy()
|
||||||
|
loop_context[item_key_var] = key
|
||||||
|
loop_context[item_val_var] = value
|
||||||
|
|
||||||
|
# Process the content template with the loop context
|
||||||
|
item_content = self._replace_variables(
|
||||||
|
content_template, loop_context
|
||||||
|
)
|
||||||
|
item_content = self._process_conditionals(
|
||||||
|
item_content, loop_context
|
||||||
|
)
|
||||||
|
result.append(item_content)
|
||||||
|
|
||||||
|
return "".join(result)
|
||||||
|
|
||||||
|
# Process {% for key, value in dict.items() %} ... {% endfor %}
|
||||||
|
dict_pattern = r"{%\s*for\s+(.*?),\s*(.*?)\s+in\s+(.*?)\.items\(\)\s*%}(.*?){%\s*endfor\s*%}"
|
||||||
|
template = re.sub(
|
||||||
|
dict_pattern, replace_dict_loop, template, flags=re.DOTALL
|
||||||
|
)
|
||||||
|
|
||||||
|
# Then, process regular list iteration
|
||||||
|
def replace_list_loop(match):
|
||||||
|
item_var = match.group(1).strip()
|
||||||
|
collection_var = match.group(2).strip()
|
||||||
|
content_template = match.group(3)
|
||||||
|
|
||||||
|
# Handle nested collections with dot notation
|
||||||
|
if "." in collection_var:
|
||||||
|
parts = collection_var.split(".")
|
||||||
|
collection = context
|
||||||
|
for part in parts:
|
||||||
|
if isinstance(collection, dict) and part in collection:
|
||||||
|
collection = collection[part]
|
||||||
|
else:
|
||||||
|
return "" # Collection not found
|
||||||
|
else:
|
||||||
|
collection = context.get(collection_var, [])
|
||||||
|
|
||||||
|
if not isinstance(collection, (list, dict)):
|
||||||
|
return "" # Not a collection
|
||||||
|
|
||||||
|
result = []
|
||||||
|
if isinstance(collection, dict):
|
||||||
|
# If it's a dict, iterate over keys
|
||||||
|
for key in collection:
|
||||||
|
# Create a new context for each iteration
|
||||||
|
loop_context = context.copy()
|
||||||
|
loop_context[item_var] = key
|
||||||
|
|
||||||
|
# Process the content template with the loop context
|
||||||
|
item_content = self._replace_variables(
|
||||||
|
content_template, loop_context
|
||||||
|
)
|
||||||
|
item_content = self._process_conditionals(
|
||||||
|
item_content, loop_context
|
||||||
|
)
|
||||||
|
result.append(item_content)
|
||||||
|
else: # list
|
||||||
|
for item in collection:
|
||||||
|
# Create a new context for each iteration
|
||||||
|
loop_context = context.copy()
|
||||||
|
loop_context[item_var] = item
|
||||||
|
|
||||||
|
# Process the content template with the loop context
|
||||||
|
item_content = self._replace_variables(
|
||||||
|
content_template, loop_context
|
||||||
|
)
|
||||||
|
item_content = self._process_conditionals(
|
||||||
|
item_content, loop_context
|
||||||
|
)
|
||||||
|
result.append(item_content)
|
||||||
|
|
||||||
|
return "".join(result)
|
||||||
|
|
||||||
|
# Process {% for item in items %} ... {% endfor %}
|
||||||
|
list_pattern = (
|
||||||
|
r"{%\s*for\s+(.*?)\s+in\s+(.*?)\s*%}(.*?){%\s*endfor\s*%}"
|
||||||
|
)
|
||||||
|
return re.sub(
|
||||||
|
list_pattern, replace_list_loop, template, flags=re.DOTALL
|
||||||
|
)
|
||||||
|
|
||||||
|
def render(self, template_name: str, context: Dict[str, Any]) -> str:
|
||||||
|
"""Render a template with the given context."""
|
||||||
|
template = self._load_template(template_name)
|
||||||
|
|
||||||
|
# Process template directives in order
|
||||||
|
template = self._process_loops(template, context)
|
||||||
|
template = self._process_conditionals(template, context)
|
||||||
|
template = self._replace_variables(template, context)
|
||||||
|
|
||||||
|
return template
|
||||||
7
scripts/track_progress/templates/changelog_template.md
Normal file
7
scripts/track_progress/templates/changelog_template.md
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# {{ project_name }} Changelog
|
||||||
|
|
||||||
|
Last updated: {{ current_date }}
|
||||||
|
|
||||||
|
{% for entry in changelog_entries %}
|
||||||
|
{{ entry }}
|
||||||
|
{% endfor %}
|
||||||
92
scripts/track_progress/templates/feature_template.md
Normal file
92
scripts/track_progress/templates/feature_template.md
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
# {{ title }}
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This document tracks the implementation status of all features in the {{ project_name }} project.
|
||||||
|
|
||||||
|
## Feature Status Summary
|
||||||
|
|
||||||
|
| Status | Description |
|
||||||
|
|--------|-------------|
|
||||||
|
| ✅ Completed | Features that are fully implemented and tested |
|
||||||
|
| 🔄 In Progress | Features currently being implemented |
|
||||||
|
| 📝 Planned | Features planned for future implementation |
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
{% for feature_key, feature in features.items() %}
|
||||||
|
### {{ feature_key }}
|
||||||
|
|
||||||
|
**Status:** {{ feature.status_emoji }} {{ feature.status }}
|
||||||
|
**Description:** {{ feature.description }}
|
||||||
|
**Last Update:** {{ feature.date }}
|
||||||
|
{% if feature.author %}**Owner:** {{ feature.author }}{% endif %}
|
||||||
|
{% if feature.details %}**Details:** {{ feature.details }}{% endif %}
|
||||||
|
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
## Feature Categories
|
||||||
|
|
||||||
|
### Completed Features
|
||||||
|
|
||||||
|
{% for feature_key, feature in features.items() %}
|
||||||
|
{% if feature.status == 'completed' %}
|
||||||
|
- **{{ feature_key }}**: {{ feature.description }}
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
### In Progress Features
|
||||||
|
|
||||||
|
{% for feature_key, feature in features.items() %}
|
||||||
|
{% if feature.status == 'in_progress' %}
|
||||||
|
- **{{ feature_key }}**: {{ feature.description }}
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
### Planned Features
|
||||||
|
|
||||||
|
{% for feature_key, feature in features.items() %}
|
||||||
|
{% if feature.status == 'planned' %}
|
||||||
|
- **{{ feature_key }}**: {{ feature.description }}
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.feature-card {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 5px;
|
||||||
|
border-left: 5px solid #ccc;
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-card.completed {
|
||||||
|
border-left-color: #28a745;
|
||||||
|
background-color: #f0fff0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-card.in_progress {
|
||||||
|
border-left-color: #007bff;
|
||||||
|
background-color: #f0f8ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-card.planned {
|
||||||
|
border-left-color: #6c757d;
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-status {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-date {
|
||||||
|
color: #6c757d;
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
57
scripts/track_progress/templates/status_template.md
Normal file
57
scripts/track_progress/templates/status_template.md
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
# {{ project_name }} Development Status
|
||||||
|
|
||||||
|
Last updated: {{ current_date }}
|
||||||
|
|
||||||
|
## Code Statistics
|
||||||
|
|
||||||
|
{% if stats %}
|
||||||
|
Total lines of code: **{{ stats.total_lines }}**
|
||||||
|
|
||||||
|
Number of source files: **{{ stats.file_count }}**
|
||||||
|
|
||||||
|
### Files by Language
|
||||||
|
{% for lang, count in stats.languages.items() %}
|
||||||
|
- **{{ lang }}**: {{ count }} files
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
### Top Files by Line Count
|
||||||
|
|
||||||
|
| File | Lines | Language | Path |
|
||||||
|
|------|------:|----------|------|
|
||||||
|
{% for file in stats.top_files %}
|
||||||
|
| {{ file.name }} | {{ file.lines }} | {{ file.language }} | {{ file.path }} |
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
## Feature Status
|
||||||
|
|
||||||
|
| Feature | Status | Description | Last Update | Owner |
|
||||||
|
|---------|--------|-------------|-------------|-------|
|
||||||
|
{% for feature_key, feature in features.items() %}
|
||||||
|
| {{ feature.display_name }} | {{ feature.status_emoji }} {{ feature.status }} | {{ feature.description }} | {{ feature.date }} | {{ feature.author }} |
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
## Recent Updates
|
||||||
|
|
||||||
|
{% for entry in changelog_entries %}
|
||||||
|
{{ entry }}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
{% if known_issues %}
|
||||||
|
## Known Issues
|
||||||
|
|
||||||
|
{% for issue in known_issues %}
|
||||||
|
- {{ issue.description }} {% if issue.status %}({{ issue.status }}){% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if roadmap %}
|
||||||
|
## Roadmap
|
||||||
|
|
||||||
|
{% for milestone in roadmap %}
|
||||||
|
### {{ milestone.name }} ({{ milestone.target_date }})
|
||||||
|
{% for item in milestone.items %}
|
||||||
|
- {{ item.description }} {% if item.status %}[{{ item.status }}]{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
BIN
scripts/track_progress/track_progress.exe
Normal file
BIN
scripts/track_progress/track_progress.exe
Normal file
Binary file not shown.
284
scripts/track_progress/track_progress.py
Normal file
284
scripts/track_progress/track_progress.py
Normal file
@ -0,0 +1,284 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Project progress tracker that generates status documentation from Git history.
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import argparse
|
||||||
|
from datetime import datetime
|
||||||
|
from typing import Dict, Any, List, Optional
|
||||||
|
|
||||||
|
# Add the current directory to the Python path
|
||||||
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
|
||||||
|
# Import our modules
|
||||||
|
from config import Config
|
||||||
|
from code_stats import analyze_code_stats
|
||||||
|
from git_analyzer import GitAnalyzer
|
||||||
|
from feature_markdown import (
|
||||||
|
enrich_features,
|
||||||
|
emoji_status,
|
||||||
|
extract_features,
|
||||||
|
generate_features_md,
|
||||||
|
)
|
||||||
|
from template_engine import TemplateEngine
|
||||||
|
from changelog_generator import update_changelog_file
|
||||||
|
from roadmap_generator import generate_roadmap
|
||||||
|
|
||||||
|
|
||||||
|
def load_features(features_file: str) -> Dict[str, Dict[str, Any]]:
|
||||||
|
"""Load features from a Markdown file."""
|
||||||
|
# Use the improved extraction function
|
||||||
|
return extract_features(features_file)
|
||||||
|
|
||||||
|
|
||||||
|
def update_features(
|
||||||
|
features: Dict[str, Dict[str, Any]],
|
||||||
|
feature_updates: Dict[str, Dict[str, Any]],
|
||||||
|
new_features: Dict[str, Dict[str, Any]],
|
||||||
|
) -> Dict[str, Dict[str, Any]]:
|
||||||
|
"""Update features with new information from Git history."""
|
||||||
|
updated_features = features.copy()
|
||||||
|
|
||||||
|
# Update existing features
|
||||||
|
for feature_key, update in feature_updates.items():
|
||||||
|
if feature_key in updated_features:
|
||||||
|
updated_features[feature_key].update(update)
|
||||||
|
|
||||||
|
# Add new features
|
||||||
|
for feature_key, feature_data in new_features.items():
|
||||||
|
if feature_key not in updated_features:
|
||||||
|
updated_features[feature_key] = feature_data
|
||||||
|
|
||||||
|
return updated_features
|
||||||
|
|
||||||
|
|
||||||
|
def format_changelog_entries(git_data: Dict[str, Any]) -> List[str]:
|
||||||
|
"""Format changelog entries from Git data."""
|
||||||
|
entries = []
|
||||||
|
|
||||||
|
# Add changelog entries
|
||||||
|
for entry in git_data.get("changelog_entries", []):
|
||||||
|
entries.append(
|
||||||
|
f"- {entry['date']}: {entry['message']} ({entry['commit']})"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Add fix entries
|
||||||
|
for fix in git_data.get("fixes", []):
|
||||||
|
entries.append(
|
||||||
|
f"- {fix['date']}: Fixed: {fix['message']} ({fix['commit']})"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Add untagged commits
|
||||||
|
for commit in git_data.get("untagged_commits", []):
|
||||||
|
entries.append(
|
||||||
|
f"- {commit['date']}: {commit['message']} ({commit['commit']})"
|
||||||
|
)
|
||||||
|
|
||||||
|
return entries
|
||||||
|
|
||||||
|
|
||||||
|
def extract_known_issues(git_data: Dict[str, Any]) -> List[Dict[str, Any]]:
|
||||||
|
"""Extract known issues from Git data."""
|
||||||
|
issues = []
|
||||||
|
|
||||||
|
# Look for issues in the Git data
|
||||||
|
for issue in git_data.get("issues", []):
|
||||||
|
issues.append(
|
||||||
|
{"description": issue.get("message", ""), "status": "open"}
|
||||||
|
)
|
||||||
|
|
||||||
|
return issues
|
||||||
|
|
||||||
|
|
||||||
|
def enrich_features(
|
||||||
|
features: Dict[str, Dict[str, Any]]
|
||||||
|
) -> Dict[str, Dict[str, Any]]:
|
||||||
|
"""Add additional information to features for display purposes."""
|
||||||
|
enriched = {}
|
||||||
|
|
||||||
|
for key, data in features.items():
|
||||||
|
enriched[key] = data.copy()
|
||||||
|
status = data.get("status", "unknown")
|
||||||
|
enriched[key]["status_emoji"] = emoji_status(status)
|
||||||
|
|
||||||
|
# Add title-cased key for display
|
||||||
|
enriched[key]["display_name"] = key.replace("_", " ").title()
|
||||||
|
|
||||||
|
return enriched
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Main function to run the progress tracker."""
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description="Track project progress and generate status documentation."
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--config",
|
||||||
|
default="scripts/track_progress/progress_config.json",
|
||||||
|
help="Path to configuration file",
|
||||||
|
)
|
||||||
|
parser.add_argument("--project", help="Project name (overrides config)")
|
||||||
|
parser.add_argument(
|
||||||
|
"--output-dir", help="Output directory (overrides config)"
|
||||||
|
)
|
||||||
|
parser.add_argument("--repo", default=".", help="Repository path")
|
||||||
|
parser.add_argument(
|
||||||
|
"--verbose", "-v", action="store_true", help="Enable verbose output"
|
||||||
|
)
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
verbose = args.verbose
|
||||||
|
|
||||||
|
# Load configuration
|
||||||
|
config = Config(args.config)
|
||||||
|
|
||||||
|
# Override config with command-line arguments
|
||||||
|
if args.project:
|
||||||
|
config.set("project_name", args.project)
|
||||||
|
if args.output_dir:
|
||||||
|
config.set("output_dir", args.output_dir)
|
||||||
|
|
||||||
|
# Ensure output directory exists
|
||||||
|
output_dir = config.get("output_dir")
|
||||||
|
os.makedirs(output_dir, exist_ok=True)
|
||||||
|
|
||||||
|
# Initialize components
|
||||||
|
git_analyzer = GitAnalyzer(args.repo)
|
||||||
|
template_engine = TemplateEngine()
|
||||||
|
|
||||||
|
# Get repository information
|
||||||
|
repo_info = git_analyzer.get_repo_info()
|
||||||
|
|
||||||
|
# Analyze Git history
|
||||||
|
git_data = git_analyzer.analyze_commits(
|
||||||
|
limit=config.get("git_log_limit"),
|
||||||
|
untagged_limit=config.get("untagged_commit_limit"),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Analyze code statistics
|
||||||
|
code_stats = analyze_code_stats(
|
||||||
|
root_dir=args.repo,
|
||||||
|
exclude_dirs=config.get("exclude_dirs"),
|
||||||
|
source_extensions=config.get("source_extensions"),
|
||||||
|
top_files_limit=config.get("top_files_limit"),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Load existing features
|
||||||
|
features_file = config.get("features_file")
|
||||||
|
if not os.path.isabs(features_file):
|
||||||
|
# If it's not an absolute path, make it relative to the repo root, not the output dir
|
||||||
|
features_file = os.path.join(args.repo, features_file)
|
||||||
|
|
||||||
|
if verbose:
|
||||||
|
print(f"Loading features from: {features_file}")
|
||||||
|
|
||||||
|
features = load_features(features_file)
|
||||||
|
|
||||||
|
if verbose:
|
||||||
|
print(f"Loaded {len(features)} features")
|
||||||
|
|
||||||
|
# Update features with Git data
|
||||||
|
features = update_features(
|
||||||
|
features,
|
||||||
|
git_data.get("feature_updates", {}),
|
||||||
|
git_data.get("new_features", {}),
|
||||||
|
)
|
||||||
|
|
||||||
|
if verbose:
|
||||||
|
print(f"Updated features count: {len(features)}")
|
||||||
|
|
||||||
|
# Enrich features with emojis and other display information
|
||||||
|
enriched_features = enrich_features(features)
|
||||||
|
|
||||||
|
# Format changelog entries
|
||||||
|
changelog_entries = format_changelog_entries(git_data)
|
||||||
|
|
||||||
|
# Extract known issues
|
||||||
|
known_issues = extract_known_issues(git_data)
|
||||||
|
|
||||||
|
# Generate roadmap
|
||||||
|
roadmap = generate_roadmap(
|
||||||
|
git_data.get("milestones", {}), git_data.get("roadmap_items", {})
|
||||||
|
)
|
||||||
|
|
||||||
|
# Prepare template context
|
||||||
|
context = {
|
||||||
|
"project_name": config.get("project_name"),
|
||||||
|
"current_date": datetime.now().strftime("%Y-%m-%d"),
|
||||||
|
"repo_info": repo_info,
|
||||||
|
"stats": code_stats,
|
||||||
|
"features": enriched_features,
|
||||||
|
"changelog_entries": changelog_entries,
|
||||||
|
"known_issues": known_issues,
|
||||||
|
"roadmap": roadmap,
|
||||||
|
}
|
||||||
|
|
||||||
|
# Generate status document
|
||||||
|
status_template = config.get("templates", {}).get(
|
||||||
|
"status", "status_template.md"
|
||||||
|
)
|
||||||
|
status_content = template_engine.render(status_template, context)
|
||||||
|
|
||||||
|
status_file = os.path.join(args.repo, output_dir, config.get("status_doc"))
|
||||||
|
os.makedirs(os.path.dirname(status_file), exist_ok=True)
|
||||||
|
with open(status_file, "w", encoding="utf-8") as f:
|
||||||
|
f.write(status_content)
|
||||||
|
|
||||||
|
# Generate features document using the improved function
|
||||||
|
features_content = generate_features_md(
|
||||||
|
features, config.get("project_name")
|
||||||
|
)
|
||||||
|
|
||||||
|
# Ensure directory exists for features file
|
||||||
|
os.makedirs(os.path.dirname(features_file), exist_ok=True)
|
||||||
|
|
||||||
|
# Create backup of original features file
|
||||||
|
if os.path.exists(features_file):
|
||||||
|
backup_file = (
|
||||||
|
f"{features_file}.bak.{datetime.now().strftime('%Y%m%d%H%M%S')}"
|
||||||
|
)
|
||||||
|
with open(features_file, "r", encoding="utf-8") as src:
|
||||||
|
with open(backup_file, "w", encoding="utf-8") as dst:
|
||||||
|
dst.write(src.read())
|
||||||
|
if verbose:
|
||||||
|
print(f"Created backup: {backup_file}")
|
||||||
|
|
||||||
|
# Write the new features file
|
||||||
|
with open(features_file, "w", encoding="utf-8") as f:
|
||||||
|
f.write(features_content)
|
||||||
|
|
||||||
|
# Update changelog
|
||||||
|
changelog_file = config.get(
|
||||||
|
"changelog_file", "CHANGELOG.md"
|
||||||
|
) # Default to CHANGELOG.md
|
||||||
|
|
||||||
|
if not os.path.isabs(changelog_file):
|
||||||
|
# If it's a relative path, make it relative to the repo root, not the output dir
|
||||||
|
changelog_file = os.path.join(args.repo, changelog_file)
|
||||||
|
|
||||||
|
if verbose:
|
||||||
|
print(f"Updating changelog file: {changelog_file}")
|
||||||
|
|
||||||
|
# Ensure the directory exists
|
||||||
|
changelog_dir = os.path.dirname(changelog_file)
|
||||||
|
if changelog_dir: # Only create directory if there is one
|
||||||
|
os.makedirs(changelog_dir, exist_ok=True)
|
||||||
|
|
||||||
|
update_changelog_file(
|
||||||
|
git_data.get("changelog_entries", []),
|
||||||
|
changelog_file,
|
||||||
|
max_entries=config.get("git_log_limit"),
|
||||||
|
verbose=verbose,
|
||||||
|
)
|
||||||
|
|
||||||
|
print(f"Progress tracking updated:")
|
||||||
|
print(f"- Status document: {status_file}")
|
||||||
|
print(f"- Features document: {features_file}")
|
||||||
|
print(f"- Changelog: {changelog_file}")
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
Loading…
x
Reference in New Issue
Block a user