attune-ai 2.0.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/__init__.py +358 -0
- attune/adaptive/__init__.py +13 -0
- attune/adaptive/task_complexity.py +127 -0
- attune/agent_monitoring.py +414 -0
- attune/cache/__init__.py +117 -0
- attune/cache/base.py +166 -0
- attune/cache/dependency_manager.py +256 -0
- attune/cache/hash_only.py +251 -0
- attune/cache/hybrid.py +457 -0
- attune/cache/storage.py +285 -0
- attune/cache_monitor.py +356 -0
- attune/cache_stats.py +298 -0
- attune/cli/__init__.py +152 -0
- attune/cli/__main__.py +12 -0
- attune/cli/commands/__init__.py +1 -0
- attune/cli/commands/batch.py +264 -0
- attune/cli/commands/cache.py +248 -0
- attune/cli/commands/help.py +331 -0
- attune/cli/commands/info.py +140 -0
- attune/cli/commands/inspect.py +436 -0
- attune/cli/commands/inspection.py +57 -0
- attune/cli/commands/memory.py +48 -0
- attune/cli/commands/metrics.py +92 -0
- attune/cli/commands/orchestrate.py +184 -0
- attune/cli/commands/patterns.py +207 -0
- attune/cli/commands/profiling.py +202 -0
- attune/cli/commands/provider.py +98 -0
- attune/cli/commands/routing.py +285 -0
- attune/cli/commands/setup.py +96 -0
- attune/cli/commands/status.py +235 -0
- attune/cli/commands/sync.py +166 -0
- attune/cli/commands/tier.py +121 -0
- attune/cli/commands/utilities.py +114 -0
- attune/cli/commands/workflow.py +579 -0
- attune/cli/core.py +32 -0
- attune/cli/parsers/__init__.py +68 -0
- attune/cli/parsers/batch.py +118 -0
- attune/cli/parsers/cache.py +65 -0
- attune/cli/parsers/help.py +41 -0
- attune/cli/parsers/info.py +26 -0
- attune/cli/parsers/inspect.py +66 -0
- attune/cli/parsers/metrics.py +42 -0
- attune/cli/parsers/orchestrate.py +61 -0
- attune/cli/parsers/patterns.py +54 -0
- attune/cli/parsers/provider.py +40 -0
- attune/cli/parsers/routing.py +110 -0
- attune/cli/parsers/setup.py +42 -0
- attune/cli/parsers/status.py +47 -0
- attune/cli/parsers/sync.py +31 -0
- attune/cli/parsers/tier.py +33 -0
- attune/cli/parsers/workflow.py +77 -0
- attune/cli/utils/__init__.py +1 -0
- attune/cli/utils/data.py +242 -0
- attune/cli/utils/helpers.py +68 -0
- attune/cli_legacy.py +3957 -0
- attune/cli_minimal.py +1159 -0
- attune/cli_router.py +437 -0
- attune/cli_unified.py +814 -0
- attune/config/__init__.py +66 -0
- attune/config/xml_config.py +286 -0
- attune/config.py +545 -0
- attune/coordination.py +870 -0
- attune/core.py +1511 -0
- attune/core_modules/__init__.py +15 -0
- attune/cost_tracker.py +626 -0
- attune/dashboard/__init__.py +41 -0
- attune/dashboard/app.py +512 -0
- attune/dashboard/simple_server.py +435 -0
- attune/dashboard/standalone_server.py +547 -0
- attune/discovery.py +306 -0
- attune/emergence.py +306 -0
- attune/exceptions.py +123 -0
- attune/feedback_loops.py +373 -0
- attune/hot_reload/README.md +473 -0
- attune/hot_reload/__init__.py +62 -0
- attune/hot_reload/config.py +83 -0
- attune/hot_reload/integration.py +229 -0
- attune/hot_reload/reloader.py +298 -0
- attune/hot_reload/watcher.py +183 -0
- attune/hot_reload/websocket.py +177 -0
- attune/levels.py +577 -0
- attune/leverage_points.py +441 -0
- attune/logging_config.py +261 -0
- attune/mcp/__init__.py +10 -0
- attune/mcp/server.py +506 -0
- attune/memory/__init__.py +237 -0
- attune/memory/claude_memory.py +469 -0
- attune/memory/config.py +224 -0
- attune/memory/control_panel.py +1290 -0
- attune/memory/control_panel_support.py +145 -0
- attune/memory/cross_session.py +845 -0
- attune/memory/edges.py +179 -0
- attune/memory/encryption.py +159 -0
- attune/memory/file_session.py +770 -0
- attune/memory/graph.py +570 -0
- attune/memory/long_term.py +913 -0
- attune/memory/long_term_types.py +99 -0
- attune/memory/mixins/__init__.py +25 -0
- attune/memory/mixins/backend_init_mixin.py +249 -0
- attune/memory/mixins/capabilities_mixin.py +208 -0
- attune/memory/mixins/handoff_mixin.py +208 -0
- attune/memory/mixins/lifecycle_mixin.py +49 -0
- attune/memory/mixins/long_term_mixin.py +352 -0
- attune/memory/mixins/promotion_mixin.py +109 -0
- attune/memory/mixins/short_term_mixin.py +182 -0
- attune/memory/nodes.py +179 -0
- attune/memory/redis_bootstrap.py +540 -0
- attune/memory/security/__init__.py +31 -0
- attune/memory/security/audit_logger.py +932 -0
- attune/memory/security/pii_scrubber.py +640 -0
- attune/memory/security/secrets_detector.py +678 -0
- attune/memory/short_term.py +2192 -0
- attune/memory/simple_storage.py +302 -0
- attune/memory/storage/__init__.py +15 -0
- attune/memory/storage_backend.py +167 -0
- attune/memory/summary_index.py +583 -0
- attune/memory/types.py +446 -0
- attune/memory/unified.py +182 -0
- attune/meta_workflows/__init__.py +74 -0
- attune/meta_workflows/agent_creator.py +248 -0
- attune/meta_workflows/builtin_templates.py +567 -0
- attune/meta_workflows/cli_commands/__init__.py +56 -0
- attune/meta_workflows/cli_commands/agent_commands.py +321 -0
- attune/meta_workflows/cli_commands/analytics_commands.py +442 -0
- attune/meta_workflows/cli_commands/config_commands.py +232 -0
- attune/meta_workflows/cli_commands/memory_commands.py +182 -0
- attune/meta_workflows/cli_commands/template_commands.py +354 -0
- attune/meta_workflows/cli_commands/workflow_commands.py +382 -0
- attune/meta_workflows/cli_meta_workflows.py +59 -0
- attune/meta_workflows/form_engine.py +292 -0
- attune/meta_workflows/intent_detector.py +409 -0
- attune/meta_workflows/models.py +569 -0
- attune/meta_workflows/pattern_learner.py +738 -0
- attune/meta_workflows/plan_generator.py +384 -0
- attune/meta_workflows/session_context.py +397 -0
- attune/meta_workflows/template_registry.py +229 -0
- attune/meta_workflows/workflow.py +984 -0
- attune/metrics/__init__.py +12 -0
- attune/metrics/collector.py +31 -0
- attune/metrics/prompt_metrics.py +194 -0
- attune/models/__init__.py +172 -0
- attune/models/__main__.py +13 -0
- attune/models/adaptive_routing.py +437 -0
- attune/models/auth_cli.py +444 -0
- attune/models/auth_strategy.py +450 -0
- attune/models/cli.py +655 -0
- attune/models/empathy_executor.py +354 -0
- attune/models/executor.py +257 -0
- attune/models/fallback.py +762 -0
- attune/models/provider_config.py +282 -0
- attune/models/registry.py +472 -0
- attune/models/tasks.py +359 -0
- attune/models/telemetry/__init__.py +71 -0
- attune/models/telemetry/analytics.py +594 -0
- attune/models/telemetry/backend.py +196 -0
- attune/models/telemetry/data_models.py +431 -0
- attune/models/telemetry/storage.py +489 -0
- attune/models/token_estimator.py +420 -0
- attune/models/validation.py +280 -0
- attune/monitoring/__init__.py +52 -0
- attune/monitoring/alerts.py +946 -0
- attune/monitoring/alerts_cli.py +448 -0
- attune/monitoring/multi_backend.py +271 -0
- attune/monitoring/otel_backend.py +362 -0
- attune/optimization/__init__.py +19 -0
- attune/optimization/context_optimizer.py +272 -0
- attune/orchestration/__init__.py +67 -0
- attune/orchestration/agent_templates.py +707 -0
- attune/orchestration/config_store.py +499 -0
- attune/orchestration/execution_strategies.py +2111 -0
- attune/orchestration/meta_orchestrator.py +1168 -0
- attune/orchestration/pattern_learner.py +696 -0
- attune/orchestration/real_tools.py +931 -0
- attune/pattern_cache.py +187 -0
- attune/pattern_library.py +542 -0
- attune/patterns/debugging/all_patterns.json +81 -0
- attune/patterns/debugging/workflow_20260107_1770825e.json +77 -0
- attune/patterns/refactoring_memory.json +89 -0
- attune/persistence.py +564 -0
- attune/platform_utils.py +265 -0
- attune/plugins/__init__.py +28 -0
- attune/plugins/base.py +361 -0
- attune/plugins/registry.py +268 -0
- attune/project_index/__init__.py +32 -0
- attune/project_index/cli.py +335 -0
- attune/project_index/index.py +667 -0
- attune/project_index/models.py +504 -0
- attune/project_index/reports.py +474 -0
- attune/project_index/scanner.py +777 -0
- attune/project_index/scanner_parallel.py +291 -0
- attune/prompts/__init__.py +61 -0
- attune/prompts/config.py +77 -0
- attune/prompts/context.py +177 -0
- attune/prompts/parser.py +285 -0
- attune/prompts/registry.py +313 -0
- attune/prompts/templates.py +208 -0
- attune/redis_config.py +302 -0
- attune/redis_memory.py +799 -0
- attune/resilience/__init__.py +56 -0
- attune/resilience/circuit_breaker.py +256 -0
- attune/resilience/fallback.py +179 -0
- attune/resilience/health.py +300 -0
- attune/resilience/retry.py +209 -0
- attune/resilience/timeout.py +135 -0
- attune/routing/__init__.py +43 -0
- attune/routing/chain_executor.py +433 -0
- attune/routing/classifier.py +217 -0
- attune/routing/smart_router.py +234 -0
- attune/routing/workflow_registry.py +343 -0
- attune/scaffolding/README.md +589 -0
- attune/scaffolding/__init__.py +35 -0
- attune/scaffolding/__main__.py +14 -0
- attune/scaffolding/cli.py +240 -0
- attune/scaffolding/templates/base_wizard.py.jinja2 +121 -0
- attune/scaffolding/templates/coach_wizard.py.jinja2 +321 -0
- attune/scaffolding/templates/domain_wizard.py.jinja2 +408 -0
- attune/scaffolding/templates/linear_flow_wizard.py.jinja2 +203 -0
- attune/socratic/__init__.py +256 -0
- attune/socratic/ab_testing.py +958 -0
- attune/socratic/blueprint.py +533 -0
- attune/socratic/cli.py +703 -0
- attune/socratic/collaboration.py +1114 -0
- attune/socratic/domain_templates.py +924 -0
- attune/socratic/embeddings.py +738 -0
- attune/socratic/engine.py +794 -0
- attune/socratic/explainer.py +682 -0
- attune/socratic/feedback.py +772 -0
- attune/socratic/forms.py +629 -0
- attune/socratic/generator.py +732 -0
- attune/socratic/llm_analyzer.py +637 -0
- attune/socratic/mcp_server.py +702 -0
- attune/socratic/session.py +312 -0
- attune/socratic/storage.py +667 -0
- attune/socratic/success.py +730 -0
- attune/socratic/visual_editor.py +860 -0
- attune/socratic/web_ui.py +958 -0
- attune/telemetry/__init__.py +39 -0
- attune/telemetry/agent_coordination.py +475 -0
- attune/telemetry/agent_tracking.py +367 -0
- attune/telemetry/approval_gates.py +545 -0
- attune/telemetry/cli.py +1231 -0
- attune/telemetry/commands/__init__.py +14 -0
- attune/telemetry/commands/dashboard_commands.py +696 -0
- attune/telemetry/event_streaming.py +409 -0
- attune/telemetry/feedback_loop.py +567 -0
- attune/telemetry/usage_tracker.py +591 -0
- attune/templates.py +754 -0
- attune/test_generator/__init__.py +38 -0
- attune/test_generator/__main__.py +14 -0
- attune/test_generator/cli.py +234 -0
- attune/test_generator/generator.py +355 -0
- attune/test_generator/risk_analyzer.py +216 -0
- attune/test_generator/templates/unit_test.py.jinja2 +272 -0
- attune/tier_recommender.py +384 -0
- attune/tools.py +183 -0
- attune/trust/__init__.py +28 -0
- attune/trust/circuit_breaker.py +579 -0
- attune/trust_building.py +527 -0
- attune/validation/__init__.py +19 -0
- attune/validation/xml_validator.py +281 -0
- attune/vscode_bridge.py +173 -0
- attune/workflow_commands.py +780 -0
- attune/workflow_patterns/__init__.py +33 -0
- attune/workflow_patterns/behavior.py +249 -0
- attune/workflow_patterns/core.py +76 -0
- attune/workflow_patterns/output.py +99 -0
- attune/workflow_patterns/registry.py +255 -0
- attune/workflow_patterns/structural.py +288 -0
- attune/workflows/__init__.py +539 -0
- attune/workflows/autonomous_test_gen.py +1268 -0
- attune/workflows/base.py +2667 -0
- attune/workflows/batch_processing.py +342 -0
- attune/workflows/bug_predict.py +1084 -0
- attune/workflows/builder.py +273 -0
- attune/workflows/caching.py +253 -0
- attune/workflows/code_review.py +1048 -0
- attune/workflows/code_review_adapters.py +312 -0
- attune/workflows/code_review_pipeline.py +722 -0
- attune/workflows/config.py +645 -0
- attune/workflows/dependency_check.py +644 -0
- attune/workflows/document_gen/__init__.py +25 -0
- attune/workflows/document_gen/config.py +30 -0
- attune/workflows/document_gen/report_formatter.py +162 -0
- attune/workflows/document_gen/workflow.py +1426 -0
- attune/workflows/document_manager.py +216 -0
- attune/workflows/document_manager_README.md +134 -0
- attune/workflows/documentation_orchestrator.py +1205 -0
- attune/workflows/history.py +510 -0
- attune/workflows/keyboard_shortcuts/__init__.py +39 -0
- attune/workflows/keyboard_shortcuts/generators.py +391 -0
- attune/workflows/keyboard_shortcuts/parsers.py +416 -0
- attune/workflows/keyboard_shortcuts/prompts.py +295 -0
- attune/workflows/keyboard_shortcuts/schema.py +193 -0
- attune/workflows/keyboard_shortcuts/workflow.py +509 -0
- attune/workflows/llm_base.py +363 -0
- attune/workflows/manage_docs.py +87 -0
- attune/workflows/manage_docs_README.md +134 -0
- attune/workflows/manage_documentation.py +821 -0
- attune/workflows/new_sample_workflow1.py +149 -0
- attune/workflows/new_sample_workflow1_README.md +150 -0
- attune/workflows/orchestrated_health_check.py +849 -0
- attune/workflows/orchestrated_release_prep.py +600 -0
- attune/workflows/output.py +413 -0
- attune/workflows/perf_audit.py +863 -0
- attune/workflows/pr_review.py +762 -0
- attune/workflows/progress.py +785 -0
- attune/workflows/progress_server.py +322 -0
- attune/workflows/progressive/README 2.md +454 -0
- attune/workflows/progressive/README.md +454 -0
- attune/workflows/progressive/__init__.py +82 -0
- attune/workflows/progressive/cli.py +219 -0
- attune/workflows/progressive/core.py +488 -0
- attune/workflows/progressive/orchestrator.py +723 -0
- attune/workflows/progressive/reports.py +520 -0
- attune/workflows/progressive/telemetry.py +274 -0
- attune/workflows/progressive/test_gen.py +495 -0
- attune/workflows/progressive/workflow.py +589 -0
- attune/workflows/refactor_plan.py +694 -0
- attune/workflows/release_prep.py +895 -0
- attune/workflows/release_prep_crew.py +969 -0
- attune/workflows/research_synthesis.py +404 -0
- attune/workflows/routing.py +168 -0
- attune/workflows/secure_release.py +593 -0
- attune/workflows/security_adapters.py +297 -0
- attune/workflows/security_audit.py +1329 -0
- attune/workflows/security_audit_phase3.py +355 -0
- attune/workflows/seo_optimization.py +633 -0
- attune/workflows/step_config.py +234 -0
- attune/workflows/telemetry_mixin.py +269 -0
- attune/workflows/test5.py +125 -0
- attune/workflows/test5_README.md +158 -0
- attune/workflows/test_coverage_boost_crew.py +849 -0
- attune/workflows/test_gen/__init__.py +52 -0
- attune/workflows/test_gen/ast_analyzer.py +249 -0
- attune/workflows/test_gen/config.py +88 -0
- attune/workflows/test_gen/data_models.py +38 -0
- attune/workflows/test_gen/report_formatter.py +289 -0
- attune/workflows/test_gen/test_templates.py +381 -0
- attune/workflows/test_gen/workflow.py +655 -0
- attune/workflows/test_gen.py +54 -0
- attune/workflows/test_gen_behavioral.py +477 -0
- attune/workflows/test_gen_parallel.py +341 -0
- attune/workflows/test_lifecycle.py +526 -0
- attune/workflows/test_maintenance.py +627 -0
- attune/workflows/test_maintenance_cli.py +590 -0
- attune/workflows/test_maintenance_crew.py +840 -0
- attune/workflows/test_runner.py +622 -0
- attune/workflows/tier_tracking.py +531 -0
- attune/workflows/xml_enhanced_crew.py +285 -0
- attune_ai-2.0.0.dist-info/METADATA +1026 -0
- attune_ai-2.0.0.dist-info/RECORD +457 -0
- attune_ai-2.0.0.dist-info/WHEEL +5 -0
- attune_ai-2.0.0.dist-info/entry_points.txt +26 -0
- attune_ai-2.0.0.dist-info/licenses/LICENSE +201 -0
- attune_ai-2.0.0.dist-info/licenses/LICENSE_CHANGE_ANNOUNCEMENT.md +101 -0
- attune_ai-2.0.0.dist-info/top_level.txt +5 -0
- attune_healthcare/__init__.py +13 -0
- attune_healthcare/monitors/__init__.py +9 -0
- attune_healthcare/monitors/clinical_protocol_monitor.py +315 -0
- attune_healthcare/monitors/monitoring/__init__.py +44 -0
- attune_healthcare/monitors/monitoring/protocol_checker.py +300 -0
- attune_healthcare/monitors/monitoring/protocol_loader.py +214 -0
- attune_healthcare/monitors/monitoring/sensor_parsers.py +306 -0
- attune_healthcare/monitors/monitoring/trajectory_analyzer.py +389 -0
- attune_llm/README.md +553 -0
- attune_llm/__init__.py +28 -0
- attune_llm/agent_factory/__init__.py +53 -0
- attune_llm/agent_factory/adapters/__init__.py +85 -0
- attune_llm/agent_factory/adapters/autogen_adapter.py +312 -0
- attune_llm/agent_factory/adapters/crewai_adapter.py +483 -0
- attune_llm/agent_factory/adapters/haystack_adapter.py +298 -0
- attune_llm/agent_factory/adapters/langchain_adapter.py +362 -0
- attune_llm/agent_factory/adapters/langgraph_adapter.py +333 -0
- attune_llm/agent_factory/adapters/native.py +228 -0
- attune_llm/agent_factory/adapters/wizard_adapter.py +423 -0
- attune_llm/agent_factory/base.py +305 -0
- attune_llm/agent_factory/crews/__init__.py +67 -0
- attune_llm/agent_factory/crews/code_review.py +1113 -0
- attune_llm/agent_factory/crews/health_check.py +1262 -0
- attune_llm/agent_factory/crews/refactoring.py +1128 -0
- attune_llm/agent_factory/crews/security_audit.py +1018 -0
- attune_llm/agent_factory/decorators.py +287 -0
- attune_llm/agent_factory/factory.py +558 -0
- attune_llm/agent_factory/framework.py +193 -0
- attune_llm/agent_factory/memory_integration.py +328 -0
- attune_llm/agent_factory/resilient.py +320 -0
- attune_llm/agents_md/__init__.py +22 -0
- attune_llm/agents_md/loader.py +218 -0
- attune_llm/agents_md/parser.py +271 -0
- attune_llm/agents_md/registry.py +307 -0
- attune_llm/claude_memory.py +466 -0
- attune_llm/cli/__init__.py +8 -0
- attune_llm/cli/sync_claude.py +487 -0
- attune_llm/code_health.py +1313 -0
- attune_llm/commands/__init__.py +51 -0
- attune_llm/commands/context.py +375 -0
- attune_llm/commands/loader.py +301 -0
- attune_llm/commands/models.py +231 -0
- attune_llm/commands/parser.py +371 -0
- attune_llm/commands/registry.py +429 -0
- attune_llm/config/__init__.py +29 -0
- attune_llm/config/unified.py +291 -0
- attune_llm/context/__init__.py +22 -0
- attune_llm/context/compaction.py +455 -0
- attune_llm/context/manager.py +434 -0
- attune_llm/contextual_patterns.py +361 -0
- attune_llm/core.py +907 -0
- attune_llm/git_pattern_extractor.py +435 -0
- attune_llm/hooks/__init__.py +24 -0
- attune_llm/hooks/config.py +306 -0
- attune_llm/hooks/executor.py +289 -0
- attune_llm/hooks/registry.py +302 -0
- attune_llm/hooks/scripts/__init__.py +39 -0
- attune_llm/hooks/scripts/evaluate_session.py +201 -0
- attune_llm/hooks/scripts/first_time_init.py +285 -0
- attune_llm/hooks/scripts/pre_compact.py +207 -0
- attune_llm/hooks/scripts/session_end.py +183 -0
- attune_llm/hooks/scripts/session_start.py +163 -0
- attune_llm/hooks/scripts/suggest_compact.py +225 -0
- attune_llm/learning/__init__.py +30 -0
- attune_llm/learning/evaluator.py +438 -0
- attune_llm/learning/extractor.py +514 -0
- attune_llm/learning/storage.py +560 -0
- attune_llm/levels.py +227 -0
- attune_llm/pattern_confidence.py +414 -0
- attune_llm/pattern_resolver.py +272 -0
- attune_llm/pattern_summary.py +350 -0
- attune_llm/providers.py +967 -0
- attune_llm/routing/__init__.py +32 -0
- attune_llm/routing/model_router.py +362 -0
- attune_llm/security/IMPLEMENTATION_SUMMARY.md +413 -0
- attune_llm/security/PHASE2_COMPLETE.md +384 -0
- attune_llm/security/PHASE2_SECRETS_DETECTOR_COMPLETE.md +271 -0
- attune_llm/security/QUICK_REFERENCE.md +316 -0
- attune_llm/security/README.md +262 -0
- attune_llm/security/__init__.py +62 -0
- attune_llm/security/audit_logger.py +929 -0
- attune_llm/security/audit_logger_example.py +152 -0
- attune_llm/security/pii_scrubber.py +640 -0
- attune_llm/security/secrets_detector.py +678 -0
- attune_llm/security/secrets_detector_example.py +304 -0
- attune_llm/security/secure_memdocs.py +1192 -0
- attune_llm/security/secure_memdocs_example.py +278 -0
- attune_llm/session_status.py +745 -0
- attune_llm/state.py +246 -0
- attune_llm/utils/__init__.py +5 -0
- attune_llm/utils/tokens.py +349 -0
- attune_software/SOFTWARE_PLUGIN_README.md +57 -0
- attune_software/__init__.py +13 -0
- attune_software/cli/__init__.py +120 -0
- attune_software/cli/inspect.py +362 -0
- attune_software/cli.py +574 -0
- attune_software/plugin.py +188 -0
- workflow_scaffolding/__init__.py +11 -0
- workflow_scaffolding/__main__.py +12 -0
- workflow_scaffolding/cli.py +206 -0
- workflow_scaffolding/generator.py +265 -0
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
"""First-Time Initialization Hook
|
|
2
|
+
|
|
3
|
+
Checks if Empathy Framework is initialized in the current project.
|
|
4
|
+
If not, prompts user with initialization dialog.
|
|
5
|
+
|
|
6
|
+
Copyright 2025 Smart-AI-Memory
|
|
7
|
+
Licensed under Fair Source License 0.9
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
import logging
|
|
12
|
+
from datetime import datetime
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Any
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
# Default configuration template
|
|
19
|
+
DEFAULT_CONFIG = """# Empathy Framework Configuration
|
|
20
|
+
# Generated: {timestamp}
|
|
21
|
+
|
|
22
|
+
agent:
|
|
23
|
+
name: empathy-assistant
|
|
24
|
+
model_tier: capable
|
|
25
|
+
empathy_level: 3
|
|
26
|
+
|
|
27
|
+
hooks:
|
|
28
|
+
enabled: true
|
|
29
|
+
log_executions: false
|
|
30
|
+
|
|
31
|
+
learning:
|
|
32
|
+
enabled: true
|
|
33
|
+
auto_evaluate: true
|
|
34
|
+
quality_threshold: good
|
|
35
|
+
max_patterns_per_session: 10
|
|
36
|
+
|
|
37
|
+
context:
|
|
38
|
+
auto_compact: true
|
|
39
|
+
token_threshold: 80
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
# Directories to create
|
|
43
|
+
INIT_DIRECTORIES = [
|
|
44
|
+
".empathy",
|
|
45
|
+
".attune/compact_states",
|
|
46
|
+
".attune/learned_skills",
|
|
47
|
+
".attune/sessions",
|
|
48
|
+
".attune/patterns",
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def get_project_root(**context: Any) -> Path:
|
|
53
|
+
"""Get the project root directory.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
**context: Hook context with project_path
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
Project root path
|
|
60
|
+
"""
|
|
61
|
+
project_path = context.get("project_path")
|
|
62
|
+
if project_path:
|
|
63
|
+
return Path(project_path)
|
|
64
|
+
|
|
65
|
+
# Fall back to current working directory
|
|
66
|
+
return Path.cwd()
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def is_initialized(project_root: Path) -> bool:
|
|
70
|
+
"""Check if Empathy Framework is initialized in the project.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
project_root: Project root directory
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
True if .empathy directory exists with config
|
|
77
|
+
"""
|
|
78
|
+
empathy_dir = project_root / ".empathy"
|
|
79
|
+
config_file = project_root / "attune.config.yaml"
|
|
80
|
+
|
|
81
|
+
return empathy_dir.exists() or config_file.exists()
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def get_never_ask_file(project_root: Path) -> Path:
|
|
85
|
+
"""Get path to the 'never ask' marker file."""
|
|
86
|
+
return project_root / ".empathy_never_init"
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def should_skip_init(project_root: Path) -> bool:
|
|
90
|
+
"""Check if user previously said 'never ask again'."""
|
|
91
|
+
return get_never_ask_file(project_root).exists()
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def mark_never_ask(project_root: Path) -> None:
|
|
95
|
+
"""Mark project to never ask about init again."""
|
|
96
|
+
marker = get_never_ask_file(project_root)
|
|
97
|
+
marker.write_text(f"Created: {datetime.now().isoformat()}\n")
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def initialize_project(project_root: Path) -> dict[str, Any]:
|
|
101
|
+
"""Initialize Empathy Framework in the project.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
project_root: Project root directory
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
Initialization result
|
|
108
|
+
"""
|
|
109
|
+
result = {
|
|
110
|
+
"success": True,
|
|
111
|
+
"created_directories": [],
|
|
112
|
+
"created_files": [],
|
|
113
|
+
"errors": [],
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
# Create directories
|
|
117
|
+
for dir_path in INIT_DIRECTORIES:
|
|
118
|
+
full_path = project_root / dir_path
|
|
119
|
+
try:
|
|
120
|
+
full_path.mkdir(parents=True, exist_ok=True)
|
|
121
|
+
result["created_directories"].append(str(dir_path))
|
|
122
|
+
logger.info("Created directory: %s", dir_path)
|
|
123
|
+
except OSError as e:
|
|
124
|
+
result["errors"].append(f"Failed to create {dir_path}: {e}")
|
|
125
|
+
logger.error("Failed to create directory %s: %s", dir_path, e)
|
|
126
|
+
|
|
127
|
+
# Create config file
|
|
128
|
+
config_path = project_root / "attune.config.yaml"
|
|
129
|
+
if not config_path.exists():
|
|
130
|
+
try:
|
|
131
|
+
config_content = DEFAULT_CONFIG.format(timestamp=datetime.now().isoformat())
|
|
132
|
+
config_path.write_text(config_content)
|
|
133
|
+
result["created_files"].append("attune.config.yaml")
|
|
134
|
+
logger.info("Created config file: attune.config.yaml")
|
|
135
|
+
except OSError as e:
|
|
136
|
+
result["errors"].append(f"Failed to create config: {e}")
|
|
137
|
+
logger.error("Failed to create config file: %s", e)
|
|
138
|
+
|
|
139
|
+
# Create .gitignore entries file
|
|
140
|
+
gitignore_additions = project_root / ".empathy" / ".gitignore_additions"
|
|
141
|
+
try:
|
|
142
|
+
gitignore_content = """# Add these to your .gitignore:
|
|
143
|
+
.attune/compact_states/
|
|
144
|
+
.attune/sessions/
|
|
145
|
+
.attune/learned_skills/
|
|
146
|
+
"""
|
|
147
|
+
gitignore_additions.write_text(gitignore_content)
|
|
148
|
+
result["created_files"].append(".attune/.gitignore_additions")
|
|
149
|
+
except OSError:
|
|
150
|
+
pass # Non-critical
|
|
151
|
+
|
|
152
|
+
if result["errors"]:
|
|
153
|
+
result["success"] = False
|
|
154
|
+
|
|
155
|
+
return result
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def check_init(**context: Any) -> dict[str, Any]:
|
|
159
|
+
"""Check if initialization is needed and return appropriate response.
|
|
160
|
+
|
|
161
|
+
This is called on SessionStart to check if the project needs initialization.
|
|
162
|
+
|
|
163
|
+
Args:
|
|
164
|
+
**context: Hook context
|
|
165
|
+
|
|
166
|
+
Returns:
|
|
167
|
+
Dict with initialization status and prompt if needed
|
|
168
|
+
"""
|
|
169
|
+
project_root = get_project_root(**context)
|
|
170
|
+
|
|
171
|
+
result = {
|
|
172
|
+
"checked": True,
|
|
173
|
+
"project_root": str(project_root),
|
|
174
|
+
"timestamp": datetime.now().isoformat(),
|
|
175
|
+
"needs_init": False,
|
|
176
|
+
"prompt_user": False,
|
|
177
|
+
"already_initialized": False,
|
|
178
|
+
"skipped": False,
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
# Check if already initialized
|
|
182
|
+
if is_initialized(project_root):
|
|
183
|
+
result["already_initialized"] = True
|
|
184
|
+
logger.debug("Project already initialized: %s", project_root)
|
|
185
|
+
return result
|
|
186
|
+
|
|
187
|
+
# Check if user said "never ask"
|
|
188
|
+
if should_skip_init(project_root):
|
|
189
|
+
result["skipped"] = True
|
|
190
|
+
logger.debug("Skipping init prompt (user preference): %s", project_root)
|
|
191
|
+
return result
|
|
192
|
+
|
|
193
|
+
# Need to prompt user
|
|
194
|
+
result["needs_init"] = True
|
|
195
|
+
result["prompt_user"] = True
|
|
196
|
+
result["prompt"] = {
|
|
197
|
+
"header": "Setup",
|
|
198
|
+
"question": "Welcome! Set up Empathy Framework for this project?",
|
|
199
|
+
"options": [
|
|
200
|
+
{
|
|
201
|
+
"label": "Yes, initialize now",
|
|
202
|
+
"description": "Create .attune/ folder and default config",
|
|
203
|
+
"action": "init",
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
"label": "Not now",
|
|
207
|
+
"description": "Ask me again next session",
|
|
208
|
+
"action": "skip_once",
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
"label": "Never for this project",
|
|
212
|
+
"description": "Don't ask again for this project",
|
|
213
|
+
"action": "never",
|
|
214
|
+
},
|
|
215
|
+
],
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
logger.info("Project needs initialization: %s", project_root)
|
|
219
|
+
return result
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def handle_init_response(action: str, **context: Any) -> dict[str, Any]:
|
|
223
|
+
"""Handle user's response to the initialization prompt.
|
|
224
|
+
|
|
225
|
+
Args:
|
|
226
|
+
action: User's selected action (init, skip_once, never)
|
|
227
|
+
**context: Hook context
|
|
228
|
+
|
|
229
|
+
Returns:
|
|
230
|
+
Result of the action
|
|
231
|
+
"""
|
|
232
|
+
project_root = get_project_root(**context)
|
|
233
|
+
|
|
234
|
+
if action == "init":
|
|
235
|
+
result = initialize_project(project_root)
|
|
236
|
+
if result["success"]:
|
|
237
|
+
result["message"] = "Empathy Framework initialized successfully!"
|
|
238
|
+
else:
|
|
239
|
+
result["message"] = "Initialization completed with errors."
|
|
240
|
+
return result
|
|
241
|
+
|
|
242
|
+
elif action == "never":
|
|
243
|
+
mark_never_ask(project_root)
|
|
244
|
+
return {
|
|
245
|
+
"success": True,
|
|
246
|
+
"action": "never",
|
|
247
|
+
"message": "Got it! Won't ask again for this project.",
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
else: # skip_once
|
|
251
|
+
return {
|
|
252
|
+
"success": True,
|
|
253
|
+
"action": "skip_once",
|
|
254
|
+
"message": "No problem! I'll ask again next time.",
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def main(**context: Any) -> dict[str, Any]:
|
|
259
|
+
"""Main hook entry point.
|
|
260
|
+
|
|
261
|
+
Called on SessionStart to check if initialization is needed.
|
|
262
|
+
|
|
263
|
+
Args:
|
|
264
|
+
**context: Hook context
|
|
265
|
+
|
|
266
|
+
Returns:
|
|
267
|
+
Initialization check result
|
|
268
|
+
"""
|
|
269
|
+
return check_init(**context)
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
if __name__ == "__main__":
|
|
273
|
+
# Allow running as a script for testing
|
|
274
|
+
import sys
|
|
275
|
+
|
|
276
|
+
logging.basicConfig(level=logging.INFO, format="%(message)s")
|
|
277
|
+
|
|
278
|
+
if len(sys.argv) > 1 and sys.argv[1] == "--init":
|
|
279
|
+
# Test initialization
|
|
280
|
+
result = initialize_project(Path.cwd())
|
|
281
|
+
else:
|
|
282
|
+
# Test check
|
|
283
|
+
result = main(project_path=str(Path.cwd()))
|
|
284
|
+
|
|
285
|
+
print(json.dumps(result, indent=2))
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
"""Pre-Compact Hook Script
|
|
2
|
+
|
|
3
|
+
Saves collaboration state before context compaction occurs.
|
|
4
|
+
Ensures trust levels, patterns, and handoffs are preserved.
|
|
5
|
+
|
|
6
|
+
Architectural patterns inspired by everything-claude-code by Affaan Mustafa.
|
|
7
|
+
See: https://github.com/affaan-m/everything-claude-code (MIT License)
|
|
8
|
+
See: ACKNOWLEDGMENTS.md for full attribution.
|
|
9
|
+
|
|
10
|
+
Copyright 2025 Smart AI Memory, LLC
|
|
11
|
+
Licensed under Fair Source 0.9
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import logging
|
|
17
|
+
from datetime import datetime
|
|
18
|
+
from typing import Any
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def run_pre_compact(context: dict[str, Any]) -> dict[str, Any]:
|
|
24
|
+
"""Execute pre-compaction state preservation.
|
|
25
|
+
|
|
26
|
+
This hook is called before context compaction to save
|
|
27
|
+
the current collaboration state for restoration after
|
|
28
|
+
the context window resets.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
context: Hook context containing:
|
|
32
|
+
- collaboration_state: Current CollaborationState
|
|
33
|
+
- context_manager: ContextManager instance
|
|
34
|
+
- session_id: Current session identifier
|
|
35
|
+
- current_phase: Current work phase (if any)
|
|
36
|
+
- pending_work: Description of pending work (if any)
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
Dict with:
|
|
40
|
+
- state_saved: Whether state was successfully saved
|
|
41
|
+
- saved_path: Path to saved state file
|
|
42
|
+
- trust_level: Trust level that was preserved
|
|
43
|
+
- patterns_count: Number of patterns preserved
|
|
44
|
+
- has_handoff: Whether a handoff was saved
|
|
45
|
+
- restoration_available: Whether restoration will be available
|
|
46
|
+
- message: Human-readable summary
|
|
47
|
+
"""
|
|
48
|
+
try:
|
|
49
|
+
# Get required components from context
|
|
50
|
+
collaboration_state = context.get("collaboration_state")
|
|
51
|
+
context_manager = context.get("context_manager")
|
|
52
|
+
|
|
53
|
+
if not collaboration_state:
|
|
54
|
+
logger.warning("Pre-compact: No collaboration state provided")
|
|
55
|
+
return {
|
|
56
|
+
"state_saved": False,
|
|
57
|
+
"saved_path": None,
|
|
58
|
+
"trust_level": None,
|
|
59
|
+
"patterns_count": 0,
|
|
60
|
+
"has_handoff": False,
|
|
61
|
+
"restoration_available": False,
|
|
62
|
+
"message": "No collaboration state available to save",
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
# Initialize context manager if not provided
|
|
66
|
+
if not context_manager:
|
|
67
|
+
from attune_llm.context import ContextManager
|
|
68
|
+
|
|
69
|
+
context_manager = ContextManager()
|
|
70
|
+
|
|
71
|
+
# Set session tracking if provided
|
|
72
|
+
if session_id := context.get("session_id"):
|
|
73
|
+
context_manager.session_id = session_id
|
|
74
|
+
|
|
75
|
+
if current_phase := context.get("current_phase"):
|
|
76
|
+
context_manager.current_phase = current_phase
|
|
77
|
+
|
|
78
|
+
# Create SBAR handoff for pending work if provided
|
|
79
|
+
pending_work = context.get("pending_work")
|
|
80
|
+
if pending_work:
|
|
81
|
+
context_manager.set_handoff(
|
|
82
|
+
situation=pending_work.get("situation", "Context compaction in progress"),
|
|
83
|
+
background=pending_work.get("background", "Session state being preserved"),
|
|
84
|
+
assessment=pending_work.get("assessment", "Work can continue after restoration"),
|
|
85
|
+
recommendation=pending_work.get("recommendation", "Restore state and continue"),
|
|
86
|
+
priority=pending_work.get("priority", "normal"),
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
# Save the state
|
|
90
|
+
saved_path = context_manager.save_for_compaction(collaboration_state)
|
|
91
|
+
|
|
92
|
+
# Extract the compact state for reporting
|
|
93
|
+
compact_state = context_manager.extract_compact_state(collaboration_state)
|
|
94
|
+
|
|
95
|
+
# Build success message
|
|
96
|
+
patterns_count = len(compact_state.detected_patterns)
|
|
97
|
+
message_parts = [
|
|
98
|
+
"State preserved successfully.",
|
|
99
|
+
f"Trust level: {compact_state.trust_level:.2f}",
|
|
100
|
+
f"Empathy level: {compact_state.empathy_level}",
|
|
101
|
+
]
|
|
102
|
+
|
|
103
|
+
if patterns_count > 0:
|
|
104
|
+
message_parts.append(f"Patterns preserved: {patterns_count}")
|
|
105
|
+
|
|
106
|
+
if compact_state.pending_handoff:
|
|
107
|
+
message_parts.append(
|
|
108
|
+
f"Handoff recorded ({compact_state.pending_handoff.priority} priority)"
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
logger.info(f"Pre-compact: Saved state to {saved_path}")
|
|
112
|
+
|
|
113
|
+
return {
|
|
114
|
+
"state_saved": True,
|
|
115
|
+
"saved_path": str(saved_path),
|
|
116
|
+
"trust_level": compact_state.trust_level,
|
|
117
|
+
"empathy_level": compact_state.empathy_level,
|
|
118
|
+
"patterns_count": patterns_count,
|
|
119
|
+
"has_handoff": compact_state.pending_handoff is not None,
|
|
120
|
+
"restoration_available": True,
|
|
121
|
+
"saved_at": datetime.now().isoformat(),
|
|
122
|
+
"message": " | ".join(message_parts),
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
except Exception as e:
|
|
126
|
+
logger.exception(f"Pre-compact hook failed: {e}")
|
|
127
|
+
return {
|
|
128
|
+
"state_saved": False,
|
|
129
|
+
"saved_path": None,
|
|
130
|
+
"trust_level": None,
|
|
131
|
+
"patterns_count": 0,
|
|
132
|
+
"has_handoff": False,
|
|
133
|
+
"restoration_available": False,
|
|
134
|
+
"error": str(e),
|
|
135
|
+
"message": f"Failed to save state: {e}",
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def generate_compaction_summary(
|
|
140
|
+
collaboration_state: Any,
|
|
141
|
+
include_patterns: bool = True,
|
|
142
|
+
include_history: bool = False,
|
|
143
|
+
) -> str:
|
|
144
|
+
"""Generate a summary suitable for including in compacted context.
|
|
145
|
+
|
|
146
|
+
This can be used to create a brief summary that gets included
|
|
147
|
+
in the compacted context window.
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
collaboration_state: Current CollaborationState
|
|
151
|
+
include_patterns: Whether to include pattern summaries
|
|
152
|
+
include_history: Whether to include recent interaction summary
|
|
153
|
+
|
|
154
|
+
Returns:
|
|
155
|
+
Formatted summary string
|
|
156
|
+
"""
|
|
157
|
+
lines = [
|
|
158
|
+
"## Session Context Summary",
|
|
159
|
+
"",
|
|
160
|
+
f"**User**: {collaboration_state.user_id}",
|
|
161
|
+
f"**Trust**: {collaboration_state.trust_level:.2f}",
|
|
162
|
+
f"**Empathy Level**: {collaboration_state.current_level}",
|
|
163
|
+
f"**Interactions**: {len(collaboration_state.interactions)}",
|
|
164
|
+
"",
|
|
165
|
+
]
|
|
166
|
+
|
|
167
|
+
if include_patterns and collaboration_state.detected_patterns:
|
|
168
|
+
lines.append("### Known Patterns")
|
|
169
|
+
for pattern in collaboration_state.detected_patterns[:5]:
|
|
170
|
+
confidence_pct = int(pattern.confidence * 100)
|
|
171
|
+
lines.append(f"- {pattern.trigger} → {pattern.action} ({confidence_pct}%)")
|
|
172
|
+
lines.append("")
|
|
173
|
+
|
|
174
|
+
if include_history and collaboration_state.interactions:
|
|
175
|
+
lines.append("### Recent Context")
|
|
176
|
+
# Get last few exchanges
|
|
177
|
+
recent = collaboration_state.interactions[-6:]
|
|
178
|
+
for interaction in recent:
|
|
179
|
+
role = interaction.role.capitalize()
|
|
180
|
+
content = interaction.content[:100]
|
|
181
|
+
if len(interaction.content) > 100:
|
|
182
|
+
content += "..."
|
|
183
|
+
lines.append(f"- **{role}**: {content}")
|
|
184
|
+
lines.append("")
|
|
185
|
+
|
|
186
|
+
# Add preferences summary
|
|
187
|
+
if collaboration_state.preferences:
|
|
188
|
+
lines.append("### Preferences")
|
|
189
|
+
for key, value in list(collaboration_state.preferences.items())[:5]:
|
|
190
|
+
lines.append(f"- **{key}**: {value}")
|
|
191
|
+
lines.append("")
|
|
192
|
+
|
|
193
|
+
return "\n".join(lines)
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
# Main entry point for hook execution
|
|
197
|
+
if __name__ == "__main__":
|
|
198
|
+
# Example usage for testing
|
|
199
|
+
print("Pre-compact hook script")
|
|
200
|
+
print("This script is called before context compaction")
|
|
201
|
+
print()
|
|
202
|
+
print("Required context keys:")
|
|
203
|
+
print(" - collaboration_state: CollaborationState object")
|
|
204
|
+
print(" - context_manager: ContextManager instance (optional)")
|
|
205
|
+
print(" - session_id: Current session ID (optional)")
|
|
206
|
+
print(" - current_phase: Current work phase (optional)")
|
|
207
|
+
print(" - pending_work: Dict with SBAR fields (optional)")
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
"""Session End Hook
|
|
2
|
+
|
|
3
|
+
Persists session state and triggers pattern evaluation.
|
|
4
|
+
Ported from everything-claude-code/scripts/hooks/session-end.js
|
|
5
|
+
|
|
6
|
+
Copyright 2025 Smart-AI-Memory
|
|
7
|
+
Licensed under Fair Source License 0.9
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
import logging
|
|
12
|
+
from datetime import datetime
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Any
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def get_sessions_dir() -> Path:
|
|
20
|
+
"""Get the sessions directory path."""
|
|
21
|
+
return Path.home() / ".empathy" / "sessions"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def save_session_state(
|
|
25
|
+
session_id: str,
|
|
26
|
+
state: dict[str, Any],
|
|
27
|
+
) -> Path:
|
|
28
|
+
"""Save session state to a file.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
session_id: Unique session identifier
|
|
32
|
+
state: Session state to save
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
Path to saved file
|
|
36
|
+
|
|
37
|
+
"""
|
|
38
|
+
sessions_dir = get_sessions_dir()
|
|
39
|
+
sessions_dir.mkdir(parents=True, exist_ok=True)
|
|
40
|
+
|
|
41
|
+
# Create filename with timestamp
|
|
42
|
+
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
43
|
+
filename = f"session_{session_id}_{timestamp}.json"
|
|
44
|
+
file_path = sessions_dir / filename
|
|
45
|
+
|
|
46
|
+
# Add metadata
|
|
47
|
+
state_with_meta = {
|
|
48
|
+
**state,
|
|
49
|
+
"saved_at": datetime.now().isoformat(),
|
|
50
|
+
"session_id": session_id,
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
with open(file_path, "w") as f:
|
|
54
|
+
json.dump(state_with_meta, f, indent=2, default=str)
|
|
55
|
+
|
|
56
|
+
logger.info("Saved session state to %s", file_path)
|
|
57
|
+
return file_path
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def cleanup_old_sessions(max_sessions: int = 50) -> int:
|
|
61
|
+
"""Remove old session files beyond max_sessions.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
max_sessions: Maximum number of session files to keep
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
Number of files removed
|
|
68
|
+
|
|
69
|
+
"""
|
|
70
|
+
sessions_dir = get_sessions_dir()
|
|
71
|
+
|
|
72
|
+
if not sessions_dir.exists():
|
|
73
|
+
return 0
|
|
74
|
+
|
|
75
|
+
session_files = sorted(
|
|
76
|
+
sessions_dir.glob("session_*.json"),
|
|
77
|
+
key=lambda p: p.stat().st_mtime,
|
|
78
|
+
reverse=True,
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
removed = 0
|
|
82
|
+
for old_file in session_files[max_sessions:]:
|
|
83
|
+
try:
|
|
84
|
+
old_file.unlink()
|
|
85
|
+
removed += 1
|
|
86
|
+
logger.debug("Removed old session file: %s", old_file)
|
|
87
|
+
except OSError as e:
|
|
88
|
+
logger.warning("Failed to remove %s: %s", old_file, e)
|
|
89
|
+
|
|
90
|
+
return removed
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def main(**context: Any) -> dict[str, Any]:
|
|
94
|
+
"""Session end hook main function.
|
|
95
|
+
|
|
96
|
+
Persists:
|
|
97
|
+
- Trust level and collaboration state
|
|
98
|
+
- Detected patterns and preferences
|
|
99
|
+
- Interaction statistics
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
**context: Hook context with session state
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
Session end summary
|
|
106
|
+
|
|
107
|
+
"""
|
|
108
|
+
session_id = context.get("session_id", datetime.now().strftime("%Y%m%d%H%M%S"))
|
|
109
|
+
|
|
110
|
+
# Extract state to persist
|
|
111
|
+
state_to_save = {
|
|
112
|
+
"trust_level": context.get("trust_level", 0.5),
|
|
113
|
+
"empathy_level": context.get("empathy_level", 4),
|
|
114
|
+
"interaction_count": context.get("interaction_count", 0),
|
|
115
|
+
"detected_patterns": context.get("detected_patterns", []),
|
|
116
|
+
"user_preferences": context.get("user_preferences", {}),
|
|
117
|
+
"completed_phases": context.get("completed_phases", []),
|
|
118
|
+
"pending_handoff": context.get("pending_handoff"),
|
|
119
|
+
"metrics": {
|
|
120
|
+
"success_rate": context.get("success_rate", 0),
|
|
121
|
+
"total_tokens": context.get("total_tokens", 0),
|
|
122
|
+
"total_cost": context.get("total_cost", 0),
|
|
123
|
+
},
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
result = {
|
|
127
|
+
"session_id": session_id,
|
|
128
|
+
"timestamp": datetime.now().isoformat(),
|
|
129
|
+
"state_saved": False,
|
|
130
|
+
"file_path": None,
|
|
131
|
+
"old_sessions_removed": 0,
|
|
132
|
+
"evaluate_for_learning": False,
|
|
133
|
+
"messages": [],
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
try:
|
|
137
|
+
# Save session state
|
|
138
|
+
file_path = save_session_state(session_id, state_to_save)
|
|
139
|
+
result["state_saved"] = True
|
|
140
|
+
result["file_path"] = str(file_path)
|
|
141
|
+
result["messages"].append(f"[SessionEnd] State saved to {file_path.name}")
|
|
142
|
+
|
|
143
|
+
# Cleanup old sessions
|
|
144
|
+
removed = cleanup_old_sessions()
|
|
145
|
+
result["old_sessions_removed"] = removed
|
|
146
|
+
if removed > 0:
|
|
147
|
+
result["messages"].append(f"[SessionEnd] Removed {removed} old session file(s)")
|
|
148
|
+
|
|
149
|
+
# Check if session should be evaluated for learning
|
|
150
|
+
min_interactions = context.get("min_learning_interactions", 10)
|
|
151
|
+
if state_to_save["interaction_count"] >= min_interactions:
|
|
152
|
+
result["evaluate_for_learning"] = True
|
|
153
|
+
result["messages"].append(
|
|
154
|
+
f"[SessionEnd] Session has {state_to_save['interaction_count']} "
|
|
155
|
+
f"interactions - evaluate for pattern extraction"
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
except Exception as e:
|
|
159
|
+
logger.error("Failed to save session state: %s", e)
|
|
160
|
+
result["messages"].append(f"[SessionEnd] Error: {e}")
|
|
161
|
+
|
|
162
|
+
# Log messages
|
|
163
|
+
for msg in result["messages"]:
|
|
164
|
+
logger.info(msg)
|
|
165
|
+
|
|
166
|
+
return result
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
if __name__ == "__main__":
|
|
170
|
+
# Allow running as a script for testing
|
|
171
|
+
|
|
172
|
+
logging.basicConfig(level=logging.INFO, format="%(message)s")
|
|
173
|
+
|
|
174
|
+
# Simulate some context
|
|
175
|
+
test_context = {
|
|
176
|
+
"session_id": "test_session",
|
|
177
|
+
"trust_level": 0.75,
|
|
178
|
+
"interaction_count": 15,
|
|
179
|
+
"detected_patterns": [{"type": "preference", "value": "concise responses"}],
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
result = main(**test_context)
|
|
183
|
+
print(json.dumps(result, indent=2))
|