empathy-framework 5.0.3__py3-none-any.whl → 5.1.1__py3-none-any.whl

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 (59) hide show
  1. {empathy_framework-5.0.3.dist-info → empathy_framework-5.1.1.dist-info}/METADATA +259 -142
  2. {empathy_framework-5.0.3.dist-info → empathy_framework-5.1.1.dist-info}/RECORD +58 -28
  3. empathy_framework-5.1.1.dist-info/licenses/LICENSE +201 -0
  4. empathy_framework-5.1.1.dist-info/licenses/LICENSE_CHANGE_ANNOUNCEMENT.md +101 -0
  5. empathy_os/__init__.py +1 -1
  6. empathy_os/cli/commands/batch.py +5 -5
  7. empathy_os/cli/commands/routing.py +1 -1
  8. empathy_os/cli/commands/workflow.py +2 -1
  9. empathy_os/cli/parsers/cache 2.py +65 -0
  10. empathy_os/cli_minimal.py +3 -3
  11. empathy_os/cli_router 2.py +416 -0
  12. empathy_os/cli_router.py +12 -0
  13. empathy_os/dashboard/__init__.py +1 -2
  14. empathy_os/dashboard/app 2.py +512 -0
  15. empathy_os/dashboard/app.py +1 -1
  16. empathy_os/dashboard/simple_server 2.py +403 -0
  17. empathy_os/dashboard/standalone_server 2.py +536 -0
  18. empathy_os/memory/types 2.py +441 -0
  19. empathy_os/meta_workflows/intent_detector.py +71 -0
  20. empathy_os/models/__init__.py +19 -0
  21. empathy_os/models/adaptive_routing 2.py +437 -0
  22. empathy_os/models/auth_cli.py +444 -0
  23. empathy_os/models/auth_strategy.py +450 -0
  24. empathy_os/project_index/scanner_parallel 2.py +291 -0
  25. empathy_os/telemetry/agent_coordination 2.py +478 -0
  26. empathy_os/telemetry/agent_coordination.py +3 -3
  27. empathy_os/telemetry/agent_tracking 2.py +350 -0
  28. empathy_os/telemetry/agent_tracking.py +1 -2
  29. empathy_os/telemetry/approval_gates 2.py +563 -0
  30. empathy_os/telemetry/event_streaming 2.py +405 -0
  31. empathy_os/telemetry/event_streaming.py +3 -3
  32. empathy_os/telemetry/feedback_loop 2.py +557 -0
  33. empathy_os/telemetry/feedback_loop.py +1 -1
  34. empathy_os/vscode_bridge 2.py +173 -0
  35. empathy_os/workflows/__init__.py +8 -0
  36. empathy_os/workflows/autonomous_test_gen.py +569 -0
  37. empathy_os/workflows/bug_predict.py +45 -0
  38. empathy_os/workflows/code_review.py +92 -22
  39. empathy_os/workflows/document_gen.py +594 -62
  40. empathy_os/workflows/llm_base.py +363 -0
  41. empathy_os/workflows/perf_audit.py +69 -0
  42. empathy_os/workflows/progressive/README 2.md +454 -0
  43. empathy_os/workflows/progressive/__init__ 2.py +92 -0
  44. empathy_os/workflows/progressive/cli 2.py +242 -0
  45. empathy_os/workflows/progressive/core 2.py +488 -0
  46. empathy_os/workflows/progressive/orchestrator 2.py +701 -0
  47. empathy_os/workflows/progressive/reports 2.py +528 -0
  48. empathy_os/workflows/progressive/telemetry 2.py +280 -0
  49. empathy_os/workflows/progressive/test_gen 2.py +514 -0
  50. empathy_os/workflows/progressive/workflow 2.py +628 -0
  51. empathy_os/workflows/release_prep.py +54 -0
  52. empathy_os/workflows/security_audit.py +154 -79
  53. empathy_os/workflows/test_gen.py +60 -0
  54. empathy_os/workflows/test_gen_behavioral.py +477 -0
  55. empathy_os/workflows/test_gen_parallel.py +341 -0
  56. empathy_framework-5.0.3.dist-info/licenses/LICENSE +0 -139
  57. {empathy_framework-5.0.3.dist-info → empathy_framework-5.1.1.dist-info}/WHEEL +0 -0
  58. {empathy_framework-5.0.3.dist-info → empathy_framework-5.1.1.dist-info}/entry_points.txt +0 -0
  59. {empathy_framework-5.0.3.dist-info → empathy_framework-5.1.1.dist-info}/top_level.txt +0 -0
