@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.
Files changed (124) hide show
  1. package/CHANGELOG.md +526 -0
  2. package/LICENSE +21 -0
  3. package/README.md +620 -0
  4. package/bin/devflow-checkpoint.js +10 -0
  5. package/bin/devflow-collab.js +10 -0
  6. package/bin/devflow-cost.js +10 -0
  7. package/bin/devflow-create-persona.js +10 -0
  8. package/bin/devflow-init.js +10 -0
  9. package/bin/devflow-memory.js +10 -0
  10. package/bin/devflow-new-doc.js +10 -0
  11. package/bin/devflow-personalize.js +10 -0
  12. package/bin/devflow-setup-checkpoint.js +10 -0
  13. package/bin/devflow-story.js +10 -0
  14. package/bin/devflow-tech-debt.js +10 -0
  15. package/bin/devflow-validate-overrides.js +10 -0
  16. package/bin/devflow-validate.js +10 -0
  17. package/bin/devflow-version.js +10 -0
  18. package/lib/constants.js +30 -0
  19. package/lib/exec-python.js +78 -0
  20. package/lib/python-check.js +178 -0
  21. package/package.json +64 -0
  22. package/tooling/.automation/agents/architect.md +135 -0
  23. package/tooling/.automation/agents/ba.md +70 -0
  24. package/tooling/.automation/agents/dev.md +79 -0
  25. package/tooling/.automation/agents/maintainer.md +97 -0
  26. package/tooling/.automation/agents/pm.md +116 -0
  27. package/tooling/.automation/agents/reviewer.md +141 -0
  28. package/tooling/.automation/agents/sm.md +61 -0
  29. package/tooling/.automation/agents/writer.md +193 -0
  30. package/tooling/.automation/config.ps1.template +61 -0
  31. package/tooling/.automation/config.sh.template +48 -0
  32. package/tooling/.automation/memory/.gitkeep +6 -0
  33. package/tooling/.automation/memory/knowledge/kg_integration-test.json +94 -0
  34. package/tooling/.automation/memory/knowledge/kg_test-story.json +300 -0
  35. package/tooling/.automation/memory/shared/shared_integration-test.json +30 -0
  36. package/tooling/.automation/memory/shared/shared_test-story.json +78 -0
  37. package/tooling/.automation/overrides/templates/README.md +113 -0
  38. package/tooling/.automation/overrides/templates/architect/README.md +27 -0
  39. package/tooling/.automation/overrides/templates/architect/cloud-native.yaml +92 -0
  40. package/tooling/.automation/overrides/templates/architect/enterprise-architect.yaml +85 -0
  41. package/tooling/.automation/overrides/templates/architect/pragmatic-minimalist.yaml +88 -0
  42. package/tooling/.automation/overrides/templates/ba/README.md +27 -0
  43. package/tooling/.automation/overrides/templates/ba/agile-storyteller.yaml +86 -0
  44. package/tooling/.automation/overrides/templates/ba/domain-expert.yaml +91 -0
  45. package/tooling/.automation/overrides/templates/ba/requirements-engineer.yaml +89 -0
  46. package/tooling/.automation/overrides/templates/dev/README.md +32 -0
  47. package/tooling/.automation/overrides/templates/dev/junior-mentored.yaml +39 -0
  48. package/tooling/.automation/overrides/templates/dev/performance-engineer.yaml +43 -0
  49. package/tooling/.automation/overrides/templates/dev/rapid-prototyper.yaml +52 -0
  50. package/tooling/.automation/overrides/templates/dev/security-focused.yaml +43 -0
  51. package/tooling/.automation/overrides/templates/dev/senior-fullstack.yaml +39 -0
  52. package/tooling/.automation/overrides/templates/maintainer/README.md +27 -0
  53. package/tooling/.automation/overrides/templates/maintainer/devops-maintainer.yaml +113 -0
  54. package/tooling/.automation/overrides/templates/maintainer/legacy-steward.yaml +94 -0
  55. package/tooling/.automation/overrides/templates/maintainer/oss-maintainer.yaml +94 -0
  56. package/tooling/.automation/overrides/templates/pm/README.md +27 -0
  57. package/tooling/.automation/overrides/templates/pm/agile-pm.yaml +91 -0
  58. package/tooling/.automation/overrides/templates/pm/hybrid-delivery.yaml +87 -0
  59. package/tooling/.automation/overrides/templates/pm/traditional-pm.yaml +91 -0
  60. package/tooling/.automation/overrides/templates/reviewer/README.md +11 -0
  61. package/tooling/.automation/overrides/templates/reviewer/mentoring-reviewer.yaml +45 -0
  62. package/tooling/.automation/overrides/templates/reviewer/quick-sanity.yaml +50 -0
  63. package/tooling/.automation/overrides/templates/reviewer/thorough-critic.yaml +48 -0
  64. package/tooling/.automation/overrides/templates/sm/README.md +11 -0
  65. package/tooling/.automation/overrides/templates/sm/agile-coach.yaml +52 -0
  66. package/tooling/.automation/overrides/templates/sm/startup-pm.yaml +50 -0
  67. package/tooling/.automation/overrides/templates/sm/technical-lead.yaml +47 -0
  68. package/tooling/.automation/overrides/templates/user-profile.template.yaml +62 -0
  69. package/tooling/.automation/overrides/templates/writer/README.md +27 -0
  70. package/tooling/.automation/overrides/templates/writer/api-documentarian.yaml +99 -0
  71. package/tooling/.automation/overrides/templates/writer/docs-as-code.yaml +108 -0
  72. package/tooling/.automation/overrides/templates/writer/user-guide-author.yaml +100 -0
  73. package/tooling/completions/DevflowCompletion.ps1 +213 -0
  74. package/tooling/completions/_run-story +116 -0
  75. package/tooling/completions/run-story-completion.bash +136 -0
  76. package/tooling/docs/DOC-STANDARD.md +717 -0
  77. package/tooling/docs/sprint-status.yaml.template +24 -0
  78. package/tooling/docs/templates/bug-report.md +234 -0
  79. package/tooling/docs/templates/migration-spec.md +274 -0
  80. package/tooling/docs/templates/refactor-spec.md +86 -0
  81. package/tooling/docs/templates/tech-debt.md +86 -0
  82. package/tooling/scripts/context_checkpoint.py +556 -0
  83. package/tooling/scripts/cost_dashboard.py +617 -0
  84. package/tooling/scripts/create-persona.py +690 -0
  85. package/tooling/scripts/create-persona.sh +435 -0
  86. package/tooling/scripts/init-project-workflow.ps1 +651 -0
  87. package/tooling/scripts/init-project-workflow.py +70 -0
  88. package/tooling/scripts/init-project-workflow.sh +746 -0
  89. package/tooling/scripts/lib/__init__.py +35 -0
  90. package/tooling/scripts/lib/agent_handoff.py +526 -0
  91. package/tooling/scripts/lib/agent_router.py +698 -0
  92. package/tooling/scripts/lib/checkpoint-integration.ps1 +245 -0
  93. package/tooling/scripts/lib/checkpoint-integration.sh +191 -0
  94. package/tooling/scripts/lib/claude-cli.ps1 +952 -0
  95. package/tooling/scripts/lib/claude-cli.sh +1293 -0
  96. package/tooling/scripts/lib/cost_config.py +222 -0
  97. package/tooling/scripts/lib/cost_display.py +443 -0
  98. package/tooling/scripts/lib/cost_tracker.py +710 -0
  99. package/tooling/scripts/lib/currency_converter.py +328 -0
  100. package/tooling/scripts/lib/errors.py +438 -0
  101. package/tooling/scripts/lib/override-loader.sh +286 -0
  102. package/tooling/scripts/lib/pair_programming.py +589 -0
  103. package/tooling/scripts/lib/shared_memory.py +637 -0
  104. package/tooling/scripts/lib/swarm_orchestrator.py +689 -0
  105. package/tooling/scripts/memory_summarize.py +324 -0
  106. package/tooling/scripts/new-doc.ps1 +405 -0
  107. package/tooling/scripts/new-doc.py +93 -0
  108. package/tooling/scripts/new-doc.sh +534 -0
  109. package/tooling/scripts/personalize_agent.py +385 -0
  110. package/tooling/scripts/rollback-migration.sh +540 -0
  111. package/tooling/scripts/run-collab.ps1 +251 -0
  112. package/tooling/scripts/run-collab.py +605 -0
  113. package/tooling/scripts/run-collab.sh +110 -0
  114. package/tooling/scripts/run-story.ps1 +490 -0
  115. package/tooling/scripts/run-story.py +387 -0
  116. package/tooling/scripts/run-story.sh +467 -0
  117. package/tooling/scripts/setup-checkpoint-service.ps1 +219 -0
  118. package/tooling/scripts/setup-checkpoint-service.py +87 -0
  119. package/tooling/scripts/setup-checkpoint-service.sh +236 -0
  120. package/tooling/scripts/tech-debt-tracker.py +608 -0
  121. package/tooling/scripts/update_version.py +244 -0
  122. package/tooling/scripts/validate-overrides.py +511 -0
  123. package/tooling/scripts/validate-overrides.sh +432 -0
  124. 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()