attune-ai 2.1.4__py3-none-any.whl → 2.2.0__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 (123) hide show
  1. attune/cli/__init__.py +3 -55
  2. attune/cli/commands/batch.py +4 -12
  3. attune/cli/commands/cache.py +7 -15
  4. attune/cli/commands/provider.py +17 -0
  5. attune/cli/commands/routing.py +3 -1
  6. attune/cli/commands/setup.py +122 -0
  7. attune/cli/commands/tier.py +1 -3
  8. attune/cli/commands/workflow.py +31 -0
  9. attune/cli/parsers/cache.py +1 -0
  10. attune/cli/parsers/help.py +1 -3
  11. attune/cli/parsers/provider.py +7 -0
  12. attune/cli/parsers/routing.py +1 -3
  13. attune/cli/parsers/setup.py +7 -0
  14. attune/cli/parsers/status.py +1 -3
  15. attune/cli/parsers/tier.py +1 -3
  16. attune/cli_minimal.py +34 -28
  17. attune/cli_router.py +9 -7
  18. attune/cli_unified.py +3 -0
  19. attune/core.py +190 -0
  20. attune/dashboard/app.py +4 -2
  21. attune/dashboard/simple_server.py +3 -1
  22. attune/dashboard/standalone_server.py +7 -3
  23. attune/mcp/server.py +54 -102
  24. attune/memory/long_term.py +0 -2
  25. attune/memory/short_term/__init__.py +84 -0
  26. attune/memory/short_term/base.py +467 -0
  27. attune/memory/short_term/batch.py +219 -0
  28. attune/memory/short_term/caching.py +227 -0
  29. attune/memory/short_term/conflicts.py +265 -0
  30. attune/memory/short_term/cross_session.py +122 -0
  31. attune/memory/short_term/facade.py +655 -0
  32. attune/memory/short_term/pagination.py +215 -0
  33. attune/memory/short_term/patterns.py +271 -0
  34. attune/memory/short_term/pubsub.py +286 -0
  35. attune/memory/short_term/queues.py +244 -0
  36. attune/memory/short_term/security.py +300 -0
  37. attune/memory/short_term/sessions.py +250 -0
  38. attune/memory/short_term/streams.py +249 -0
  39. attune/memory/short_term/timelines.py +234 -0
  40. attune/memory/short_term/transactions.py +186 -0
  41. attune/memory/short_term/working.py +252 -0
  42. attune/meta_workflows/cli_commands/__init__.py +3 -0
  43. attune/meta_workflows/cli_commands/agent_commands.py +0 -4
  44. attune/meta_workflows/cli_commands/analytics_commands.py +0 -6
  45. attune/meta_workflows/cli_commands/config_commands.py +0 -5
  46. attune/meta_workflows/cli_commands/memory_commands.py +0 -5
  47. attune/meta_workflows/cli_commands/template_commands.py +0 -5
  48. attune/meta_workflows/cli_commands/workflow_commands.py +0 -6
  49. attune/meta_workflows/workflow.py +1 -1
  50. attune/models/adaptive_routing.py +4 -8
  51. attune/models/auth_cli.py +3 -9
  52. attune/models/auth_strategy.py +2 -4
  53. attune/models/provider_config.py +20 -1
  54. attune/models/telemetry/analytics.py +0 -2
  55. attune/models/telemetry/backend.py +0 -3
  56. attune/models/telemetry/storage.py +0 -2
  57. attune/orchestration/_strategies/__init__.py +156 -0
  58. attune/orchestration/_strategies/base.py +231 -0
  59. attune/orchestration/_strategies/conditional_strategies.py +373 -0
  60. attune/orchestration/_strategies/conditions.py +369 -0
  61. attune/orchestration/_strategies/core_strategies.py +491 -0
  62. attune/orchestration/_strategies/data_classes.py +64 -0
  63. attune/orchestration/_strategies/nesting.py +233 -0
  64. attune/orchestration/execution_strategies.py +58 -1567
  65. attune/orchestration/meta_orchestrator.py +1 -3
  66. attune/project_index/scanner.py +1 -3
  67. attune/project_index/scanner_parallel.py +7 -5
  68. attune/socratic_router.py +1 -3
  69. attune/telemetry/agent_coordination.py +9 -3
  70. attune/telemetry/agent_tracking.py +16 -3
  71. attune/telemetry/approval_gates.py +22 -5
  72. attune/telemetry/cli.py +3 -3
  73. attune/telemetry/commands/dashboard_commands.py +24 -8
  74. attune/telemetry/event_streaming.py +8 -2
  75. attune/telemetry/feedback_loop.py +10 -2
  76. attune/tools.py +1 -0
  77. attune/workflow_commands.py +1 -3
  78. attune/workflows/__init__.py +53 -10
  79. attune/workflows/autonomous_test_gen.py +160 -104
  80. attune/workflows/base.py +48 -664
  81. attune/workflows/batch_processing.py +2 -4
  82. attune/workflows/compat.py +156 -0
  83. attune/workflows/cost_mixin.py +141 -0
  84. attune/workflows/data_classes.py +92 -0
  85. attune/workflows/document_gen/workflow.py +11 -14
  86. attune/workflows/history.py +62 -37
  87. attune/workflows/llm_base.py +2 -4
  88. attune/workflows/migration.py +422 -0
  89. attune/workflows/output.py +3 -9
  90. attune/workflows/parsing_mixin.py +427 -0
  91. attune/workflows/perf_audit.py +3 -1
  92. attune/workflows/progress.py +10 -13
  93. attune/workflows/release_prep.py +5 -1
  94. attune/workflows/routing.py +0 -2
  95. attune/workflows/secure_release.py +2 -1
  96. attune/workflows/security_audit.py +19 -14
  97. attune/workflows/security_audit_phase3.py +28 -22
  98. attune/workflows/seo_optimization.py +29 -29
  99. attune/workflows/test_gen/test_templates.py +1 -4
  100. attune/workflows/test_gen/workflow.py +0 -2
  101. attune/workflows/test_gen_behavioral.py +7 -20
  102. attune/workflows/test_gen_parallel.py +6 -4
  103. {attune_ai-2.1.4.dist-info → attune_ai-2.2.0.dist-info}/METADATA +4 -3
  104. {attune_ai-2.1.4.dist-info → attune_ai-2.2.0.dist-info}/RECORD +119 -94
  105. {attune_ai-2.1.4.dist-info → attune_ai-2.2.0.dist-info}/entry_points.txt +0 -2
  106. attune_healthcare/monitors/monitoring/__init__.py +9 -9
  107. attune_llm/agent_factory/__init__.py +6 -6
  108. attune_llm/commands/__init__.py +10 -10
  109. attune_llm/commands/models.py +3 -3
  110. attune_llm/config/__init__.py +8 -8
  111. attune_llm/learning/__init__.py +3 -3
  112. attune_llm/learning/extractor.py +5 -3
  113. attune_llm/learning/storage.py +5 -3
  114. attune_llm/security/__init__.py +17 -17
  115. attune_llm/utils/tokens.py +3 -1
  116. attune/cli_legacy.py +0 -3957
  117. attune/memory/short_term.py +0 -2192
  118. attune/workflows/manage_docs.py +0 -87
  119. attune/workflows/test5.py +0 -125
  120. {attune_ai-2.1.4.dist-info → attune_ai-2.2.0.dist-info}/WHEEL +0 -0
  121. {attune_ai-2.1.4.dist-info → attune_ai-2.2.0.dist-info}/licenses/LICENSE +0 -0
  122. {attune_ai-2.1.4.dist-info → attune_ai-2.2.0.dist-info}/licenses/LICENSE_CHANGE_ANNOUNCEMENT.md +0 -0
  123. {attune_ai-2.1.4.dist-info → attune_ai-2.2.0.dist-info}/top_level.txt +0 -0