@@ -60,6 +60,7 @@ class ReleasePreparationWorkflow(BaseWorkflow):
60
60
  skip_approve_if_clean: bool = True,
61
61
  use_security_crew: bool = False,
62
62
  crew_config: dict | None = None,
63
+ enable_auth_strategy: bool = True,
63
64
  **kwargs: Any,
64
65
  ):
65
66
  """Initialize release preparation workflow.
@@ -68,6 +69,7 @@ class ReleasePreparationWorkflow(BaseWorkflow):
68
69
  skip_approve_if_clean: Skip premium approval if all checks pass
69
70
  use_security_crew: Enable SecurityAuditCrew for comprehensive security audit
70
71
  crew_config: Configuration dict for SecurityAuditCrew
72
+ enable_auth_strategy: Enable intelligent auth routing (default: True)
71
73
  **kwargs: Additional arguments passed to BaseWorkflow
72
74
 
73
75
  """
@@ -75,7 +77,9 @@ class ReleasePreparationWorkflow(BaseWorkflow):
75
77
  self.skip_approve_if_clean = skip_approve_if_clean
76
78
  self.use_security_crew = use_security_crew
77
79
  self.crew_config = crew_config or {}
80
+ self.enable_auth_strategy = enable_auth_strategy
78
81
  self._has_blockers: bool = False
82
+ self._auth_mode_used: str | None = None
79
83
 
80
84
  # Dynamically configure stages based on security crew setting
81
85
  if use_security_crew:
@@ -137,6 +141,52 @@ class ReleasePreparationWorkflow(BaseWorkflow):
137
141
  Executes lint, type checking, and tests.
138
142
  """
139
143
  target_path = input_data.get("path", ".")
144
+
145
+ # === AUTH STRATEGY INTEGRATION ===
146
+ if self.enable_auth_strategy:
147
+ try:
148
+ import logging
149
+ from pathlib import Path
150
+
151
+ from empathy_os.models import (
152
+ count_lines_of_code,
153
+ get_auth_strategy,
154
+ get_module_size_category,
155
+ )
156
+ logger = logging.getLogger(__name__)
157
+
158
+ # Calculate total LOC for project/directory
159
+ target = Path(target_path)
160
+ total_lines = 0
161
+ if target.is_file():
162
+ total_lines = count_lines_of_code(target)
163
+ elif target.is_dir():
164
+ for py_file in target.rglob("*.py"):
165
+ try:
166
+ total_lines += count_lines_of_code(py_file)
167
+ except Exception:
168
+ pass
169
+
170
+ if total_lines > 0:
171
+ strategy = get_auth_strategy()
172
+ recommended_mode = strategy.get_recommended_mode(total_lines)
173
+ self._auth_mode_used = recommended_mode.value
174
+
175
+ size_category = get_module_size_category(total_lines)
176
+ logger.info(f"Release prep target: {target_path} ({total_lines:,} LOC, {size_category})")
177
+ logger.info(f"Recommended auth mode: {recommended_mode.value}")
178
+
179
+ cost_estimate = strategy.estimate_cost(total_lines, recommended_mode)
180
+ if recommended_mode.value == "subscription":
181
+ logger.info(f"Cost: {cost_estimate['quota_cost']}")
182
+ else:
183
+ logger.info(f"Cost: ~${cost_estimate['monetary_cost']:.4f}")
184
+
185
+ except Exception as e:
186
+ import logging
187
+ logger = logging.getLogger(__name__)
188
+ logger.warning(f"Auth strategy detection failed: {e}")
189
+
140
190
  checks: dict[str, dict] = {}
141
191
 
142
192
  # Lint check (ruff)
@@ -640,6 +690,10 @@ Provide a comprehensive release readiness assessment."""
640
690
  "model_tier_used": tier.value,
