@voodocs/cli 0.3.2 → 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,27 +1,10 @@
1
- """@voodocs
2
- module_purpose: "Core context system commands (init, generate, check, diagram, validate, etc.)"
3
- dependencies: [
4
- "yaml_utils: YAML parsing and serialization",
5
- "models: Data structures for context",
6
- "ai_integrations: AI assistant integration",
7
- "checker: Invariant checking system",
8
- "diagram: Architecture diagram generation"
9
- ]
10
- assumptions: [
11
- "Context file (.voodocs.context) is in project root",
12
- "Git is available for commit/author detection",
13
- "Python 3.11+ is installed",
14
- "Project root is current working directory"
15
- ]
16
- invariants: [
17
- "Context file must be valid YAML",
18
- "Version numbers must follow semver (major.minor)",
19
- "All commands must return 0 (success) or 1 (error) exit code",
20
- "Context updates must increment version number",
21
- "Generated files must be UTF-8 encoded"
22
- ]
23
- security_model: "Read context from disk, write only with user confirmation for init"
24
- performance_model: "O(1) for most commands, O(n) for generate where n=number of source files"
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}
25
8
 
26
9
  Context System Commands
27
10
 
@@ -39,6 +22,26 @@ from .yaml_utils import (
39
22
  add_to_gitignore,
40
23
  format_context_as_markdown
41
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
42
45
 
43
46
 
44
47
  def get_project_root() -> Path:
@@ -206,63 +209,76 @@ def cmd_context_init(force: bool = False) -> int:
206
209
  Returns:
207
210
  Exit code (0 for success, 1 for error)
208
211
  """
209
- print("🎯 VooDocs Context Initialization")
210
- print()
211
-
212
- # Check if context file already exists
213
- if context_file_exists() and not force:
214
- print("❌ Error: .voodocs.context already exists in this directory.")
215
- print(" Use --force to overwrite the existing file.")
216
- return 1
217
-
218
- # Detect project information
219
- detected_version = detect_code_version()
220
- detected_repo = detect_repository()
221
-
222
- print("Let's set up your project context. This will create a .voodocs.context file.")
223
- print()
224
-
225
- # Prompt for project information
226
- project_name = prompt_user("Project name")
227
-
228
- project_purpose = prompt_user("Project purpose (one sentence)")
229
-
230
- if detected_version:
231
- code_version = prompt_user("Current code version", detected_version)
232
- else:
233
- code_version = prompt_user("Current code version", "1.0.0")
234
-
235
- # Validate version format
236
- if not code_version.count('.') >= 2:
237
- print(f"⚠️ Warning: '{code_version}' doesn't look like semantic versioning (MAJOR.MINOR.PATCH)")
238
- if not prompt_yes_no("Continue anyway?", default=False):
239
- print("Aborted.")
240
- return 1
241
-
242
- if detected_repo:
243
- repository = prompt_user("Repository URL (leave empty to skip)", detected_repo, required=False)
244
- else:
245
- repository = prompt_user("Repository URL (leave empty to skip)", "", required=False)
246
-
247
- license_name = prompt_user("License (leave empty to skip)", "", required=False)
248
-
249
- print()
250
- print("📝 Configuration Summary:")
251
- print(f" Project Name: {project_name}")
252
- print(f" Purpose: {project_purpose}")
253
- print(f" Code Version: {code_version}")
254
- if repository:
255
- print(f" Repository: {repository}")
256
- if license_name:
257
- print(f" License: {license_name}")
258
- print()
259
-
260
- if not prompt_yes_no("Create context file?", default=True):
261
- print("Aborted.")
262
- return 1
263
-
264
- # Create context file
265
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...")
266
282
  context = create_minimal_context(
267
283
  project_name=project_name,
268
284
  project_purpose=project_purpose,
@@ -271,20 +287,20 @@ def cmd_context_init(force: bool = False) -> int:
271
287
  license=license_name if license_name else None
272
288
  )
273
289
 
274
- context_path = get_context_file_path()
290
+ step(f"Writing context file to {context_path}...")
275
291
  write_context_yaml(context, context_path)
276
292
 
277
293
  print()
278
- print(f" Created .voodocs.context")
279
- print(f" Context version: {context.versioning.context_version}")
294
+ success("Context file created successfully!")
295
+ info(f"Context version: {context.versioning.context_version}")
280
296
  print()
281
297
 
282
298
  # Ask about .gitignore