attune/cli/__init__.py CHANGED
@@ -60,21 +60,9 @@ def main() -> int:
60
60
 
61
61
  register_all_parsers(subparsers)
62
62
 
63
- # TODO: Import and register remaining commands from cli.py
64
- # This is a partial refactoring - additional commands still in cli.py
65
- # For now, if command not found in new structure, fall back to old cli.py
66
- #
67
- # NOTE: Temporarily disabled to avoid conflicts with extracted commands.
68
- # Commands that have been extracted:
69
- # - help, tier, info, patterns, status (Phase 1)
70
- # - workflow, inspect (run, inspect, export, import) (Phase 2)
71
- # Once all commands are extracted, the old cli.py will be removed entirely.
72
- #
73
- # try:
74
- # from attune import cli as old_cli
75
- # _register_legacy_commands(subparsers, old_cli)
76
- # except ImportError:
77
- # pass # Old cli.py not available or already moved
63
+ # NOTE: CLI refactoring is COMPLETE (v2.1.5)
64
+ # All 30 commands have been extracted from the monolithic cli_legacy.py
65
+ # to the new modular structure in cli/commands/ and cli/parsers/.
78
66
 
79
67
  # Parse arguments
80
68
  args = parser.parse_args()
@@ -107,46 +95,6 @@ def main() -> int:
107
95
  return 0
108
96
 
109
97
 
