@pjmendonca/devflow 1.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +526 -0
- package/LICENSE +21 -0
- package/README.md +620 -0
- package/bin/devflow-checkpoint.js +10 -0
- package/bin/devflow-collab.js +10 -0
- package/bin/devflow-cost.js +10 -0
- package/bin/devflow-create-persona.js +10 -0
- package/bin/devflow-init.js +10 -0
- package/bin/devflow-memory.js +10 -0
- package/bin/devflow-new-doc.js +10 -0
- package/bin/devflow-personalize.js +10 -0
- package/bin/devflow-setup-checkpoint.js +10 -0
- package/bin/devflow-story.js +10 -0
- package/bin/devflow-tech-debt.js +10 -0
- package/bin/devflow-validate-overrides.js +10 -0
- package/bin/devflow-validate.js +10 -0
- package/bin/devflow-version.js +10 -0
- package/lib/constants.js +30 -0
- package/lib/exec-python.js +78 -0
- package/lib/python-check.js +178 -0
- package/package.json +64 -0
- package/tooling/.automation/agents/architect.md +135 -0
- package/tooling/.automation/agents/ba.md +70 -0
- package/tooling/.automation/agents/dev.md +79 -0
- package/tooling/.automation/agents/maintainer.md +97 -0
- package/tooling/.automation/agents/pm.md +116 -0
- package/tooling/.automation/agents/reviewer.md +141 -0
- package/tooling/.automation/agents/sm.md +61 -0
- package/tooling/.automation/agents/writer.md +193 -0
- package/tooling/.automation/config.ps1.template +61 -0
- package/tooling/.automation/config.sh.template +48 -0
- package/tooling/.automation/memory/.gitkeep +6 -0
- package/tooling/.automation/memory/knowledge/kg_integration-test.json +94 -0
- package/tooling/.automation/memory/knowledge/kg_test-story.json +300 -0
- package/tooling/.automation/memory/shared/shared_integration-test.json +30 -0
- package/tooling/.automation/memory/shared/shared_test-story.json +78 -0
- package/tooling/.automation/overrides/templates/README.md +113 -0
- package/tooling/.automation/overrides/templates/architect/README.md +27 -0
- package/tooling/.automation/overrides/templates/architect/cloud-native.yaml +92 -0
- package/tooling/.automation/overrides/templates/architect/enterprise-architect.yaml +85 -0
- package/tooling/.automation/overrides/templates/architect/pragmatic-minimalist.yaml +88 -0
- package/tooling/.automation/overrides/templates/ba/README.md +27 -0
- package/tooling/.automation/overrides/templates/ba/agile-storyteller.yaml +86 -0
- package/tooling/.automation/overrides/templates/ba/domain-expert.yaml +91 -0
- package/tooling/.automation/overrides/templates/ba/requirements-engineer.yaml +89 -0
- package/tooling/.automation/overrides/templates/dev/README.md +32 -0
- package/tooling/.automation/overrides/templates/dev/junior-mentored.yaml +39 -0
- package/tooling/.automation/overrides/templates/dev/performance-engineer.yaml +43 -0
- package/tooling/.automation/overrides/templates/dev/rapid-prototyper.yaml +52 -0
- package/tooling/.automation/overrides/templates/dev/security-focused.yaml +43 -0
- package/tooling/.automation/overrides/templates/dev/senior-fullstack.yaml +39 -0
- package/tooling/.automation/overrides/templates/maintainer/README.md +27 -0
- package/tooling/.automation/overrides/templates/maintainer/devops-maintainer.yaml +113 -0
- package/tooling/.automation/overrides/templates/maintainer/legacy-steward.yaml +94 -0
- package/tooling/.automation/overrides/templates/maintainer/oss-maintainer.yaml +94 -0
- package/tooling/.automation/overrides/templates/pm/README.md +27 -0
- package/tooling/.automation/overrides/templates/pm/agile-pm.yaml +91 -0
- package/tooling/.automation/overrides/templates/pm/hybrid-delivery.yaml +87 -0
- package/tooling/.automation/overrides/templates/pm/traditional-pm.yaml +91 -0
- package/tooling/.automation/overrides/templates/reviewer/README.md +11 -0
- package/tooling/.automation/overrides/templates/reviewer/mentoring-reviewer.yaml +45 -0
- package/tooling/.automation/overrides/templates/reviewer/quick-sanity.yaml +50 -0
- package/tooling/.automation/overrides/templates/reviewer/thorough-critic.yaml +48 -0
- package/tooling/.automation/overrides/templates/sm/README.md +11 -0
- package/tooling/.automation/overrides/templates/sm/agile-coach.yaml +52 -0
- package/tooling/.automation/overrides/templates/sm/startup-pm.yaml +50 -0
- package/tooling/.automation/overrides/templates/sm/technical-lead.yaml +47 -0
- package/tooling/.automation/overrides/templates/user-profile.template.yaml +62 -0
- package/tooling/.automation/overrides/templates/writer/README.md +27 -0
- package/tooling/.automation/overrides/templates/writer/api-documentarian.yaml +99 -0
- package/tooling/.automation/overrides/templates/writer/docs-as-code.yaml +108 -0
- package/tooling/.automation/overrides/templates/writer/user-guide-author.yaml +100 -0
- package/tooling/completions/DevflowCompletion.ps1 +213 -0
- package/tooling/completions/_run-story +116 -0
- package/tooling/completions/run-story-completion.bash +136 -0
- package/tooling/docs/DOC-STANDARD.md +717 -0
- package/tooling/docs/sprint-status.yaml.template +24 -0
- package/tooling/docs/templates/bug-report.md +234 -0
- package/tooling/docs/templates/migration-spec.md +274 -0
- package/tooling/docs/templates/refactor-spec.md +86 -0
- package/tooling/docs/templates/tech-debt.md +86 -0
- package/tooling/scripts/context_checkpoint.py +556 -0
- package/tooling/scripts/cost_dashboard.py +617 -0
- package/tooling/scripts/create-persona.py +690 -0
- package/tooling/scripts/create-persona.sh +435 -0
- package/tooling/scripts/init-project-workflow.ps1 +651 -0
- package/tooling/scripts/init-project-workflow.py +70 -0
- package/tooling/scripts/init-project-workflow.sh +746 -0
- package/tooling/scripts/lib/__init__.py +35 -0
- package/tooling/scripts/lib/agent_handoff.py +526 -0
- package/tooling/scripts/lib/agent_router.py +698 -0
- package/tooling/scripts/lib/checkpoint-integration.ps1 +245 -0
- package/tooling/scripts/lib/checkpoint-integration.sh +191 -0
- package/tooling/scripts/lib/claude-cli.ps1 +952 -0
- package/tooling/scripts/lib/claude-cli.sh +1293 -0
- package/tooling/scripts/lib/cost_config.py +222 -0
- package/tooling/scripts/lib/cost_display.py +443 -0
- package/tooling/scripts/lib/cost_tracker.py +710 -0
- package/tooling/scripts/lib/currency_converter.py +328 -0
- package/tooling/scripts/lib/errors.py +438 -0
- package/tooling/scripts/lib/override-loader.sh +286 -0
- package/tooling/scripts/lib/pair_programming.py +589 -0
- package/tooling/scripts/lib/shared_memory.py +637 -0
- package/tooling/scripts/lib/swarm_orchestrator.py +689 -0
- package/tooling/scripts/memory_summarize.py +324 -0
- package/tooling/scripts/new-doc.ps1 +405 -0
- package/tooling/scripts/new-doc.py +93 -0
- package/tooling/scripts/new-doc.sh +534 -0
- package/tooling/scripts/personalize_agent.py +385 -0
- package/tooling/scripts/rollback-migration.sh +540 -0
- package/tooling/scripts/run-collab.ps1 +251 -0
- package/tooling/scripts/run-collab.py +605 -0
- package/tooling/scripts/run-collab.sh +110 -0
- package/tooling/scripts/run-story.ps1 +490 -0
- package/tooling/scripts/run-story.py +387 -0
- package/tooling/scripts/run-story.sh +467 -0
- package/tooling/scripts/setup-checkpoint-service.ps1 +219 -0
- package/tooling/scripts/setup-checkpoint-service.py +87 -0
- package/tooling/scripts/setup-checkpoint-service.sh +236 -0
- package/tooling/scripts/tech-debt-tracker.py +608 -0
- package/tooling/scripts/update_version.py +244 -0
- package/tooling/scripts/validate-overrides.py +511 -0
- package/tooling/scripts/validate-overrides.sh +432 -0
- package/tooling/scripts/validate_setup.py +539 -0
|
@@ -0,0 +1,556 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Context Checkpoint Manager for Claude Code Sessions
|
|
4
|
+
|
|
5
|
+
Monitors Claude Code sessions for context window warnings and automatically:
|
|
6
|
+
1. Saves session state before context compaction
|
|
7
|
+
2. Creates checkpoint files with conversation history
|
|
8
|
+
3. Clears the session
|
|
9
|
+
4. Restarts with saved context
|
|
10
|
+
|
|
11
|
+
Usage:
|
|
12
|
+
python3 tooling/scripts/context_checkpoint.py --session-id <id>
|
|
13
|
+
python3 tooling/scripts/context_checkpoint.py --watch-logs
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import argparse
|
|
17
|
+
import json
|
|
18
|
+
import os
|
|
19
|
+
import re
|
|
20
|
+
import signal
|
|
21
|
+
import subprocess
|
|
22
|
+
import sys
|
|
23
|
+
import time
|
|
24
|
+
from datetime import datetime
|
|
25
|
+
from pathlib import Path
|
|
26
|
+
from typing import Optional
|
|
27
|
+
|
|
28
|
+
# Configuration
|
|
29
|
+
PROJECT_ROOT = Path(__file__).parent.parent.parent
|
|
30
|
+
CHECKPOINT_DIR = PROJECT_ROOT / "tooling" / ".automation" / "checkpoints"
|
|
31
|
+
LOGS_DIR = PROJECT_ROOT / "tooling" / ".automation" / "logs"
|
|
32
|
+
# Platform-specific CLI command
|
|
33
|
+
CLAUDE_CLI = "claude.cmd" if sys.platform == "win32" else "claude"
|
|
34
|
+
|
|
35
|
+
# Context thresholds
|
|
36
|
+
CONTEXT_WARNING_THRESHOLD = 0.75 # 75% - Start warning
|
|
37
|
+
CONTEXT_CRITICAL_THRESHOLD = 0.85 # 85% - Auto-checkpoint
|
|
38
|
+
CONTEXT_EMERGENCY_THRESHOLD = 0.95 # 95% - Force checkpoint
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
# Colors for terminal output
|
|
42
|
+
class Colors:
|
|
43
|
+
HEADER = "\033[95m"
|
|
44
|
+
BLUE = "\033[94m"
|
|
45
|
+
CYAN = "\033[96m"
|
|
46
|
+
GREEN = "\033[92m"
|
|
47
|
+
YELLOW = "\033[93m"
|
|
48
|
+
RED = "\033[91m"
|
|
49
|
+
BOLD = "\033[1m"
|
|
50
|
+
UNDERLINE = "\033[4m"
|
|
51
|
+
END = "\033[0m"
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class ContextCheckpointManager:
|
|
55
|
+
"""Manages context checkpointing for Claude Code sessions."""
|
|
56
|
+
|
|
57
|
+
def __init__(self, session_id: Optional[str] = None):
|
|
58
|
+
self.session_id = session_id
|
|
59
|
+
self.checkpoint_dir = CHECKPOINT_DIR
|
|
60
|
+
self.logs_dir = LOGS_DIR
|
|
61
|
+
self.running = True
|
|
62
|
+
self.last_checkpoint_time = None
|
|
63
|
+
self.checkpoint_count = 0
|
|
64
|
+
|
|
65
|
+
# Create directories
|
|
66
|
+
self.checkpoint_dir.mkdir(parents=True, exist_ok=True)
|
|
67
|
+
self.logs_dir.mkdir(parents=True, exist_ok=True)
|
|
68
|
+
|
|
69
|
+
# Handle graceful shutdown (Windows-compatible)
|
|
70
|
+
signal.signal(signal.SIGINT, self._signal_handler)
|
|
71
|
+
# SIGTERM is not available on Windows
|
|
72
|
+
if sys.platform != "win32":
|
|
73
|
+
signal.signal(signal.SIGTERM, self._signal_handler)
|
|
74
|
+
|
|
75
|
+
def _signal_handler(self, signum, frame):
|
|
76
|
+
"""Handle shutdown signals gracefully."""
|
|
77
|
+
print(f"\n{Colors.YELLOW}📦 Shutting down checkpoint manager...{Colors.END}")
|
|
78
|
+
self.running = False
|
|
79
|
+
sys.exit(0)
|
|
80
|
+
|
|
81
|
+
def _log(self, message: str, level: str = "INFO"):
|
|
82
|
+
"""Log a message with timestamp and color."""
|
|
83
|
+
timestamp = datetime.now().strftime("%H:%M:%S")
|
|
84
|
+
color_map = {
|
|
85
|
+
"INFO": Colors.CYAN,
|
|
86
|
+
"SUCCESS": Colors.GREEN,
|
|
87
|
+
"WARNING": Colors.YELLOW,
|
|
88
|
+
"ERROR": Colors.RED,
|
|
89
|
+
"CRITICAL": Colors.RED + Colors.BOLD,
|
|
90
|
+
}
|
|
91
|
+
color = color_map.get(level, Colors.END)
|
|
92
|
+
print(f"{Colors.BOLD}[{timestamp}]{Colors.END} {color}{message}{Colors.END}")
|
|
93
|
+
|
|
94
|
+
def extract_context_usage(self, output: str) -> Optional[float]:
|
|
95
|
+
"""Extract context usage percentage from Claude output.
|
|
96
|
+
|
|
97
|
+
Looks for patterns like:
|
|
98
|
+
- "Token usage: 75000/200000"
|
|
99
|
+
- "Context: 75%"
|
|
100
|
+
- "⚠️ CONTEXT WARNING"
|
|
101
|
+
"""
|
|
102
|
+
# Pattern 1: Token usage: X/Y
|
|
103
|
+
match = re.search(r"Token usage:\s*(\d+)/(\d+)", output)
|
|
104
|
+
if match:
|
|
105
|
+
used = int(match.group(1))
|
|
106
|
+
total = int(match.group(2))
|
|
107
|
+
return used / total
|
|
108
|
+
|
|
109
|
+
# Pattern 2: Context: X%
|
|
110
|
+
match = re.search(r"Context:\s*(\d+)%", output)
|
|
111
|
+
if match:
|
|
112
|
+
return int(match.group(1)) / 100
|
|
113
|
+
|
|
114
|
+
# Pattern 3: Warning messages
|
|
115
|
+
if "⚠️ CONTEXT WARNING" in output or "Approaching context limit" in output:
|
|
116
|
+
return 0.90 # Assume 90% if warning present
|
|
117
|
+
|
|
118
|
+
return None
|
|
119
|
+
|
|
120
|
+
def get_current_conversation(self) -> dict:
|
|
121
|
+
"""Extract current conversation from Claude session.
|
|
122
|
+
|
|
123
|
+
Uses Claude CLI to get session history if available.
|
|
124
|
+
"""
|
|
125
|
+
try:
|
|
126
|
+
# Try to export session history
|
|
127
|
+
result = subprocess.run(
|
|
128
|
+
[CLAUDE_CLI, "session", "export", "--format", "json"],
|
|
129
|
+
capture_output=True,
|
|
130
|
+
text=True,
|
|
131
|
+
timeout=10,
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
if result.returncode == 0 and result.stdout:
|
|
135
|
+
return json.loads(result.stdout)
|
|
136
|
+
|
|
137
|
+
self._log("Could not export session via CLI, using fallback", "WARNING")
|
|
138
|
+
return self._fallback_conversation_extract()
|
|
139
|
+
|
|
140
|
+
except subprocess.TimeoutExpired:
|
|
141
|
+
self._log("Session export timed out", "ERROR")
|
|
142
|
+
return self._fallback_conversation_extract()
|
|
143
|
+
except Exception as e:
|
|
144
|
+
self._log(f"Error extracting conversation: {e}", "ERROR")
|
|
145
|
+
return self._fallback_conversation_extract()
|
|
146
|
+
|
|
147
|
+
def _fallback_conversation_extract(self) -> dict:
|
|
148
|
+
"""Fallback method to extract conversation from logs."""
|
|
149
|
+
# Check for recent log files
|
|
150
|
+
log_files = sorted(
|
|
151
|
+
self.logs_dir.glob("*.log"), key=lambda p: p.stat().st_mtime, reverse=True
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
if not log_files:
|
|
155
|
+
return {"messages": [], "metadata": {}}
|
|
156
|
+
|
|
157
|
+
latest_log = log_files[0]
|
|
158
|
+
try:
|
|
159
|
+
with open(latest_log) as f:
|
|
160
|
+
content = f.read()
|
|
161
|
+
return {
|
|
162
|
+
"messages": [{"role": "system", "content": content}],
|
|
163
|
+
"metadata": {
|
|
164
|
+
"source": "log_file",
|
|
165
|
+
"file": str(latest_log),
|
|
166
|
+
"timestamp": datetime.now().isoformat(),
|
|
167
|
+
},
|
|
168
|
+
}
|
|
169
|
+
except Exception as e:
|
|
170
|
+
self._log(f"Error reading log file: {e}", "ERROR")
|
|
171
|
+
return {"messages": [], "metadata": {}}
|
|
172
|
+
|
|
173
|
+
def create_checkpoint(self, context_level: float, reason: str = "auto") -> Path:
|
|
174
|
+
"""Create a checkpoint of the current session.
|
|
175
|
+
|
|
176
|
+
Args:
|
|
177
|
+
context_level: Current context usage (0.0 to 1.0)
|
|
178
|
+
reason: Reason for checkpoint (auto, manual, emergency)
|
|
179
|
+
|
|
180
|
+
Returns:
|
|
181
|
+
Path to checkpoint file
|
|
182
|
+
"""
|
|
183
|
+
self.checkpoint_count += 1
|
|
184
|
+
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
185
|
+
checkpoint_id = f"checkpoint_{timestamp}_{self.checkpoint_count}"
|
|
186
|
+
|
|
187
|
+
self._log(f"💾 Creating checkpoint: {checkpoint_id}", "INFO")
|
|
188
|
+
|
|
189
|
+
# Extract current conversation
|
|
190
|
+
conversation = self.get_current_conversation()
|
|
191
|
+
|
|
192
|
+
# Create checkpoint data
|
|
193
|
+
checkpoint_data = {
|
|
194
|
+
"checkpoint_id": checkpoint_id,
|
|
195
|
+
"timestamp": datetime.now().isoformat(),
|
|
196
|
+
"context_level": context_level,
|
|
197
|
+
"reason": reason,
|
|
198
|
+
"session_id": self.session_id,
|
|
199
|
+
"conversation": conversation,
|
|
200
|
+
"metadata": {
|
|
201
|
+
"project_root": str(PROJECT_ROOT),
|
|
202
|
+
"working_directory": os.getcwd(),
|
|
203
|
+
"checkpoint_count": self.checkpoint_count,
|
|
204
|
+
},
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
# Save checkpoint file
|
|
208
|
+
checkpoint_file = self.checkpoint_dir / f"{checkpoint_id}.json"
|
|
209
|
+
with open(checkpoint_file, "w") as f:
|
|
210
|
+
json.dump(checkpoint_data, f, indent=2)
|
|
211
|
+
|
|
212
|
+
# Create summary file (human-readable)
|
|
213
|
+
summary_file = self.checkpoint_dir / f"{checkpoint_id}_summary.md"
|
|
214
|
+
self._create_checkpoint_summary(checkpoint_data, summary_file)
|
|
215
|
+
|
|
216
|
+
self._log(f"✅ Checkpoint saved: {checkpoint_file.name}", "SUCCESS")
|
|
217
|
+
self.last_checkpoint_time = datetime.now()
|
|
218
|
+
|
|
219
|
+
return checkpoint_file
|
|
220
|
+
|
|
221
|
+
def _create_checkpoint_summary(self, checkpoint_data: dict, output_file: Path):
|
|
222
|
+
"""Create a human-readable summary of the checkpoint."""
|
|
223
|
+
summary = f"""# Checkpoint Summary
|
|
224
|
+
|
|
225
|
+
**Checkpoint ID**: {checkpoint_data["checkpoint_id"]}
|
|
226
|
+
**Timestamp**: {checkpoint_data["timestamp"]}
|
|
227
|
+
**Context Level**: {checkpoint_data["context_level"] * 100:.1f}%
|
|
228
|
+
**Reason**: {checkpoint_data["reason"]}
|
|
229
|
+
**Session ID**: {checkpoint_data.get("session_id", "N/A")}
|
|
230
|
+
|
|
231
|
+
## Conversation Snapshot
|
|
232
|
+
|
|
233
|
+
Messages: {len(checkpoint_data["conversation"].get("messages", []))}
|
|
234
|
+
Source: {checkpoint_data["conversation"].get("metadata", {}).get("source", "unknown")}
|
|
235
|
+
|
|
236
|
+
## Metadata
|
|
237
|
+
|
|
238
|
+
- Project Root: `{checkpoint_data["metadata"]["project_root"]}`
|
|
239
|
+
- Working Directory: `{checkpoint_data["metadata"]["working_directory"]}`
|
|
240
|
+
- Checkpoint Count: {checkpoint_data["metadata"]["checkpoint_count"]}
|
|
241
|
+
|
|
242
|
+
## Recovery Instructions
|
|
243
|
+
|
|
244
|
+
To resume from this checkpoint:
|
|
245
|
+
|
|
246
|
+
```bash
|
|
247
|
+
# Option 1: Use the checkpoint file
|
|
248
|
+
python3 tooling/scripts/context_checkpoint.py --resume {checkpoint_data["checkpoint_id"]}
|
|
249
|
+
|
|
250
|
+
# Option 2: Manual resume
|
|
251
|
+
# The full conversation is in: {checkpoint_data["checkpoint_id"]}.json
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
## Next Steps
|
|
255
|
+
|
|
256
|
+
After checkpoint, the session should be cleared and restarted with:
|
|
257
|
+
1. Current working context
|
|
258
|
+
2. Active task description
|
|
259
|
+
3. Recent file changes
|
|
260
|
+
4. Todo list state
|
|
261
|
+
"""
|
|
262
|
+
|
|
263
|
+
with open(output_file, "w") as f:
|
|
264
|
+
f.write(summary)
|
|
265
|
+
|
|
266
|
+
def clear_session(self):
|
|
267
|
+
"""Clear the current Claude session."""
|
|
268
|
+
self._log("🧹 Clearing session...", "INFO")
|
|
269
|
+
try:
|
|
270
|
+
# Try to clear via CLI
|
|
271
|
+
result = subprocess.run(
|
|
272
|
+
[CLAUDE_CLI, "clear"], capture_output=True, text=True, timeout=5
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
if result.returncode == 0:
|
|
276
|
+
self._log("✅ Session cleared successfully", "SUCCESS")
|
|
277
|
+
return True
|
|
278
|
+
else:
|
|
279
|
+
self._log(f"Warning: Clear command returned {result.returncode}", "WARNING")
|
|
280
|
+
return False
|
|
281
|
+
|
|
282
|
+
except Exception as e:
|
|
283
|
+
self._log(f"Error clearing session: {e}", "ERROR")
|
|
284
|
+
return False
|
|
285
|
+
|
|
286
|
+
def resume_from_checkpoint(self, checkpoint_id: str) -> bool:
|
|
287
|
+
"""Resume a session from a checkpoint.
|
|
288
|
+
|
|
289
|
+
Args:
|
|
290
|
+
checkpoint_id: ID of checkpoint to resume from
|
|
291
|
+
|
|
292
|
+
Returns:
|
|
293
|
+
True if successful
|
|
294
|
+
"""
|
|
295
|
+
checkpoint_file = self.checkpoint_dir / f"{checkpoint_id}.json"
|
|
296
|
+
|
|
297
|
+
if not checkpoint_file.exists():
|
|
298
|
+
self._log(f"Checkpoint not found: {checkpoint_id}", "ERROR")
|
|
299
|
+
return False
|
|
300
|
+
|
|
301
|
+
self._log(f"📂 Loading checkpoint: {checkpoint_id}", "INFO")
|
|
302
|
+
|
|
303
|
+
try:
|
|
304
|
+
with open(checkpoint_file) as f:
|
|
305
|
+
checkpoint_data = json.load(f)
|
|
306
|
+
|
|
307
|
+
# Create resume prompt
|
|
308
|
+
resume_prompt = self._create_resume_prompt(checkpoint_data)
|
|
309
|
+
|
|
310
|
+
# Start new session with resume prompt
|
|
311
|
+
self._log("🚀 Starting new session with checkpoint context...", "INFO")
|
|
312
|
+
print("\n" + "=" * 80)
|
|
313
|
+
print("RESUME PROMPT (paste this into Claude Code):")
|
|
314
|
+
print("=" * 80 + "\n")
|
|
315
|
+
print(resume_prompt)
|
|
316
|
+
print("\n" + "=" * 80 + "\n")
|
|
317
|
+
|
|
318
|
+
return True
|
|
319
|
+
|
|
320
|
+
except Exception as e:
|
|
321
|
+
self._log(f"Error resuming from checkpoint: {e}", "ERROR")
|
|
322
|
+
return False
|
|
323
|
+
|
|
324
|
+
def _create_resume_prompt(self, checkpoint_data: dict) -> str:
|
|
325
|
+
"""Create a prompt to resume from checkpoint."""
|
|
326
|
+
messages = checkpoint_data["conversation"].get("messages", [])
|
|
327
|
+
|
|
328
|
+
# Extract key information
|
|
329
|
+
task_context = "Continue previous work"
|
|
330
|
+
recent_files = []
|
|
331
|
+
|
|
332
|
+
# Try to extract context from messages
|
|
333
|
+
for msg in messages[-5:]: # Last 5 messages
|
|
334
|
+
content = msg.get("content", "")
|
|
335
|
+
if "File:" in content or "file_path" in content:
|
|
336
|
+
# Extract file references
|
|
337
|
+
file_matches = re.findall(r'(?:File:|file_path["\']:\s*["\'])([^\'"]+)', content)
|
|
338
|
+
recent_files.extend(file_matches)
|
|
339
|
+
|
|
340
|
+
resume_prompt = f"""# Session Resume from Checkpoint
|
|
341
|
+
|
|
342
|
+
**Checkpoint ID**: {checkpoint_data["checkpoint_id"]}
|
|
343
|
+
**Original Timestamp**: {checkpoint_data["timestamp"]}
|
|
344
|
+
**Context Level at Checkpoint**: {checkpoint_data["context_level"] * 100:.1f}%
|
|
345
|
+
|
|
346
|
+
---
|
|
347
|
+
|
|
348
|
+
## Context Summary
|
|
349
|
+
|
|
350
|
+
You were working on: {task_context}
|
|
351
|
+
|
|
352
|
+
### Recent Files
|
|
353
|
+
{chr(10).join(f"- `{f}`" for f in recent_files[:10]) if recent_files else "No recent files tracked"}
|
|
354
|
+
|
|
355
|
+
### Session State
|
|
356
|
+
- Checkpoint Count: {checkpoint_data["metadata"]["checkpoint_count"]}
|
|
357
|
+
- Working Directory: `{checkpoint_data["metadata"]["working_directory"]}`
|
|
358
|
+
|
|
359
|
+
---
|
|
360
|
+
|
|
361
|
+
## Instructions
|
|
362
|
+
|
|
363
|
+
Please continue from where we left off. The checkpoint was created because the context window was reaching capacity.
|
|
364
|
+
|
|
365
|
+
**What to do**:
|
|
366
|
+
1. Acknowledge that you've resumed from checkpoint {checkpoint_data["checkpoint_id"]}
|
|
367
|
+
2. Ask me what specific task I'd like to continue with
|
|
368
|
+
3. Use the TodoWrite tool to create a fresh todo list for current work
|
|
369
|
+
|
|
370
|
+
**Context available**:
|
|
371
|
+
- Full checkpoint data is saved in: `tooling/.automation/checkpoints/{checkpoint_data["checkpoint_id"]}.json`
|
|
372
|
+
- You can read this file if you need to reference previous conversation details
|
|
373
|
+
|
|
374
|
+
Ready to continue!
|
|
375
|
+
"""
|
|
376
|
+
|
|
377
|
+
return resume_prompt
|
|
378
|
+
|
|
379
|
+
def watch_log_file(self, log_file: Path):
|
|
380
|
+
"""Watch a log file for context warnings and auto-checkpoint.
|
|
381
|
+
|
|
382
|
+
Args:
|
|
383
|
+
log_file: Path to log file to monitor
|
|
384
|
+
"""
|
|
385
|
+
self._log(f"👀 Watching log file: {log_file}", "INFO")
|
|
386
|
+
|
|
387
|
+
try:
|
|
388
|
+
with open(log_file) as f:
|
|
389
|
+
# Seek to end of file
|
|
390
|
+
f.seek(0, 2)
|
|
391
|
+
|
|
392
|
+
while self.running:
|
|
393
|
+
line = f.readline()
|
|
394
|
+
|
|
395
|
+
if not line:
|
|
396
|
+
time.sleep(0.5)
|
|
397
|
+
continue
|
|
398
|
+
|
|
399
|
+
# Check for context warnings
|
|
400
|
+
context_level = self.extract_context_usage(line)
|
|
401
|
+
|
|
402
|
+
if context_level:
|
|
403
|
+
self._handle_context_level(context_level)
|
|
404
|
+
|
|
405
|
+
except FileNotFoundError:
|
|
406
|
+
self._log(f"Log file not found: {log_file}", "ERROR")
|
|
407
|
+
except KeyboardInterrupt:
|
|
408
|
+
self._log("Stopping log watch...", "INFO")
|
|
409
|
+
except Exception as e:
|
|
410
|
+
self._log(f"Error watching log: {e}", "ERROR")
|
|
411
|
+
|
|
412
|
+
def _handle_context_level(self, context_level: float):
|
|
413
|
+
"""Handle different context levels with appropriate actions."""
|
|
414
|
+
percentage = context_level * 100
|
|
415
|
+
|
|
416
|
+
if context_level >= CONTEXT_EMERGENCY_THRESHOLD:
|
|
417
|
+
# EMERGENCY: Force checkpoint immediately
|
|
418
|
+
self._log(f"🚨 EMERGENCY: Context at {percentage:.1f}% - Force checkpoint!", "CRITICAL")
|
|
419
|
+
checkpoint_file = self.create_checkpoint(context_level, reason="emergency")
|
|
420
|
+
self._log(
|
|
421
|
+
"⚠️ MANUAL ACTION REQUIRED: Clear session and resume from checkpoint", "CRITICAL"
|
|
422
|
+
)
|
|
423
|
+
self._log(
|
|
424
|
+
f"Run: python3 tooling/scripts/context_checkpoint.py --resume {checkpoint_file.stem}",
|
|
425
|
+
"INFO",
|
|
426
|
+
)
|
|
427
|
+
|
|
428
|
+
elif context_level >= CONTEXT_CRITICAL_THRESHOLD:
|
|
429
|
+
# CRITICAL: Auto-checkpoint
|
|
430
|
+
self._log(f"⚠️ CRITICAL: Context at {percentage:.1f}% - Auto checkpoint", "WARNING")
|
|
431
|
+
checkpoint_file = self.create_checkpoint(context_level, reason="critical")
|
|
432
|
+
self._log("💡 Consider clearing session soon", "WARNING")
|
|
433
|
+
|
|
434
|
+
elif context_level >= CONTEXT_WARNING_THRESHOLD:
|
|
435
|
+
# WARNING: Just notify
|
|
436
|
+
self._log(f"⚡ WARNING: Context at {percentage:.1f}%", "WARNING")
|
|
437
|
+
|
|
438
|
+
def interactive_monitor(self):
|
|
439
|
+
"""Run interactive monitoring mode."""
|
|
440
|
+
self._log("🎯 Starting interactive context monitor", "INFO")
|
|
441
|
+
self._log(
|
|
442
|
+
f"Thresholds: Warning={CONTEXT_WARNING_THRESHOLD * 100}%, Critical={CONTEXT_CRITICAL_THRESHOLD * 100}%, Emergency={CONTEXT_EMERGENCY_THRESHOLD * 100}%",
|
|
443
|
+
"INFO",
|
|
444
|
+
)
|
|
445
|
+
|
|
446
|
+
print(f"\n{Colors.BOLD}Commands:{Colors.END}")
|
|
447
|
+
print(" - Type 'checkpoint' to manually create checkpoint")
|
|
448
|
+
print(" - Type 'resume <id>' to resume from checkpoint")
|
|
449
|
+
print(" - Type 'list' to list checkpoints")
|
|
450
|
+
print(" - Type 'status' to check current state")
|
|
451
|
+
print(" - Type 'quit' to exit")
|
|
452
|
+
print()
|
|
453
|
+
|
|
454
|
+
while self.running:
|
|
455
|
+
try:
|
|
456
|
+
cmd = input(f"{Colors.CYAN}> {Colors.END}").strip().lower()
|
|
457
|
+
|
|
458
|
+
if cmd == "checkpoint":
|
|
459
|
+
self.create_checkpoint(0.0, reason="manual")
|
|
460
|
+
|
|
461
|
+
elif cmd.startswith("resume "):
|
|
462
|
+
checkpoint_id = cmd.split(" ", 1)[1]
|
|
463
|
+
self.resume_from_checkpoint(checkpoint_id)
|
|
464
|
+
|
|
465
|
+
elif cmd == "list":
|
|
466
|
+
self._list_checkpoints()
|
|
467
|
+
|
|
468
|
+
elif cmd == "status":
|
|
469
|
+
self._show_status()
|
|
470
|
+
|
|
471
|
+
elif cmd in ("quit", "exit", "q"):
|
|
472
|
+
break
|
|
473
|
+
|
|
474
|
+
else:
|
|
475
|
+
print(f"{Colors.YELLOW}Unknown command: {cmd}{Colors.END}")
|
|
476
|
+
|
|
477
|
+
except KeyboardInterrupt:
|
|
478
|
+
print()
|
|
479
|
+
break
|
|
480
|
+
except Exception as e:
|
|
481
|
+
self._log(f"Error: {e}", "ERROR")
|
|
482
|
+
|
|
483
|
+
def _list_checkpoints(self):
|
|
484
|
+
"""List all available checkpoints."""
|
|
485
|
+
checkpoints = sorted(self.checkpoint_dir.glob("checkpoint_*.json"))
|
|
486
|
+
|
|
487
|
+
if not checkpoints:
|
|
488
|
+
self._log("No checkpoints found", "INFO")
|
|
489
|
+
return
|
|
490
|
+
|
|
491
|
+
print(f"\n{Colors.BOLD}Available Checkpoints:{Colors.END}")
|
|
492
|
+
for cp_file in checkpoints:
|
|
493
|
+
with open(cp_file) as f:
|
|
494
|
+
data = json.load(f)
|
|
495
|
+
timestamp = data["timestamp"]
|
|
496
|
+
context = data["context_level"] * 100
|
|
497
|
+
reason = data["reason"]
|
|
498
|
+
print(f" • {cp_file.stem} - {timestamp} - {context:.1f}% ({reason})")
|
|
499
|
+
print()
|
|
500
|
+
|
|
501
|
+
def _show_status(self):
|
|
502
|
+
"""Show current manager status."""
|
|
503
|
+
print(f"\n{Colors.BOLD}Status:{Colors.END}")
|
|
504
|
+
print(f" Session ID: {self.session_id or 'N/A'}")
|
|
505
|
+
print(f" Checkpoint Count: {self.checkpoint_count}")
|
|
506
|
+
print(f" Last Checkpoint: {self.last_checkpoint_time or 'Never'}")
|
|
507
|
+
print(f" Checkpoint Dir: {self.checkpoint_dir}")
|
|
508
|
+
print(f" Running: {self.running}")
|
|
509
|
+
print()
|
|
510
|
+
|
|
511
|
+
|
|
512
|
+
def main():
|
|
513
|
+
parser = argparse.ArgumentParser(
|
|
514
|
+
description="Context Checkpoint Manager for Claude Code",
|
|
515
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
516
|
+
epilog="""
|
|
517
|
+
Examples:
|
|
518
|
+
# Interactive monitoring mode
|
|
519
|
+
python3 tooling/scripts/context_checkpoint.py
|
|
520
|
+
|
|
521
|
+
# Watch a specific log file
|
|
522
|
+
python3 tooling/scripts/context_checkpoint.py --watch-log tooling/.automation/logs/story.log
|
|
523
|
+
|
|
524
|
+
# Resume from checkpoint
|
|
525
|
+
python3 tooling/scripts/context_checkpoint.py --resume checkpoint_20251220_143052_1
|
|
526
|
+
|
|
527
|
+
# Manual checkpoint
|
|
528
|
+
python3 tooling/scripts/context_checkpoint.py --checkpoint
|
|
529
|
+
""",
|
|
530
|
+
)
|
|
531
|
+
|
|
532
|
+
parser.add_argument("--session-id", help="Claude session ID to monitor")
|
|
533
|
+
parser.add_argument("--watch-log", help="Log file to watch for context warnings")
|
|
534
|
+
parser.add_argument("--resume", help="Resume from checkpoint ID")
|
|
535
|
+
parser.add_argument("--checkpoint", action="store_true", help="Create manual checkpoint")
|
|
536
|
+
parser.add_argument("--list", action="store_true", help="List available checkpoints")
|
|
537
|
+
|
|
538
|
+
args = parser.parse_args()
|
|
539
|
+
|
|
540
|
+
manager = ContextCheckpointManager(session_id=args.session_id)
|
|
541
|
+
|
|
542
|
+
if args.list:
|
|
543
|
+
manager._list_checkpoints()
|
|
544
|
+
elif args.checkpoint:
|
|
545
|
+
manager.create_checkpoint(0.0, reason="manual")
|
|
546
|
+
elif args.resume:
|
|
547
|
+
manager.resume_from_checkpoint(args.resume)
|
|
548
|
+
elif args.watch_log:
|
|
549
|
+
manager.watch_log_file(Path(args.watch_log))
|
|
550
|
+
else:
|
|
551
|
+
# Interactive mode
|
|
552
|
+
manager.interactive_monitor()
|
|
553
|
+
|
|
554
|
+
|
|
555
|
+
if __name__ == "__main__":
|
|
556
|
+
main()
|