@voodocs/cli 0.3.1 → 0.4.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.
@@ -1,4 +1,11 @@
1
- """
1
+ """@darkarts
2
+ ⊢commands:ctx.cli
3
+ ∂{yaml_utils,models,ai_integrations,checker,diagram}
4
+ ⚠{.voodocs.context∈root,git:available,py≥3.11,cwd=root}
5
+ ⊨{ctx:yaml,v∈semver,exit∈{0,1},∀update→v++,files:utf8}
6
+ 🔒{read:ctx,write⊳confirm}
7
+ ⚡{O(1)|most-cmds,O(n)|generate:n=src-files}
8
+
2
9
  Context System Commands
3
10
 
4
11
  Implements the command-line interface for the context system.
@@ -15,6 +22,26 @@ from .yaml_utils import (
15
22
  add_to_gitignore,
16
23
  format_context_as_markdown
17
24
  )
25
+ from .errors import (
26
+ ContextFileNotFoundError,
27
+ InvalidContextError,
28
+ GitNotAvailableError,
29
+ UserInterruptError,
30
+ InvalidProjectNameError,
31
+ InvalidVersionError
32
+ )
33
+ from .validation import (
34
+ validate_project_name,
35
+ validate_version,
36
+ validate_path,
37
+ validate_context_structure
38
+ )
39
+ from .ui import (
40
+ success, error, warning, info, step, debug,
41
+ confirm, prompt, progress,
42
+ print_header, print_section, print_key_value
43
+ )
44
+ from .module_utils import extract_modules_from_results
18
45
 
19
46
 
20
47
  def get_project_root() -> Path:
@@ -182,63 +209,76 @@ def cmd_context_init(force: bool = False) -> int:
182
209
  Returns:
183
210
  Exit code (0 for success, 1 for error)
184
211
  """
185
- print("🎯 VooDocs Context Initialization")
186
- print()
187
-
188
- # Check if context file already exists
189
- if context_file_exists() and not force:
190
- print("❌ Error: .voodocs.context already exists in this directory.")
191
- print(" Use --force to overwrite the existing file.")
192
- return 1
193
-
194
- # Detect project information
195
- detected_version = detect_code_version()
196
- detected_repo = detect_repository()
197
-
198
- print("Let's set up your project context. This will create a .voodocs.context file.")
199
- print()
200
-
201
- # Prompt for project information
202
- project_name = prompt_user("Project name")
203
-
204
- project_purpose = prompt_user("Project purpose (one sentence)")
205
-
206
- if detected_version:
207
- code_version = prompt_user("Current code version", detected_version)
208
- else:
209
- code_version = prompt_user("Current code version", "1.0.0")
210
-
211
- # Validate version format
212
- if not code_version.count('.') >= 2:
213
- print(f"⚠️ Warning: '{code_version}' doesn't look like semantic versioning (MAJOR.MINOR.PATCH)")
214
- if not prompt_yes_no("Continue anyway?", default=False):
215
- print("Aborted.")
216
- return 1
217
-
218
- if detected_repo:
219
- repository = prompt_user("Repository URL (leave empty to skip)", detected_repo, required=False)
220
- else:
221
- repository = prompt_user("Repository URL (leave empty to skip)", "", required=False)
222
-
223
- license_name = prompt_user("License (leave empty to skip)", "", required=False)
224
-
225
- print()
226
- print("📝 Configuration Summary:")
227
- print(f" Project Name: {project_name}")
228
- print(f" Purpose: {project_purpose}")
229
- print(f" Code Version: {code_version}")
230
- if repository:
231
- print(f" Repository: {repository}")
232
- if license_name:
233
- print(f" License: {license_name}")
234
- print()
235
-
236
- if not prompt_yes_no("Create context file?", default=True):
237
- print("Aborted.")
238
- return 1
239
-
240
- # Create context file
241
212
  try:
213
+ print_header("VooDocs Context Initialization")
214
+
215
+ # Check if context file already exists
216
+ context_path = get_context_file_path()
217
+ if context_file_exists() and not force:
218
+ error(f"Context file already exists: {context_path}")
219
+ info("Use --force to overwrite, or 'voodocs context update' to modify")
220
+ return 1
221
+
222
+ # Warn if overwriting
223
+ if context_file_exists() and force:
224
+ if not confirm(f"Overwrite existing context file at {context_path}?", default=False):
225
+ raise UserInterruptError("Initialization")
226
+
227
+ step("Initializing VooDocs context...")
228
+ print()
229
+
230
+ # Detect project information
231
+ detected_version = detect_code_version()
232
+ detected_repo = detect_repository()
233
+
234
+ info("Let's set up your project context. This will create a .voodocs.context file.")
235
+ print()
236
+
237
+ # Collect and validate user input
238
+ try:
239
+ # Project name with validation
240
+ default_name = get_project_root().name
241
+ name_input = prompt("Project name", default=default_name)
242
+ project_name = validate_project_name(name_input)
243
+
244
+ # Project purpose
245
+ project_purpose = prompt("Project purpose (one sentence)", required=True)
246
+
247
+ # Code version with validation
248
+ version_default = detected_version or "1.0.0"
249
+ version_input = prompt("Current code version", default=version_default)
250
+ code_version = validate_version(version_input) or "1.0.0"
251
+
252
+ # Repository (optional)
253
+ repository = prompt(
254
+ "Repository URL (leave empty to skip)",
255
+ default=detected_repo or "",
256
+ required=False
257
+ )
258
+
259
+ # License (optional)
260
+ license_name = prompt("License (leave empty to skip)", default="", required=False)
261
+
262
+ except KeyboardInterrupt:
263
+ raise UserInterruptError("Initialization")
264
+
265
+ # Show configuration summary
266
+ print()
267
+ print_section("Configuration Summary")
268
+ print_key_value("Project Name", project_name)
269
+ print_key_value("Purpose", project_purpose)
270
+ print_key_value("Code Version", code_version)
271
+ if repository:
272
+ print_key_value("Repository", repository)
273
+ if license_name:
274
+ print_key_value("License", license_name)
275
+ print()
276
+
277
+ if not confirm("Create context file?", default=True):
278
+ raise UserInterruptError("Initialization")
279
+
280
+ # Create context file
281
+ step("Creating context structure...")
242
282
  context = create_minimal_context(
243
283
  project_name=project_name,
244
284
  project_purpose=project_purpose,
@@ -247,20 +287,20 @@ def cmd_context_init(force: bool = False) -> int:
247
287
  license=license_name if license_name else None
248
288
  )
249
289
 
250
- context_path = get_context_file_path()
290
+ step(f"Writing context file to {context_path}...")
251
291
  write_context_yaml(context, context_path)
252
292
 
253
293
  print()
254
- print(f" Created .voodocs.context")
255
- print(f" Context version: {context.versioning.context_version}")
294
+ success("Context file created successfully!")
295
+ info(f"Context version: {context.versioning.context_version}")
256
296
  print()
257
297
 
258
298
  # Ask about .gitignore
259
- if prompt_yes_no("Add .voodocs.context to .gitignore? (recommended for private projects)", default=True):
299
+ if confirm("Add .voodocs.context to .gitignore? (recommended for private projects)", default=True):
260
300
  if add_to_gitignore(get_project_root()):
261
- print("Added to .gitignore")
301
+ success("Added to .gitignore")
262
302
  else:
263
- print("ℹ️ Already in .gitignore")
303
+ info("Already in .gitignore")
264
304
 
265
305
  print()
266
306
 
@@ -344,9 +384,9 @@ def cmd_context_init(force: bool = False) -> int:
344
384
  print("ℹ️ Skipped AI integration")
345
385
 
346
386
  print()
347
- print("🎉 Context initialized successfully!")
387
+ success("🎉 Context initialized successfully!")
348
388
  print()
349
- print("Next steps:")
389
+ info("Next steps:")
350
390
  print(" 1. Review the context file: voodocs context view")
351
391
  print(" 2. Auto-generate from code: voodocs context generate --update")
352
392
  print(" 3. Check invariants: voodocs context check")
@@ -356,8 +396,18 @@ def cmd_context_init(force: bool = False) -> int:
356
396
 
357
397
  return 0
358
398
 
399
+ except UserInterruptError as e:
400
+ print() # New line after interrupt
401
+ error(str(e))
402
+ return 1
403
+ except (InvalidProjectNameError, InvalidVersionError) as e:
404
+ print()
405
+ error(str(e))
406
+ return 1
359
407
  except Exception as e:
360
- print(f"❌ Error creating context file: {e}")
408
+ print()
409
+ error(f"Failed to initialize context: {e}")
410
+ warning("Run with --debug for full stack trace")
361
411
  return 1
362
412
 
363
413
 
@@ -371,19 +421,17 @@ def cmd_context_view(output_file: Optional[str] = None) -> int:
371
421
  Returns:
372
422
  Exit code (0 for success, 1 for error)
373
423
  """
