@jaguilar87/gaia-ops 1.0.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 (91) hide show
  1. package/CHANGELOG.md +315 -0
  2. package/CLAUDE.md +154 -0
  3. package/LICENSE +21 -0
  4. package/README.md +221 -0
  5. package/agents/aws-troubleshooter.md +50 -0
  6. package/agents/claude-architect.md +821 -0
  7. package/agents/devops-developer.md +92 -0
  8. package/agents/gcp-troubleshooter.md +50 -0
  9. package/agents/gitops-operator.md +360 -0
  10. package/agents/terraform-architect.md +289 -0
  11. package/bin/gaia-init.js +620 -0
  12. package/commands/architect.md +97 -0
  13. package/commands/restore-session.md +87 -0
  14. package/commands/save-session.md +88 -0
  15. package/commands/session-status.md +61 -0
  16. package/commands/speckit.add-task.md +144 -0
  17. package/commands/speckit.analyze-task.md +65 -0
  18. package/commands/speckit.implement.md +96 -0
  19. package/commands/speckit.init.md +237 -0
  20. package/commands/speckit.plan.md +88 -0
  21. package/commands/speckit.specify.md +161 -0
  22. package/commands/speckit.tasks.md +188 -0
  23. package/config/AGENTS.md +162 -0
  24. package/config/agent-catalog.md +604 -0
  25. package/config/context-contracts.md +682 -0
  26. package/config/git-standards.md +674 -0
  27. package/config/git_standards.json +69 -0
  28. package/config/orchestration-workflow.md +735 -0
  29. package/hooks/__pycache__/post_tool_use.cpython-312.pyc +0 -0
  30. package/hooks/__pycache__/pre_kubectl_security.cpython-312.pyc +0 -0
  31. package/hooks/__pycache__/pre_tool_use.cpython-312.pyc +0 -0
  32. package/hooks/__pycache__/session_start.cpython-312.pyc +0 -0
  33. package/hooks/__pycache__/subagent_stop.cpython-312.pyc +0 -0
  34. package/hooks/post_tool_use.py +463 -0
  35. package/hooks/pre_kubectl_security.py +205 -0
  36. package/hooks/pre_tool_use.py +530 -0
  37. package/hooks/session_start.py +315 -0
  38. package/hooks/subagent_stop.py +549 -0
  39. package/index.js +92 -0
  40. package/package.json +59 -0
  41. package/speckit/README.en.md +648 -0
  42. package/speckit/README.md +353 -0
  43. package/speckit/governance.md +169 -0
  44. package/speckit/scripts/check-prerequisites.sh +194 -0
  45. package/speckit/scripts/common.sh +126 -0
  46. package/speckit/scripts/create-new-feature.sh +131 -0
  47. package/speckit/scripts/init.sh +42 -0
  48. package/speckit/scripts/setup-plan.sh +95 -0
  49. package/speckit/scripts/update-agent-context.sh +718 -0
  50. package/speckit/templates/adr-template.md +118 -0
  51. package/speckit/templates/agent-file-template.md +23 -0
  52. package/speckit/templates/plan-template.md +233 -0
  53. package/speckit/templates/spec-template.md +116 -0
  54. package/speckit/templates/tasks-template-bkp.md +136 -0
  55. package/speckit/templates/tasks-template.md +345 -0
  56. package/templates/CLAUDE.template.md +170 -0
  57. package/templates/code-examples/approval_gate_workflow.py +141 -0
  58. package/templates/code-examples/clarification_workflow.py +94 -0
  59. package/templates/code-examples/commit_validation.py +86 -0
  60. package/templates/project-context.template.json +126 -0
  61. package/templates/settings.template.json +307 -0
  62. package/tools/__pycache__/agent_router.cpython-312.pyc +0 -0
  63. package/tools/__pycache__/approval_gate.cpython-312.pyc +0 -0
  64. package/tools/__pycache__/clarify_engine.cpython-312.pyc +0 -0
  65. package/tools/__pycache__/clarify_patterns.cpython-312.pyc +0 -0
  66. package/tools/__pycache__/commit_validator.cpython-312.pyc +0 -0
  67. package/tools/__pycache__/context_section_reader.cpython-312.pyc +0 -0
  68. package/tools/__pycache__/routing_dashboard.cpython-312.pyc +0 -0
  69. package/tools/__pycache__/routing_feedback.cpython-312.pyc +0 -0
  70. package/tools/__pycache__/semantic_matcher.cpython-312.pyc +0 -0
  71. package/tools/__pycache__/task_manager.cpython-312.pyc +0 -0
  72. package/tools/agent_capabilities.json +231 -0
  73. package/tools/agent_invoker_helper.py +239 -0
  74. package/tools/agent_router.py +730 -0
  75. package/tools/approval_gate.py +318 -0
  76. package/tools/clarify_engine.py +511 -0
  77. package/tools/clarify_patterns.py +356 -0
  78. package/tools/commit_validator.py +338 -0
  79. package/tools/context_provider.py +181 -0
  80. package/tools/context_section_reader.py +301 -0
  81. package/tools/demo_clarify.py +104 -0
  82. package/tools/generate_embeddings.py +168 -0
  83. package/tools/quicktriage_aws_troubleshooter.sh +45 -0
  84. package/tools/quicktriage_devops_developer.sh +38 -0
  85. package/tools/quicktriage_gcp_troubleshooter.sh +51 -0
  86. package/tools/quicktriage_gitops_operator.sh +47 -0
  87. package/tools/quicktriage_terraform_architect.sh +40 -0
  88. package/tools/semantic_matcher.py +222 -0
  89. package/tools/task_manager.py +547 -0
  90. package/tools/task_manager_README.md +395 -0
  91. package/tools/task_manager_example.py +215 -0
