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,514 @@
|
|
|
1
|
+
"""Pattern Extractor for Continuous Learning
|
|
2
|
+
|
|
3
|
+
Extracts learnable patterns from collaboration sessions.
|
|
4
|
+
Identifies and structures patterns for storage and future application.
|
|
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 hashlib
|
|
17
|
+
import logging
|
|
18
|
+
import re
|
|
19
|
+
from dataclasses import dataclass, field
|
|
20
|
+
from datetime import datetime
|
|
21
|
+
from enum import Enum
|
|
22
|
+
from typing import TYPE_CHECKING, Any
|
|
23
|
+
|
|
24
|
+
if TYPE_CHECKING:
|
|
25
|
+
from attune_llm.state import CollaborationState
|
|
26
|
+
|
|
27
|
+
logger = logging.getLogger(__name__)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class PatternCategory(Enum):
|
|
31
|
+
"""Categories of extractable patterns."""
|
|
32
|
+
|
|
33
|
+
ERROR_RESOLUTION = "error_resolution" # How errors were resolved
|
|
34
|
+
USER_CORRECTION = "user_correction" # "Actually, I meant..." patterns
|
|
35
|
+
WORKAROUND = "workaround" # Framework quirk solutions
|
|
36
|
+
PREFERENCE = "preference" # Response format preferences
|
|
37
|
+
PROJECT_SPECIFIC = "project_specific" # Project conventions
|
|
38
|
+
DEBUGGING_TECHNIQUE = "debugging_technique" # Debugging approaches
|
|
39
|
+
CODE_PATTERN = "code_pattern" # Code-related patterns
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass
|
|
43
|
+
class ExtractedPattern:
|
|
44
|
+
"""A pattern extracted from a session."""
|
|
45
|
+
|
|
46
|
+
category: PatternCategory
|
|
47
|
+
trigger: str # What triggers this pattern
|
|
48
|
+
context: str # Context in which it applies
|
|
49
|
+
resolution: str # What was done / learned
|
|
50
|
+
confidence: float # Extraction confidence (0.0-1.0)
|
|
51
|
+
source_session: str # Session ID
|
|
52
|
+
extracted_at: datetime = field(default_factory=datetime.now)
|
|
53
|
+
tags: list[str] = field(default_factory=list)
|
|
54
|
+
metadata: dict[str, Any] = field(default_factory=dict)
|
|
55
|
+
|
|
56
|
+
@property
|
|
57
|
+
def pattern_id(self) -> str:
|
|
58
|
+
"""Generate unique pattern ID."""
|
|
59
|
+
content = f"{self.category.value}:{self.trigger}:{self.resolution}"
|
|
60
|
+
return hashlib.sha256(content.encode()).hexdigest()[:12]
|
|
61
|
+
|
|
62
|
+
def to_dict(self) -> dict[str, Any]:
|
|
63
|
+
"""Convert to dictionary."""
|
|
64
|
+
return {
|
|
65
|
+
"pattern_id": self.pattern_id,
|
|
66
|
+
"category": self.category.value,
|
|
67
|
+
"trigger": self.trigger,
|
|
68
|
+
"context": self.context,
|
|
69
|
+
"resolution": self.resolution,
|
|
70
|
+
"confidence": self.confidence,
|
|
71
|
+
"source_session": self.source_session,
|
|
72
|
+
"extracted_at": self.extracted_at.isoformat(),
|
|
73
|
+
"tags": self.tags,
|
|
74
|
+
"metadata": self.metadata,
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
@classmethod
|
|
78
|
+
def from_dict(cls, data: dict[str, Any]) -> ExtractedPattern:
|
|
79
|
+
"""Create from dictionary."""
|
|
80
|
+
return cls(
|
|
81
|
+
category=PatternCategory(data["category"]),
|
|
82
|
+
trigger=data["trigger"],
|
|
83
|
+
context=data["context"],
|
|
84
|
+
resolution=data["resolution"],
|
|
85
|
+
confidence=data.get("confidence", 0.5),
|
|
86
|
+
source_session=data.get("source_session", ""),
|
|
87
|
+
extracted_at=datetime.fromisoformat(data["extracted_at"])
|
|
88
|
+
if "extracted_at" in data
|
|
89
|
+
else datetime.now(),
|
|
90
|
+
tags=data.get("tags", []),
|
|
91
|
+
metadata=data.get("metadata", {}),
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
def format_readable(self) -> str:
|
|
95
|
+
"""Format as human-readable text."""
|
|
96
|
+
lines = [
|
|
97
|
+
f"**{self.category.value.replace('_', ' ').title()}**",
|
|
98
|
+
f"- **Trigger**: {self.trigger}",
|
|
99
|
+
f"- **Context**: {self.context}",
|
|
100
|
+
f"- **Resolution**: {self.resolution}",
|
|
101
|
+
f"- **Confidence**: {self.confidence:.0%}",
|
|
102
|
+
]
|
|
103
|
+
if self.tags:
|
|
104
|
+
lines.append(f"- **Tags**: {', '.join(self.tags)}")
|
|
105
|
+
return "\n".join(lines)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class PatternExtractor:
|
|
109
|
+
"""Extracts patterns from collaboration sessions.
|
|
110
|
+
|
|
111
|
+
Analyzes session interactions to identify and structure
|
|
112
|
+
patterns that can be learned and applied in future sessions.
|
|
113
|
+
"""
|
|
114
|
+
|
|
115
|
+
# Correction indicators for user_correction patterns
|
|
116
|
+
CORRECTION_STARTERS = [
|
|
117
|
+
"actually",
|
|
118
|
+
"no,",
|
|
119
|
+
"i meant",
|
|
120
|
+
"let me clarify",
|
|
121
|
+
"to be clear",
|
|
122
|
+
"what i really want",
|
|
123
|
+
"correction:",
|
|
124
|
+
]
|
|
125
|
+
|
|
126
|
+
def __init__(
|
|
127
|
+
self,
|
|
128
|
+
min_confidence: float = 0.3,
|
|
129
|
+
max_patterns_per_session: int = 10,
|
|
130
|
+
):
|
|
131
|
+
"""Initialize the extractor.
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
min_confidence: Minimum confidence to include pattern
|
|
135
|
+
max_patterns_per_session: Maximum patterns to extract per session
|
|
136
|
+
"""
|
|
137
|
+
self._min_confidence = min_confidence
|
|
138
|
+
self._max_patterns = max_patterns_per_session
|
|
139
|
+
|
|
140
|
+
def extract_patterns(
|
|
141
|
+
self,
|
|
142
|
+
state: CollaborationState,
|
|
143
|
+
session_id: str = "",
|
|
144
|
+
) -> list[ExtractedPattern]:
|
|
145
|
+
"""Extract all patterns from a session.
|
|
146
|
+
|
|
147
|
+
Args:
|
|
148
|
+
state: Collaboration state to analyze
|
|
149
|
+
session_id: Optional session identifier
|
|
150
|
+
|
|
151
|
+
Returns:
|
|
152
|
+
List of extracted patterns
|
|
153
|
+
"""
|
|
154
|
+
patterns = []
|
|
155
|
+
|
|
156
|
+
# Extract user corrections
|
|
157
|
+
patterns.extend(self._extract_corrections(state, session_id))
|
|
158
|
+
|
|
159
|
+
# Extract error resolutions
|
|
160
|
+
patterns.extend(self._extract_error_resolutions(state, session_id))
|
|
161
|
+
|
|
162
|
+
# Extract workarounds
|
|
163
|
+
patterns.extend(self._extract_workarounds(state, session_id))
|
|
164
|
+
|
|
165
|
+
# Extract preferences
|
|
166
|
+
patterns.extend(self._extract_preferences(state, session_id))
|
|
167
|
+
|
|
168
|
+
# Extract project-specific patterns
|
|
169
|
+
patterns.extend(self._extract_project_patterns(state, session_id))
|
|
170
|
+
|
|
171
|
+
# Filter by confidence and limit
|
|
172
|
+
patterns = [p for p in patterns if p.confidence >= self._min_confidence]
|
|
173
|
+
patterns = sorted(patterns, key=lambda p: p.confidence, reverse=True)
|
|
174
|
+
patterns = patterns[: self._max_patterns]
|
|
175
|
+
|
|
176
|
+
logger.info(f"Extracted {len(patterns)} patterns from session")
|
|
177
|
+
return patterns
|
|
178
|
+
|
|
179
|
+
def _extract_corrections(
|
|
180
|
+
self,
|
|
181
|
+
state: CollaborationState,
|
|
182
|
+
session_id: str,
|
|
183
|
+
) -> list[ExtractedPattern]:
|
|
184
|
+
"""Extract user correction patterns."""
|
|
185
|
+
patterns = []
|
|
186
|
+
interactions = state.interactions
|
|
187
|
+
|
|
188
|
+
for i, interaction in enumerate(interactions):
|
|
189
|
+
if interaction.role != "user":
|
|
190
|
+
continue
|
|
191
|
+
|
|
192
|
+
content_lower = interaction.content.lower()
|
|
193
|
+
|
|
194
|
+
# Check for correction indicators
|
|
195
|
+
is_correction = any(
|
|
196
|
+
content_lower.startswith(starter) or f" {starter}" in content_lower
|
|
197
|
+
for starter in self.CORRECTION_STARTERS
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
if not is_correction:
|
|
201
|
+
continue
|
|
202
|
+
|
|
203
|
+
# Get the previous assistant message (what was corrected)
|
|
204
|
+
prev_assistant = None
|
|
205
|
+
for j in range(i - 1, -1, -1):
|
|
206
|
+
if interactions[j].role == "assistant":
|
|
207
|
+
prev_assistant = interactions[j]
|
|
208
|
+
break
|
|
209
|
+
|
|
210
|
+
# Get the resolution (next assistant message)
|
|
211
|
+
resolution = None
|
|
212
|
+
for j in range(i + 1, len(interactions)):
|
|
213
|
+
if interactions[j].role == "assistant":
|
|
214
|
+
resolution = interactions[j]
|
|
215
|
+
break
|
|
216
|
+
|
|
217
|
+
if prev_assistant and resolution:
|
|
218
|
+
pattern = ExtractedPattern(
|
|
219
|
+
category=PatternCategory.USER_CORRECTION,
|
|
220
|
+
trigger=self._summarize(prev_assistant.content, 100),
|
|
221
|
+
context=interaction.content,
|
|
222
|
+
resolution=self._summarize(resolution.content, 150),
|
|
223
|
+
confidence=0.7,
|
|
224
|
+
source_session=session_id,
|
|
225
|
+
tags=["correction", "clarification"],
|
|
226
|
+
)
|
|
227
|
+
patterns.append(pattern)
|
|
228
|
+
|
|
229
|
+
return patterns
|
|
230
|
+
|
|
231
|
+
def _extract_error_resolutions(
|
|
232
|
+
self,
|
|
233
|
+
state: CollaborationState,
|
|
234
|
+
session_id: str,
|
|
235
|
+
) -> list[ExtractedPattern]:
|
|
236
|
+
"""Extract error resolution patterns."""
|
|
237
|
+
patterns = []
|
|
238
|
+
interactions = state.interactions
|
|
239
|
+
|
|
240
|
+
error_patterns = [
|
|
241
|
+
r"error[:\s]",
|
|
242
|
+
r"exception[:\s]",
|
|
243
|
+
r"failed[:\s]",
|
|
244
|
+
r"traceback",
|
|
245
|
+
r"cannot\s",
|
|
246
|
+
r"unable to",
|
|
247
|
+
]
|
|
248
|
+
error_re = re.compile("|".join(error_patterns), re.IGNORECASE)
|
|
249
|
+
|
|
250
|
+
success_patterns = [
|
|
251
|
+
r"that works",
|
|
252
|
+
r"works now",
|
|
253
|
+
r"fixed",
|
|
254
|
+
r"solved",
|
|
255
|
+
r"thank",
|
|
256
|
+
r"perfect",
|
|
257
|
+
]
|
|
258
|
+
success_re = re.compile("|".join(success_patterns), re.IGNORECASE)
|
|
259
|
+
|
|
260
|
+
# Find error mentions and track resolutions
|
|
261
|
+
for i, interaction in enumerate(interactions):
|
|
262
|
+
if interaction.role != "user":
|
|
263
|
+
continue
|
|
264
|
+
|
|
265
|
+
if not error_re.search(interaction.content):
|
|
266
|
+
continue
|
|
267
|
+
|
|
268
|
+
# Look for successful resolution later
|
|
269
|
+
error_context = interaction.content
|
|
270
|
+
resolution_text = None
|
|
271
|
+
assistant_fix = None
|
|
272
|
+
|
|
273
|
+
for j in range(i + 1, len(interactions)):
|
|
274
|
+
other = interactions[j]
|
|
275
|
+
|
|
276
|
+
if other.role == "assistant":
|
|
277
|
+
# Capture the assistant's response to the error
|
|
278
|
+
if not assistant_fix:
|
|
279
|
+
assistant_fix = other.content
|
|
280
|
+
|
|
281
|
+
elif other.role == "user" and success_re.search(other.content):
|
|
282
|
+
# Found success confirmation
|
|
283
|
+
resolution_text = assistant_fix
|
|
284
|
+
break
|
|
285
|
+
|
|
286
|
+
if resolution_text:
|
|
287
|
+
pattern = ExtractedPattern(
|
|
288
|
+
category=PatternCategory.ERROR_RESOLUTION,
|
|
289
|
+
trigger=self._extract_error_summary(error_context),
|
|
290
|
+
context=self._summarize(error_context, 100),
|
|
291
|
+
resolution=self._summarize(resolution_text, 200),
|
|
292
|
+
confidence=0.8,
|
|
293
|
+
source_session=session_id,
|
|
294
|
+
tags=["error", "debugging", "fix"],
|
|
295
|
+
)
|
|
296
|
+
patterns.append(pattern)
|
|
297
|
+
|
|
298
|
+
return patterns
|
|
299
|
+
|
|
300
|
+
def _extract_workarounds(
|
|
301
|
+
self,
|
|
302
|
+
state: CollaborationState,
|
|
303
|
+
session_id: str,
|
|
304
|
+
) -> list[ExtractedPattern]:
|
|
305
|
+
"""Extract workaround patterns."""
|
|
306
|
+
patterns = []
|
|
307
|
+
|
|
308
|
+
workaround_indicators = [
|
|
309
|
+
r"workaround",
|
|
310
|
+
r"instead",
|
|
311
|
+
r"alternative",
|
|
312
|
+
r"hack",
|
|
313
|
+
r"trick",
|
|
314
|
+
r"work around",
|
|
315
|
+
]
|
|
316
|
+
workaround_re = re.compile("|".join(workaround_indicators), re.IGNORECASE)
|
|
317
|
+
|
|
318
|
+
for interaction in state.interactions:
|
|
319
|
+
if interaction.role != "assistant":
|
|
320
|
+
continue
|
|
321
|
+
|
|
322
|
+
if not workaround_re.search(interaction.content):
|
|
323
|
+
continue
|
|
324
|
+
|
|
325
|
+
# Extract the workaround description
|
|
326
|
+
content = interaction.content
|
|
327
|
+
|
|
328
|
+
pattern = ExtractedPattern(
|
|
329
|
+
category=PatternCategory.WORKAROUND,
|
|
330
|
+
trigger="Framework/library limitation",
|
|
331
|
+
context=self._extract_context_around_keyword(content, workaround_indicators),
|
|
332
|
+
resolution=self._summarize(content, 200),
|
|
333
|
+
confidence=0.6,
|
|
334
|
+
source_session=session_id,
|
|
335
|
+
tags=["workaround", "hack"],
|
|
336
|
+
)
|
|
337
|
+
patterns.append(pattern)
|
|
338
|
+
|
|
339
|
+
return patterns
|
|
340
|
+
|
|
341
|
+
def _extract_preferences(
|
|
342
|
+
self,
|
|
343
|
+
state: CollaborationState,
|
|
344
|
+
session_id: str,
|
|
345
|
+
) -> list[ExtractedPattern]:
|
|
346
|
+
"""Extract user preference patterns."""
|
|
347
|
+
patterns = []
|
|
348
|
+
|
|
349
|
+
preference_indicators = [
|
|
350
|
+
(r"i prefer\s+(\w+(?:\s+\w+){0,5})", "preference"),
|
|
351
|
+
(r"i like\s+(\w+(?:\s+\w+){0,5})", "like"),
|
|
352
|
+
(r"i don't like\s+(\w+(?:\s+\w+){0,5})", "dislike"),
|
|
353
|
+
(r"please always\s+(\w+(?:\s+\w+){0,5})", "always"),
|
|
354
|
+
(r"please don't\s+(\w+(?:\s+\w+){0,5})", "never"),
|
|
355
|
+
(r"i always\s+(\w+(?:\s+\w+){0,5})", "habit"),
|
|
356
|
+
]
|
|
357
|
+
|
|
358
|
+
for interaction in state.interactions:
|
|
359
|
+
if interaction.role != "user":
|
|
360
|
+
continue
|
|
361
|
+
|
|
362
|
+
content_lower = interaction.content.lower()
|
|
363
|
+
|
|
364
|
+
for pattern_str, pref_type in preference_indicators:
|
|
365
|
+
matches = re.findall(pattern_str, content_lower)
|
|
366
|
+
for match in matches:
|
|
367
|
+
pattern = ExtractedPattern(
|
|
368
|
+
category=PatternCategory.PREFERENCE,
|
|
369
|
+
trigger=pref_type,
|
|
370
|
+
context=f"User expressed: {match}",
|
|
371
|
+
resolution=self._summarize(interaction.content, 100),
|
|
372
|
+
confidence=0.5,
|
|
373
|
+
source_session=session_id,
|
|
374
|
+
tags=["preference", pref_type],
|
|
375
|
+
)
|
|
376
|
+
patterns.append(pattern)
|
|
377
|
+
|
|
378
|
+
return patterns
|
|
379
|
+
|
|
380
|
+
def _extract_project_patterns(
|
|
381
|
+
self,
|
|
382
|
+
state: CollaborationState,
|
|
383
|
+
session_id: str,
|
|
384
|
+
) -> list[ExtractedPattern]:
|
|
385
|
+
"""Extract project-specific patterns."""
|
|
386
|
+
patterns = []
|
|
387
|
+
|
|
388
|
+
# Look for conventions, file structures, naming patterns
|
|
389
|
+
convention_indicators = [
|
|
390
|
+
r"in this project",
|
|
391
|
+
r"we always",
|
|
392
|
+
r"our convention",
|
|
393
|
+
r"the pattern here",
|
|
394
|
+
r"this codebase",
|
|
395
|
+
]
|
|
396
|
+
convention_re = re.compile("|".join(convention_indicators), re.IGNORECASE)
|
|
397
|
+
|
|
398
|
+
for interaction in state.interactions:
|
|
399
|
+
if not convention_re.search(interaction.content):
|
|
400
|
+
continue
|
|
401
|
+
|
|
402
|
+
pattern = ExtractedPattern(
|
|
403
|
+
category=PatternCategory.PROJECT_SPECIFIC,
|
|
404
|
+
trigger="Project convention",
|
|
405
|
+
context=self._extract_context_around_keyword(
|
|
406
|
+
interaction.content, convention_indicators
|
|
407
|
+
),
|
|
408
|
+
resolution=self._summarize(interaction.content, 150),
|
|
409
|
+
confidence=0.6,
|
|
410
|
+
source_session=session_id,
|
|
411
|
+
tags=["project", "convention"],
|
|
412
|
+
)
|
|
413
|
+
patterns.append(pattern)
|
|
414
|
+
|
|
415
|
+
return patterns
|
|
416
|
+
|
|
417
|
+
def _summarize(self, text: str, max_length: int) -> str:
|
|
418
|
+
"""Summarize text to max length."""
|
|
419
|
+
text = text.strip()
|
|
420
|
+
if len(text) <= max_length:
|
|
421
|
+
return text
|
|
422
|
+
|
|
423
|
+
# Try to cut at sentence boundary
|
|
424
|
+
truncated = text[:max_length]
|
|
425
|
+
last_period = truncated.rfind(".")
|
|
426
|
+
last_newline = truncated.rfind("\n")
|
|
427
|
+
|
|
428
|
+
cut_point = max(last_period, last_newline)
|
|
429
|
+
if cut_point > max_length // 2:
|
|
430
|
+
return truncated[: cut_point + 1].strip()
|
|
431
|
+
|
|
432
|
+
return truncated.strip() + "..."
|
|
433
|
+
|
|
434
|
+
def _extract_error_summary(self, error_text: str) -> str:
|
|
435
|
+
"""Extract a summary of the error."""
|
|
436
|
+
# Look for common error patterns
|
|
437
|
+
error_patterns = [
|
|
438
|
+
r"(Error|Exception):\s*([^\n]+)",
|
|
439
|
+
r"(cannot|unable to)\s+([^\n.]+)",
|
|
440
|
+
r"(failed to)\s+([^\n.]+)",
|
|
441
|
+
]
|
|
442
|
+
|
|
443
|
+
for pattern in error_patterns:
|
|
444
|
+
match = re.search(pattern, error_text, re.IGNORECASE)
|
|
445
|
+
if match:
|
|
446
|
+
return match.group(0)[:100]
|
|
447
|
+
|
|
448
|
+
# Fall back to first line
|
|
449
|
+
first_line = error_text.split("\n")[0]
|
|
450
|
+
return self._summarize(first_line, 100)
|
|
451
|
+
|
|
452
|
+
def _extract_context_around_keyword(
|
|
453
|
+
self,
|
|
454
|
+
text: str,
|
|
455
|
+
keywords: list[str],
|
|
456
|
+
) -> str:
|
|
457
|
+
"""Extract context around matching keywords."""
|
|
458
|
+
text_lower = text.lower()
|
|
459
|
+
|
|
460
|
+
for keyword in keywords:
|
|
461
|
+
# Clean keyword for search
|
|
462
|
+
search_term = keyword.replace(r"\s+", " ").replace("\\", "")
|
|
463
|
+
match = re.search(search_term, text_lower, re.IGNORECASE)
|
|
464
|
+
|
|
465
|
+
if match:
|
|
466
|
+
# Get surrounding context
|
|
467
|
+
start = max(0, match.start() - 50)
|
|
468
|
+
end = min(len(text), match.end() + 100)
|
|
469
|
+
|
|
470
|
+
context = text[start:end]
|
|
471
|
+
if start > 0:
|
|
472
|
+
context = "..." + context
|
|
473
|
+
if end < len(text):
|
|
474
|
+
context = context + "..."
|
|
475
|
+
|
|
476
|
+
return context
|
|
477
|
+
|
|
478
|
+
return self._summarize(text, 150)
|
|
479
|
+
|
|
480
|
+
def categorize_pattern(
|
|
481
|
+
self,
|
|
482
|
+
trigger: str,
|
|
483
|
+
content: str,
|
|
484
|
+
) -> PatternCategory:
|
|
485
|
+
"""Categorize a pattern based on content.
|
|
486
|
+
|
|
487
|
+
Args:
|
|
488
|
+
trigger: The trigger text
|
|
489
|
+
content: The pattern content
|
|
490
|
+
|
|
491
|
+
Returns:
|
|
492
|
+
Appropriate PatternCategory
|
|
493
|
+
"""
|
|
494
|
+
content_lower = content.lower()
|
|
495
|
+
trigger_lower = trigger.lower()
|
|
496
|
+
|
|
497
|
+
if any(word in content_lower for word in ["error", "exception", "failed", "fix", "bug"]):
|
|
498
|
+
return PatternCategory.ERROR_RESOLUTION
|
|
499
|
+
|
|
500
|
+
if any(word in trigger_lower for word in ["actually", "correction", "meant", "clarify"]):
|
|
501
|
+
return PatternCategory.USER_CORRECTION
|
|
502
|
+
|
|
503
|
+
if any(word in content_lower for word in ["workaround", "instead", "alternative", "hack"]):
|
|
504
|
+
return PatternCategory.WORKAROUND
|
|
505
|
+
|
|
506
|
+
if any(word in content_lower for word in ["prefer", "like", "always", "never", "style"]):
|
|
507
|
+
return PatternCategory.PREFERENCE
|
|
508
|
+
|
|
509
|
+
if any(
|
|
510
|
+
word in content_lower for word in ["project", "codebase", "convention", "this repo"]
|
|
511
|
+
):
|
|
512
|
+
return PatternCategory.PROJECT_SPECIFIC
|
|
513
|
+
|
|
514
|
+
return PatternCategory.CODE_PATTERN
|