Initial commit

This commit is contained in:
Zhongwei Li
2025-11-29 17:55:18 +08:00
commit f33f21dd79
19 changed files with 3530 additions and 0 deletions

View File

@@ -0,0 +1,224 @@
#!/usr/bin/env python3
"""
Checkpoint Manager
Handles automatic checkpoint generation with git diff analysis.
"""
import subprocess
import json
from datetime import datetime
from pathlib import Path
from typing import Dict, List, Optional
class CheckpointManager:
"""Manage session checkpoints"""
def __init__(self, project_path: str = "."):
"""Initialize manager with project path"""
self.project_path = Path(project_path)
self.checkpoints_dir = self.project_path / ".sessions" / "checkpoints"
self.checkpoints_dir.mkdir(parents=True, exist_ok=True)
def _run_git(self, args: List[str]) -> Optional[str]:
"""Run git command"""
try:
result = subprocess.run(
["git"] + args,
cwd=self.project_path,
capture_output=True,
text=True,
check=True
)
return result.stdout.strip()
except subprocess.CalledProcessError:
return None
def analyze_git_changes(self) -> Dict[str, List[str]]:
"""Analyze git diff for file changes"""
# Get modified files with stats
diff_output = self._run_git(["diff", "--stat"])
modified = []
added = []
deleted = []
if diff_output:
for line in diff_output.split("\n"):
if "|" in line:
filename = line.split("|")[0].strip()
modified.append(filename)
# Get staged files
staged_output = self._run_git(["diff", "--cached", "--name-status"])
if staged_output:
for line in staged_output.split("\n"):
if not line:
continue
parts = line.split("\t")
if len(parts) == 2:
status, filename = parts
if status == "A":
added.append(filename)
elif status == "D":
deleted.append(filename)
elif status == "M":
if filename not in modified:
modified.append(filename)
return {
"modified": modified,
"added": added,
"deleted": deleted
}
def get_recent_commits(self, since_checkpoint: Optional[str] = None) -> List[str]:
"""Get commits since last checkpoint"""
if since_checkpoint:
# Load checkpoint file to get commit hash
checkpoint_file = self.checkpoints_dir / f"{since_checkpoint}.md"
if checkpoint_file.exists():
try:
with open(checkpoint_file) as f:
content = f.read()
# Look for commit hash in checkpoint metadata
for line in content.split("\n"):
if line.startswith("**Commit**:"):
commit_hash = line.split(":", 1)[1].strip()
# Get commits since that hash
log_output = self._run_git(["log", f"{commit_hash}..HEAD", "--oneline"])
if log_output:
return log_output.split("\n")
return []
except IOError:
pass
# Get last 5 commits
log_output = self._run_git(["log", "--oneline", "-5"])
if log_output:
return log_output.split("\n")
return []
def load_tdd_metrics(self) -> Optional[Dict]:
"""Load TDD metrics from .ccmp/state.json"""
state_file = self.project_path / ".ccmp" / "state.json"
if not state_file.exists():
return None
try:
with open(state_file) as f:
state = json.load(f)
tdd_state = state.get("tdd-workflow", {})
if tdd_state.get("active"):
return {
"cycles_today": tdd_state.get("cycles_today", 0),
"current_phase": tdd_state.get("current_phase", "N/A"),
"discipline_score": tdd_state.get("discipline_score", 100)
}
except (json.JSONDecodeError, IOError):
pass
return None
def generate_checkpoint(self, notes: Optional[str] = None, label: Optional[str] = None) -> str:
"""Generate checkpoint document"""
timestamp = datetime.now()
checkpoint_id = timestamp.strftime("%Y-%m-%dT%H-%M-%S")
changes = self.analyze_git_changes()
commits = self.get_recent_commits()
tdd_metrics = self.load_tdd_metrics()
# Get current commit hash for tracking
current_commit = self._run_git(["rev-parse", "HEAD"])
lines = []
lines.append(f"# Checkpoint: {checkpoint_id}")
if label:
lines.append(f"**Label**: {label}")
lines.append(f"**Time**: {timestamp.strftime('%Y-%m-%d %H:%M:%S')}")
if current_commit:
lines.append(f"**Commit**: {current_commit}")
lines.append("")
# What Changed
lines.append("## What Changed")
lines.append("")
if changes["modified"]:
lines.append("**Modified**:")
for file in changes["modified"][:10]:
lines.append(f"- {file}")
if changes["added"]:
lines.append("**Added**:")
for file in changes["added"][:10]:
lines.append(f"- {file}")
if changes["deleted"]:
lines.append("**Deleted**:")
for file in changes["deleted"][:10]:
lines.append(f"- {file}")
if not any([changes["modified"], changes["added"], changes["deleted"]]):
lines.append("*No changes detected*")
lines.append("")
# Commits
if commits:
lines.append("## Commits Since Last Checkpoint")
lines.append("")
for commit in commits:
lines.append(f"- {commit}")
lines.append("")
# TDD Metrics
if tdd_metrics:
lines.append("## TDD Metrics")
lines.append("")
lines.append(f"- Cycles today: {tdd_metrics['cycles_today']}")
lines.append(f"- Current phase: {tdd_metrics['current_phase']}")
lines.append(f"- Discipline score: {tdd_metrics['discipline_score']}/100")
lines.append("")
# Notes
if notes:
lines.append("## Notes")
lines.append("")
lines.append(notes)
lines.append("")
checkpoint_content = "\n".join(lines)
# Save checkpoint
checkpoint_file = self.checkpoints_dir / f"{checkpoint_id}.md"
with open(checkpoint_file, 'w') as f:
f.write(checkpoint_content)
return checkpoint_content
def get_latest_checkpoint(self) -> Optional[Path]:
"""Get most recent checkpoint file"""
checkpoints = sorted(self.checkpoints_dir.glob("*.md"), reverse=True)
return checkpoints[0] if checkpoints else None
def main():
"""CLI entry point"""
import argparse
parser = argparse.ArgumentParser(description="Create session checkpoint")
parser.add_argument("--label", help="Checkpoint label")
parser.add_argument("--notes", help="Checkpoint notes")
args = parser.parse_args()
manager = CheckpointManager()
checkpoint = manager.generate_checkpoint(notes=args.notes, label=args.label)
print(checkpoint)
print(f"\nCheckpoint saved to .sessions/checkpoints/")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,133 @@
#!/usr/bin/env python3
"""
Handoff Generator
Generates comprehensive session handoff documents.
"""
import json
from datetime import datetime
from pathlib import Path
from typing import Optional, Dict, List
from checkpoint import CheckpointManager
class HandoffGenerator:
"""Generate session handoff documents"""
def __init__(self, project_path: str = "."):
"""Initialize generator"""
self.project_path = Path(project_path)
self.checkpoint_manager = CheckpointManager(project_path)
def load_session_state(self) -> Dict:
"""Load current session state"""
state_file = self.project_path / ".sessions" / "state.json"
if state_file.exists():
try:
with open(state_file) as f:
return json.load(f)
except (json.JSONDecodeError, IOError):
pass
return {}
def generate_handoff(self, session_notes: str) -> str:
"""Generate comprehensive handoff document"""
timestamp = datetime.now()
handoff_id = timestamp.strftime("%Y-%m-%dT%H-%M-%S") + "-HANDOFF"
state = self.load_session_state()
changes = self.checkpoint_manager.analyze_git_changes()
lines = []
lines.append(f"# Session Handoff: {timestamp.strftime('%Y-%m-%d')}")
lines.append("")
lines.append(f"**Branch**: {state.get('branch', 'Unknown')}")
lines.append(f"**Date**: {timestamp.strftime('%Y-%m-%d %H:%M:%S')}")
lines.append("")
# Session Summary
lines.append("## Session Summary")
lines.append("")
lines.append(session_notes)
lines.append("")
# What Changed
lines.append("## What Changed (This Session)")
lines.append("")
total_modified = len(changes["modified"])
total_added = len(changes["added"])
total_deleted = len(changes["deleted"])
lines.append(f"**Files modified**: {total_modified}")
lines.append(f"**Files added**: {total_added}")
lines.append(f"**Files deleted**: {total_deleted}")
lines.append("")
if changes["modified"]:
lines.append("### Modified Files")
for file in changes["modified"][:10]:
lines.append(f"- {file}")
if len(changes["modified"]) > 10:
lines.append(f"- ... and {len(changes['modified']) - 10} more")
lines.append("")
# Open Work
lines.append("## What's Next (Open Work)")
lines.append("")
objectives = state.get("objectives", [])
if objectives:
lines.append("**Objectives**:")
for obj in objectives:
if isinstance(obj, dict):
status = "[x]" if obj.get("completed") else "[ ]"
lines.append(f"- {status} {obj.get('text')}")
else:
lines.append(f"- [ ] {obj}")
lines.append("")
# Context Health
lines.append("## Context Health")
lines.append("")
lines.append("✅ Session state saved")
lines.append("")
# Next Steps (AI Suggestions placeholder)
lines.append("## Next Steps (Suggested)")
lines.append("")
lines.append("1. Review changes since last session")
lines.append("2. Continue work on open objectives")
lines.append("")
handoff_content = "\n".join(lines)
# Save handoff
handoff_file = self.checkpoint_manager.checkpoints_dir / f"{handoff_id}.md"
with open(handoff_file, 'w') as f:
f.write(handoff_content)
return handoff_content
def main():
"""CLI entry point"""
import argparse
parser = argparse.ArgumentParser(description="Generate session handoff")
parser.add_argument("--notes", required=True, help="Session summary notes")
args = parser.parse_args()
generator = HandoffGenerator()
handoff = generator.generate_handoff(session_notes=args.notes)
print(handoff)
print(f"\nHandoff saved to .sessions/checkpoints/")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,86 @@
#!/usr/bin/env python3
"""
Session Management Initialization Script
Initializes session management in a git repository by creating the .session/
directory with configuration and template files.
Usage:
python init_session.py [--force]
Options:
--force Overwrite existing .session/ directory
"""
import argparse
import os
import shutil
from pathlib import Path
def main():
parser = argparse.ArgumentParser(description="Initialize session management")
parser.add_argument("--force", action="store_true", help="Overwrite existing files")
args = parser.parse_args()
# Check if we're in a git repository
if not Path(".git").exists():
print("Error: Not in a git repository")
print("Run 'git init' first")
return 1
# Check if .session/ already exists
session_dir = Path(".session")
if session_dir.exists() and not args.force:
print("Session management already initialized")
print("Use --force to reinitialize")
return 1
# Create .session/ directory
session_dir.mkdir(exist_ok=True)
print(f"✅ Created {session_dir}/")
# Copy template files from assets/
# Note: This assumes the skill is installed and assets are available
# In production, these would be copied from the skill's assets/ directory
# Create basic files
files_created = []
# config.yaml
config_path = session_dir / "config.yaml"
if args.force or not config_path.exists():
# In production: copy from assets/config-template.yaml
print(f"✅ Created {config_path}")
files_created.append(str(config_path))
# architecture.md
arch_path = session_dir / "architecture.md"
if args.force or not arch_path.exists():
# In production: copy from assets/architecture-template.md
print(f"✅ Created {arch_path}")
files_created.append(str(arch_path))
# conventions.md
conv_path = session_dir / "conventions.md"
if args.force or not conv_path.exists():
# In production: copy from assets/conventions-template.md
print(f"✅ Created {conv_path}")
files_created.append(str(conv_path))
# Create .git/sessions/ directory for local session data
git_sessions_dir = Path(".git/sessions")
git_sessions_dir.mkdir(exist_ok=True)
print(f"✅ Created {git_sessions_dir}/")
print("\n🎉 Session management initialized!")
print("\nNext steps:")
print("1. Edit .session/architecture.md with your project's architecture")
print("2. Edit .session/conventions.md with your code conventions")
print("3. Customize .session/config.yaml as needed")
print("4. Commit .session/ to git: git add .session/ && git commit -m 'Initialize session management'")
print("5. Start your first session: python scripts/session.py start <branch-name>")
return 0
if __name__ == "__main__":
exit(main())