641
691
  }
642
692
 
693
+ # Include auth mode used for telemetry
694
+ if self._auth_mode_used:
695
+ result["auth_mode_used"] = self._auth_mode_used
696
+
643
697
  # Merge parsed XML data if available
644
698
  if parsed_data.get("xml_parsed"):
645
699
  result.update(
@@ -212,6 +212,7 @@ class SecurityAuditWorkflow(BaseWorkflow):
212
212
  use_crew_for_assessment: bool = True,
213
213
  use_crew_for_remediation: bool = False,
214
214
  crew_config: dict | None = None,
215
+ enable_auth_strategy: bool = True,
215
216
  **kwargs: Any,
216
217
  ):
217
218
  """Initialize security audit workflow.
@@ -222,6 +223,8 @@ class SecurityAuditWorkflow(BaseWorkflow):
222
223
  use_crew_for_assessment: Use SecurityAuditCrew for vulnerability assessment (default: True)
223
224
  use_crew_for_remediation: Use SecurityAuditCrew for enhanced remediation (default: True)
224
225
  crew_config: Configuration dict for SecurityAuditCrew
226
+ enable_auth_strategy: If True, use intelligent subscription vs API routing
227
+ based on codebase size (default: True)
225
228
  **kwargs: Additional arguments passed to BaseWorkflow
226
229
 
227
230
  """
@@ -231,10 +234,12 @@ class SecurityAuditWorkflow(BaseWorkflow):
231
234
  self.use_crew_for_assessment = use_crew_for_assessment
232
235
  self.use_crew_for_remediation = use_crew_for_remediation
233
236
  self.crew_config = crew_config or {}
237
+ self.enable_auth_strategy = enable_auth_strategy
234
238
  self._has_critical: bool = False
235
239
  self._team_decisions: dict[str, dict] = {}
236
240
  self._crew: Any = None
237
241
  self._crew_available = False
242
+ self._auth_mode_used: str | None = None # Track which auth was recommended
238
243
  self._load_team_decisions()
239
244
 
240
245
  def _load_team_decisions(self) -> None:
@@ -312,91 +317,101 @@ class SecurityAuditWorkflow(BaseWorkflow):
312
317
 
313
318
  target = Path(target_path)
314
319
  if target.exists():
315
- for ext in file_types:
316
- for file_path in target.rglob(f"*{ext}"):
317
- # Skip excluded directories
318
- if any(skip in str(file_path) for skip in SKIP_DIRECTORIES):
319
- continue
320
-
321
- try:
322
- content = file_path.read_text(errors="ignore")
323
- lines = content.split("\n")
324
- files_scanned += 1
325
-
326
- for vuln_type, vuln_info in SECURITY_PATTERNS.items():
327
- for pattern in vuln_info["patterns"]:
328
- matches = list(re.finditer(pattern, content, re.IGNORECASE))
329
- for match in matches:
330
- # Find line number and get the line content
331
- line_num = content[: match.start()].count("\n") + 1
332
- line_content = (
333
- lines[line_num - 1] if line_num <= len(lines) else ""
334
- )
335
-
336
- # Skip if file is a security example/test file
337
- file_name = str(file_path)
338
- if any(exp in file_name for exp in SECURITY_EXAMPLE_PATHS):
320
+ # Handle both file and directory targets
321
+ files_to_scan: list[Path] = []
322
+ if target.is_file():
323
+ # Single file - check if it matches file_types
324
+ if any(str(target).endswith(ext) for ext in file_types):
325
+ files_to_scan = [target]
326
+ else:
327
+ # Directory - recursively find all matching files
328
+ for ext in file_types:
329
+ for file_path in target.rglob(f"*{ext}"):
330
+ # Skip excluded directories
331
+ if any(skip in str(file_path) for skip in SKIP_DIRECTORIES):
332
+ continue
333
+ files_to_scan.append(file_path)
334
+
335
+ for file_path in files_to_scan:
336
+ try:
337
+ content = file_path.read_text(errors="ignore")
338
+ lines = content.split("\n")
339
+ files_scanned += 1
340
+
341
+ for vuln_type, vuln_info in SECURITY_PATTERNS.items():
342
+ for pattern in vuln_info["patterns"]:
343
+ matches = list(re.finditer(pattern, content, re.IGNORECASE))
344
+ for match in matches:
345
+ # Find line number and get the line content
346
+ line_num = content[: match.start()].count("\n") + 1
347
+ line_content = (
348
+ lines[line_num - 1] if line_num <= len(lines) else ""
349
+ )
350
+
351
+ # Skip if file is a security example/test file
352
+ file_name = str(file_path)
353
+ if any(exp in file_name for exp in SECURITY_EXAMPLE_PATHS):
354
+ continue
355
+
356
+ # Skip if this looks like detection/scanning code
357
+ if self._is_detection_code(line_content, match.group()):
358
+ continue
359
+
360
+ # Phase 2: Skip safe SQL parameterization patterns
361
+ if vuln_type == "sql_injection":
362
+ if self._is_safe_sql_parameterization(
363
+ line_content,
364
+ match.group(),
365
+ content,
366
+ ):
367
+ continue
368
+
369
+ # Skip fake/test credentials
370
+ if vuln_type == "hardcoded_secret":
371
+ if self._is_fake_credential(match.group()):
339
372
  continue
