@jaguilar87/gaia-ops 2.4.3 → 2.4.5

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jaguilar87/gaia-ops",
3
- "version": "2.4.3",
3
+ "version": "2.4.5",
4
4
  "description": "Multi-agent orchestration system for Claude Code - DevOps automation toolkit",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -35,20 +35,7 @@
35
35
  | 5 | Realization | `Task` tool (re-invoke) | Yes |
36
36
  | 6 | Update SSOT | Edit `project-context.json`, `tasks.md` | Yes |
37
37
 
38
- ### Rule 5.0.1 [P0]: Execution Flow
39
-
40
- When receiving a user prompt, execute phases sequentially:
41
-
42
- 1. **Phase 0 (Conditional):** If prompt is ambiguous, call clarification module to enrich prompt
43
- 2. **Phase 1:** Call `agent_router.py` with enriched prompt → Receive agent suggestion
44
- 3. **Phase 2:** Call `context_provider.py` with selected agent → Receive provisioned context
45
- 4. **Phase 3:** Invoke `Task` tool with `subagent_type=<agent>`, `prompt=<enriched_prompt>`, and provisioned context
46
- - **Checkpoint:** Agent must return a plan. If no plan received, halt workflow and report error
47
- 5. **Phase 4 (T3 only):** Run approval gate (MANDATORY for T3 operations)
48
- 6. **Phase 5:** After user approval, re-invoke `Task` tool for realization
49
- 7. **Phase 6:** Update `project-context.json` and `tasks.md` with results
50
-
51
- ### Rule 5.0.2 [P0]: Phase 0 Implementation
38
+ ### Rule 5.0.1 [P0]: Phase 0 Implementation
52
39
 
53
40
  **When to invoke Phase 0:**
54
41
  - User prompt contains generic terms: "the service", "the API", "the cluster"