@@ -0,0 +1,547 @@
1
+ """
2
+ Task Manager
3
+
4
+ Efficient task file operations for large projects without loading entire files.
5
+ This utility is designed to handle tasks.md files that exceed Claude's Read tool
6
+ token limits (>25,000 tokens) by using targeted Grep and Edit operations.
7
+
8
+ Usage:
9
+ from task_manager import TaskManager
10
+
11
+ # Initialize with path to tasks.md file
12
+ tm = TaskManager("/path/to/tasks.md")
13
+
14
+ # Mark a task as complete
15
+ tm.mark_task_complete("T045")
16
+
17
+ # Get pending tasks
18
+ pending = tm.get_pending_tasks(limit=10)
19
+
20
+ # Get full details for a specific task
21
+ details = tm.get_task_details("T045")
22
+
23
+ Architecture:
24
+ - Uses Grep tool for searching (avoids loading full file)
25
+ - Uses Edit tool for targeted updates (no full-file rewrites)
26
+ - Parses task metadata from HTML comments
27
+ - Handles large files efficiently
28
+
29
+ Task Format Recognition:
30
+ Tasks follow this pattern:
31
+ ```
32
+ - [ ] T045 Deploy query-api HelmRelease
33
+ <!-- 🤖 Agent: gitops-operator | ✅ T3 | ⚡ 0.95 -->
34
+ <!-- 🏷️ Tags: #kubernetes #helm -->
35
+
36
+ **Description:** Create HelmRelease manifest...
37
+
38
+ **Acceptance Criteria:**
39
+ - Criterion 1
40
+ - Criterion 2
41
+ ```
42
+ """
43
+
44
+ import os
45
+ import re
46
+ import subprocess
47
+ from typing import Dict, List, Any, Optional, Tuple
48
+
49
+
50
+ class TaskManager:
51
+ """Efficient task file operations for large projects"""
52
+
53
+ def __init__(self, tasks_file_path: str):
54
+ """
55
+ Initialize with path to tasks.md file
56
+
57
+ Args:
58
+ tasks_file_path: Absolute path to tasks.md file
59
+
60
+ Raises:
61
+ FileNotFoundError: If tasks.md file doesn't exist
62
+ """
63
+ self.tasks_file_path = os.path.abspath(tasks_file_path)
64
+
65
+ if not os.path.exists(self.tasks_file_path):
66
+ raise FileNotFoundError(
67
+ f"Tasks file not found: {self.tasks_file_path}"
68
+ )
69
+
70
+ self.file_dir = os.path.dirname(self.tasks_file_path)
71
+
72
+ def mark_task_complete(self, task_id: str) -> bool:
73
+ r"""
74
+ Mark a task as complete using Grep to find + Edit to update.
75
+
76
+ Args:
77
+ task_id: Task identifier (e.g., "T045")
78
+
79
+ Returns:
80
+ True if task was marked complete, False if not found or already complete
81
+
82
+ Process:
83
+ 1. Use Grep to find line: "^- \[ \] {task_id}"
84
+ 2. Verify task is pending (has [ ] checkbox)
85
+ 3. Replace "- [ ]" with "- [x]" on that specific line
86
+ 4. Use Edit to write back
87
+
88
+ Raises:
89
+ ValueError: If task_id not found in file
90
+ """
91
+ # Sanitize task_id (remove any special chars for safety)
92
+ task_id = task_id.strip().upper()
93
+
94
+ if not re.match(r'^T\d+$', task_id):
95
+ raise ValueError(
96
+ f"Invalid task_id format: {task_id}. Must be 'T' followed by digits (e.g., T045)"
97
+ )
98
+
99
+ # Search for the task line using grep
100
+ # Pattern: "- [ ] T045" (pending task)
101
+ try:
102
+ result = subprocess.run(
103
+ ['grep', '-n', f'^- \\[ \\] {task_id} ', self.tasks_file_path],
104
+ capture_output=True,
105
+ text=True,
106
+ check=False
107
+ )
108
+
109
+ if result.returncode != 0:
110
+ # Task not found or already completed
111
+ # Check if task exists but is already complete
112
+ completed_result = subprocess.run(
113
+ ['grep', '-n', f'^- \\[x\\] {task_id} ', self.tasks_file_path],
114
+ capture_output=True,
115
+ text=True,
116
+ check=False
117
+ )
118
+
119
+ if completed_result.returncode == 0:
120
+ # Task already complete
121
+ return False
122
+ else:
123
+ # Task not found at all
124
+ raise ValueError(
125
+ f"Task {task_id} not found in {self.tasks_file_path}"
126
+ )
127
+
128
+ # Parse the line number and content
129
+ # Format: "123:- [ ] T045 Task description"
130
+ line_output = result.stdout.strip()
131
+ if not line_output:
132
+ raise ValueError(f"Task {task_id} not found")
133
+
134
+ # Get the line with task checkbox
135
+ line_content = line_output.split(':', 1)[1] if ':' in line_output else line_output
136
+
137
+ # Replace [ ] with [x]
138
+ new_line_content = line_content.replace('- [ ]', '- [x]', 1)
139
+
140
+ # Use sed to edit the file in place
141
+ # Find the line and replace it
142
+ subprocess.run(
143
+ ['sed', '-i', f's/^- \\[ \\] {task_id} /- [x] {task_id} /', self.tasks_file_path],
144
+ check=True
145
+ )
146
+
147
+ return True
148
+
149
+ except subprocess.CalledProcessError as e:
150
+ raise RuntimeError(f"Failed to mark task complete: {e}")
151
+
152
+ def get_pending_tasks(self, limit: int = 10) -> List[Dict[str, str]]:
153
+ r"""
154
+ Get list of pending tasks using Grep.
155
+
156
+ Args:
157
+ limit: Maximum number of tasks to return
158
+
159
+ Returns:
160
+ List of dicts with keys: task_id, title, line_number
161
+
162
+ Process:
163
+ 1. Use Grep: "^- \[ \] T[0-9]+" to find pending tasks
164
+ 2. Parse results to extract task ID and title
165
+ 3. Return up to 'limit' results
166
+ """
167
+ try:
168
+ # Search for pending tasks: "- [ ] T001"
169
+ result = subprocess.run(
170
+ ['grep', '-n', '^- \\[ \\] T[0-9]\\+', self.tasks_file_path],
171
+ capture_output=True,
172
+ text=True,
173
+ check=False
174
+ )
175
+
176
+ if result.returncode != 0:
177
+ # No pending tasks found
178
+ return []
179
+
180
+ # Parse the grep output
181
+ # Format: "123:- [ ] T045 Deploy query-api HelmRelease"
182
+ lines = result.stdout.strip().split('\n')
183
+ tasks = []
184
+
185
+ for line in lines[:limit]:
186
+ if not line:
187
+ continue
188
+
189
+ # Extract line number and content
190
+ parts = line.split(':', 1)
191
+ if len(parts) != 2:
192
+ continue
193
+
194
+ line_number = parts[0]
195
+ content = parts[1]
196
+
197
+ # Extract task ID and title
198
+ # Pattern: "- [ ] T045 Title of the task"
199
+ match = re.match(r'^- \[ \] (T\d+)\s+(.+)$', content)
200
+ if match:
201
+ task_id = match.group(1)
202
+ title = match.group(2)
203
+
204
+ tasks.append({
205
+ 'task_id': task_id,
206
+ 'title': title,
207
+ 'line_number': int(line_number)
208
+ })
209
+
210
+ return tasks
211
+
212
+ except subprocess.CalledProcessError as e:
213
+ raise RuntimeError(f"Failed to get pending tasks: {e}")
214
+
215
+ def get_task_details(self, task_id: str) -> Dict[str, Any]:
216
+ """
217
+ Load full details for a specific task.
218
+
219
+ Args:
220
+ task_id: Task identifier (e.g., "T045")
221
+
222
+ Returns:
223
+ Dict with keys: task_id, title, description, metadata, line_number, status
224
+
225
+ Process:
226
+ 1. Use Grep to find task start line (both pending and complete)
227
+ 2. Read that line + next 20 lines (captures description + metadata)
228
+ 3. Parse task block to extract metadata from HTML comments
229
+
230
+ Raises:
231
+ ValueError: If task_id not found
232
+ """
233
+ # Sanitize task_id
234
+ task_id = task_id.strip().upper()
235
+
236
+ if not re.match(r'^T\d+$', task_id):
237
+ raise ValueError(
238
+ f"Invalid task_id format: {task_id}. Must be 'T' followed by digits"
239
+ )
240
+
241
+ try:
242
+ # Search for task (both pending and complete)
243
+ # Pattern: "- [ ] T045" or "- [x] T045"
244
+ result = subprocess.run(
245
+ ['grep', '-n', f'^- \\[.\\] {task_id} ', self.tasks_file_path],
246
+ capture_output=True,
247
+ text=True,
248
+ check=False
249
+ )
250
+
251
+ if result.returncode != 0:
252
+ raise ValueError(f"Task {task_id} not found in {self.tasks_file_path}")
253
+
254
+ # Parse the line number
255
+ line_output = result.stdout.strip()
256
+ line_number = int(line_output.split(':')[0])
257
+
258
+ # Determine status
259
+ status = 'completed' if '- [x]' in line_output else 'pending'
260
+
261
+ # Extract title
262
+ title_match = re.search(r'^- \[.\] T\d+\s+(.+)$', line_output.split(':', 1)[1])
263
+ title = title_match.group(1) if title_match else "Unknown"
264
+
265
+ # Read the task block (next 25 lines for full context)
266
+ # Use sed to extract lines
267
+ end_line = line_number + 25
268
+ lines_result = subprocess.run(
269
+ ['sed', '-n', f'{line_number},{end_line}p', self.tasks_file_path],
270
+ capture_output=True,
271
+ text=True,
272
+ check=True
273
+ )
274
+
275
+ task_block = lines_result.stdout
276
+
277
+ # Parse metadata from HTML comments
278
+ metadata = self._parse_task_metadata(task_block)
279
+
280
+ # Extract description
281
+ description = self._extract_description(task_block)
282
+
283
+ # Extract acceptance criteria
284
+ acceptance_criteria = self._extract_acceptance_criteria(task_block)
285
+
286
+ return {
287
+ 'task_id': task_id,
288
+ 'title': title,
289
+ 'status': status,
290
+ 'line_number': line_number,
291
+ 'metadata': metadata,
292
+ 'description': description,
293
+ 'acceptance_criteria': acceptance_criteria
294
+ }
295
+
296
+ except subprocess.CalledProcessError as e:
297
+ raise RuntimeError(f"Failed to get task details: {e}")
298
+
299
+ def _parse_task_metadata(self, task_block: str) -> Dict[str, Any]:
300
+ """
301
+ Parse metadata from HTML comments in task block.
302
+
303
+ Extracts:
304
+ - Agent name
305
+ - Security tier
306
+ - Confidence score
307
+ - Tags
308
+ - Skills
309
+
310
+ Args:
311
+ task_block: Text block containing the task
312
+
313
+ Returns:
314
+ Dict with extracted metadata
315
+ """
316
+ metadata = {
317
+ 'agent': None,
318
+ 'security_tier': None,
319
+ 'confidence': None,
320
+ 'tags': [],
321
+ 'skill': None,
322
+ 'fallback': None,
323
+ 'result': None
324
+ }
325
+
326
+ # Extract agent, tier, and confidence
327
+ # Pattern: <!-- 🤖 Agent: gitops-operator | ✅ T3 | ⚡ 0.95 -->
328
+ agent_match = re.search(
329
+ r'<!-- 🤖 Agent: ([a-z-]+) \| [^|]+ ([T0-3]+) \| [^0-9]+ ([\d.]+) -->',
330
+ task_block
331
+ )
332
+ if agent_match:
333
+ metadata['agent'] = agent_match.group(1)
334
+ metadata['security_tier'] = agent_match.group(2)
335
+ metadata['confidence'] = float(agent_match.group(3))
336
+
337
+ # Extract tags
338
+ # Pattern: <!-- 🏷️ Tags: #kubernetes #helm -->
339
+ tags_match = re.search(r'<!-- 🏷️ Tags: (.+) -->', task_block)
340
+ if tags_match:
341
+ tags_str = tags_match.group(1)
342
+ metadata['tags'] = [tag.strip() for tag in tags_str.split('#') if tag.strip()]
343
+
344
+ # Extract skill
345
+ # Pattern: <!-- 🎯 skill: testing_validation (10.0) -->
346
+ skill_match = re.search(r'<!-- 🎯 skill: ([a-z_]+) \(([\d.]+)\) -->', task_block)
347
+ if skill_match:
348
+ metadata['skill'] = {
349
+ 'name': skill_match.group(1),
350
+ 'score': float(skill_match.group(2))
351
+ }
352
+
353
+ # Extract fallback agent
354
+ # Pattern: <!-- 🔄 Fallback: terraform-architect -->
355
+ fallback_match = re.search(r'<!-- 🔄 Fallback: ([a-z-]+) -->', task_block)
356
+ if fallback_match:
357
+ metadata['fallback'] = fallback_match.group(1)
358
+
359
+ # Extract result
360
+ # Pattern: <!-- 📍 Result: ... -->
361
+ result_match = re.search(r'<!-- 📍 Result: (.+) -->', task_block)
362
+ if result_match:
363
+ metadata['result'] = result_match.group(1)
364
+
365
+ return metadata
366
+
367
+ def _extract_description(self, task_block: str) -> Optional[str]:
368
+ """
369
+ Extract description from task block.
370
+
371
+ Pattern: **Description:** followed by text until next section
372
+
373
+ Args:
374
+ task_block: Text block containing the task
375
+
376
+ Returns:
377
+ Description text or None if not found
378
+ """
379
+ desc_match = re.search(
380
+ r'\*\*Description:\*\*\s+(.+?)(?=\*\*|\n\n|$)',
381
+ task_block,
382
+ re.DOTALL
383
+ )
384
+ if desc_match:
385
+ return desc_match.group(1).strip()
386
+ return None
387
+
388
+ def _extract_acceptance_criteria(self, task_block: str) -> List[str]:
389
+ """
390
+ Extract acceptance criteria from task block.
391
+
392
+ Pattern: **Acceptance Criteria:** followed by bullet list
393
+
394
+ Args:
395
+ task_block: Text block containing the task
396
+
397
+ Returns:
398
+ List of acceptance criteria strings
399
+ """
400
+ criteria = []
401
+
402
+ # Find the acceptance criteria section
403
+ criteria_match = re.search(
404
+ r'\*\*Acceptance Criteria:\*\*(.+?)(?=\*\*|\n\n|$)',
405
+ task_block,
406
+ re.DOTALL
407
+ )
408
+
409
+ if criteria_match:
410
+ criteria_text = criteria_match.group(1)
411
+ # Extract bullet points (lines starting with -)
412
+ lines = criteria_text.split('\n')
413
+ for line in lines:
414
+ line = line.strip()
415
+ if line.startswith('- '):
416
+ criteria.append(line[2:].strip())
417
+
418
+ return criteria
419
+
420
+ def get_task_statistics(self) -> Dict[str, Any]:
421
+ """
422
+ Get overall statistics for the tasks file.
423
+
424
+ Returns:
425
+ Dict with statistics:
426
+ - total_tasks: Total number of tasks
427
+ - pending_tasks: Number of pending tasks
428
+ - completed_tasks: Number of completed tasks
429
+ - completion_rate: Percentage complete
430
+ """
431
+ try:
432
+ # Count total tasks (both pending and complete)
433
+ total_result = subprocess.run(
434
+ ['grep', '-c', '^- \\[.\\] T[0-9]\\+', self.tasks_file_path],
435
+ capture_output=True,
436
+ text=True,
437
+ check=False
438
+ )
439
+ total_tasks = int(total_result.stdout.strip()) if total_result.returncode == 0 else 0
440
+
441
+ # Count completed tasks
442
+ completed_result = subprocess.run(
443
+ ['grep', '-c', '^- \\[x\\] T[0-9]\\+', self.tasks_file_path],
444
+ capture_output=True,
445
+ text=True,
446
+ check=False
447
+ )
448
+ completed_tasks = int(completed_result.stdout.strip()) if completed_result.returncode == 0 else 0
449
+
450
+ # Calculate pending
451
+ pending_tasks = total_tasks - completed_tasks
452
+
453
+ # Calculate completion rate
454
+ completion_rate = (completed_tasks / total_tasks * 100) if total_tasks > 0 else 0.0
455
+
456
+ return {
457
+ 'total_tasks': total_tasks,
458
+ 'pending_tasks': pending_tasks,
459
+ 'completed_tasks': completed_tasks,
460
+ 'completion_rate': round(completion_rate, 2)
461
+ }
462
+
463
+ except Exception as e:
464
+ raise RuntimeError(f"Failed to get task statistics: {e}")
465
+
466
+
467
+ # Test function for demonstration
468
+ def test_task_manager():
469
+ """
470
+ Test function demonstrating usage of TaskManager.
471
+
472
+ This function requires a sample tasks.md file to be present.
473
+ """
474
+ import sys
475
+
476
+ print("=" * 60)
477
+ print("TaskManager Test Suite")
478
+ print("=" * 60)
479
+
480
+ # Check if path is provided
481
+ if len(sys.argv) < 2:
482
+ print("\nUsage: python task_manager.py <path_to_tasks.md>")
483
+ print("\nExample:")
484
+ print(" python task_manager.py /path/to/tasks.md")
485
+ return
486
+
487
+ tasks_file = sys.argv[1]
488
+
489
+ try:
490
+ # Initialize TaskManager
491
+ print(f"\n1. Initializing TaskManager with: {tasks_file}")
492
+ tm = TaskManager(tasks_file)
493
+ print(" ✅ TaskManager initialized successfully")
494
+
495
+ # Get statistics
496
+ print("\n2. Getting task statistics...")
497
+ stats = tm.get_task_statistics()
498
+ print(f" Total tasks: {stats['total_tasks']}")
499
+ print(f" Pending: {stats['pending_tasks']}")
500
+ print(f" Completed: {stats['completed_tasks']}")
501
+ print(f" Completion rate: {stats['completion_rate']}%")
502
+
503
+ # Get pending tasks
504
+ print("\n3. Getting pending tasks (limit 5)...")
505
+ pending = tm.get_pending_tasks(limit=5)
506
+ print(f" Found {len(pending)} pending tasks:")
507
+ for task in pending:
508
+ print(f" - {task['task_id']}: {task['title']}")
509
+
510
+ # Get details for first pending task
511
+ if pending:
512
+ task_id = pending[0]['task_id']
513
+ print(f"\n4. Getting details for {task_id}...")
514
+ details = tm.get_task_details(task_id)
515
+ print(f" Task ID: {details['task_id']}")
516
+ print(f" Title: {details['title']}")
517
+ print(f" Status: {details['status']}")
518
+ print(f" Agent: {details['metadata'].get('agent', 'N/A')}")
519
+ print(f" Security Tier: {details['metadata'].get('security_tier', 'N/A')}")
520
+ print(f" Tags: {', '.join(details['metadata'].get('tags', []))}")
521
+
522
+ if details.get('description'):
523
+ print(f" Description: {details['description'][:100]}...")
524
+
525
+ # Test marking task complete (COMMENTED OUT - Destructive operation)
526
+ # print(f"\n5. Testing mark_task_complete (dry run)...")
527
+ # print(f" Would mark {task_id} as complete")
528
+ # Uncomment to actually test:
529
+ # result = tm.mark_task_complete(task_id)
530
+ # print(f" Result: {result}")
531
+
532
+ print("\n" + "=" * 60)
533
+ print("✅ All tests passed successfully")
534
+ print("=" * 60)
535
+
536
+ except FileNotFoundError as e:
537
+ print(f"\n❌ Error: {e}")
538
+ sys.exit(1)
539
+ except Exception as e:
540
+ print(f"\n❌ Unexpected error: {e}")
541
+ import traceback
542
+ traceback.print_exc()
543
+ sys.exit(1)
544
+
545
+
546
+ if __name__ == '__main__':
547
+ test_task_manager()