283
- 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):
284
300
  if add_to_gitignore(get_project_root()):
285
- print("Added to .gitignore")
301
+ success("Added to .gitignore")
286
302
  else:
287
- print("ℹ️ Already in .gitignore")
303
+ info("Already in .gitignore")
288
304
 
289
305
  print()
290
306
 
@@ -368,9 +384,9 @@ def cmd_context_init(force: bool = False) -> int:
368
384
  print("ℹ️ Skipped AI integration")
369
385
 
370
386
  print()
371
- print("🎉 Context initialized successfully!")
387
+ success("🎉 Context initialized successfully!")
372
388
  print()
373
- print("Next steps:")
389
+ info("Next steps:")
374
390
  print(" 1. Review the context file: voodocs context view")
375
391
  print(" 2. Auto-generate from code: voodocs context generate --update")
376
392
  print(" 3. Check invariants: voodocs context check")
@@ -380,8 +396,18 @@ def cmd_context_init(force: bool = False) -> int:
380
396
 
381
397
  return 0
382
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
383
407
  except Exception as e:
384
- 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")
385
411
  return 1
386
412
 
387
413
 
@@ -395,19 +421,17 @@ def cmd_context_view(output_file: Optional[str] = None) -> int:
395
421
  Returns:
396
422
  Exit code (0 for success, 1 for error)
397
423
  """
398
- if not context_file_exists():
399
- print("❌ Error: No .voodocs.context file found in this directory.")
400
- print(" Run 'voodocs context init' to create one.")
401
- return 1
402
-
403
424
  try:
404
- # Read context file
425
+ # Check if context file exists
405
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
406
431
  data = read_context_yaml(context_path)
407
432
 
408
433
  if not data:
409
- print("❌ Error: Failed to read .voodocs.context file.")
410
- return 1
434
+ raise InvalidContextError(str(context_path), "Failed to read YAML data")
411
435
 
412
436
  # Parse and format as Markdown
413
437
  from .yaml_utils import parse_context_file
@@ -417,17 +441,25 @@ def cmd_context_view(output_file: Optional[str] = None) -> int:
417
441
  if output_file:
418
442
  # Write to file
419
443
  output_path = Path(output_file)
444
+ output_path.parent.mkdir(parents=True, exist_ok=True)
420
445
  with open(output_path, 'w', encoding='utf-8') as f:
421
446
  f.write(markdown)
422
- print(f"Context exported to {output_file}")
447
+ success(f"Context exported to {output_file}")
423
448
  return 0
424
449
  else:
425
450
  # Print to console
426
451
  print(markdown)
427
452
  return 0
428
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
429
460
  except Exception as e:
430
- print(f" Error viewing context: {e}")
461
+ error(f"Failed to view context: {e}")
462
+ warning("Run with --debug for full stack trace")
431
463
  return 1
432
464
 
433
465
 
@@ -438,19 +470,17 @@ def cmd_context_status() -> int:
438
470
  Returns:
439
471
  Exit code (0 for success, 1 for error)
440
472
  """
441
- if not context_file_exists():
442
- print("❌ No context file found")
443
- print(" Run 'voodocs context init' to create one.")
444
- return 1
445
-
446
473
  try:
447
- # Read context file
474
+ # Check if context file exists
448
475
  context_path = get_context_file_path()
476
+ if not context_file_exists():
477
+ raise ContextFileNotFoundError(str(context_path))
478
+
479
+ # Read context file
449
480
  data = read_context_yaml(context_path)
450
481
 
451
482
  if not data:
452
- print("❌ Error: Failed to read .voodocs.context file.")
453
- return 1
483
+ raise InvalidContextError(str(context_path), "Failed to read YAML data")
454
484
 
455
485
  # Extract key information
456
486
  versioning = data.get('versioning', {})
@@ -479,12 +509,19 @@ def cmd_context_status() -> int:
479
509
  print(f"Known Issues: {len(known_issues)}")
480
510
 
481
511
  print()
482
- print("Use 'voodocs context view' to see the full context.")
512
+ info("Use 'voodocs context view' to see the full context.")
483
513
 
484
514
  return 0
485
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
486
522
  except Exception as e:
487
- print(f" Error reading context: {e}")
523
+ error(f"Failed to read context status: {e}")
524
+ warning("Run with --debug for full stack trace")
488
525
  return 1
489
526
 
490
527
 