374
- if not context_file_exists():
375
- print("❌ Error: No .voodocs.context file found in this directory.")
376
- print(" Run 'voodocs context init' to create one.")
377
- return 1
378
-
379
424
  try:
380
- # Read context file
425
+ # Check if context file exists
381
426
  context_path = get_context_file_path()
427
+ if not context_file_exists():
428
+ raise ContextFileNotFoundError(str(context_path))
429
+
430
+ # Read and parse context file
382
431
  data = read_context_yaml(context_path)
383
432
 
384
433
  if not data:
385
- print("❌ Error: Failed to read .voodocs.context file.")
386
- return 1
434
+ raise InvalidContextError(str(context_path), "Failed to read YAML data")
387
435
 
388
436
  # Parse and format as Markdown
389
437
  from .yaml_utils import parse_context_file
@@ -393,17 +441,25 @@ def cmd_context_view(output_file: Optional[str] = None) -> int:
393
441
  if output_file:
394
442
  # Write to file
395
443
  output_path = Path(output_file)
444
+ output_path.parent.mkdir(parents=True, exist_ok=True)
396
445
  with open(output_path, 'w', encoding='utf-8') as f:
397
446
  f.write(markdown)
398
- print(f"Context exported to {output_file}")
447
+ success(f"Context exported to {output_file}")
399
448
  return 0
400
449
  else:
401
450
  # Print to console
402
451
  print(markdown)
403
452
  return 0
404
453
 
454
+ except ContextFileNotFoundError as e:
455
+ error(str(e))
456
+ return 1
457
+ except InvalidContextError as e:
458
+ error(str(e))
459
+ return 1
405
460
  except Exception as e:
406
- print(f" Error viewing context: {e}")
461
+ error(f"Failed to view context: {e}")
462
+ warning("Run with --debug for full stack trace")
407
463
  return 1
408
464
 
409
465
 
@@ -414,19 +470,17 @@ def cmd_context_status() -> int:
414
470
  Returns:
415
471
  Exit code (0 for success, 1 for error)
