attune-ai 2.1.5__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.
- attune/cli/__init__.py +3 -59
- attune/cli/commands/batch.py +4 -12
- attune/cli/commands/cache.py +7 -15
- attune/cli/commands/provider.py +17 -0
- attune/cli/commands/routing.py +3 -1
- attune/cli/commands/setup.py +122 -0
- attune/cli/commands/tier.py +1 -3
- attune/cli/commands/workflow.py +31 -0
- attune/cli/parsers/cache.py +1 -0
- attune/cli/parsers/help.py +1 -3
- attune/cli/parsers/provider.py +7 -0
- attune/cli/parsers/routing.py +1 -3
- attune/cli/parsers/setup.py +7 -0
- attune/cli/parsers/status.py +1 -3
- attune/cli/parsers/tier.py +1 -3
- attune/cli_minimal.py +9 -3
- attune/cli_router.py +9 -7
- attune/cli_unified.py +3 -0
- attune/dashboard/app.py +3 -1
- attune/dashboard/simple_server.py +3 -1
- attune/dashboard/standalone_server.py +7 -3
- attune/mcp/server.py +54 -102
- attune/memory/long_term.py +0 -2
- attune/memory/short_term/__init__.py +84 -0
- attune/memory/short_term/base.py +467 -0
- attune/memory/short_term/batch.py +219 -0
- attune/memory/short_term/caching.py +227 -0
- attune/memory/short_term/conflicts.py +265 -0
- attune/memory/short_term/cross_session.py +122 -0
- attune/memory/short_term/facade.py +655 -0
- attune/memory/short_term/pagination.py +215 -0
- attune/memory/short_term/patterns.py +271 -0
- attune/memory/short_term/pubsub.py +286 -0
- attune/memory/short_term/queues.py +244 -0
- attune/memory/short_term/security.py +300 -0
- attune/memory/short_term/sessions.py +250 -0
- attune/memory/short_term/streams.py +249 -0
- attune/memory/short_term/timelines.py +234 -0
- attune/memory/short_term/transactions.py +186 -0
- attune/memory/short_term/working.py +252 -0
- attune/meta_workflows/cli_commands/__init__.py +3 -0
- attune/meta_workflows/cli_commands/agent_commands.py +0 -4
- attune/meta_workflows/cli_commands/analytics_commands.py +0 -6
- attune/meta_workflows/cli_commands/config_commands.py +0 -5
- attune/meta_workflows/cli_commands/memory_commands.py +0 -5
- attune/meta_workflows/cli_commands/template_commands.py +0 -5
- attune/meta_workflows/cli_commands/workflow_commands.py +0 -6
- attune/models/adaptive_routing.py +4 -8
- attune/models/auth_cli.py +3 -9
- attune/models/auth_strategy.py +2 -4
- attune/models/telemetry/analytics.py +0 -2
- attune/models/telemetry/backend.py +0 -3
- attune/models/telemetry/storage.py +0 -2
- attune/orchestration/_strategies/__init__.py +156 -0
- attune/orchestration/_strategies/base.py +231 -0
- attune/orchestration/_strategies/conditional_strategies.py +373 -0
- attune/orchestration/_strategies/conditions.py +369 -0
- attune/orchestration/_strategies/core_strategies.py +491 -0
- attune/orchestration/_strategies/data_classes.py +64 -0
- attune/orchestration/_strategies/nesting.py +233 -0
- attune/orchestration/execution_strategies.py +58 -1567
- attune/orchestration/meta_orchestrator.py +1 -3
- attune/project_index/scanner.py +1 -3
- attune/project_index/scanner_parallel.py +7 -5
- attune/socratic_router.py +1 -3
- attune/telemetry/agent_coordination.py +9 -3
- attune/telemetry/agent_tracking.py +16 -3
- attune/telemetry/approval_gates.py +22 -5
- attune/telemetry/cli.py +1 -3
- attune/telemetry/commands/dashboard_commands.py +24 -8
- attune/telemetry/event_streaming.py +8 -2
- attune/telemetry/feedback_loop.py +10 -2
- attune/tools.py +1 -0
- attune/workflow_commands.py +1 -3
- attune/workflows/__init__.py +53 -10
- attune/workflows/autonomous_test_gen.py +158 -102
- attune/workflows/base.py +48 -672
- attune/workflows/batch_processing.py +1 -3
- attune/workflows/compat.py +156 -0
- attune/workflows/cost_mixin.py +141 -0
- attune/workflows/data_classes.py +92 -0
- attune/workflows/document_gen/workflow.py +11 -14
- attune/workflows/history.py +62 -37
- attune/workflows/llm_base.py +1 -3
- attune/workflows/migration.py +422 -0
- attune/workflows/output.py +2 -7
- attune/workflows/parsing_mixin.py +427 -0
- attune/workflows/perf_audit.py +3 -1
- attune/workflows/progress.py +9 -11
- attune/workflows/release_prep.py +5 -1
- attune/workflows/routing.py +0 -2
- attune/workflows/secure_release.py +2 -1
- attune/workflows/security_audit.py +19 -14
- attune/workflows/security_audit_phase3.py +28 -22
- attune/workflows/seo_optimization.py +27 -27
- attune/workflows/test_gen/test_templates.py +1 -4
- attune/workflows/test_gen/workflow.py +0 -2
- attune/workflows/test_gen_behavioral.py +6 -19
- attune/workflows/test_gen_parallel.py +6 -4
- {attune_ai-2.1.5.dist-info → attune_ai-2.2.0.dist-info}/METADATA +4 -3
- {attune_ai-2.1.5.dist-info → attune_ai-2.2.0.dist-info}/RECORD +116 -91
- {attune_ai-2.1.5.dist-info → attune_ai-2.2.0.dist-info}/entry_points.txt +0 -2
- attune_healthcare/monitors/monitoring/__init__.py +9 -9
- attune_llm/agent_factory/__init__.py +6 -6
- attune_llm/commands/__init__.py +10 -10
- attune_llm/commands/models.py +3 -3
- attune_llm/config/__init__.py +8 -8
- attune_llm/learning/__init__.py +3 -3
- attune_llm/learning/extractor.py +5 -3
- attune_llm/learning/storage.py +5 -3
- attune_llm/security/__init__.py +17 -17
- attune_llm/utils/tokens.py +3 -1
- attune/cli_legacy.py +0 -3978
- attune/memory/short_term.py +0 -2192
- attune/workflows/manage_docs.py +0 -87
- attune/workflows/test5.py +0 -125
- {attune_ai-2.1.5.dist-info → attune_ai-2.2.0.dist-info}/WHEEL +0 -0
- {attune_ai-2.1.5.dist-info → attune_ai-2.2.0.dist-info}/licenses/LICENSE +0 -0
- {attune_ai-2.1.5.dist-info → attune_ai-2.2.0.dist-info}/licenses/LICENSE_CHANGE_ANNOUNCEMENT.md +0 -0
- {attune_ai-2.1.5.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
|
-
#
|
|
64
|
-
#
|
|
65
|
-
#
|
|
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,50 +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.commands.patterns import (
|
|
129
|
-
cmd_patterns_export,
|
|
130
|
-
cmd_patterns_list,
|
|
131
|
-
cmd_patterns_resolve,
|
|
132
|
-
)
|
|
133
|
-
|
|
134
|
-
patterns_parser = subparsers.add_parser("patterns", help="Pattern management")
|
|
135
|
-
patterns_sub = patterns_parser.add_subparsers(dest="patterns_command")
|
|
136
|
-
|
|
137
|
-
p_list = patterns_sub.add_parser("list", help="List patterns")
|
|
138
|
-
p_list.set_defaults(func=cmd_patterns_list)
|
|
139
|
-
|
|
140
|
-
p_export = patterns_sub.add_parser("export", help="Export patterns")
|
|
141
|
-
p_export.add_argument("output", help="Output file")
|
|
142
|
-
p_export.set_defaults(func=cmd_patterns_export)
|
|
143
|
-
|
|
144
|
-
p_resolve = patterns_sub.add_parser("resolve", help="Resolve pattern")
|
|
145
|
-
p_resolve.add_argument("pattern_id", help="Pattern ID")
|
|
146
|
-
p_resolve.set_defaults(func=cmd_patterns_resolve)
|
|
147
|
-
except (ImportError, AttributeError):
|
|
148
|
-
pass # Commands not available
|
|
149
|
-
|
|
150
|
-
# Add other legacy commands as needed...
|
|
151
|
-
# This list will shrink as commands are extracted
|
|
152
|
-
|
|
153
|
-
|
|
154
98
|
# Preserve backward compatibility
|
|
155
99
|
if __name__ == "__main__":
|
|
156
100
|
sys.exit(main())
|
attune/cli/commands/batch.py
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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}")
|
attune/cli/commands/cache.py
CHANGED
|
@@ -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[
|
|
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[
|
|
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[
|
|
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:")
|
attune/cli/commands/provider.py
CHANGED
|
@@ -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()
|
attune/cli/commands/routing.py
CHANGED
|
@@ -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(
|
|
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:
|
attune/cli/commands/setup.py
CHANGED
|
@@ -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")
|
attune/cli/commands/tier.py
CHANGED
|
@@ -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}%)")
|
attune/cli/commands/workflow.py
CHANGED
|
@@ -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")
|
attune/cli/parsers/cache.py
CHANGED
attune/cli/parsers/help.py
CHANGED
|
@@ -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
|
attune/cli/parsers/provider.py
CHANGED
|
@@ -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)
|
attune/cli/parsers/routing.py
CHANGED
|
@@ -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(
|
attune/cli/parsers/setup.py
CHANGED
|
@@ -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)
|
attune/cli/parsers/status.py
CHANGED
|
@@ -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")
|
attune/cli/parsers/tier.py
CHANGED
|
@@ -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
|
@@ -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(
|
|
1077
|
-
|
|
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")
|
|
@@ -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(
|
|
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":
|
attune/cli_router.py
CHANGED
|
@@ -195,9 +195,7 @@ class HybridRouter:
|
|
|
195
195
|
with open(self.preferences_path, "w") as f:
|
|
196
196
|
yaml.dump(data, f, default_flow_style=False)
|
|
197
197
|
|
|
198
|
-
async def route(
|
|
199
|
-
self, user_input: str, context: dict[str, Any] | None = None
|
|
200
|
-
) -> dict[str, Any]:
|
|
198
|
+
async def route(self, user_input: str, context: dict[str, Any] | None = None) -> dict[str, Any]:
|
|
201
199
|
"""Route user input to appropriate command or workflow.
|
|
202
200
|
|
|
203
201
|
Args:
|
|
@@ -242,7 +240,8 @@ class HybridRouter:
|
|
|
242
240
|
"args": args,
|
|
243
241
|
"original": command,
|
|
244
242
|
"confidence": 1.0,
|
|
245
|
-
"instruction": f"Use Skill tool with skill='{skill}'"
|
|
243
|
+
"instruction": f"Use Skill tool with skill='{skill}'"
|
|
244
|
+
+ (f", args='{args}'" if args else ""),
|
|
246
245
|
}
|
|
247
246
|
|
|
248
247
|
def _infer_command(self, keyword: str) -> dict[str, Any] | None:
|
|
@@ -271,7 +270,8 @@ class HybridRouter:
|
|
|
271
270
|
"original": keyword,
|
|
272
271
|
"confidence": pref.confidence,
|
|
273
272
|
"source": "learned",
|
|
274
|
-
"instruction": f"Use Skill tool with skill='{pref.skill}'"
|
|
273
|
+
"instruction": f"Use Skill tool with skill='{pref.skill}'"
|
|
274
|
+
+ (f", args='{pref.args}'" if pref.args else ""),
|
|
275
275
|
}
|
|
276
276
|
|
|
277
277
|
# Check built-in keyword map
|
|
@@ -284,7 +284,8 @@ class HybridRouter:
|
|
|
284
284
|
"original": keyword,
|
|
285
285
|
"confidence": 0.9,
|
|
286
286
|
"source": "builtin",
|
|
287
|
-
"instruction": f"Use Skill tool with skill='{skill}'"
|
|
287
|
+
"instruction": f"Use Skill tool with skill='{skill}'"
|
|
288
|
+
+ (f", args='{args}'" if args else ""),
|
|
288
289
|
}
|
|
289
290
|
|
|
290
291
|
# Check for hub names (show hub menu)
|
|
@@ -329,7 +330,8 @@ class HybridRouter:
|
|
|
329
330
|
"reasoning": decision.reasoning,
|
|
330
331
|
"original": text,
|
|
331
332
|
"source": "natural_language",
|
|
332
|
-
"instruction": f"Use Skill tool with skill='{skill}'"
|
|
333
|
+
"instruction": f"Use Skill tool with skill='{skill}'"
|
|
334
|
+
+ (f", args='{args}'" if args else ""),
|
|
333
335
|
}
|
|
334
336
|
|
|
335
337
|
def _workflow_to_skill(self, workflow: str) -> tuple[str, str]:
|
attune/cli_unified.py
CHANGED
attune/dashboard/app.py
CHANGED
|
@@ -377,7 +377,9 @@ async def get_underperforming_stages(threshold: float = 0.7):
|
|
|
377
377
|
all_underperforming = []
|
|
378
378
|
|
|
379
379
|
for workflow in workflows:
|
|
380
|
-
underperforming = feedback.get_underperforming_stages(
|
|
380
|
+
underperforming = feedback.get_underperforming_stages(
|
|
381
|
+
workflow, quality_threshold=threshold
|
|
382
|
+
)
|
|
381
383
|
for stage_name, stats in underperforming:
|
|
382
384
|
all_underperforming.append(
|
|
383
385
|
{
|
|
@@ -378,7 +378,9 @@ class DashboardHandler(BaseHTTPRequestHandler):
|
|
|
378
378
|
all_underperforming = []
|
|
379
379
|
|
|
380
380
|
for workflow in workflows:
|
|
381
|
-
underperforming = feedback.get_underperforming_stages(
|
|
381
|
+
underperforming = feedback.get_underperforming_stages(
|
|
382
|
+
workflow, quality_threshold=threshold
|
|
383
|
+
)
|
|
382
384
|
for stage_name, stats in underperforming:
|
|
383
385
|
all_underperforming.append(
|
|
384
386
|
{
|
|
@@ -305,7 +305,11 @@ class StandaloneDashboardHandler(BaseHTTPRequestHandler):
|
|
|
305
305
|
|
|
306
306
|
result.append(
|
|
307
307
|
{
|
|
308
|
-
"event_id":
|
|
308
|
+
"event_id": (
|
|
309
|
+
entry_id.decode("utf-8")
|
|
310
|
+
if isinstance(entry_id, bytes)
|
|
311
|
+
else entry_id
|
|
312
|
+
),
|
|
309
313
|
"event_type": event_type,
|
|
310
314
|
"timestamp": timestamp,
|
|
311
315
|
"data": data,
|
|
@@ -423,7 +427,7 @@ class StandaloneDashboardHandler(BaseHTTPRequestHandler):
|
|
|
423
427
|
|
|
424
428
|
# Calculate stats
|
|
425
429
|
result = []
|
|
426
|
-
for
|
|
430
|
+
for _group_key, group in feedback_groups.items():
|
|
427
431
|
qualities = group["qualities"]
|
|
428
432
|
if qualities:
|
|
429
433
|
avg_quality = sum(qualities) / len(qualities)
|
|
@@ -476,7 +480,7 @@ class StandaloneDashboardHandler(BaseHTTPRequestHandler):
|
|
|
476
480
|
|
|
477
481
|
# Find underperforming stages
|
|
478
482
|
result = []
|
|
479
|
-
for
|
|
483
|
+
for _group_key, group in feedback_groups.items():
|
|
480
484
|
qualities = group["qualities"]
|
|
481
485
|
if qualities:
|
|
482
486
|
avg_quality = sum(qualities) / len(qualities)
|