@@ -498,22 +535,20 @@ def cmd_context_update(description: Optional[str] = None) -> int:
498
535
  Returns:
499
536
  Exit code (0 for success, 1 for error)
500
537
  """
501
- if not context_file_exists():
502
- print("❌ Error: No .voodocs.context file found in this directory.")
503
- print(" Run 'voodocs context init' to create one.")
504
- return 1
505
-
506
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
+
507
544
  from datetime import date
508
545
  from .models import Change
509
546
 
510
547
  # Read current context
511
- context_path = get_context_file_path()
512
548
  data = read_context_yaml(context_path)
513
549
 
514
550
  if not data:
515
- print("❌ Error: Failed to read .voodocs.context file.")
516
- return 1
551
+ raise InvalidContextError(str(context_path), "Failed to read YAML data")
517
552
 
518
553
  # Get current versions
519
554
  versioning = data.get('versioning', {})
@@ -523,14 +558,13 @@ def cmd_context_update(description: Optional[str] = None) -> int:
523
558
  # Parse context version
524
559
  parts = current_context_version.split('.')
525
560
  if len(parts) != 2:
526
- print(f"❌ Error: Invalid context version format: {current_context_version}")
527
- return 1
561
+ raise ValueError(f"Invalid context version format: {current_context_version}. Expected MAJOR.MINOR format.")
528
562
 
529
563
  major, minor = parts
530
564
  new_minor = int(minor) + 1
531
565
  new_context_version = f"{major}.{new_minor}"
532
566
 
533
- print(f"📝 Updating Context")
567
+ step("Updating context...")
534
568
  print()
535
569
  print(f"Current version: {current_context_version}")
536
570
  print(f"New version: {new_context_version}")
@@ -538,9 +572,10 @@ def cmd_context_update(description: Optional[str] = None) -> int:
538
572
 
539
573
  # Prompt for description if not provided
540
574
  if not description:
541
- description = prompt_user("Change description", "", required=False)
542
- if not description:
543
- description = "Context updated"
575
+ try:
576
+ description = prompt("Change description", default="Context updated", required=False)
577
+ except KeyboardInterrupt:
578
+ raise UserInterruptError("Context update")
544
579
 
545
580
  # Update versioning
546
581
  today = date.today().isoformat()
@@ -571,20 +606,34 @@ def cmd_context_update(description: Optional[str] = None) -> int:
571
606
  data['changes'].append(change_entry)
572
607
 
573
608
  # Write updated context
609
+ step("Writing updated context...")
574
610
  from .yaml_utils import parse_context_file
575
611
  context = parse_context_file(data)
576
612
  write_context_yaml(context, context_path)
577
613
 
578
- print(f"✅ Context updated to version {new_context_version}")
579
- print(f" Description: {description}")
614
+ print()
615
+ success(f"Context updated to version {new_context_version}")
616
+ info(f"Description: {description}")
580
617
  print()
581
618
 
582
619
  return 0
583
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
584
634
  except Exception as e:
585
- print(f" Error updating context: {e}")
586
- import traceback
587
- traceback.print_exc()
635
+ error(f"Failed to update context: {e}")
636
+ warning("Run with --debug for full stack trace")
588
637
  return 1
589
638
 
590
639
 
@@ -599,21 +648,19 @@ def cmd_context_sync(code_version: Optional[str] = None, force: bool = False) ->
599
648
  Returns:
600
649
  Exit code (0 for success, 1 for error)
601
650
  """
602
- if not context_file_exists():
603
- print("❌ Error: No .voodocs.context file found in this directory.")
604
- print(" Run 'voodocs context init' to create one.")
605
- return 1
606
-
607
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
+
608
657
  from datetime import date
609
658
 
610
659
  # Read current context
611
- context_path = get_context_file_path()
612
660
  data = read_context_yaml(context_path)
613
661
 
614
662
  if not data:
615
- print("❌ Error: Failed to read .voodocs.context file.")
616
- return 1
663
+ raise InvalidContextError(str(context_path), "Failed to read YAML data")
617
664
 
618
665
  # Get current versions
619
666
  versioning = data.get('versioning', {})
@@ -623,30 +670,33 @@ def cmd_context_sync(code_version: Optional[str] = None, force: bool = False) ->
623
670
  # If no code version provided, try to detect it
624
671
  if not code_version:
625
672
  detected_version = detect_code_version()