110
- def _register_legacy_commands(subparsers, old_cli):
111
- """Temporarily register commands not yet extracted from old cli.py.
112
-
113
- This function provides backward compatibility during the refactoring process.
114
- As commands are extracted into the new structure, they should be removed
115
- from this registration.
116
-
117
- Args:
118
- subparsers: ArgumentParser subparsers object
119
- old_cli: Reference to old cli module
120
-
121
- Note:
122
- This is a TEMPORARY function that will be removed once all commands
123
- are extracted from the monolithic cli.py file.
124
- """
125
- # Import command functions that haven't been extracted yet
126
- try:
127
- # Patterns commands
128
- from attune.cli import cmd_patterns_export, cmd_patterns_list, cmd_patterns_resolve
129
-
130
- patterns_parser = subparsers.add_parser("patterns", help="Pattern management")
131
- patterns_sub = patterns_parser.add_subparsers(dest="patterns_command")
132
-
133
- p_list = patterns_sub.add_parser("list", help="List patterns")
134
- p_list.set_defaults(func=cmd_patterns_list)
135
-
136
- p_export = patterns_sub.add_parser("export", help="Export patterns")
137
- p_export.add_argument("output", help="Output file")
138
- p_export.set_defaults(func=cmd_patterns_export)
139
-
140
- p_resolve = patterns_sub.add_parser("resolve", help="Resolve pattern")
141
- p_resolve.add_argument("pattern_id", help="Pattern ID")
142
- p_resolve.set_defaults(func=cmd_patterns_resolve)
143
- except (ImportError, AttributeError):
144
- pass # Commands not available
145
-
146
- # Add other legacy commands as needed...
147
- # This list will shrink as commands are extracted
148
-
149
-
150
98
  # Preserve backward compatibility
151
99
  if __name__ == "__main__":
152
100
  sys.exit(main())
@@ -184,12 +184,8 @@ def cmd_batch_results(args):
184
184
  print(f" Total: {len(results)} results")
185
185
 
186
186
  # Summary
187
- succeeded = sum(
188
- 1 for r in results if r.get("result", {}).get("type") == "succeeded"
189
- )
190
- errored = sum(
191
- 1 for r in results if r.get("result", {}).get("type") == "errored"
192
- )
187
+ succeeded = sum(1 for r in results if r.get("result", {}).get("type") == "succeeded")
188
+ errored = sum(1 for r in results if r.get("result", {}).get("type") == "errored")
193
189
 
194
190
  print(f" Succeeded: {succeeded}")
195
191
  print(f" Errored: {errored}")
@@ -242,12 +238,8 @@ def cmd_batch_wait(args):
242
238
  print(f" Total: {len(results)} results")
243
239
 
244
240
  # Summary
245
- succeeded = sum(
246
- 1 for r in results if r.get("result", {}).get("type") == "succeeded"
247
- )
248
- errored = sum(
249
- 1 for r in results if r.get("result", {}).get("type") == "errored"
250
- )
241
+ succeeded = sum(1 for r in results if r.get("result", {}).get("type") == "succeeded")
242
+ errored = sum(1 for r in results if r.get("result", {}).get("type") == "errored")
251
243
 
252
244
  print(f" Succeeded: {succeeded}")
253
245
  print(f" Errored: {errored}")
@@ -93,14 +93,10 @@ def _collect_cache_stats(days: int = 7) -> dict[str, Any]:
93
93
  for line in f:
94
94
  # Try to extract timestamp
95
95
  # Common format: 2026-01-27 21:30:45,123
96
- timestamp_match = re.match(
97
- r"(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})", line
98
- )
96
+ timestamp_match = re.match(r"(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})", line)
99
97
  if timestamp_match:
100
98
  try:
101
- log_time = datetime.strptime(
102
- timestamp_match.group(1), "%Y-%m-%d %H:%M:%S"
103
- )
99
+ log_time = datetime.strptime(timestamp_match.group(1), "%Y-%m-%d %H:%M:%S")
104
100
  if log_time < cutoff_date:
105
101
  continue # Skip old entries
106
102
  except ValueError:
@@ -140,9 +136,7 @@ def _collect_cache_stats(days: int = 7) -> dict[str, Any]:
140
136
  }
141
137
 
142
138
  # Calculate metrics
143
- cache_hit_rate = (
144
- (cache_hits / total_requests * 100) if total_requests > 0 else 0.0
145
- )
139
+ cache_hit_rate = (cache_hits / total_requests * 100) if total_requests > 0 else 0.0
146
140
 