416
472
  """
417
- if not context_file_exists():
418
- print("❌ No context file found")
419
- print(" Run 'voodocs context init' to create one.")
420
- return 1
421
-
422
473
  try:
423
- # Read context file
474
+ # Check if context file exists
424
475
  context_path = get_context_file_path()
476
+ if not context_file_exists():
477
+ raise ContextFileNotFoundError(str(context_path))
478
+
479
+ # Read context file
425
480
  data = read_context_yaml(context_path)
426
481
 
427
482
  if not data:
428
- print("❌ Error: Failed to read .voodocs.context file.")
429
- return 1
483
+ raise InvalidContextError(str(context_path), "Failed to read YAML data")
430
484
 
431
485
  # Extract key information
432
486
  versioning = data.get('versioning', {})
@@ -455,12 +509,19 @@ def cmd_context_status() -> int:
455
509
  print(f"Known Issues: {len(known_issues)}")
456
510
 
457
511
  print()
458
- print("Use 'voodocs context view' to see the full context.")
512
+ info("Use 'voodocs context view' to see the full context.")
459
513
 
460
514
  return 0
461
515
 
516
+ except ContextFileNotFoundError as e:
517
+ error(str(e))
518
+ return 1
519
+ except InvalidContextError as e:
520
+ error(str(e))
521
+ return 1
462
522
  except Exception as e:
463
- print(f" Error reading context: {e}")
523
+ error(f"Failed to read context status: {e}")
524
+ warning("Run with --debug for full stack trace")
464
525
  return 1
465
526
 
466
527
 
@@ -474,22 +535,20 @@ def cmd_context_update(description: Optional[str] = None) -> int:
474
535
  Returns:
475
536
  Exit code (0 for success, 1 for error)
476
537
  """
477
- if not context_file_exists():
478
- print("❌ Error: No .voodocs.context file found in this directory.")
479
- print(" Run 'voodocs context init' to create one.")
480
- return 1
481
-
482
538
  try:
539
+ # Check if context file exists
540
+ context_path = get_context_file_path()
541
+ if not context_file_exists():
542
+ raise ContextFileNotFoundError(str(context_path))
543
+
483
544
  from datetime import date
484
545
  from .models import Change
485
546
 
486
547
  # Read current context
487
- context_path = get_context_file_path()
488
548
  data = read_context_yaml(context_path)
489
549
 
490
550
  if not data:
491
- print("❌ Error: Failed to read .voodocs.context file.")
492
- return 1
551
+ raise InvalidContextError(str(context_path), "Failed to read YAML data")
493
552
 
494
553
  # Get current versions
495
554
  versioning = data.get('versioning', {})
@@ -499,14 +558,13 @@ def cmd_context_update(description: Optional[str] = None) -> int:
499
558
  # Parse context version
500
559
  parts = current_context_version.split('.')
501
560
  if len(parts) != 2:
502
- print(f"❌ Error: Invalid context version format: {current_context_version}")
503
- return 1
561
+ raise ValueError(f"Invalid context version format: {current_context_version}. Expected MAJOR.MINOR format.")
504
562
 
505
563
  major, minor = parts
506
564
  new_minor = int(minor) + 1
507
565
  new_context_version = f"{major}.{new_minor}"
508
566
 
509
- print(f"📝 Updating Context")
567
+ step("Updating context...")
510
568
  print()
511
569
  print(f"Current version: {current_context_version}")
512
570
  print(f"New version: {new_context_version}")
@@ -514,9 +572,10 @@ def cmd_context_update(description: Optional[str] = None) -> int:
514
572
 
515
573
  # Prompt for description if not provided
516
574
  if not description:
517
- description = prompt_user("Change description", "", required=False)
518
- if not description:
519
- description = "Context updated"
575
+ try:
576
+ description = prompt("Change description", default="Context updated", required=False)
577
+ except KeyboardInterrupt:
578
+ raise UserInterruptError("Context update")
520
579
 
521
580
  # Update versioning
522
581
  today = date.today().isoformat()
@@ -547,20 +606,34 @@ def cmd_context_update(description: Optional[str] = None) -> int:
547
606
  data['changes'].append(change_entry)
548
607
 
549
608
  # Write updated context
609
+ step("Writing updated context...")
550
610
  from .yaml_utils import parse_context_file
551
611
  context = parse_context_file(data)
552
612
  write_context_yaml(context, context_path)
553
613
 
554
- print(f"✅ Context updated to version {new_context_version}")
555
- print(f" Description: {description}")
614
+ print()
615
+ success(f"Context updated to version {new_context_version}")
616
+ info(f"Description: {description}")
556
617
  print()
557
618
 
558
619
  return 0
559
620
 
621
+ except ContextFileNotFoundError as e:
622
+ error(str(e))
623
+ return 1
624
+ except InvalidContextError as e:
625
+ error(str(e))
626
+ return 1
627
+ except UserInterruptError as e:
628
+ print()
629
+ error(str(e))
630
+ return 1
631
+ except ValueError as e:
632
+ error(str(e))
633
+ return 1
560
634
  except Exception as e:
561
- print(f" Error updating context: {e}")
562
- import traceback
563
- traceback.print_exc()
635
+ error(f"Failed to update context: {e}")
636
+ warning("Run with --debug for full stack trace")
564
637
  return 1
565
638
 
566
639
 
@@ -575,21 +648,19 @@ def cmd_context_sync(code_version: Optional[str] = None, force: bool = False) ->
575
648
  Returns:
576
649
  Exit code (0 for success, 1 for error)