340
373
 
341
- # Skip if this looks like detection/scanning code
342
- if self._is_detection_code(line_content, match.group()):
374
+ # Phase 2: Skip safe random usage (tests, demos, documented)
375
+ if vuln_type == "insecure_random":
376
+ if self._is_safe_random_usage(
377
+ line_content,
378
+ file_name,
379
+ content,
380
+ ):
343
381
  continue
344
382
 
345
- # Phase 2: Skip safe SQL parameterization patterns
346
- if vuln_type == "sql_injection":
347
- if self._is_safe_sql_parameterization(
348
- line_content,
349
- match.group(),
350
- content,
351
- ):
352
- continue
353
-
354
- # Skip fake/test credentials
355
- if vuln_type == "hardcoded_secret":
356
- if self._is_fake_credential(match.group()):
357
- continue
358
-
359
- # Phase 2: Skip safe random usage (tests, demos, documented)
360
- if vuln_type == "insecure_random":
361
- if self._is_safe_random_usage(
362
- line_content,
363
- file_name,
364
- content,
365
- ):
366
- continue
367
-
368
- # Skip command_injection in documentation strings
369
- if vuln_type == "command_injection":
370
- if self._is_documentation_or_string(
371
- line_content,
372
- match.group(),
373
- ):
374
- continue
375
-
376
- # Check if this is a test file - downgrade to informational
377
- is_test_file = any(
378
- re.search(pat, file_name) for pat in TEST_FILE_PATTERNS
379
- )
380
-
381
- # Skip test file findings for hardcoded_secret (expected in tests)
382
- if is_test_file and vuln_type == "hardcoded_secret":
383
+ # Skip command_injection in documentation strings
384
+ if vuln_type == "command_injection":
385
+ if self._is_documentation_or_string(
386
+ line_content,
387
+ match.group(),
388
+ ):
383
389
  continue
384
390
 
385
- findings.append(
386
- {
387
- "type": vuln_type,
388
- "file": str(file_path),
389
- "line": line_num,
390
- "match": match.group()[:100],
391
- "severity": (
392
- "low" if is_test_file else vuln_info["severity"]
393
- ),
394
- "owasp": vuln_info["owasp"],
395
- "is_test": is_test_file,
396
- },
397
- )
398
- except OSError:
399
- continue
391
+ # Check if this is a test file - downgrade to informational
392
+ is_test_file = any(
393
+ re.search(pat, file_name) for pat in TEST_FILE_PATTERNS
394
+ )
395
+
396
+ # Skip test file findings for hardcoded_secret (expected in tests)
397
+ if is_test_file and vuln_type == "hardcoded_secret":
398
+ continue
399
+
400
+ findings.append(
401
+ {
402
+ "type": vuln_type,
403
+ "file": str(file_path),
404
+ "line": line_num,
405
+ "match": match.group()[:100],
406
+ "severity": (
407
+ "low" if is_test_file else vuln_info["severity"]
408
+ ),
409
+ "owasp": vuln_info["owasp"],
410
+ "is_test": is_test_file,
411
+ },
412
+ )
413
+ except OSError:
414
+ continue
400
415
 