147
141
  return {
148
142
  "days_analyzed": days,
@@ -154,9 +148,7 @@ def _collect_cache_stats(days: int = 7) -> dict[str, Any]:
154
148
  "total_cache_read_tokens": total_cache_read_tokens,
155
149
  "total_cache_write_tokens": total_cache_write_tokens,
156
150
  "total_savings": round(total_savings, 4),
157
- "avg_savings_per_hit": (
158
- round(total_savings / cache_hits, 4) if cache_hits > 0 else 0.0
159
- ),
151
+ "avg_savings_per_hit": (round(total_savings / cache_hits, 4) if cache_hits > 0 else 0.0),
160
152
  }
161
153
 
162
154
 
@@ -193,7 +185,7 @@ def _display_cache_report(stats: dict[str, Any], verbose: bool = False):
193
185
  # Cost savings
194
186
  print("💰 Cost Savings:")
195
187
  print(f" Total Saved: ${stats['total_savings']:.4f}")
196
- if stats['cache_hits'] > 0:
188
+ if stats["cache_hits"] > 0:
197
189
  print(f" Avg Savings per Hit: ${stats['avg_savings_per_hit']:.4f}")
198
190
  print()
199
191
 
@@ -205,7 +197,7 @@ def _display_cache_report(stats: dict[str, Any], verbose: bool = False):
205
197
  print()
206
198
 
207
199
  # Performance assessment
208
- hit_rate = stats['cache_hit_rate']
200
+ hit_rate = stats["cache_hit_rate"]
209
201
  print("📈 Performance Assessment:")
210
202
  if hit_rate >= 50:
211
203
  print(" ✅ EXCELLENT - Cache is working effectively")
@@ -222,7 +214,7 @@ def _display_cache_report(stats: dict[str, Any], verbose: bool = False):
222
214
  print()
223
215
 
224
216
  # Recommendations
225
- if stats['total_requests'] < 10:
217
+ if stats["total_requests"] < 10:
226
218
  print("ℹ️ Note: Limited data available. Run more workflows for accurate stats.")
227
219
  elif hit_rate < 30:
228
220
  print("💡 Recommendations:")
@@ -96,3 +96,20 @@ def cmd_provider_set(args):
96
96
 
97
97
  print(f"✓ Default provider set to: {provider}")
98
98
  print(f" Saved to: {validated_workflows_path}")
99
+
100
+
101
+ def cmd_provider_hybrid(args):
102
+ """Configure hybrid mode (DEPRECATED in v5.0.0).
103
+
104
+ Hybrid mode is no longer supported. This command now configures
105
+ Anthropic as the sole provider.
106
+
107
+ Args:
108
+ args: Namespace object from argparse (no additional attributes used).
109
+
110
+ Returns:
111
+ None: Launches interactive Anthropic configuration.
112
+ """
113
+ from attune.models.provider_config import configure_hybrid_interactive
114
+
115
+ configure_hybrid_interactive()
@@ -82,7 +82,9 @@ def cmd_routing_stats(args: Any) -> int:
82
82
  )
83
83
 
84
84
  print(f" Best model: {best_model[0]}")
85
- print(f" ({best_model[1]['success_rate']:.1%} success, ${best_model[1]['avg_cost']:.4f}/call)")
85
+ print(
86
+ f" ({best_model[1]['success_rate']:.1%} success, ${best_model[1]['avg_cost']:.4f}/call)"
87
+ )
86
88
 
87
89
  # Cost savings potential
88
90
  if len(stats["models_used"]) > 1:
@@ -94,3 +94,125 @@ def cmd_validate(args):
94
94
  logger.exception(f"Unexpected error validating configuration: {e}")
95
95
  logger.error(f"✗ Configuration invalid: {e}")
96
96
  sys.exit(1)