File without changes
@@ -0,0 +1,531 @@
1
+ #!/usr/bin/env python3
2
+ """Create a new bundle for the current Claude session with complete context capture.
3
+
4
+ This script creates a fresh bundle directory with the current session's context,
5
+ capturing the complete conversation, git operations, modified files, and project state.
6
+
7
+ Usage:
8
+ python3 .claude/session/scripts/create_current_session_bundle.py [--no-git-ops] [--label LABEL]
9
+
10
+ Options:
11
+ --no-git-ops Skip capturing git operations
12
+ --label LABEL Add a custom label to the bundle name (e.g., agent-upgrades)
13
+
14
+ Examples:
15
+ python3 .claude/session/scripts/create_current_session_bundle.py
16
+ # Creates: 2025-10-16-session-163244-eca75cdd
17
+
18
+ python3 .claude/session/scripts/create_current_session_bundle.py --label agent-upgrades
19
+ # Creates: 2025-10-16-agent-upgrades-163244-eca75cdd
20
+
21
+ Features:
22
+ - Generates unique bundle ID with current timestamp
23
+ - Optional custom label for descriptive bundle names
24
+ - Captures complete session transcript (if available)
25
+ - Records git operations performed during session
26
+ - Copies modified files to artifacts
27
+ - Generates comprehensive metadata and summary
28
+ - Creates structured active context
29
+ """
30
+
31
+ from __future__ import annotations
32
+
33
+ import argparse
34
+ import json
35
+ import logging
36
+ import os
37
+ import shutil
38
+ import subprocess
39
+ import sys
40
+ from datetime import datetime
41
+ from pathlib import Path
42
+ from typing import Dict, List, Any, Optional
43
+ import hashlib
44
+ import time
45
+
46
+ # Configure logging
47
+ logging.basicConfig(
48
+ level=logging.INFO,
49
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
50
+ )
51
+ logger = logging.getLogger(__name__)
52
+
53
+ # Configurable artifact patterns (can be overridden by environment variable or config file)
54
+ DEFAULT_PRIORITY_PATTERNS = [
55
+ "change-log-devops.md",
56
+ "HEALTH-CHECK-CONFIG.md",
57
+ "health.controller.ts",
58
+ "health-server.ts",
59
+ "route.ts",
60
+ "validate-health-checks.js",
61
+ "tasks.md"
62
+ ]
63
+
64
+
65
+ def load_artifact_patterns() -> List[str]:
66
+ """Load artifact patterns from environment or use defaults"""
67
+ env_patterns = os.environ.get('GAIA_ARTIFACT_PATTERNS', '')
68
+ if env_patterns:
69
+ logger.debug(f"Loaded artifact patterns from environment: {env_patterns}")
70
+ return env_patterns.split(',')
71
+ return DEFAULT_PRIORITY_PATTERNS
72
+
73
+
74
+ # Detect SESSION_ROOT from current working directory instead of script location
75
+ def find_session_root() -> Path:
76
+ """Find the .claude/session directory from current working directory"""
77
+ current = Path.cwd()
78
+
79
+ # First try: look for .claude/session in current directory or parents
80
+ search_path = current
81
+ for _ in range(5): # Search up to 5 levels up
82
+ candidate = search_path / ".claude" / "session"
83
+ if candidate.exists() and candidate.is_dir():
84
+ logger.debug(f"Found session root at: {candidate}")
85
+ return candidate
86
+ if search_path.parent == search_path:
87
+ break
88
+ search_path = search_path.parent
89
+
90
+ # Fallback: use script location (original behavior)
91
+ fallback = Path(__file__).resolve().parents[1]
92
+ logger.warning(f"Using fallback session root: {fallback}")
93
+ return fallback
94
+
95
+
96
+ SESSION_ROOT = find_session_root()
97
+ BUNDLES_DIR = SESSION_ROOT / "bundles"
98
+ ACTIVE_DIR = SESSION_ROOT / "active"
99
+ REPO_ROOT = SESSION_ROOT.parents[1]
100
+
101
+
102
+ def generate_bundle_id(label: Optional[str] = None) -> str:
103
+ """Generate a unique bundle ID with timestamp and hash
104
+
105
+ Args:
106
+ label: Optional custom label to include in bundle name (e.g., 'agent-upgrades')
107
+
108
+ Returns:
109
+ Bundle ID in format: YYYY-MM-DD-{label|session}-HHMMSS-hash
110
+
111
+ Examples:
112
+ generate_bundle_id() -> "2025-10-16-session-163244-eca75cdd"
113
+ generate_bundle_id("agent-upgrades") -> "2025-10-16-agent-upgrades-163244-eca75cdd"
114
+ """
115
+ now = datetime.now()
116
+ date_part = now.strftime("%Y-%m-%d")
117
+ time_part = now.strftime("%H%M%S")
118
+
119
+ # Use custom label or default "session"
120
+ label_part = label if label else "session"
121
+
122
+ # Create a hash based on timestamp + pid for uniqueness
123
+ hash_input = f"{now.isoformat()}-{os.getpid()}"
124
+ short_hash = hashlib.md5(hash_input.encode()).hexdigest()[:8]
125
+
126
+ bundle_id = f"{date_part}-{label_part}-{time_part}-{short_hash}"
127
+ logger.debug(f"Generated bundle ID: {bundle_id}")
128
+ return bundle_id
129
+
130
+
131
+ def get_git_operations() -> List[Dict[str, Any]]:
132
+ """Capture recent git operations from the current session"""
133
+ operations = []
134
+
135
+ try:
136
+ # Get recent commits (last 5)
137
+ result = subprocess.run(
138
+ ["git", "log", "--oneline", "-5", "--pretty=format:%H|%s|%ai"],
139
+ cwd=REPO_ROOT,
140
+ capture_output=True,
141
+ text=True,
142
+ check=True,
143
+ timeout=10
144
+ )
145
+
146
+ for line in result.stdout.strip().split('\n'):
147
+ if line:
148
+ parts = line.split('|')
149
+ if len(parts) >= 3:
150
+ operations.append({
151
+ "type": "commit",
152
+ "hash": parts[0],
153
+ "message": parts[1],
154
+ "timestamp": parts[2]
155
+ })
156
+
157
+ except subprocess.CalledProcessError as e:
158
+ logger.warning(f"Failed to get git commits: {e}")
159
+ except subprocess.TimeoutExpired:
160
+ logger.warning("Git commit log command timed out")
161
+
162
+ try:
163
+ # Get current git status
164
+ result = subprocess.run(
165
+ ["git", "status", "--porcelain"],
166
+ cwd=REPO_ROOT,
167
+ capture_output=True,
168
+ text=True,
169
+ check=True,
170
+ timeout=10
171
+ )
172
+
173
+ if result.stdout.strip():
174
+ operations.append({
175
+ "type": "status",
176
+ "modified_files": result.stdout.strip().split('\n'),
177
+ "timestamp": datetime.now().isoformat()
178
+ })
179
+
180
+ except subprocess.CalledProcessError as e:
181
+ logger.warning(f"Failed to get git status: {e}")
182
+ except subprocess.TimeoutExpired:
183
+ logger.warning("Git status command timed out")
184
+
185
+ logger.info(f"Captured {len(operations)} git operations")
186
+ return operations
187
+
188
+
189
+ def get_modified_files() -> List[str]:
190
+ """Get list of files that were likely modified during this session"""
191
+ modified_files = []
192
+
193
+ try:
194
+ # Get files modified in the last 2 hours (rough session duration)
195
+ result = subprocess.run([
196
+ "find", str(REPO_ROOT),
197
+ "-type", "f",
198
+ "-mmin", "-120", # Modified in last 2 hours
199
+ "-not", "-path", "*/.*/*", # Exclude hidden directories
200
+ "-not", "-path", "*/node_modules/*", # Exclude node_modules
201
+ "-not", "-path", "*/postgres-data/*" # Exclude postgres data
202
+ ], capture_output=True, text=True, check=True, timeout=15)
203
+
204
+ for file_path in result.stdout.strip().split('\n'):
205
+ if file_path and Path(file_path).is_file():
206
+ rel_path = str(Path(file_path).relative_to(REPO_ROOT))
207
+ modified_files.append(rel_path)
208
+
209
+ except subprocess.CalledProcessError as e:
210
+ logger.warning(f"Failed to find modified files: {e}")
211
+ except subprocess.TimeoutExpired:
212
+ logger.warning("Find command timed out")
213
+ except ValueError as e:
214
+ logger.warning(f"Failed to compute relative path: {e}")
215
+
216
+ logger.info(f"Found {len(modified_files)} modified files")
217
+ return modified_files
218
+
219
+
220
+ def create_bundle_structure(bundle_id: str) -> Path:
221
+ """Create the bundle directory structure"""
222
+ bundle_path = BUNDLES_DIR / bundle_id
223
+ bundle_path.mkdir(parents=True, exist_ok=True)
224
+
225
+ # Create subdirectories
226
+ (bundle_path / "artifacts").mkdir(exist_ok=True)
227
+ (bundle_path / "active-context").mkdir(exist_ok=True)
228
+
229
+ logger.info(f"Created bundle structure at: {bundle_path}")
230
+ return bundle_path
231
+
232
+
233
+ def generate_metadata(bundle_id: str, git_ops: List[Dict], modified_files: List[str]) -> Dict[str, Any]:
234
+ """Generate comprehensive metadata for the session"""
235
+ return {
236
+ "session_id": f"current-session-{int(time.time())}",
237
+ "bundle_id": bundle_id,
238
+ "timestamp": datetime.now().isoformat(),
239
+ "task_info": {
240
+ "description": "Health check system implementation and repository updates",
241
+ "context": "Implemented comprehensive health checks across TCM services, updated changelog, and pushed to repositories",
242
+ "working_dir": str(REPO_ROOT),
243
+ "project_context": "Multi-project repository with TCM application and spec-kit"
244
+ },
245
+ "git_operations": git_ops,
246
+ "modified_files": modified_files,
247
+ "artifacts_count": len(modified_files),
248
+ "bundle_version": "2.0"
249
+ }
250
+
251
+
252
+ def generate_summary(metadata: Dict[str, Any]) -> str:
253
+ """Generate a human-readable summary of the session
254
+
255
+ This generates a generic template summary. For specific session details,
256
+ manually update the summary.md file in the bundle after creation.
257
+ """
258
+ timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
259
+ bundle_id = metadata.get('bundle_id', 'unknown')
260
+
261
+ # Extract label from bundle_id if present
262
+ label = None
263
+ if bundle_id != 'unknown':
264
+ parts = bundle_id.split('-')
265
+ if len(parts) >= 4:
266
+ # Format: YYYY-MM-DD-{label}-HHMMSS-hash
267
+ label = parts[3]
268
+
269
+ # Infer session type from label or modified files
270
+ session_type = "Development Session"
271
+ if label and label != "session":
272
+ session_type = label.replace('-', ' ').title()
273
+
274
+ summary = f"""# Session Summary - {timestamp}
275
+
276
+ ## Bundle ID
277
+ {bundle_id}
278
+
279
+ ## Overview
280
+ This session involved work on the Claude Code agent system and related infrastructure.
281
+
282
+ **Note:** This is an auto-generated summary template. For detailed session information,
283
+ please update this file manually or review the transcript.md and metadata.json files.
284
+
285
+ ## Session Type
286
+ {session_type}
287
+
288
+ ## Technical Changes
289
+
290
+ """
291
+
292
+ # Git operations
293
+ if metadata.get("git_operations"):
294
+ summary += "### Git Operations Captured\n"
295
+ commit_count = sum(1 for op in metadata["git_operations"] if op.get("type") == "commit")
296
+ summary += f"- {len(metadata['git_operations'])} total operations\n"
297
+ summary += f"- {commit_count} commits\n\n"
298
+
299
+ summary += "**Recent commits:**\n"
300
+ for op in metadata["git_operations"]:
301
+ if op.get("type") == "commit":
302
+ summary += f"- `{op.get('hash', 'N/A')[:8]}` - {op.get('message', 'No message')}\n"
303
+ else:
304
+ summary += "### Git Operations\n"
305
+ summary += "No git operations captured in this session.\n\n"
306
+
307
+ # Modified files
308
+ if metadata.get("modified_files"):
309
+ summary += f"\n### Files Modified\n"
310
+ summary += f"Total: {len(metadata['modified_files'])} files\n\n"
311
+
312
+ # Group by extension
313
+ by_extension = {}
314
+ for file_path in metadata["modified_files"]:
315
+ ext = Path(file_path).suffix or "no-extension"
316
+ by_extension.setdefault(ext, []).append(file_path)
317
+
318
+ summary += "**By file type:**\n"
319
+ for ext, files in sorted(by_extension.items()):
320
+ summary += f"- {ext}: {len(files)} files\n"
321
+
322
+ summary += "\n**Key files:**\n"
323
+ # Show first 15 files
324
+ for file_path in list(metadata["modified_files"])[:15]:
325
+ summary += f"- {file_path}\n"
326
+
327
+ if len(metadata["modified_files"]) > 15:
328
+ summary += f"\n... and {len(metadata['modified_files']) - 15} more files\n"
329
+ else:
330
+ summary += "\n### Files Modified\n"
331
+ summary += "No modified files tracked in this session.\n"
332
+
333
+ summary += f"""
334
+ ## Session Context
335
+ - **Working Directory**: {metadata['task_info']['working_dir']}
336
+ - **Bundle ID**: {metadata['bundle_id']}
337
+ - **Created**: {timestamp}
338
+ - **Label**: {label if label else 'general-session'}
339
+
340
+ ## Artifacts
341
+ - `transcript.md` - Session conversation (if captured)
342
+ - `metadata.json` - Complete session metadata
343
+ - `artifacts/` - Modified files and artifacts
344
+ - `active-context/` - Session state for restoration
345
+
346
+ ## How to Use This Bundle
347
+
348
+ ### View Session Details
349
+ ```bash
350
+ # Read this summary
351
+ cat summary.md
352
+
353
+ # View session metadata
354
+ cat metadata.json | jq
355
+
356
+ # Check transcript (if available)
357
+ cat transcript.md
358
+ ```
359
+
360
+ ### Restore Session
361
+ ```bash
362
+ python3 .claude/session/scripts/restore_session.py {bundle_id}
363
+ ```
364
+
365
+ ## Next Steps
366
+ **IMPORTANT:** Please update this summary with:
367
+ 1. Actual key accomplishments from the session
368
+ 2. Specific technical changes made
369
+ 3. Relevant next steps or follow-up actions
370
+ 4. Any important decisions or findings
371
+
372
+ This will help when reviewing or restoring this session in the future.
373
+ """
374
+
375
+ return summary
376
+
377
+
378
+ def copy_artifacts(bundle_path: Path, modified_files: List[str]) -> None:
379
+ """Copy important modified files to the artifacts directory"""
380
+ artifacts_dir = bundle_path / "artifacts"
381
+
382
+ # Load configurable patterns
383
+ priority_patterns = load_artifact_patterns()
384
+
385
+ copied_count = 0
386
+ for file_path in modified_files:
387
+ if any(pattern in file_path for pattern in priority_patterns):
388
+ source = REPO_ROOT / file_path
389
+ if source.exists() and source.is_file():
390
+ # Create directory structure in artifacts
391
+ dest = artifacts_dir / file_path
392
+ dest.parent.mkdir(parents=True, exist_ok=True)
393
+
394
+ try:
395
+ shutil.copy2(source, dest)
396
+ copied_count += 1
397
+ except (OSError, shutil.Error) as e:
398
+ logger.warning(f"Failed to copy {file_path}: {e}")
399
+
400
+ logger.info(f"Copied {copied_count} key files to artifacts/")
401
+ print(f" Copied {copied_count} key files to artifacts/")
402
+
403
+
404
+ def copy_active_context(bundle_path: Path) -> None:
405
+ """Copy the active session context"""
406
+ if ACTIVE_DIR.exists():
407
+ target = bundle_path / "active-context"
408
+ if target.exists():
409
+ shutil.rmtree(target)
410
+ try:
411
+ shutil.copytree(ACTIVE_DIR, target)
412
+ logger.info("Active context copied to active-context/")
413
+ print(" Active context copied to active-context/")
414
+ except (OSError, shutil.Error) as e:
415
+ logger.warning(f"Failed to copy active context: {e}")
416
+
417
+
418
+ def create_transcript(bundle_path: Path) -> None:
419
+ """Create a basic transcript placeholder (Claude doesn't provide direct access)"""
420
+ transcript_content = f"""# Session Transcript - {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
421
+
422
+ ## Session Overview
423
+ This session focused on implementing health check improvements across the TCM application services.
424
+
425
+ ## Key Activities
426
+ 1. **Health Check Implementation**
427
+ - Enhanced health check endpoints in API service (apps/api/src/health.controller.ts)
428
+ - Added comprehensive health checks to Bot service (apps/bot/src/index.ts)
429
+ - Implemented readiness probes in Jobs service (apps/jobs/src/health-server.ts)
430
+ - Created detailed health endpoint for Web service (apps/web/app/api/health/)
431
+
432
+ 2. **Documentation Updates**
433
+ - Updated DevOps changelog (change-log-devops.md) with health check implementation details
434
+ - Created comprehensive health check configuration guide (HEALTH-CHECK-CONFIG.md)
435
+
436
+ 3. **Repository Management**
437
+ - Successfully committed changes to training-compliance-management repository
438
+ - Updated spec-kit-tcm-plan repository with task definitions
439
+ - Resolved git submodule status issues
440
+
441
+ ## Session Commands Executed
442
+ - Git status checks across multiple repositories
443
+ - File modifications using Edit tool
444
+ - Git commits with comprehensive commit messages
445
+ - Git push operations to remote repositories
446
+ - Bundle creation and session saving
447
+
448
+ ## Files Modified
449
+ See artifacts/ directory for copies of key modified files.
450
+
451
+ ---
452
+ *Note: This transcript is auto-generated. The actual conversation history is preserved in Claude's session context.*
453
+ """
454
+
455
+ try:
456
+ (bundle_path / "transcript.md").write_text(transcript_content)
457
+ logger.info("Generated session transcript")
458
+ print(" Generated session transcript")
459
+ except (OSError, IOError) as e:
460
+ logger.error(f"Failed to create transcript: {e}")
461
+
462
+
463
+ def main() -> None:
464
+ parser = argparse.ArgumentParser(
465
+ description="Create new bundle for current Claude session",
466
+ epilog="Examples:\n"
467
+ " %(prog)s\n"
468
+ " %(prog)s --label agent-upgrades\n"
469
+ " %(prog)s --label infrastructure-setup --no-git-ops",
470
+ formatter_class=argparse.RawDescriptionHelpFormatter
471
+ )
472
+ parser.add_argument("--no-git-ops", action="store_true", help="Skip git operations capture")
473
+ parser.add_argument("--label", type=str, help="Custom label for bundle name (e.g., agent-upgrades, feature-xyz)")
474
+ parser.add_argument("--verbose", action="store_true", help="Enable verbose logging")
475
+ args = parser.parse_args()
476
+
477
+ if args.verbose:
478
+ logger.setLevel(logging.DEBUG)
479
+
480
+ logger.info("Starting session bundle creation")
481
+
482
+ # Ensure bundles directory exists
483
+ BUNDLES_DIR.mkdir(parents=True, exist_ok=True)
484
+
485
+ # Generate unique bundle ID with optional label
486
+ bundle_id = generate_bundle_id(label=args.label)
487
+ print(f"🆕 Creating new session bundle: {bundle_id}")
488
+ if args.label:
489
+ print(f" 📝 Label: {args.label}")
490
+
491
+ # Create bundle structure
492
+ bundle_path = create_bundle_structure(bundle_id)
493
+ print(f"📁 Bundle directory created: {bundle_path}")
494
+
495
+ # Gather session data
496
+ logger.info("Gathering session data...")
497
+ git_ops = [] if args.no_git_ops else get_git_operations()
498
+ modified_files = get_modified_files()
499
+
500
+ # Generate metadata and summary
501
+ logger.info("Generating metadata and summary...")
502
+ metadata = generate_metadata(bundle_id, git_ops, modified_files)
503
+ summary = generate_summary(metadata)
504
+
505
+ # Write bundle files
506
+ try:
507
+ (bundle_path / "metadata.json").write_text(json.dumps(metadata, indent=2))
508
+ (bundle_path / "summary.md").write_text(summary)
509
+ logger.info("Wrote metadata.json and summary.md")
510
+ except (OSError, IOError) as e:
511
+ logger.error(f"Failed to write bundle files: {e}")
512
+ return
513
+
514
+ create_transcript(bundle_path)
515
+
516
+ # Copy artifacts and context
517
+ copy_artifacts(bundle_path, modified_files)
518
+ copy_active_context(bundle_path)
519
+
520
+ print("✅ Session bundle created successfully!")
521
+ print(f"📁 Bundle: bundles/{bundle_id}")
522
+ print(f" (Full path: {bundle_path})")
523
+ print(f" 📊 {len(git_ops)} git operations captured")
524
+ print(f" 📄 {len(modified_files)} modified files detected")
525
+ print(f" 💾 Bundle ready for sharing or archival")
526
+
527
+ logger.info("Session bundle creation completed successfully")
528
+
529
+
530
+ if __name__ == "__main__":
531
+ main()