577
650
  """
578
- if not context_file_exists():
579
- print("❌ Error: No .voodocs.context file found in this directory.")
580
- print(" Run 'voodocs context init' to create one.")
581
- return 1
582
-
583
651
  try:
652
+ # Check if context file exists
653
+ context_path = get_context_file_path()
654
+ if not context_file_exists():
655
+ raise ContextFileNotFoundError(str(context_path))
656
+
584
657
  from datetime import date
585
658
 
586
659
  # Read current context
587
- context_path = get_context_file_path()
588
660
  data = read_context_yaml(context_path)
589
661
 
590
662
  if not data:
591
- print("❌ Error: Failed to read .voodocs.context file.")
592
- return 1
663
+ raise InvalidContextError(str(context_path), "Failed to read YAML data")
593
664
 
594
665
  # Get current versions
595
666
  versioning = data.get('versioning', {})
@@ -599,30 +670,33 @@ def cmd_context_sync(code_version: Optional[str] = None, force: bool = False) ->
599
670
  # If no code version provided, try to detect it
600
671
  if not code_version:
601
672
  detected_version = detect_code_version()
602
- if detected_version:
603
- code_version = prompt_user(
604
- "New code version",
605
- detected_version,
606
- required=True
607
- )
608
- else:
609
- code_version = prompt_user(
610
- "New code version",
611
- required=True
612
- )
673
+ try:
674
+ if detected_version:
675
+ code_version = prompt(
676
+ "New code version",
677
+ default=detected_version,
678
+ required=True
679
+ )
680
+ else:
681
+ code_version = prompt(
682
+ "New code version",
683
+ required=True
684
+ )
685
+ except KeyboardInterrupt:
686
+ raise UserInterruptError("Context sync")
613
687
 
614
688
  # Validate version format
615
- if code_version.count('.') < 2:
616
- print(f"❌ Error: Invalid version format: {code_version}")
617
- print(" Expected: MAJOR.MINOR.PATCH (e.g., 2.0.0)")
618
- return 1
689
+ try:
690
+ code_version = validate_version(code_version, allow_empty=False)
691
+ except InvalidVersionError as e:
692
+ raise InvalidVersionError(code_version)
619
693
 
620
694
  # Extract major versions
621
695
  current_major = current_code_version.split('.')[0]
622
696
  new_major = code_version.split('.')[0]
623
697
  context_major = current_context_version.split('.')[0]
624
698
 
625
- print(f"🔄 Syncing Context with Code Version")
699
+ step("Syncing context with code version...")
626
700
  print()
627
701
  print(f"Current code version: {current_code_version}")
628
702
  print(f"New code version: {code_version}")
@@ -639,9 +713,8 @@ def cmd_context_sync(code_version: Optional[str] = None, force: bool = False) ->
639
713
  print()
640
714
 
641
715
  if not force:
642
- if not prompt_yes_no("Continue with reset?", default=True):
643
- print("Aborted.")
644
- return 1
716
+ if not confirm("Continue with reset?", default=True):
717
+ raise UserInterruptError("Context sync")
645
718
 
646
719
  # Update versions
647
720
  today = date.today().isoformat()
@@ -677,8 +750,8 @@ def cmd_context_sync(code_version: Optional[str] = None, force: bool = False) ->
677
750
  context = parse_context_file(data)
678
751
  write_context_yaml(context, context_path)
679
752
 
680
- print(f"Context version reset to {new_context_version}")
681
- print(f" Code version updated to {code_version}")
753
+ success(f"Context version reset to {new_context_version}")
754
+ info(f"Code version updated to {code_version}")
682
755
  print()
683
756
 
684
757
  elif context_major != new_major:
@@ -722,8 +795,8 @@ def cmd_context_sync(code_version: Optional[str] = None, force: bool = False) ->
722
795
  context = parse_context_file(data)
723
796
  write_context_yaml(context, context_path)
724
797
 
725
- print(f"Context synced to {new_context_version}")
726
- print(f" Code version updated to {code_version}")
798
+ success(f"Context synced to {new_context_version}")
799
+ info(f"Code version updated to {code_version}")
727
800
  print()
728
801
 
729
802
  else:
@@ -736,16 +809,28 @@ def cmd_context_sync(code_version: Optional[str] = None, force: bool = False) ->
736
809
  context = parse_context_file(data)
737
810
  write_context_yaml(context, context_path)
738
811
 
739
- print(f"Code version updated to {code_version}")
740
- print(f" Context version unchanged: {current_context_version}")
812
+ success(f"Code version updated to {code_version}")
813
+ info(f"Context version unchanged: {current_context_version}")
741
814
  print()
742
815
 
743
816
  return 0
744
817
 
818
+ except ContextFileNotFoundError as e:
819
+ error(str(e))
820
+ return 1
821
+ except InvalidContextError as e:
822
+ error(str(e))
823
+ return 1
824
+ except InvalidVersionError as e:
825
+ error(str(e))
826
+ return 1
827
+ except UserInterruptError as e:
828
+ print()
829
+ error(str(e))
830
+ return 1
745
831
  except Exception as e:
746
- print(f" Error syncing context: {e}")
747
- import traceback
748
- traceback.print_exc()
832
+ error(f"Failed to sync context: {e}")
833
+ warning("Run with --debug for full stack trace")
749
834
  return 1
750
835
 
751
836
 
@@ -756,26 +841,24 @@ def cmd_context_history() -> int:
756
841
  Returns:
757
842
  Exit code (0 for success, 1 for error)
758
843
  """
759
- if not context_file_exists():
760
- print("❌ Error: No .voodocs.context file found in this directory.")
761
- print(" Run 'voodocs context init' to create one.")
762
- return 1
763
-
764
844
  try:
765
- # Read current context
845
+ # Check if context file exists
766
846
  context_path = get_context_file_path()
847
+ if not context_file_exists():
848
+ raise ContextFileNotFoundError(str(context_path))
849
+
850
+ # Read current context
767
851
  data = read_context_yaml(context_path)