97
+
98
+
99
+ def cmd_setup(args):
100
+ """Interactive setup workflow.
101
+
102
+ Guides user through initial framework configuration step by step.
103
+ Updated for Claude-native v5.0.0 (Anthropic-only).
104
+
105
+ Args:
106
+ args: Namespace object from argparse (no additional attributes used).
107
+
108
+ Returns:
109
+ None: Creates attune.config.yml with user's choices.
110
+ """
111
+ print("🧙 Attune AI Setup Workflow")
112
+ print("=" * 50)
113
+ print("\nI'll help you set up your Attune AI configuration.\n")
114
+
115
+ # Step 1: Use case
116
+ print("1. What's your primary use case?")
117
+ print(" [1] Software development")
118
+ print(" [2] Healthcare applications")
119
+ print(" [3] Customer support")
120
+ print(" [4] Other")
121
+
122
+ use_case_choice = input("\nYour choice (1-4): ").strip()
123
+ use_case_map = {
124
+ "1": "software_development",
125
+ "2": "healthcare",
126
+ "3": "customer_support",
127
+ "4": "general",
128
+ }
129
+ use_case = use_case_map.get(use_case_choice, "general")
130
+
131
+ # Step 2: Empathy level
132
+ print("\n2. What empathy level do you want to target?")
133
+ print(" [1] Level 1 - Reactive (basic Q&A)")
134
+ print(" [2] Level 2 - Guided (asks clarifying questions)")
135
+ print(" [3] Level 3 - Proactive (offers improvements)")
136
+ print(" [4] Level 4 - Anticipatory (predicts problems) ⭐ Recommended")
137
+ print(" [5] Level 5 - Transformative (reshapes workflows)")
138
+
139
+ level_choice = input("\nYour choice (1-5) [4]: ").strip() or "4"
140
+ target_level = int(level_choice) if level_choice in ["1", "2", "3", "4", "5"] else 4
141
+
142
+ # Step 3: LLM provider (Anthropic-only as of v5.0.0)
143
+ print("\n3. LLM Provider: Anthropic Claude")
144
+ print(" Attune AI is Claude-native and uses Anthropic exclusively.")
145
+
146
+ from attune.models.provider_config import ProviderConfig
147
+
148
+ config_detected = ProviderConfig.auto_detect()
149
+ if config_detected.available_providers:
150
+ print(" ✓ ANTHROPIC_API_KEY detected")
151
+ else:
152
+ print(" ⚠️ ANTHROPIC_API_KEY not detected")
153
+ print(" Set your API key: export ANTHROPIC_API_KEY='your-key-here'")
154
+ print(" Get key at: https://console.anthropic.com/settings/keys")
155
+
156
+ # Step 4: User ID
157
+ print("\n4. What user ID should we use?")
158
+ user_id = input("User ID [default_user]: ").strip() or "default_user"
159
+
160
+ # Generate configuration
161
+ config = {
162
+ "user_id": user_id,
163
+ "target_level": target_level,
164
+ "confidence_threshold": 0.75,
165
+ "persistence_enabled": True,
166
+ "persistence_backend": "sqlite",
167
+ "persistence_path": ".attune",
168
+ "metrics_enabled": True,
169
+ "use_case": use_case,
170
+ "llm_provider": "anthropic",
171
+ }
172
+
173
+ # Save configuration
174
+ output_file = "attune.config.yml"
175
+ print(f"\n5. Creating configuration file: {output_file}")
176
+
177
+ # Write YAML config
178
+ yaml_content = f"""# Attune AI Configuration
179
+ # Generated by setup workflow
180
+
181
+ # Core settings
182
+ user_id: "{config["user_id"]}"
183
+ target_level: {config["target_level"]}
184
+ confidence_threshold: {config["confidence_threshold"]}
185
+
186
+ # Use case
187
+ use_case: "{config["use_case"]}"
188
+
189
+ # Persistence
190
+ persistence_enabled: {str(config["persistence_enabled"]).lower()}
191
+ persistence_backend: "{config["persistence_backend"]}"
192
+ persistence_path: "{config["persistence_path"]}"
193
+
194
+ # Metrics
195
+ metrics_enabled: {str(config["metrics_enabled"]).lower()}
196
+
197
+ # LLM Provider (Claude-native v5.0.0)
198
+ llm_provider: "anthropic"
199
+ """
200
+
201
+ validated_output = _validate_file_path(output_file)
202
+ with open(validated_output, "w") as f:
203
+ f.write(yaml_content)
204
+
205
+ print(f" ✓ Created {validated_output}")
206
+
207
+ print("\n" + "=" * 50)
208
+ print("✅ Setup complete!")
209
+ print("\nNext steps:")
210
+ print(f" 1. Edit {output_file} to customize settings")
211
+
212
+ if not config_detected.available_providers:
213
+ print(" 2. Set ANTHROPIC_API_KEY environment variable")
214
+ print(" 3. Run: attune run --config attune.config.yml")
215
+ else:
216
+ print(" 2. Run: attune run --config attune.config.yml")
217
+
218
+ print("\nHappy coding! 🧠✨\n")
@@ -109,9 +109,7 @@ def cmd_tier_stats(args):
109
109
 
110
110
  print(" BUG TYPE DISTRIBUTION")
111
111
  print(" " + "-" * 40)
112
- sorted_types = sorted(
113
- stats["bug_type_distribution"].items(), key=lambda x: x[1], reverse=True
114
- )
112
+ sorted_types = sorted(stats["bug_type_distribution"].items(), key=lambda x: x[1], reverse=True)
115
113
  for bug_type, count in sorted_types[:10]:
116
114
  percent = (count / stats["total_patterns"]) * 100
117
115
  print(f" {bug_type:20} {count:3} ({percent:5.1f}%)")
@@ -14,6 +14,11 @@ from attune.logging_config import get_logger
14
14
  from attune.workflows import get_workflow