626
- if detected_version:
627
- code_version = prompt_user(
628
- "New code version",
629
- detected_version,
630
- required=True
631
- )
632
- else:
633
- code_version = prompt_user(
634
- "New code version",
635
- required=True
636
- )
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")
637
687
 
638
688
  # Validate version format
639
- if code_version.count('.') < 2:
640
- print(f"❌ Error: Invalid version format: {code_version}")
641
- print(" Expected: MAJOR.MINOR.PATCH (e.g., 2.0.0)")
642
- return 1
689
+ try:
690
+ code_version = validate_version(code_version, allow_empty=False)
691
+ except InvalidVersionError as e:
692
+ raise InvalidVersionError(code_version)
643
693
 
644
694
  # Extract major versions
645
695
  current_major = current_code_version.split('.')[0]
646
696
  new_major = code_version.split('.')[0]
647
697
  context_major = current_context_version.split('.')[0]
648
698
 
649
- print(f"🔄 Syncing Context with Code Version")
699
+ step("Syncing context with code version...")
650
700
  print()
651
701
  print(f"Current code version: {current_code_version}")
652
702
  print(f"New code version: {code_version}")
@@ -663,9 +713,8 @@ def cmd_context_sync(code_version: Optional[str] = None, force: bool = False) ->
663
713
  print()
664
714
 
665
715
  if not force:
666
- if not prompt_yes_no("Continue with reset?", default=True):
667
- print("Aborted.")
668
- return 1
716
+ if not confirm("Continue with reset?", default=True):
717
+ raise UserInterruptError("Context sync")
669
718
 
670
719
  # Update versions
671
720
  today = date.today().isoformat()
@@ -701,8 +750,8 @@ def cmd_context_sync(code_version: Optional[str] = None, force: bool = False) ->
701
750
  context = parse_context_file(data)
702
751
  write_context_yaml(context, context_path)
703
752
 
704
- print(f"Context version reset to {new_context_version}")
705
- 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}")
706
755
  print()
707
756
 
708
757
  elif context_major != new_major:
@@ -746,8 +795,8 @@ def cmd_context_sync(code_version: Optional[str] = None, force: bool = False) ->
746
795
  context = parse_context_file(data)
747
796
  write_context_yaml(context, context_path)
748
797
 
749
- print(f"Context synced to {new_context_version}")
750
- 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}")
751
800
  print()
752
801
 
753
802
  else:
@@ -760,16 +809,28 @@ def cmd_context_sync(code_version: Optional[str] = None, force: bool = False) ->
760
809
  context = parse_context_file(data)
761
810
  write_context_yaml(context, context_path)
762
811
 
763
- print(f"Code version updated to {code_version}")
764
- 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}")
765
814
  print()
766
815
 
767
816
  return 0
768
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
769
831
  except Exception as e:
770
- print(f" Error syncing context: {e}")
771
- import traceback
772
- traceback.print_exc()
832
+ error(f"Failed to sync context: {e}")
833
+ warning("Run with --debug for full stack trace")
773
834
  return 1
774
835
 
775
836
 
@@ -780,26 +841,24 @@ def cmd_context_history() -> int:
780
841
  Returns:
781
842
  Exit code (0 for success, 1 for error)
782
843
  """
783
- if not context_file_exists():
784
- print("❌ Error: No .voodocs.context file found in this directory.")
785
- print(" Run 'voodocs context init' to create one.")
786
- return 1
787
-
788
844
  try:
789
- # Read current context
845
+ # Check if context file exists
790
846
  context_path = get_context_file_path()
847
+ if not context_file_exists():
848
+ raise ContextFileNotFoundError(str(context_path))
849
+
850
+ # Read current context
791
851
  data = read_context_yaml(context_path)
792
852
 
793
853
  if not data:
794
- print("❌ Error: Failed to read .voodocs.context file.")
795
- return 1
854
+ raise InvalidContextError(str(context_path), "Failed to read YAML data")
796
855
 
797
856
  # Get changes
798
857
  changes = data.get('changes', [])
799
858
 
800
859
  if not changes:
801
- print("📜 No change history found.")
802
- 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'.")
803
862
  return 0
804
863
 
805
864
  print("📜 Context Version History")
@@ -830,8 +889,15 @@ def cmd_context_history() -> int:
830
889
 
831
890
  return 0
832
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
833
898
  except Exception as e:
834
- print(f" Error reading history: {e}")
899
+ error(f"Failed to read history: {e}")
900
+ warning("Run with --debug for full stack trace")
835
901
  return 1
836
902
 
837
903
 
@@ -846,19 +912,17 @@ def cmd_context_diff(version1: Optional[str] = None, version2: Optional[str] = N
846
912
  Returns:
847
913
  Exit code (0 for success, 1 for error)
848
914
  """