768
852
 
769
853
  if not data:
770
- print("❌ Error: Failed to read .voodocs.context file.")
771
- return 1
854
+ raise InvalidContextError(str(context_path), "Failed to read YAML data")
772
855
 
773
856
  # Get changes
774
857
  changes = data.get('changes', [])
775
858
 
776
859
  if not changes:
777
- print("📜 No change history found.")
778
- print(" Changes will be recorded when you run 'voodocs context update'.")
860
+ warning("No change history found.")
861
+ info("Changes will be recorded when you run 'voodocs context update'.")
779
862
  return 0
780
863
 
781
864
  print("📜 Context Version History")
@@ -806,8 +889,15 @@ def cmd_context_history() -> int:
806
889
 
807
890
  return 0
808
891
 
892
+ except ContextFileNotFoundError as e:
893
+ error(str(e))
894
+ return 1
895
+ except InvalidContextError as e:
896
+ error(str(e))
897
+ return 1
809
898
  except Exception as e:
810
- print(f" Error reading history: {e}")
899
+ error(f"Failed to read history: {e}")
900
+ warning("Run with --debug for full stack trace")
811
901
  return 1
812
902
 
813
903
 
@@ -822,19 +912,17 @@ def cmd_context_diff(version1: Optional[str] = None, version2: Optional[str] = N
822
912
  Returns:
823
913
  Exit code (0 for success, 1 for error)
824
914
  """
825
- if not context_file_exists():
826
- print("❌ Error: No .voodocs.context file found in this directory.")
827
- print(" Run 'voodocs context init' to create one.")
828
- return 1
829
-
830
915
  try:
831
- # Read current context
916
+ # Check if context file exists
832
917
  context_path = get_context_file_path()
918
+ if not context_file_exists():
919
+ raise ContextFileNotFoundError(str(context_path))
920
+
921
+ # Read current context
833
922
  data = read_context_yaml(context_path)
834
923
 
835
924
  if not data:
836
- print("❌ Error: Failed to read .voodocs.context file.")
837
- return 1
925
+ raise InvalidContextError(str(context_path), "Failed to read YAML data")
838
926
 
839
927
  # Get current version
840
928
  versioning = data.get('versioning', {})
@@ -848,8 +936,8 @@ def cmd_context_diff(version1: Optional[str] = None, version2: Optional[str] = N
848
936
  if not version1:
849
937
  changes = data.get('changes', [])
850
938
  if len(changes) < 2:
851
- print("📊 Not enough version history to compare.")
852
- print(" Need at least 2 versions. Run 'voodocs context history' to see available versions.")
939
+ warning("Not enough version history to compare.")
940
+ info("Need at least 2 versions. Run 'voodocs context history' to see available versions.")
853
941
  return 0
854
942
 
855
943
  # Get the two most recent versions
@@ -881,7 +969,7 @@ def cmd_context_diff(version1: Optional[str] = None, version2: Optional[str] = N
881
969
  continue
882
970
 
883
971
  if not relevant_changes:
884
- print(f"No changes found between v{version1} and v{version2}")
972
+ info(f"No changes found between v{version1} and v{version2}")
885
973
  return 0
886
974
 
887
975
  print(f"Changes ({len(relevant_changes)}):")
@@ -900,14 +988,19 @@ def cmd_context_diff(version1: Optional[str] = None, version2: Optional[str] = N
900
988
  print(f" Code: v{code_version}")
901
989
  print()
902
990
 
903
- print(f"💡 Tip: Use 'voodocs context history' to see the full timeline.")
991
+ info("Tip: Use 'voodocs context history' to see the full timeline.")
904
992
 
905
993
  return 0
906
994
 
995
+ except ContextFileNotFoundError as e:
996
+ error(str(e))
997
+ return 1
998
+ except InvalidContextError as e:
999
+ error(str(e))
1000
+ return 1
907
1001
  except Exception as e:
908
- print(f" Error comparing versions: {e}")
909
- import traceback
910
- traceback.print_exc()
1002
+ error(f"Failed to compare versions: {e}")
1003
+ warning("Run with --debug for full stack trace")
911
1004
  return 1
912
1005
 
913
1006
 
@@ -922,21 +1015,19 @@ def cmd_context_validate(check_version: bool = False, check_invariants: bool = F
922
1015
  Returns:
923
1016
  Exit code (0 for success, 1 for warnings, 2 for errors)
924
1017
  """
925
- if not context_file_exists():
926
- print("❌ Error: No .voodocs.context file found in this directory.")
927
- print(" Run 'voodocs context init' to create one.")
928
- return 2
929
-
930
1018
  try:
931
- # Read current context
1019
+ # Check if context file exists
932
1020
  context_path = get_context_file_path()
1021
+ if not context_file_exists():
1022
+ raise ContextFileNotFoundError(str(context_path))
1023
+
1024
+ # Read current context
933
1025
  data = read_context_yaml(context_path)
934
1026
 
935
1027
  if not data:
936
- print("❌ Error: Failed to read .voodocs.context file.")
937
- return 2
1028
+ raise InvalidContextError(str(context_path), "Failed to read YAML data")
938
1029
 
939
- print("🔍 Validating Context File")
1030
+ step("Validating context file...")
940
1031
  print()
941
1032
 
942
1033
  errors = []
@@ -1056,7 +1147,7 @@ def cmd_context_validate(check_version: bool = False, check_invariants: bool = F
1056
1147
  break
1057
1148
 
1058
1149
  if not errors and not warnings:
1059
- print("Context file is valid!")
1150
+ success("Context file is valid!")
1060
1151
  print()
1061
1152
 
1062
1153
  # Show summary
@@ -1069,23 +1160,28 @@ def cmd_context_validate(check_version: bool = False, check_invariants: bool = F
1069
1160
 
1070
1161
  # Show suggestions if any
1071
1162
  if suggestions:
1072
- print("💡 Suggestions:")
1163
+ info("Suggestions:")
1073
1164
  for suggestion in suggestions[:5]: # Limit to 5
1074
1165
  print(f" • {suggestion}")
1075
1166
  print()
1076
1167
 
1077
1168
  return 0
1078
1169
  elif errors:
1079
- print(f"Validation failed with {len(errors)} error(s) and {len(warnings)} warning(s)")
1170
+ error(f"Validation failed with {len(errors)} error(s) and {len(warnings)} warning(s)")
1080
1171
  return 2
1081
1172
  else:
1082
- print(f"⚠️ Validation passed with {len(warnings)} warning(s)")
1173
+ warning(f"Validation passed with {len(warnings)} warning(s)")
1083
1174
  return 1
1084
1175
 
1176
+ except ContextFileNotFoundError as e:
1177
+ error(str(e))
1178
+ return 2
1179
+ except InvalidContextError as e:
1180
+ error(str(e))
1181
+ return 2
1085
1182
  except Exception as e:
1086
- print(f" Error validating context: {e}")
1087
- import traceback
1088
- traceback.print_exc()
1183
+ error(f"Failed to validate context: {e}")
1184
+ warning("Run with --debug for full stack trace")
1089
1185
  return 2
1090
1186
 
1091
1187
 
@@ -1103,20 +1199,16 @@ def cmd_context_generate(source_dir: Optional[str] = None, update_existing: bool
1103
1199
  from pathlib import Path
1104
1200
  import sys
1105
1201
 
1106
- # Determine source directory
1107
- if source_dir:
1108
- scan_path = Path(source_dir).resolve()
1109
- else:
1110
- scan_path = Path.cwd()
1111
-
1112
- if not scan_path.exists():
1113
- print(f"❌ Error: Directory not found: {scan_path}")
1114
- return 1
1115
-
1116
- print(f"🔍 Scanning for @voodocs annotations in: {scan_path}")
1117
- print()
1118
-
1119
1202
  try:
1203
+ # Determine source directory
1204
+ if source_dir:
1205
+ scan_path = validate_path(source_dir, must_exist=True, must_be_dir=True)
1206
+ else:
1207
+ scan_path = Path.cwd()
1208
+
1209
+ step(f"Scanning for @voodocs annotations in: {scan_path}")
1210
+ print()
1211
+
1120
1212
  # Import the annotation parser
1121
1213
  sys.path.insert(0, str(Path(__file__).parent.parent))
1122
1214
  from annotations.parser import AnnotationParser
@@ -1126,14 +1218,16 @@ def cmd_context_generate(source_dir: Optional[str] = None, update_existing: bool
1126
1218
  results = parser.parse_directory(scan_path)
1127
1219
 
1128
1220
  if not results:
1129
- print("⚠️ No @voodocs annotations found.")
1130
- print(" Add @voodocs annotations to your code first.")
1221
+ warning("No @voodocs annotations found.")
1222
+ info("Add @voodocs annotations to your code first.")
1131
1223
  return 1
1132
1224
 
1133
1225
  # Extract global invariants from all annotations
1134
1226
  global_invariants = set()
1135
1227
  assumptions = []
1136
- modules_info = {}
1228
+
1229
+ # Extract modules using helper function
1230
+ modules_info = extract_modules_from_results(results, scan_path)
1137
1231
 
1138
1232
  for parsed in results:
1139
1233
  # Get module annotation
@@ -1152,14 +1246,6 @@ def cmd_context_generate(source_dir: Optional[str] = None, update_existing: bool
1152
1246
  'source': Path(parsed.source_file).relative_to(scan_path).as_posix()
1153
1247
  })
1154
1248
 
1155
- # Store module info
1156
- if hasattr(module_ann, 'module_purpose') and module_ann.module_purpose:
1157
- rel_path = Path(parsed.source_file).relative_to(scan_path).as_posix()
1158
- modules_info[rel_path] = {
1159
- 'purpose': module_ann.module_purpose,
1160
- 'dependencies': getattr(module_ann, 'dependencies', [])
1161
- }
1162
-
1163
1249
  # Extract function-level invariants that might be global
1164
1250
  for func_ann in module_ann.functions:
1165
1251
  if hasattr(func_ann, 'invariants') and func_ann.invariants:
@@ -1246,28 +1332,37 @@ def cmd_context_generate(source_dir: Optional[str] = None, update_existing: bool
1246
1332
  context = parse_context_file(data)
1247
1333
  write_context_yaml(context, context_path)
1248
1334
 
1249
- print(f"✅ Context updated from code annotations")
1250
- print(f" Added {len(new_invs)} new invariants")
1251
- print(f" Added {len(assumptions) - len(existing_assumption_texts)} new assumptions")
1252
1335
  print()
1253
- print("💡 Tip: Run 'voodocs context view' to see the updated context")
1336
+ success("Context updated from code annotations")
1337
+ info(f"Added {len(new_invs)} new invariants")
1338
+ info(f"Added {len(assumptions) - len(existing_assumption_texts)} new assumptions")
1339
+ print()
1340
+ info("Tip: Run 'voodocs context view' to see the updated context")
1254
1341
 
1255
1342
  else:
1256
1343
  # Create new context (should not reach here, but handle gracefully)
1257
- print("❌ Error: Context generation requires an existing context file.")
1258
- print(" Run 'voodocs context init' first, then use 'voodocs context generate --from-code'")
1344
+ error("Context generation requires an existing context file.")
1345
+ info("Run 'voodocs context init' first, then use 'voodocs context generate --update'")
1259
1346
  return 1
1260
1347
 
1261
1348
  return 0
1262
1349
 
1350
+ except InvalidContextError as e:
1351
+ error(str(e))
1352
+ return 1
1353
+ except ContextFileNotFoundError as e:
1354
+ error(str(e))
1355
+ return 1
1356
+ except InvalidContextError as e:
1357
+ error(str(e))
1358
+ return 1
1263
1359
  except ImportError as e:
1264
- print(f"❌ Error: Failed to import annotation parser: {e}")
1265
- print(" Make sure VooDocs is properly installed.")
1360
+ error(f"Failed to import annotation parser: {e}")
1361
+ warning("Make sure VooDocs is properly installed")
1266
1362
  return 1
1267
1363
  except Exception as e:
1268
- print(f" Error generating context: {e}")
1269
- import traceback
1270
- traceback.print_exc()
1364
+ error(f"Failed to generate context: {e}")
1365
+ warning("Run with --debug for full stack trace")
1271
1366
  return 1
1272
1367
 
1273
1368
 
@@ -1283,29 +1378,26 @@ def cmd_context_query(query: str, section: Optional[str] = None, format: str = '
1283
1378
  Returns:
1284
1379
  Exit code (0 for success, 1 for error)
1285
1380
  """
1286
- if not context_file_exists():
1287
- print("❌ Error: No .voodocs.context file found in this directory.")
1288
- print(" Run 'voodocs context init' to create one.")
1289
- return 1
1290
-
1291
1381
  try:
1382
+ # Check if context file exists
1383
+ context_path = get_context_file_path()
1384
+ if not context_file_exists():
1385
+ raise ContextFileNotFoundError(str(context_path))
1386
+
1292
1387
  import re
1293
1388
  import json
1294
1389
 
1295
1390
  # Read context
1296
- context_path = get_context_file_path()
1297
1391
  data = read_context_yaml(context_path)
1298
1392
 
1299
1393
  if not data:
1300
- print("❌ Error: Failed to read .voodocs.context file.")
1301
- return 1
1394
+ raise InvalidContextError(str(context_path), "Failed to read YAML data")
1302
1395
 
1303
1396
  # Compile regex pattern (case-insensitive)
1304
1397
  try:
1305
1398
  pattern = re.compile(query, re.IGNORECASE)
1306
1399
  except re.error as e:
1307
- print(f"❌ Error: Invalid regex pattern: {e}")
1308
- return 1
1400
+ raise ValueError(f"Invalid regex pattern: {e}")
1309
1401
 
1310
1402
  # Define searchable sections
1311
1403
  searchable_sections = {
@@ -1327,8 +1419,8 @@ def cmd_context_query(query: str, section: Optional[str] = None, format: str = '
1327
1419
  # Filter by section if specified
1328
1420
  if section:
1329
1421
  if section not in searchable_sections:
1330
- print(f"❌ Error: Unknown section '{section}'")
1331
- print(f" Available sections: {', '.join(searchable_sections.keys())}")
1422
+ error(f"Unknown section '{section}'")
1423
+ info(f"Available sections: {', '.join(searchable_sections.keys())}")
1332
1424
  return 1
1333
1425
  searchable_sections = {section: searchable_sections[section]}
1334
1426
 
@@ -1358,9 +1450,9 @@ def cmd_context_query(query: str, section: Optional[str] = None, format: str = '
1358
1450
 
1359
1451
  # Display results
1360
1452
  if not all_results:
1361
- print(f"🔍 No results found for: {query}")
1453
+ info(f"No results found for: {query}")
1362
1454
  if section:
1363
- print(f" (searched in: {section})")
1455
+ info(f"(searched in: {section})")
1364
1456
  return 0
1365
1457
 
1366
1458
  if format == 'json':
@@ -1401,10 +1493,18 @@ def cmd_context_query(query: str, section: Optional[str] = None, format: str = '
1401
1493
 
1402
1494
  return 0
1403
1495
 
1496
+ except ContextFileNotFoundError as e:
1497
+ error(str(e))
1498
+ return 1
1499
+ except InvalidContextError as e:
1500
+ error(str(e))
1501
+ return 1
1502
+ except ValueError as e:
1503
+ error(str(e))
1504
+ return 1
1404
1505
  except Exception as e:
1405
- print(f" Error querying context: {e}")
1406
- import traceback
1407
- traceback.print_exc()
1506
+ error(f"Failed to query context: {e}")
1507
+ warning("Run with --debug for full stack trace")
1408
1508
  return 1
1409
1509
 
1410
1510
 
@@ -1499,30 +1599,28 @@ def cmd_context_check(
1499
1599
  Returns:
1500
1600
  Exit code (0 for all passed, 1 for violations found, 2 for error)
1501
1601
  """
1502
- if not context_file_exists():
1503
- print("❌ Error: No .voodocs.context file found in this directory.")
1504
- print(" Run 'voodocs context init' to create one.")
1505
- return 2
1506
-
1507
1602
  try:
1603
+ # Check if context file exists
1604
+ context_path = get_context_file_path()
1605
+ if not context_file_exists():
1606
+ raise ContextFileNotFoundError(str(context_path))
1607
+
1508
1608
  from .checker import InvariantChecker, ViolationSeverity
1509
1609
  import json
1510
1610
 
1511
1611
  # Read context
1512
- context_path = get_context_file_path()
1513
1612
  data = read_context_yaml(context_path)
1514
1613
 
1515
1614
  if not data:
1516
- print("❌ Error: Failed to read .voodocs.context file.")
1517
- return 2
1615
+ raise InvalidContextError(str(context_path), "Failed to read YAML data")
1518
1616
 
1519
1617
  # Get invariants
1520
1618
  invariants_data = data.get('invariants', {})
1521
1619
  global_invariants = invariants_data.get('global', [])
1522
1620
 
1523
1621
  if not global_invariants:
1524
- print("⚠️ No invariants found in context file.")
1525
- print(" Add invariants to the 'invariants.global' section.")
1622
+ warning("No invariants found in context file.")
1623
+ info("Add invariants to the 'invariants.global' section.")
1526
1624
  return 0
1527
1625
 
1528
1626
  # Filter invariants if specified
@@ -1637,13 +1735,19 @@ def cmd_context_check(
1637
1735
  return 1
1638
1736
  return 0
1639
1737
 
1738
+ except ContextFileNotFoundError as e:
1739
+ error(str(e))
1740
+ return 2
1741
+ except InvalidContextError as e:
1742
+ error(str(e))
1743
+ return 2
1640
1744
  except ImportError as e:
1641
- print(f"❌ Error: Failed to import checker: {e}")
1745
+ error(f"Failed to import checker: {e}")
1746
+ warning("Make sure all dependencies are installed")
1642
1747
  return 2
1643
1748
  except Exception as e:
1644
- print(f" Error checking invariants: {e}")
1645
- import traceback
1646
- traceback.print_exc()
1749
+ error(f"Failed to check invariants: {e}")
1750
+ warning("Run with --debug for full stack trace")
1647
1751
  return 2
1648
1752
 
1649
1753
 
@@ -1666,21 +1770,19 @@ def cmd_context_diagram(
1666
1770
  Returns:
1667
1771
  Exit code (0 for success, 1 for error)
1668
1772
  """
1669
- if not context_file_exists():
1670
- print("❌ Error: No .voodocs.context file found in this directory.")
1671
- print(" Run 'voodocs context init' to create one.")
1672
- return 1
1673
-
1674
1773
  try:
1774
+ # Check if context file exists
1775
+ context_path = get_context_file_path()
1776
+ if not context_file_exists():
1777
+ raise ContextFileNotFoundError(str(context_path))
1778
+
1675
1779
  from .diagram import DiagramGenerator
1676
1780
 
1677
1781
  # Read context
1678
- context_path = get_context_file_path()
1679
1782
  data = read_context_yaml(context_path)
1680
1783
 
1681
1784
  if not data:
1682
- print("❌ Error: Failed to read .voodocs.context file.")
1683
- return 1
1785
+ raise InvalidContextError(str(context_path), "Failed to read YAML data")
1684
1786
 
1685
1787
  # Initialize generator
1686
1788
  generator = DiagramGenerator()
@@ -1689,20 +1791,20 @@ def cmd_context_diagram(
1689
1791
  diagrams = {}
1690
1792
 
1691
1793
  if diagram_type in ['modules', 'all']:
1692
- print(f"📊 Generating module diagram...")
1794
+ step("Generating module diagram...")
1693
1795
  diagrams['modules'] = generator.generate_module_diagram(data, output_format)
1694
1796
 
1695
1797
  if diagram_type in ['dependencies', 'all']:
1696
- print(f"📊 Generating dependency diagram...")
1798
+ step("Generating dependency diagram...")
1697
1799
  diagrams['dependencies'] = generator.generate_dependency_diagram(data, output_format)
1698
1800
 
1699
1801
  if diagram_type in ['flow', 'all']:
1700
- print(f"📊 Generating flow diagram...")
1802
+ step("Generating flow diagram...")
1701
1803
  diagrams['flow'] = generator.generate_flow_diagram(data, output_format)
1702
1804
 
1703
1805
  if not diagrams:
1704
- print(f"❌ Error: Unknown diagram type: {diagram_type}")
1705
- print(" Valid types: modules, dependencies, flow, all")
1806
+ error(f"Unknown diagram type: {diagram_type}")
1807
+ info("Valid types: modules, dependencies, flow, all")
1706
1808
  return 1
1707
1809
 
1708
1810
  # Output diagrams
@@ -1747,7 +1849,7 @@ def cmd_context_diagram(
1747
1849
  else:
1748
1850
  # Save source only
1749
1851
  output_path.write_text(diagrams[diagram_type])
1750
- print(f"Diagram saved to: {output_path}")
1852
+ success(f"Diagram saved to: {output_path}")
1751
1853
  else:
1752
1854
  # Print to console
1753
1855
  for dtype, diagram_source in diagrams.items():
@@ -1760,11 +1862,17 @@ def cmd_context_diagram(
1760
1862
 
1761
1863
  return 0
1762
1864
 
1865
+ except ContextFileNotFoundError as e:
1866
+ error(str(e))
1867
+ return 1
1868
+ except InvalidContextError as e:
1869
+ error(str(e))
1870
+ return 1
1763
1871
  except ImportError as e:
1764
- print(f"❌ Error: Failed to import diagram generator: {e}")
1872
+ error(f"Failed to import diagram generator: {e}")
1873
+ warning("Make sure all dependencies are installed")
1765
1874
  return 1
1766
1875
  except Exception as e:
1767
- print(f" Error generating diagram: {e}")
1768
- import traceback
1769
- traceback.print_exc()
1876
+ error(f"Failed to generate diagram: {e}")
1877
+ warning("Run with --debug for full stack trace")
1770
1878
  return 1