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:
Stan44 2025-05-12 18:37:02 -05:00
parent 5352e907b9
commit 1b7601120c
32 changed files with 2988 additions and 588 deletions

7
.gitignore vendored
View File

@ -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.*

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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}")

View File

@ -0,0 +1,2 @@
# Changelog

View 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
```

View File

@ -0,0 +1 @@
# This file makes the directory a Python package

View 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

View 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

View 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

View 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>

View 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

View 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)

View 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

View 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"
}
}

View 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

View 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

View 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`

View 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())

View 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

View 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

View 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)

View 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

View 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()

View 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

View File

@ -0,0 +1,7 @@
# {{ project_name }} Changelog
Last updated: {{ current_date }}
{% for entry in changelog_entries %}
{{ entry }}
{% endfor %}

View 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>

View 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 %}

Binary file not shown.

View 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())