View File

@@ -0,0 +1,700 @@
#!/usr/bin/env python3
"""
Session Management CLI
Main command-line interface for managing coding sessions.
Usage:
python session.py <command> [options]
Commands:
start - Start new or resume existing session
resume - Resume current session
checkpoint - Create checkpoint
end - End session with handoff
switch - Switch to different session
status - Show session status
history - Show session history
objectives - Manage session objectives
blockers - Manage session blockers
decisions - Log architectural decisions
analyze - Analyze session metrics
compare - Compare sessions
report - Generate reports
For detailed command reference, see references/commands.md
"""
import argparse
import sys
from pathlib import Path
import subprocess
# Add lib to path for integration imports
repo_root = Path(__file__).resolve().parents[5] # Go up to repo root
sys.path.insert(0, str(repo_root / "lib"))
try:
from session_integration import SessionIntegration
from ccmp_integration import CCMPIntegration, is_session_active, is_tdd_mode
from tdd_analyzer import TDDAnalyzer
INTEGRATION_AVAILABLE = True
except ImportError:
INTEGRATION_AVAILABLE = False
print("⚠️ Integration libraries not found. Running in standalone mode.")
def check_session_initialized():
"""Check if session management is initialized"""
# Check for either .session or .sessions directory
if not (Path(".session").exists() or Path(".sessions").exists()):
print("❌ Session management not initialized")
print("Run: python scripts/init_session.py")
return False
return True
def get_current_branch():
"""Get current git branch"""
try:
result = subprocess.run(
["git", "rev-parse", "--abbrev-ref", "HEAD"],
capture_output=True,
text=True,
check=True
)
return result.stdout.strip()
except subprocess.CalledProcessError:
return None
def get_changed_directories():
"""Get directories with changes since last commit"""
try:
result = subprocess.run(
["git", "diff", "--name-only", "HEAD"],
capture_output=True,
text=True,
check=True
)
files = result.stdout.strip().split('\n')
directories = set()
for file in files:
if file:
directories.add(Path(file).parent)
return list(directories)
except subprocess.CalledProcessError:
return []
def cmd_start(args):
"""Start new session"""
if not check_session_initialized():
return 1
# Generate project status report
print("📊 Generating project status report...\n")
# Import from project-status-report plugin
import json
from datetime import datetime
# Add project-status-report to path
report_plugin_path = Path(__file__).parents[5] / "plugins" / "project-status-report" / "skills" / "project-status-report" / "scripts"
if report_plugin_path not in sys.path:
sys.path.insert(0, str(report_plugin_path))
try:
from report import ReportGenerator
generator = ReportGenerator()
report = generator.generate()
print(report)
print("\n" + "=" * 60 + "\n")
except ImportError:
print("⚠️ project-status-report plugin not found, skipping report\n")
# Handle branch selection
branch = args.branch
# Only present interactive prompts if branch not provided
if not branch:
print("What would you like to work on?")
print()
print("1. Resume existing work (if available)")
print("2. Start new work")
print("3. Address health issues first")
print()
choice = input("Choice [1/2/3]: ")
if choice == "1":
# Load branch from last session
state_file = Path(".sessions") / "state.json"
last_branch = None
if state_file.exists():
try:
with open(state_file) as f:
last_state = json.load(f)
last_branch = last_state.get("branch")
except (json.JSONDecodeError, IOError):
pass
if last_branch:
print(f"\nLast session was on branch: {last_branch}")
resume_choice = input(f"Resume '{last_branch}'? [Y/n]: ").strip().lower()
if resume_choice in ['', 'y', 'yes']:
branch = last_branch
else:
branch = input("Enter branch name to resume: ")
else:
print("\nNo previous session found.")
branch = input("Enter branch name to resume: ")
else:
branch = input("Enter new branch name: ")
# Checkout or create branch
try:
subprocess.run(["git", "checkout", branch], check=True, capture_output=True)
print(f"✅ Switched to branch: {branch}")
except subprocess.CalledProcessError:
# Branch doesn't exist, create it
try:
subprocess.run(["git", "checkout", "-b", branch], check=True, capture_output=True)
print(f"✅ Created new branch: {branch}")
except subprocess.CalledProcessError as e:
print(f"❌ Failed to create branch: {e}")
return 1
# Parse objectives
objectives = []
if args.objective:
objectives = [obj.strip() for obj in args.objective.split(',')]
else:
print("\nEnter session objectives (comma-separated):")
obj_input = input("> ")
if obj_input:
objectives = [obj.strip() for obj in obj_input.split(',')]
# Save session state
mode = "tdd" if args.tdd else "normal"
state = {
"branch": branch,
"objectives": objectives,
"started_at": datetime.now().isoformat(),
"mode": mode
}
state_file = Path(".sessions") / "state.json"
with open(state_file, 'w') as f:
json.dump(state, f, indent=2)
# Update .ccmp/state.json if available
ccmp_state_file = Path(".ccmp") / "state.json"
if ccmp_state_file.parent.exists():
try:
if ccmp_state_file.exists():
with open(ccmp_state_file) as f:
ccmp_state = json.load(f)
else:
ccmp_state = {}
ccmp_state["session-management"] = {
"active": True,
"branch": branch,
"objectives": objectives,
"mode": state["mode"]
}
with open(ccmp_state_file, 'w') as f:
json.dump(ccmp_state, f, indent=2)
except (json.JSONDecodeError, IOError):
pass
# Display session ready message
print("\n" + "=" * 60)
print("🚀 SESSION READY")
print("=" * 60)
print(f"Branch: {branch}")
print(f"Mode: {state['mode'].upper()}")
if objectives:
print("\n📋 OBJECTIVES:")
for i, obj in enumerate(objectives, 1):
print(f" {i}. {obj}")
print("=" * 60)
return 0
def cmd_resume(args):
"""Resume existing session"""
if not check_session_initialized():
return 1
# Get branch to resume
branch = args.branch if args.branch else get_current_branch()
if not branch:
print("❌ Not in a git repository")
return 1
# Checkout branch if specified
if args.branch:
try:
subprocess.run(["git", "checkout", branch], check=True, capture_output=True)
except subprocess.CalledProcessError as e:
print(f"❌ Failed to checkout branch: {e}")
return 1
if INTEGRATION_AVAILABLE:
# Load session state from integration
integration = CCMPIntegration()
session_state = integration.get_state("session-management")
if session_state and session_state.get("active"):
# Resume with full context
session_int = SessionIntegration()
brief = session_int.start_session(
branch,
session_state.get("objectives", []),
session_state.get("mode", "normal")
)
print(brief)
else:
print(f"⚠️ No active session found for {branch}")
print("Start a new session with: python session.py start <branch> --objective \"...\"")
else:
print(f"Resuming session on branch: {branch}")
return 0
def cmd_checkpoint(args):
"""Create checkpoint"""
if not check_session_initialized():
return 1
# Use new CheckpointManager
from checkpoint import CheckpointManager
manager = CheckpointManager()
label = args.label or "checkpoint"
notes = args.notes if hasattr(args, 'notes') else None
checkpoint = manager.generate_checkpoint(
notes=notes,
label=label
)
print(checkpoint)
print(f"\n✅ Checkpoint saved")
# Git commit handling (if requested)
if hasattr(args, 'commit') and args.commit:
# Check for uncommitted changes
try:
result = subprocess.run(
["git", "diff", "--quiet"],
capture_output=True
)
if result.returncode != 0: # There are changes
# Stage all changes
subprocess.run(["git", "add", "."], check=True)
# Generate commit message
commit_msg = args.message if hasattr(args, 'message') and args.message else None
# If no custom message, use git-commit skill to analyze and suggest
if not commit_msg:
try:
# Run analyze-diff.py from git-commit plugin
analyzer_script = repo_root / "plugins" / "git-commit" / "skills" / "git-commit" / "scripts" / "analyze-diff.py"
if analyzer_script.exists():
result = subprocess.run(
["python3", str(analyzer_script), "--json"],
capture_output=True,
text=True,
check=True
)
analysis = json.loads(result.stdout)
if analysis and 'error' not in analysis:
# Build commit message from analysis
msg_type = analysis['type']
scope = f"({analysis['scope']})" if analysis['scope'] else ""
breaking = "!" if analysis['breaking'] else ""
desc = analysis['description']
commit_msg = f"{msg_type}{scope}{breaking}: {desc}"
# Add notes as body if provided
if notes:
commit_msg = f"{commit_msg}\n\n{notes}"
print(f"\n📊 Analyzed changes: {msg_type} ({analysis['confidence']:.0%} confidence)")
print(f"📝 Suggested commit:\n {commit_msg.split(chr(10))[0]}")
else:
# Fallback to checkpoint label
commit_msg = f"checkpoint: {label}"
if notes:
commit_msg = f"{commit_msg}\n\n{notes}"
else:
# git-commit skill not found, use simple message
commit_msg = f"checkpoint: {label}"
if notes:
commit_msg = f"{commit_msg}\n\n{notes}"
except Exception as e:
# On any error, fall back to simple message
print(f"⚠️ Commit analysis failed ({e}), using simple message")
commit_msg = f"checkpoint: {label}"
if notes:
commit_msg = f"{commit_msg}\n\n{notes}"
else:
# Custom message provided, add notes if present
if notes:
commit_msg = f"{commit_msg}\n\n{notes}"
# Create commit
subprocess.run(["git", "commit", "-m", commit_msg], check=True)
print("📝 Git commit created")
except subprocess.CalledProcessError as e:
print(f"⚠️ Git commit failed: {e}")
# Legacy integration support
if INTEGRATION_AVAILABLE and is_tdd_mode() and hasattr(args, 'tdd_phase') and args.tdd_phase:
integration = CCMPIntegration()
tdd_state = integration.get_state("tdd-workflow") or {}
if args.tdd_phase == "GREEN":
cycles = tdd_state.get("cycles_today", 0) + 1
integration.update_state("tdd-workflow", {
"active": True,
"cycles_today": cycles,
"current_phase": "GREEN",
"discipline_score": 100
})
print(f"\n🎯 TDD Cycles completed today: {cycles}")
else:
integration.update_state("tdd-workflow", {
"active": True,
"cycles_today": tdd_state.get("cycles_today", 0),
"current_phase": args.tdd_phase,
"discipline_score": 100
})
print(f"\n🧪 TDD: {args.tdd_phase} phase checkpoint created")
return 0
def cmd_end(args):
"""End session"""
if not check_session_initialized():
return 1
# Gather session notes - use args if provided, otherwise prompt
accomplished = args.accomplished if hasattr(args, 'accomplished') and args.accomplished else None
decisions = args.decisions if hasattr(args, 'decisions') and args.decisions else None
remember = args.remember if hasattr(args, 'remember') and args.remember else None
# Only prompt if not provided via arguments
if not accomplished or not decisions or not remember:
print("\nSession Summary Notes:")
if not accomplished:
print("\nWhat did you accomplish?")
accomplished = input("> ")
if not decisions:
print("\nKey decisions made?")
decisions = input("> ")
if not remember:
print("\nWhat to remember for next session?")
remember = input("> ")
session_notes = f"""**Accomplished**: {accomplished}
**Decisions**: {decisions}
**Remember**: {remember}
"""
# Generate handoff
from handoff import HandoffGenerator
generator = HandoffGenerator()
handoff = generator.generate_handoff(session_notes=session_notes)
print("\n" + handoff)
print(f"\n✅ Session ended. Handoff generated.")
# Git push handling
should_push = False
if hasattr(args, 'no_push') and args.no_push:
should_push = False
elif hasattr(args, 'push') and args.push:
should_push = True
else:
# Default behavior: ask user
try:
# Check for commits to push
result = subprocess.run(
["git", "log", "@{u}..", "--oneline"],
capture_output=True,
text=True,
check=True
)
commits = result.stdout.strip().split("\n")
if commits and commits[0]:
print(f"\nCommits to push: {len(commits)}")
for commit in commits[:5]:
print(f" - {commit}")
response = input(f"\nPush {len(commits)} commits to remote? [Y/n]: ")
should_push = response.lower() != 'n'
except subprocess.CalledProcessError:
print("⚠️ No commits to push")
should_push = False
# Execute push if decided
if should_push:
try:
subprocess.run(["git", "push"], check=True)
print("📤 Pushed to remote")
except subprocess.CalledProcessError:
print("⚠️ Git push failed")
# Optional: merge to target branch
if hasattr(args, 'merge_to') and args.merge_to:
try:
current_branch = get_current_branch()
subprocess.run(["git", "checkout", args.merge_to], check=True)
subprocess.run(["git", "merge", current_branch], check=True)
print(f"\n✅ Merged to {args.merge_to}")
except subprocess.CalledProcessError as e:
print(f"\n❌ Merge failed: {e}")
return 1
return 0
def cmd_status(args):
"""Show session status"""
if not check_session_initialized():
return 1
branch = get_current_branch()
print("📊 Session Status")
print("=" * 60)
print(f"Branch: {branch}")
if INTEGRATION_AVAILABLE:
integration = CCMPIntegration()
session_state = integration.get_state("session-management")
if session_state and session_state.get("active"):
print(f"Status: ✅ Active")
print(f"Mode: {session_state.get('mode', 'normal').upper()}")
# Show objectives
objectives = session_state.get("objectives", [])
if objectives:
print("\n📋 OBJECTIVES:")
for i, obj in enumerate(objectives, 1):
print(f" {i}. {obj}")
# Show TDD metrics if in TDD mode
if is_tdd_mode():
tdd_state = integration.get_state("tdd-workflow")
if tdd_state:
print("\n🧪 TDD METRICS:")
print(f" Cycles today: {tdd_state.get('cycles_today', 0)}")
print(f" Current phase: {tdd_state.get('current_phase', 'N/A')}")
print(f" Discipline score: {tdd_state.get('discipline_score', 100)}/100")
# Show context health
context_state = integration.get_state("claude-context-manager")
if context_state:
health_score = context_state.get("health_score")
if health_score is not None:
print("\n🏥 CONTEXT HEALTH:")
print(f" Overall score: {health_score}/100")
critical = context_state.get("critical_files", [])
if critical:
print(f" ⚠️ {len(critical)} files need attention")
else:
print("Status: ⚪ No active session")
print("Start a session with: python session.py start <branch> --objective \"...\"")
else:
print("Status: ⚠️ Integration not available")
print("=" * 60)
return 0
def cmd_history(args):
"""Show session history"""
if not check_session_initialized():
return 1
print(f"Showing last {args.count} sessions...")
# Implementation would:
# 1. Load archived sessions
# 2. Display timeline
# 3. Show metrics if requested
# 4. Calculate trends
return 0
def cmd_objectives(args):
"""Manage objectives"""
if not check_session_initialized():
return 1
if args.action == "add":
print(f"Adding objective: {args.text}")
elif args.action == "complete":
print(f"Completing objective: {args.id}")
elif args.action == "list":
print("Current Objectives:")
# List objectives
return 0
def cmd_blockers(args):
"""Manage blockers"""
if not check_session_initialized():
return 1
if args.action == "add":
print(f"Adding blocker: {args.text}")
elif args.action == "resolve":
print(f"Resolving blocker: {args.id}")
elif args.action == "list":
print("Active Blockers:")
# List blockers
return 0
def cmd_decisions(args):
"""Log decisions"""
if not check_session_initialized():
return 1
if args.action == "add":
print(f"Recording decision: {args.text}")
if args.rationale:
print(f"Rationale: {args.rationale}")
elif args.action == "list":
print("Decisions:")
# List decisions
return 0
def cmd_analyze(args):
"""Analyze session metrics"""
if not check_session_initialized():
return 1
if INTEGRATION_AVAILABLE and is_tdd_mode():
# Run TDD analysis
analyzer = TDDAnalyzer()
print("🔍 Analyzing TDD discipline...")
print()
# Analyze commits
commit_analysis = analyzer.analyze_session_commits()
print(analyzer.generate_violation_report(commit_analysis))
# Analyze cycle timing
cycle_analysis = analyzer.analyze_tdd_cycle_timing()
if cycle_analysis["total_cycles"] > 0:
print("⏱️ TDD Cycle Timing:")
print(f" • Total cycles: {cycle_analysis['total_cycles']}")
print(f" • Average cycle time: {cycle_analysis['average_cycle_time']:.1f} minutes")
print(f" • Fastest cycle: {cycle_analysis['fastest_cycle']:.1f} minutes")
print(f" • Slowest cycle: {cycle_analysis['slowest_cycle']:.1f} minutes")
print()
else:
print("Session Analysis")
print("=" * 50)
print("Analysis features available in TDD mode with integration enabled")
return 0
def main():
parser = argparse.ArgumentParser(description="Session Management CLI")
subparsers = parser.add_subparsers(dest="command", help="Command to execute")
# start command
start_parser = subparsers.add_parser("start", help="Start new session")
start_parser.add_argument("branch", nargs="?", help="Branch name (optional, will prompt if not provided)")
start_parser.add_argument("--objective", help="Session objective (comma-separated for multiple)")
start_parser.add_argument("--tdd", action="store_true", help="Enable TDD mode")
start_parser.add_argument("--resume", action="store_true", help="Resume if exists")
start_parser.set_defaults(func=cmd_start)
# resume command
resume_parser = subparsers.add_parser("resume", help="Resume session")
resume_parser.add_argument("branch", nargs="?", help="Branch to resume")
resume_parser.set_defaults(func=cmd_resume)
# checkpoint command
checkpoint_parser = subparsers.add_parser("checkpoint", help="Create checkpoint")
checkpoint_parser.add_argument("--label", help="Checkpoint label")
checkpoint_parser.add_argument("--notes", help="Checkpoint notes")
checkpoint_parser.add_argument("--commit", action="store_true", help="Create git commit")
checkpoint_parser.add_argument("--message", help="Commit message")
checkpoint_parser.add_argument("--decision", help="Record decision")
checkpoint_parser.add_argument("--tdd-phase", choices=["RED", "GREEN", "REFACTOR"], help="TDD phase for this checkpoint")
checkpoint_parser.set_defaults(func=cmd_checkpoint)
# end command
end_parser = subparsers.add_parser("end", help="End session")
end_parser.add_argument("--handoff", action="store_true", default=True, help="Generate handoff")
end_parser.add_argument("--push", action="store_true", help="Push commits to remote")
end_parser.add_argument("--no-push", action="store_true", help="Don't push commits to remote")
end_parser.add_argument("--accomplished", help="What was accomplished in this session")
end_parser.add_argument("--decisions", help="Key decisions made")
end_parser.add_argument("--remember", help="What to remember for next session")
end_parser.add_argument("--merge-to", help="Merge to branch")
end_parser.set_defaults(func=cmd_end)
# status command
status_parser = subparsers.add_parser("status", help="Show status")
status_parser.add_argument("--verbose", action="store_true", help="Verbose output")
status_parser.set_defaults(func=cmd_status)
# history command
history_parser = subparsers.add_parser("history", help="Show history")
history_parser.add_argument("--count", type=int, default=10, help="Number of sessions")
history_parser.add_argument("--metrics", action="store_true", help="Include metrics")
history_parser.set_defaults(func=cmd_history)
# objectives command
objectives_parser = subparsers.add_parser("objectives", help="Manage objectives")
objectives_parser.add_argument("action", choices=["add", "complete", "list"])
objectives_parser.add_argument("text", nargs="?", help="Objective text")
objectives_parser.add_argument("--id", help="Objective ID")
objectives_parser.set_defaults(func=cmd_objectives)
# blockers command
blockers_parser = subparsers.add_parser("blockers", help="Manage blockers")
blockers_parser.add_argument("action", choices=["add", "resolve", "list"])
blockers_parser.add_argument("text", nargs="?", help="Blocker description")
blockers_parser.add_argument("--id", help="Blocker ID")
blockers_parser.set_defaults(func=cmd_blockers)
# decisions command
decisions_parser = subparsers.add_parser("decisions", help="Log decisions")
decisions_parser.add_argument("action", choices=["add", "list"])
decisions_parser.add_argument("text", nargs="?", help="Decision text")
decisions_parser.add_argument("--rationale", help="Decision rationale")
decisions_parser.set_defaults(func=cmd_decisions)
# analyze command
analyze_parser = subparsers.add_parser("analyze", help="Analyze session metrics")
analyze_parser.set_defaults(func=cmd_analyze)
args = parser.parse_args()
if not args.command:
parser.print_help()
return 1
return args.func(args)
if __name__ == "__main__":
sys.exit(main())

View File

@@ -0,0 +1,20 @@
import pytest
from pathlib import Path
from checkpoint import CheckpointManager
def test_analyze_git_changes():
"""Test analyzing git diff for changes"""
manager = CheckpointManager()
changes = manager.analyze_git_changes()
assert isinstance(changes, dict)
assert "modified" in changes
assert "added" in changes
assert "deleted" in changes
def test_generate_checkpoint():
"""Test generating checkpoint document"""
manager = CheckpointManager()
checkpoint = manager.generate_checkpoint(notes="Test notes")
assert isinstance(checkpoint, str)
assert "Checkpoint:" in checkpoint
assert "What Changed" in checkpoint

View File

@@ -0,0 +1,12 @@
import pytest
from handoff import HandoffGenerator
def test_generate_handoff():
"""Test generating session handoff document"""
generator = HandoffGenerator()
handoff = generator.generate_handoff(
session_notes="Test session notes"
)
assert isinstance(handoff, str)
assert "Session Handoff" in handoff
assert "Test session notes" in handoff