15
15
  from attune.workflows import list_workflows as get_workflow_list
16
16
  from attune.workflows.config import WorkflowConfig, create_example_config
17
+ from attune.workflows.migration import (
18
+ WORKFLOW_ALIASES,
19
+ resolve_workflow_migration,
20
+ show_migration_tip,
21
+ )
17
22
 
18
23
  logger = get_logger(__name__)
19
24
 
@@ -127,6 +132,13 @@ def cmd_workflow(args):
127
132
  return 1
128
133
 
129
134
  try:
135
+ # Check for workflow migration (describe still works with old names)
136
+ if name in WORKFLOW_ALIASES:
137
+ resolved_name, migration_kwargs, _ = resolve_workflow_migration(name)
138
+ if resolved_name and resolved_name != name:
139
+ print(f"ℹ️ '{name}' is now '{resolved_name}'")
140
+ name = resolved_name
141
+
130
142
  workflow_cls = get_workflow(name)
131
143
  provider = getattr(args, "provider", None)
132
144
  workflow = workflow_cls(provider=provider)
@@ -165,6 +177,16 @@ def cmd_workflow(args):
165
177
  return 1
166
178
 
167
179
  try:
180
+ # Check for workflow migration (environment-aware)
181
+ original_name = name
182
+ migration_kwargs = {}
183
+ was_migrated = False
184
+
185
+ if name in WORKFLOW_ALIASES:
186
+ name, migration_kwargs, was_migrated = resolve_workflow_migration(name)
187
+ if was_migrated:
188
+ logger.info(f"Migrated '{original_name}' -> '{name}' with {migration_kwargs}")
189
+
168
190
  workflow_cls = get_workflow(name)
169
191
 
170
192
  # Get provider from CLI arg, or fall back to config's default_provider
@@ -197,6 +219,11 @@ def cmd_workflow(args):
197
219
  health_score_threshold = getattr(args, "health_score_threshold", 100)
198
220
  workflow_kwargs["health_score_threshold"] = health_score_threshold
199
221
 
222
+ # Merge migration kwargs (from workflow consolidation)
223
+ for key, value in migration_kwargs.items():
224
+ if not key.startswith("_") and key in init_params:
225
+ workflow_kwargs[key] = value
226
+
200
227
  workflow = workflow_cls(**workflow_kwargs)
201
228
 
202
229
  # Parse input
@@ -367,6 +394,10 @@ def cmd_workflow(args):
367
394
  error_msg = error_msg or "Unknown error"
368
395
  print(f"\n✗ Workflow failed: {error_msg}\n")
369
396
 
397
+ # Show migration tip if workflow was migrated (helps users learn new syntax)
398
+ if was_migrated and original_name != name:
399
+ show_migration_tip(original_name, name, migration_kwargs)
400
+
370
401
  except KeyError:
371
402
  print(f"Error: Workflow '{name}' not found")
372
403
  print("\nRun 'empathy workflow list' to see available workflows")
@@ -15,6 +15,7 @@ def register_parsers(subparsers):
15
15
  None: Adds cache subparser with stats and clear subcommands