849
- if not context_file_exists():
850
- print("❌ Error: No .voodocs.context file found in this directory.")
851
- print(" Run 'voodocs context init' to create one.")
852
- return 1
853
-
854
915
  try:
855
- # Read current context
916
+ # Check if context file exists
856
917
  context_path = get_context_file_path()
918
+ if not context_file_exists():
919
+ raise ContextFileNotFoundError(str(context_path))
920
+
921
+ # Read current context
857
922
  data = read_context_yaml(context_path)
858
923
 
859
924
  if not data:
860
- print("❌ Error: Failed to read .voodocs.context file.")
861
- return 1
925
+ raise InvalidContextError(str(context_path), "Failed to read YAML data")
862
926
 
863
927
  # Get current version
864
928
  versioning = data.get('versioning', {})
@@ -872,8 +936,8 @@ def cmd_context_diff(version1: Optional[str] = None, version2: Optional[str] = N
872
936
  if not version1:
873
937
  changes = data.get('changes', [])
874
938
  if len(changes) < 2:
875
- print("📊 Not enough version history to compare.")
876
- 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.")
877
941
  return 0
878
942
 
879
943
  # Get the two most recent versions
@@ -905,7 +969,7 @@ def cmd_context_diff(version1: Optional[str] = None, version2: Optional[str] = N
905
969
  continue
906
970
 
907
971
  if not relevant_changes:
908
- print(f"No changes found between v{version1} and v{version2}")
972
+ info(f"No changes found between v{version1} and v{version2}")
909
973
  return 0
910
974
 
911
975
  print(f"Changes ({len(relevant_changes)}):")
@@ -924,14 +988,19 @@ def cmd_context_diff(version1: Optional[str] = None, version2: Optional[str] = N
924
988
  print(f" Code: v{code_version}")
925
989
  print()
926
990
 
927
- print(f"💡 Tip: Use 'voodocs context history' to see the full timeline.")
991
+ info("Tip: Use 'voodocs context history' to see the full timeline.")
928
992
 
929
993
  return 0
930
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
931
1001
  except Exception as e:
932
- print(f" Error comparing versions: {e}")
933
- import traceback
934
- traceback.print_exc()
1002
+ error(f"Failed to compare versions: {e}")
1003
+ warning("Run with --debug for full stack trace")
935
1004
  return 1
936
1005
 
937
1006
 
@@ -946,21 +1015,19 @@ def cmd_context_validate(check_version: bool = False, check_invariants: bool = F
946
1015
  Returns:
947
1016
  Exit code (0 for success, 1 for warnings, 2 for errors)
948
1017
  """
949
- if not context_file_exists():
950
- print("❌ Error: No .voodocs.context file found in this directory.")
951
- print(" Run 'voodocs context init' to create one.")
952
- return 2
953
-
954
1018
  try:
955
- # Read current context
1019
+ # Check if context file exists
956
1020
  context_path = get_context_file_path()
1021
+ if not context_file_exists():
1022
+ raise ContextFileNotFoundError(str(context_path))
1023
+
1024
+ # Read current context
957
1025
  data = read_context_yaml(context_path)
958
1026
 
959
1027
  if not data:
960
- print("❌ Error: Failed to read .voodocs.context file.")
961
- return 2
1028
+ raise InvalidContextError(str(context_path), "Failed to read YAML data")
962
1029
 
963
- print("🔍 Validating Context File")
1030
+ step("Validating context file...")
964
1031
  print()
965
1032
 
966
1033
  errors = []
@@ -1080,7 +1147,7 @@ def cmd_context_validate(check_version: bool = False, check_invariants: bool = F
1080
1147
  break
1081
1148
 
1082
1149
  if not errors and not warnings:
1083
- print("Context file is valid!")
1150
+ success("Context file is valid!")
1084
1151
  print()
1085
1152
 
1086
1153
  # Show summary
@@ -1093,23 +1160,28 @@ def cmd_context_validate(check_version: bool = False, check_invariants: bool = F
1093
1160
 
1094
1161
  # Show suggestions if any
1095
1162
  if suggestions:
1096
- print("💡 Suggestions:")
1163
+ info("Suggestions:")
1097
1164
  for suggestion in suggestions[:5]: # Limit to 5
1098
1165
  print(f" • {suggestion}")
1099
1166
  print()
1100
1167
 
1101
1168
  return 0
1102
1169
  elif errors:
1103
- 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)")
1104
1171
  return 2
1105
1172
  else:
1106
- print(f"⚠️ Validation passed with {len(warnings)} warning(s)")
1173
+ warning(f"Validation passed with {len(warnings)} warning(s)")
1107
1174
  return 1
1108
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
1109
1182
  except Exception as e:
1110
- print(f" Error validating context: {e}")
1111
- import traceback
1112
- traceback.print_exc()
1183
+ error(f"Failed to validate context: {e}")
1184
+ warning("Run with --debug for full stack trace")
1113
1185
  return 2
1114
1186
 
1115
1187
 
@@ -1127,20 +1199,16 @@ def cmd_context_generate(source_dir: Optional[str] = None, update_existing: bool
1127
1199
  from pathlib import Path
1128
1200
  import sys
1129
1201
 
1130
- # Determine source directory
1131
- if source_dir:
1132
- scan_path = Path(source_dir).resolve()
1133
- else:
1134
- scan_path = Path.cwd()
1135
-
1136
- if not scan_path.exists():
1137
- print(f"❌ Error: Directory not found: {scan_path}")
1138
- return 1
1139
-
1140
- print(f"🔍 Scanning for @voodocs annotations in: {scan_path}")
1141
- print()
1142
-
1143
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
+
1144
1212
  # Import the annotation parser
1145
1213
  sys.path.insert(0, str(Path(__file__).parent.parent))
1146
1214
  from annotations.parser import AnnotationParser
@@ -1150,14 +1218,16 @@ def cmd_context_generate(source_dir: Optional[str] = None, update_existing: bool
1150
1218
  results = parser.parse_directory(scan_path)
1151
1219
 
1152
1220
  if not results:
1153
- print("⚠️ No @voodocs annotations found.")
1154
- print(" Add @voodocs annotations to your code first.")
1221
+ warning("No @voodocs annotations found.")
1222
+ info("Add @voodocs annotations to your code first.")
1155
1223
  return 1
1156
1224
 
1157
1225
  # Extract global invariants from all annotations
1158
1226
  global_invariants = set()
1159
1227
  assumptions = []
1160
- modules_info = {}
1228
+
1229
+ # Extract modules using helper function
1230
+ modules_info = extract_modules_from_results(results, scan_path)
1161
1231
 
1162
1232
  for parsed in results:
1163
1233
  # Get module annotation
@@ -1176,14 +1246,6 @@ def cmd_context_generate(source_dir: Optional[str] = None, update_existing: bool
1176
1246
  'source': Path(parsed.source_file).relative_to(scan_path).as_posix()
1177
1247
  })
1178
1248
 
1179
- # Store module info
1180
- if hasattr(module_ann, 'module_purpose') and module_ann.module_purpose:
1181
- rel_path = Path(parsed.source_file).relative_to(scan_path).as_posix()
1182
- modules_info[rel_path] = {
1183
- 'purpose': module_ann.module_purpose,
1184
- 'dependencies': getattr(module_ann, 'dependencies', [])
1185
- }
1186
-
1187
1249
  # Extract function-level invariants that might be global
1188
1250
  for func_ann in module_ann.functions:
1189
1251
  if hasattr(func_ann, 'invariants') and func_ann.invariants:
@@ -1270,28 +1332,37 @@ def cmd_context_generate(source_dir: Optional[str] = None, update_existing: bool
1270
1332
  context = parse_context_file(data)
1271
1333
  write_context_yaml(context, context_path)
1272
1334
 
1273
- print(f"✅ Context updated from code annotations")
1274
- print(f" Added {len(new_invs)} new invariants")
1275
- print(f" Added {len(assumptions) - len(existing_assumption_texts)} new assumptions")
1276
1335
  print()
1277
- 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")
1278
1341
 
1279
1342
  else:
1280
1343
  # Create new context (should not reach here, but handle gracefully)
1281
- print("❌ Error: Context generation requires an existing context file.")
1282
- 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'")
1283
1346
  return 1
1284
1347
 
1285
1348
  return 0
1286
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
1287
1359
  except ImportError as e:
1288
- print(f"❌ Error: Failed to import annotation parser: {e}")
1289
- print(" Make sure VooDocs is properly installed.")
1360
+ error(f"Failed to import annotation parser: {e}")
1361
+ warning("Make sure VooDocs is properly installed")
1290
1362
  return 1
1291
1363
  except Exception as e:
1292
- print(f" Error generating context: {e}")
1293
- import traceback
1294
- traceback.print_exc()
1364
+ error(f"Failed to generate context: {e}")
1365
+ warning("Run with --debug for full stack trace")
1295
1366
  return 1
1296
1367
 
1297
1368
 
@@ -1307,29 +1378,26 @@ def cmd_context_query(query: str, section: Optional[str] = None, format: str = '
1307
1378
  Returns:
1308
1379
  Exit code (0 for success, 1 for error)
1309
1380
  """
1310
- if not context_file_exists():
1311
- print("❌ Error: No .voodocs.context file found in this directory.")
1312
- print(" Run 'voodocs context init' to create one.")
1313
- return 1
1314
-
1315
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
+
1316
1387
  import re
1317
1388
  import json
1318
1389
 
1319
1390
  # Read context
1320
- context_path = get_context_file_path()
1321
1391
  data = read_context_yaml(context_path)
1322
1392
 
1323
1393
  if not data:
1324
- print("❌ Error: Failed to read .voodocs.context file.")
1325
- return 1
1394
+ raise InvalidContextError(str(context_path), "Failed to read YAML data")
1326
1395
 
1327
1396
  # Compile regex pattern (case-insensitive)
1328
1397
  try:
1329
1398
  pattern = re.compile(query, re.IGNORECASE)
1330
1399
  except re.error as e:
1331
- print(f"❌ Error: Invalid regex pattern: {e}")
1332
- return 1
1400
+ raise ValueError(f"Invalid regex pattern: {e}")
1333
1401
 
1334
1402
  # Define searchable sections
1335
1403
  searchable_sections = {
@@ -1351,8 +1419,8 @@ def cmd_context_query(query: str, section: Optional[str] = None, format: str = '
1351
1419
  # Filter by section if specified
1352
1420
  if section:
1353
1421
  if section not in searchable_sections:
1354
- print(f"❌ Error: Unknown section '{section}'")
1355
- print(f" Available sections: {', '.join(searchable_sections.keys())}")
1422
+ error(f"Unknown section '{section}'")
1423
+ info(f"Available sections: {', '.join(searchable_sections.keys())}")
1356
1424
  return 1
1357
1425
  searchable_sections = {section: searchable_sections[section]}
1358
1426
 
@@ -1382,9 +1450,9 @@ def cmd_context_query(query: str, section: Optional[str] = None, format: str = '
1382
1450
 
1383
1451
  # Display results
1384
1452
  if not all_results:
1385
- print(f"🔍 No results found for: {query}")
1453
+ info(f"No results found for: {query}")
1386
1454
  if section:
1387
- print(f" (searched in: {section})")
1455
+ info(f"(searched in: {section})")
1388
1456
  return 0
1389
1457
 
1390
1458
  if format == 'json':
@@ -1425,10 +1493,18 @@ def cmd_context_query(query: str, section: Optional[str] = None, format: str = '
1425
1493
 
1426
1494
  return 0
1427
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
1428
1505
  except Exception as e:
1429
- print(f" Error querying context: {e}")
1430
- import traceback
1431
- traceback.print_exc()
1506
+ error(f"Failed to query context: {e}")
1507
+ warning("Run with --debug for full stack trace")
1432
1508
  return 1
1433
1509
 
1434
1510
 
@@ -1523,30 +1599,28 @@ def cmd_context_check(
1523
1599
  Returns:
1524
1600
  Exit code (0 for all passed, 1 for violations found, 2 for error)
1525
1601
  """
1526
- if not context_file_exists():
1527
- print("❌ Error: No .voodocs.context file found in this directory.")
1528
- print(" Run 'voodocs context init' to create one.")
1529
- return 2
1530
-
1531
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
+
1532
1608
  from .checker import InvariantChecker, ViolationSeverity
1533
1609
  import json
1534
1610
 
1535
1611
  # Read context
1536
- context_path = get_context_file_path()
1537
1612
  data = read_context_yaml(context_path)
1538
1613
 
1539
1614
  if not data:
1540
- print("❌ Error: Failed to read .voodocs.context file.")
1541
- return 2
1615
+ raise InvalidContextError(str(context_path), "Failed to read YAML data")
1542
1616
 
1543
1617
  # Get invariants
1544
1618
  invariants_data = data.get('invariants', {})
1545
1619
  global_invariants = invariants_data.get('global', [])
1546
1620
 
1547
1621
  if not global_invariants:
1548
- print("⚠️ No invariants found in context file.")
1549
- 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.")
1550
1624
  return 0
1551
1625
 
1552
1626
  # Filter invariants if specified
@@ -1661,13 +1735,19 @@ def cmd_context_check(
1661
1735
  return 1
1662
1736
  return 0
1663
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
1664
1744
  except ImportError as e:
1665
- print(f"❌ Error: Failed to import checker: {e}")
1745
+ error(f"Failed to import checker: {e}")
1746
+ warning("Make sure all dependencies are installed")
1666
1747
  return 2
1667
1748
  except Exception as e:
1668
- print(f" Error checking invariants: {e}")
1669
- import traceback
1670
- traceback.print_exc()
1749
+ error(f"Failed to check invariants: {e}")
1750
+ warning("Run with --debug for full stack trace")
1671
1751
  return 2
1672
1752
 
1673
1753
 
@@ -1690,21 +1770,19 @@ def cmd_context_diagram(
1690
1770
  Returns:
1691
1771
  Exit code (0 for success, 1 for error)
1692
1772
  """
1693
- if not context_file_exists():
1694
- print("❌ Error: No .voodocs.context file found in this directory.")
1695
- print(" Run 'voodocs context init' to create one.")
1696
- return 1
1697
-
1698
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
+
1699
1779
  from .diagram import DiagramGenerator
1700
1780
 
1701
1781
  # Read context
1702
- context_path = get_context_file_path()
1703
1782
  data = read_context_yaml(context_path)
1704
1783
 
1705
1784
  if not data:
1706
- print("❌ Error: Failed to read .voodocs.context file.")
1707
- return 1
1785
+ raise InvalidContextError(str(context_path), "Failed to read YAML data")
1708
1786
 
1709
1787
  # Initialize generator
1710
1788
  generator = DiagramGenerator()
@@ -1713,20 +1791,20 @@ def cmd_context_diagram(
1713
1791
  diagrams = {}
1714
1792
 
1715
1793
  if diagram_type in ['modules', 'all']:
1716
- print(f"📊 Generating module diagram...")
1794
+ step("Generating module diagram...")
1717
1795
  diagrams['modules'] = generator.generate_module_diagram(data, output_format)
1718
1796
 
1719
1797
  if diagram_type in ['dependencies', 'all']:
1720
- print(f"📊 Generating dependency diagram...")
1798
+ step("Generating dependency diagram...")
1721
1799
  diagrams['dependencies'] = generator.generate_dependency_diagram(data, output_format)
1722
1800
 
1723
1801
  if diagram_type in ['flow', 'all']:
1724
- print(f"📊 Generating flow diagram...")
1802
+ step("Generating flow diagram...")
1725
1803
  diagrams['flow'] = generator.generate_flow_diagram(data, output_format)
1726
1804
 
1727
1805
  if not diagrams:
1728
- print(f"❌ Error: Unknown diagram type: {diagram_type}")
1729
- print(" Valid types: modules, dependencies, flow, all")
1806
+ error(f"Unknown diagram type: {diagram_type}")
1807
+ info("Valid types: modules, dependencies, flow, all")
1730
1808
  return 1
1731
1809
 
1732
1810
  # Output diagrams
@@ -1771,7 +1849,7 @@ def cmd_context_diagram(
1771
1849
  else:
1772
1850
  # Save source only
1773
1851
  output_path.write_text(diagrams[diagram_type])
1774
- print(f"Diagram saved to: {output_path}")
1852
+ success(f"Diagram saved to: {output_path}")
1775
1853
  else:
1776
1854
  # Print to console
1777
1855
  for dtype, diagram_source in diagrams.items():
@@ -1784,11 +1862,17 @@ def cmd_context_diagram(
1784
1862
 
1785
1863
  return 0
1786
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
1787
1871
  except ImportError as e:
1788
- 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")
1789
1874
  return 1
1790
1875
  except Exception as e:
1791
- print(f" Error generating diagram: {e}")
1792
- import traceback
1793
- traceback.print_exc()
1876
+ error(f"Failed to generate diagram: {e}")
1877
+ warning("Run with --debug for full stack trace")
1794
1878
  return 1