401
416
  # Phase 3: Apply AST-based filtering for command injection
402
417
  try:
@@ -421,6 +436,64 @@ class SecurityAuditWorkflow(BaseWorkflow):
421
436
  except Exception as e:
422
437
  logger.warning(f"Phase 3 filtering failed: {e}")
423
438
 
439
+ # === AUTH STRATEGY INTEGRATION ===
440
+ # Detect codebase size and recommend auth mode (first stage only)
441
+ if self.enable_auth_strategy:
442
+ try:
443
+ from empathy_os.models import (
444
+ count_lines_of_code,
445
+ get_auth_strategy,
446
+ get_module_size_category,
447
+ )
448
+
449
+ # Calculate codebase size
450
+ codebase_lines = 0
451
+ if target.exists():
452
+ if target.is_file():
453
+ codebase_lines = count_lines_of_code(target)
454
+ elif target.is_dir():
455
+ # Sum lines across all Python files
456
+ for py_file in target.rglob("*.py"):
457
+ try:
458
+ codebase_lines += count_lines_of_code(py_file)
459
+ except Exception:
460
+ pass
461
+
462
+ if codebase_lines > 0:
463
+ # Get auth strategy (first-time setup if needed)
464
+ strategy = get_auth_strategy()
465
+
466
+ # Get recommended auth mode
467
+ recommended_mode = strategy.get_recommended_mode(codebase_lines)
468
+ self._auth_mode_used = recommended_mode.value
469
+
470
+ # Get size category
471
+ size_category = get_module_size_category(codebase_lines)
472
+
473
+ # Log recommendation
474
+ logger.info(
475
+ f"Codebase: {target} ({codebase_lines} LOC, {size_category})"
476
+ )
477
+ logger.info(f"Recommended auth mode: {recommended_mode.value}")
478
+
479
+ # Get cost estimate
480
+ cost_estimate = strategy.estimate_cost(codebase_lines, recommended_mode)
481
+
482
+ if recommended_mode.value == "subscription":
483
+ logger.info(
484
+ f"Cost: {cost_estimate['quota_cost']} "
485
+ f"(fits in {cost_estimate['fits_in_context']} context)"
486
+ )
487
+ else: # API
488
+ logger.info(
489
+ f"Cost: ~${cost_estimate['monetary_cost']:.4f} "
490
+ f"(1M context window)"
491
+ )
492
+
493
+ except Exception as e:
494
+ # Don't fail workflow if auth strategy fails
495
+ logger.warning(f"Auth strategy detection failed: {e}")
496
+
424
497
  input_tokens = len(str(input_data)) // 4
425
498
  output_tokens = len(str(findings)) // 4
426
499
 
@@ -991,6 +1064,8 @@ Provide a detailed remediation plan with specific fixes."""
991
1064
  "risk_level": assessment.get("risk_level", "unknown"),
992
1065
  "model_tier_used": tier.value,
993
1066
  "crew_enhanced": crew_enhanced,
1067
+ "auth_mode_used": self._auth_mode_used, # Track recommended auth mode
1068
+ **input_data, # Merge all previous stage data
994
1069
  }
995
1070
 
996
1071
  # Add crew-specific fields if enhanced
@@ -391,6 +391,7 @@ class TestGenerationWorkflow(BaseWorkflow):
391
391
  min_tests_for_review: int = 10,
392
392
  write_tests: bool = False,
393
393
  output_dir: str = "tests/generated",
394
+ enable_auth_strategy: bool = True,
394
395
  **kwargs: Any,
395
396
  ):
396
397
  """Initialize test generation workflow.