16
16
  """
17
17
  from ..commands.cache import cmd_cache_clear, cmd_cache_stats
18
+
18
19
  # Main cache command
19
20
  cache_parser = subparsers.add_parser(
20
21
  "cache",
@@ -20,9 +20,7 @@ def register_parsers(subparsers):
20
20
  # cheatsheet command
21
21
  parser_cheatsheet = subparsers.add_parser("cheatsheet", help="Quick reference guide")
22
22
  parser_cheatsheet.add_argument("--category", help="Specific category to show")
23
- parser_cheatsheet.add_argument(
24
- "--compact", action="store_true", help="Show commands only"
25
- )
23
+ parser_cheatsheet.add_argument("--compact", action="store_true", help="Show commands only")
26
24
  parser_cheatsheet.set_defaults(func=help_commands.cmd_cheatsheet)
27
25
 
28
26
  # onboard command
@@ -38,3 +38,10 @@ def register_parsers(subparsers):
38
38
  help="Provider name (anthropic only)",
39
39
  )
40
40
  p_set.set_defaults(func=provider.cmd_provider_set)
41
+
42
+ # Provider hybrid command (deprecated)
43
+ p_hybrid = provider_sub.add_parser(
44
+ "hybrid",
45
+ help="Configure hybrid mode (DEPRECATED - now Anthropic only)",
46
+ )
47
+ p_hybrid.set_defaults(func=provider.cmd_provider_hybrid)
@@ -28,9 +28,7 @@ def register_parsers(subparsers):
28
28
  )
29
29
 
30
30
  # Routing subcommands
31
- routing_subparsers = routing_parser.add_subparsers(
32
- dest="routing_command", required=True
33
- )
31
+ routing_subparsers = routing_parser.add_subparsers(dest="routing_command", required=True)
34
32
 
35
33
  # routing stats command
36
34
  stats_parser = routing_subparsers.add_parser(
@@ -40,3 +40,10 @@ def register_parsers(subparsers):
40
40
  help="Path to configuration file to validate",
41
41
  )
42
42
  parser_validate.set_defaults(func=setup.cmd_validate)
43
+
44
+ # Setup wizard command
45
+ parser_setup = subparsers.add_parser(
46
+ "setup",
47
+ help="Interactive setup workflow for first-time configuration",
48
+ )
49
+ parser_setup.set_defaults(func=setup.cmd_setup)
@@ -36,9 +36,7 @@ def register_parsers(subparsers):
36
36
  parser_health.add_argument("--deep", action="store_true", help="Run all checks")
37
37
  parser_health.add_argument("--fix", action="store_true", help="Auto-fix issues")
38
38
  parser_health.add_argument("--dry-run", action="store_true", help="Preview fixes only")
39
- parser_health.add_argument(
40
- "--interactive", action="store_true", help="Interactive fix mode"
41
- )
39
+ parser_health.add_argument("--interactive", action="store_true", help="Interactive fix mode")
42
40
  parser_health.add_argument("--project-root", default=".", help="Project root")
43
41
  parser_health.add_argument("--details", action="store_true", help="Show details")
44
42
  parser_health.add_argument("--full", action="store_true", help="Show all details")
@@ -18,9 +18,7 @@ def register_parsers(subparsers):
18
18
  tier_subparsers = tier_parser.add_subparsers(dest="tier_command")
19
19
 
20
20
  # tier recommend
21
- parser_recommend = tier_subparsers.add_parser(
22
- "recommend", help="Get tier recommendation"
23
- )
21
+ parser_recommend = tier_subparsers.add_parser("recommend", help="Get tier recommendation")
24
22
  parser_recommend.add_argument("description", help="Bug or task description")
25
23
  parser_recommend.add_argument("--files", help="Comma-separated list of files")
26
24
  parser_recommend.add_argument(
attune/cli_minimal.py CHANGED
@@ -5,29 +5,29 @@ IMPORTANT: This CLI is for automation only (git hooks, scripts, CI/CD).
5
5
  For interactive use, use Claude Code skills in VSCode or Claude Desktop.
6
6
 
7
7
  Automation commands:
8
- empathy workflow list List available workflows
9
- empathy workflow run <name> Execute a workflow
10
- empathy workflow info <name> Show workflow details
8
+ attune workflow list List available workflows
9
+ attune workflow run <name> Execute a workflow
10
+ attune workflow info <name> Show workflow details
11
11
 
12
12
  Monitoring commands:
13
- empathy dashboard start Start agent coordination dashboard
14
- (opens web UI at http://localhost:8000)
13
+ attune dashboard start Start agent coordination dashboard
14
+ (opens web UI at http://localhost:8000)
15
15
 
16
16
  Utility commands:
17
- empathy telemetry show Display usage summary
18
- empathy telemetry savings Show cost savings
19
- empathy telemetry export Export to CSV/JSON
20
- empathy telemetry routing-stats Show adaptive routing statistics
21
- empathy telemetry routing-check Check for tier upgrade recommendations
22
- empathy telemetry models Show model performance by provider
23
- empathy telemetry agents Show active agents and their status
24
- empathy telemetry signals Show coordination signals for an agent
17
+ attune telemetry show Display usage summary
18
+ attune telemetry savings Show cost savings
19
+ attune telemetry export Export to CSV/JSON
20
+ attune telemetry routing-stats Show adaptive routing statistics
21
+ attune telemetry routing-check Check for tier upgrade recommendations
22
+ attune telemetry models Show model performance by provider
23
+ attune telemetry agents Show active agents and their status
24
+ attune telemetry signals Show coordination signals for an agent
25
25
 
26
- empathy provider show Show current provider config
27
- empathy provider set <name> Set provider (anthropic, openai, hybrid)
26
+ attune provider show Show current provider config
27
+ attune provider set <name> Set provider (anthropic, openai, hybrid)
28
28
 
29
- empathy validate Validate configuration
30
- empathy version Show version
29
+ attune validate Validate configuration
30
+ attune version Show version
31
31
 
32
32
  For interactive development, use Claude Code skills:
33
33
  /dev Developer tools (commit, review, debug, refactor)
@@ -58,7 +58,7 @@ def get_version() -> str:
58
58
  try:
59
59
  from importlib.metadata import version
60
60
 
61
- return version("empathy-framework")
61
+ return version("attune-ai")
62
62
  except Exception: # noqa: BLE001
63
63
  # INTENTIONAL: Fallback for dev installs without metadata
64
64
  return "dev"
@@ -90,7 +90,7 @@ def cmd_workflow_list(args: Namespace) -> int:
90
90
 
91
91
  print("-" * 60)
92
92
  print(f"\nTotal: {len(workflows)} workflows")
93
- print("\nRun a workflow: empathy workflow run <name>")
93
+ print("\nRun a workflow: attune workflow run <name>")
94
94
  return 0
95
95
 
96
96
 
@@ -429,7 +429,7 @@ def cmd_telemetry_routing_stats(args: Namespace) -> int:
429
429
 
430
430
  except ImportError as e:
431
431
  print(f"❌ Adaptive routing not available: {e}")
432
- print(" Ensure empathy-framework is installed with telemetry support")
432
+ print(" Ensure attune-ai is installed with telemetry support")
433
433
  return 1
434
434
  except Exception as e: # noqa: BLE001
435
435
  # INTENTIONAL: CLI commands should catch all errors and report gracefully
@@ -920,7 +920,7 @@ def cmd_validate(args: Namespace) -> int:
920
920
  def cmd_version(args: Namespace) -> int:
921
921
  """Show version information."""
922
922
  version = get_version()
923
- print(f"empathy-framework {version}")
923
+ print(f"attune-ai {version}")
924
924
 
925
925
  if args.verbose:
926
926
  print(f"\nPython: {sys.version}")
@@ -930,7 +930,7 @@ def cmd_version(args: Namespace) -> int:
930
930
  try:
931
931
  from importlib.metadata import requires
932
932
 
933
- reqs = requires("empathy-framework") or []
933
+ reqs = requires("attune-ai") or []
934
934
  print(f"\nDependencies: {len(reqs)}")
935
935
  except Exception: # noqa: BLE001
936
936
  pass
@@ -1073,8 +1073,12 @@ Documentation: https://smartaimemory.com/framework-docs/
1073
1073
 
1074
1074
  # dashboard start
1075
1075
  start_parser = dashboard_sub.add_parser("start", help="Start dashboard web server")
1076
- start_parser.add_argument("--host", default="127.0.0.1", help="Host to bind to (default: 127.0.0.1)")
1077
- start_parser.add_argument("--port", type=int, default=8000, help="Port to bind to (default: 8000)")
1076
+ start_parser.add_argument(
1077
+ "--host", default="127.0.0.1", help="Host to bind to (default: 127.0.0.1)"
1078
+ )
1079
+ start_parser.add_argument(
1080
+ "--port", type=int, default=8000, help="Port to bind to (default: 8000)"
1081
+ )
1078
1082
 
1079
1083
  # --- Utility commands ---
1080
1084
  subparsers.add_parser("validate", help="Validate configuration")
@@ -1104,7 +1108,7 @@ def main(argv: list[str] | None = None) -> int:
1104
1108
  elif args.workflow_command == "run":
1105
1109
  return cmd_workflow_run(args)
1106
1110
  else:
1107
- print("Usage: empathy workflow {list|info|run}")
1111
+ print("Usage: attune workflow {list|info|run}")
1108
1112
  return 1
1109
1113
 
1110
1114
  elif args.command == "telemetry":
@@ -1125,7 +1129,9 @@ def main(argv: list[str] | None = None) -> int:
1125
1129
  elif args.telemetry_command == "signals":
1126
1130
  return cmd_telemetry_signals(args)
1127
1131
  else:
1128
- print("Usage: empathy telemetry {show|savings|export|routing-stats|routing-check|models|agents|signals}")
1132
+ print(
1133
+ "Usage: attune telemetry {show|savings|export|routing-stats|routing-check|models|agents|signals}"
1134
+ )
1129
1135
  return 1
1130
1136
 
1131
1137
  elif args.command == "provider":
@@ -1134,14 +1140,14 @@ def main(argv: list[str] | None = None) -> int:
1134
1140
  elif args.provider_command == "set":
1135
1141
  return cmd_provider_set(args)
1136
1142
  else:
1137
- print("Usage: empathy provider {show|set}")
1143
+ print("Usage: attune provider {show|set}")
1138
1144
  return 1
1139
1145
 
1140
1146
  elif args.command == "dashboard":
1141
1147
  if args.dashboard_command == "start":
1142
1148
  return cmd_dashboard_start(args)
1143
1149
  else:
1144
- print("Usage: empathy dashboard start [--host HOST] [--port PORT]")
1150
+ print("Usage: attune dashboard start [--host HOST] [--port PORT]")
1145
1151
  return 1
1146
1152
 
1147
1153
  elif args.command == "validate":