@@ -400,6 +401,7 @@ class TestGenerationWorkflow(BaseWorkflow):
400
401
  min_tests_for_review: Minimum tests generated to trigger premium review
401
402
  write_tests: If True, write generated tests to output_dir
402
403
  output_dir: Directory to write generated test files
404
+ enable_auth_strategy: Enable intelligent auth routing (default: True)
403
405
  **kwargs: Additional arguments passed to BaseWorkflow
404
406
 
405
407
  """
@@ -408,8 +410,10 @@ class TestGenerationWorkflow(BaseWorkflow):
408
410
  self.min_tests_for_review = min_tests_for_review
409
411
  self.write_tests = write_tests
410
412
  self.output_dir = output_dir
413
+ self.enable_auth_strategy = enable_auth_strategy
411
414
  self._test_count: int = 0
412
415
  self._bug_hotspots: list[str] = []
416
+ self._auth_mode_used: str | None = None
413
417
  self._load_bug_hotspots()
414
418
 
415
419
  def _load_bug_hotspots(self) -> None:
@@ -482,6 +486,57 @@ class TestGenerationWorkflow(BaseWorkflow):
482
486
  target_path = input_data.get("path", ".")
483
487
  file_types = input_data.get("file_types", [".py"])
484
488
 
489
+ # === AUTH STRATEGY INTEGRATION ===
490
+ if self.enable_auth_strategy:
491
+ try:
492
+ import logging
493
+ from pathlib import Path
494
+
495
+ from empathy_os.models import (
496
+ count_lines_of_code,
497
+ get_auth_strategy,
498
+ get_module_size_category,
499
+ )
500
+
501
+ logger = logging.getLogger(__name__)
502
+
503
+ # Calculate total LOC for the project/path
504
+ target = Path(target_path)
505
+ total_lines = 0
506
+ if target.is_file():
507
+ total_lines = count_lines_of_code(target)
508
+ elif target.is_dir():
509
+ # Estimate total lines for directory
510
+ for py_file in target.rglob("*.py"):
511
+ try:
512
+ total_lines += count_lines_of_code(py_file)
513
+ except Exception:
514
+ pass
515
+
516
+ if total_lines > 0:
517
+ strategy = get_auth_strategy()
518
+ recommended_mode = strategy.get_recommended_mode(total_lines)
519
+ self._auth_mode_used = recommended_mode.value
520
+
521
+ size_category = get_module_size_category(total_lines)
522
+ logger.info(
523
+ f"Test generation target: {target_path} "
524
+ f"({total_lines:,} LOC, {size_category})"
525
+ )
526
+ logger.info(f"Recommended auth mode: {recommended_mode.value}")
527
+
528
+ cost_estimate = strategy.estimate_cost(total_lines, recommended_mode)
529
+ if recommended_mode.value == "subscription":
530
+ logger.info(f"Cost: {cost_estimate['quota_cost']}")
531
+ else:
532
+ logger.info(f"Cost: ~${cost_estimate['monetary_cost']:.4f}")
533
+
534
+ except Exception as e:
535
+ import logging
536
+
537
+ logger = logging.getLogger(__name__)
538
+ logger.warning(f"Auth strategy detection failed: {e}")
539
+
485
540
  # Parse configurable limits with sensible defaults
486
541
  max_files_to_scan = input_data.get("max_files_to_scan", 1000)
487
542
  max_file_size_kb = input_data.get("max_file_size_kb", 200)
@@ -1452,6 +1507,11 @@ END OF REQUIRED FORMAT - output nothing after recommendations."""
1452
1507
 
1453
1508
  # Replace the previous analysis with the final, accurate report
1454
1509
  input_data["analysis_report"] = report
1510
+
1511
+ # Include auth mode used for telemetry
1512
+ if self._auth_mode_used:
1513
+ input_data["auth_mode_used"] = self._auth_mode_used
1514
+
1455
1515
  return input_data, total_in, total_out
1456
1516
 
1457
1517
  def _response_contains_questions(self, response: str) -> bool: