utim-cli 2.4.0__tar.gz → 2.4.1__tar.gz
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.
- {utim_cli-2.4.0 → utim_cli-2.4.1}/CHANGELOG.md +6 -0
- {utim_cli-2.4.0/utim_cli.egg-info → utim_cli-2.4.1}/PKG-INFO +1 -1
- {utim_cli-2.4.0 → utim_cli-2.4.1}/pyproject.toml +1 -1
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim/__init__.py +1 -1
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/__init__.py +1 -1
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/_version.py +1 -1
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/agent.py +15 -1
- utim_cli-2.4.1/utim_cli/architecture_indexer.py +251 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/bootstrap.py +66 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/brain.py +33 -2
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/client_utils.py +9 -138
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/constants.py +2 -2
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/orchestrator.py +53 -9
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/orchestrator_helpers.py +91 -64
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/orchestrator_llm_transport.py +42 -18
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/orchestrator_tool_exec.py +126 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/orchestrator_turn_history.py +26 -16
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/db.py +44 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/docs_md/about.md +6 -6
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/docs_md/docs.md +761 -761
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/docs_md/terms.md +1 -1
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/key_rotator.py +1680 -97
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/models.py +102 -14
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/router.py +72 -119
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/routes/__init__.py +2 -1
- utim_cli-2.4.1/utim_cli/server/routes/admin_providers_routes.py +511 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/routes/completion_routes.py +422 -427
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/routes/quota_routes.py +46 -8
- utim_cli-2.4.1/utim_cli/syntax_fix.py +715 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/tools.py +53 -20
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/tui/model_dialog.py +58 -20
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/utim.py +25 -3
- utim_cli-2.4.1/utim_cli/voice.py +537 -0
- utim_cli-2.4.1/utim_cli/voice_handler.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1/utim_cli.egg-info}/PKG-INFO +1 -1
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli.egg-info/SOURCES.txt +5 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/LICENSE +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/MANIFEST.in +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/README.md +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/setup.cfg +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/setup.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/_chip_test.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/_verify_repro.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/_verify_syntax.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/_wizard_smoke_test.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/ask_helper.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/auth.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/backup.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/billing.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/blender_agent.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/builtin/skills/utim-guide/SKILL.md +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/config.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/context_inspector.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/context_pruner.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/crawler/__init__.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/crawler/engine.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/crawler/markdown_distiller.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/dependency_manager.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/doctor.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/evidence_registry.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/harbor.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/knowledge_graph.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/local_db.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/logger.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/mcp_clean_wrapper.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/mcp_client.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/mcp_registry.json +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/models.txt +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/personalities.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/platform_installer.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/plugins/__init__.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/plugins/hooks.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/plugins/loader.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/plugins/manager.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/plugins/registry.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/plugins/runtime.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/plugins/schema.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/plugins/tool_loader.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/reflection.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/report.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/scrapy_search.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/sdk/__init__.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/sdk/config.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/sdk/context.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/sdk/events.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/sdk/handlers.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/sdk/session.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/sdk/sidecar.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/__init__.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/admin_auth.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/attribution.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/audit_log.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/auth.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/batch_processor.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/captcha.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/cli_auth.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/concurrency.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/docs_md/SECRET_PROVISIONING.md +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/docs_md/changelog.md +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/docs_md/features.md +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/docs_md/license.md +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/docs_md/pricing.md +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/docs_md/privacy.md +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/docs_md/refund.md +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/docs_md/support.md +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/email_utils.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/exchange_rate.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/firebase.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/fix_duplicate_users.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/history.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/local_llm.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/logging_config.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/micro_batcher.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/model_agent.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/plan_integrity.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/pricing_updater.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/provision_build.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/rate_limit.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/rewards_engine.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/routes/admin_bonus_routes.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/routes/admin_db_routes.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/routes/auth_routes.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/routes/billing_routes.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/routes/credit_routes.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/routes/feedback_routes.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/routes/marketplace_routes.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/routes/quota_share_routes.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/routes/referral_routes.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/routes/rewards_routes.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/routes/security_routes.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/routes/session_routes.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/routes/share_routes.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/routes/webhook_routes.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/security_alerts.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/server.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/storage_nodes.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/utils.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/share.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/share_tui.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/situational_scoring.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/state.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/subagent_manager.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/task_dispatcher.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/task_logger.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/tools_grep.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/tui/__init__.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/tui/compression_dialog.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/tui/feedback_dialog.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/tui/history_dialog.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/tui/marketplace_app_state.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/tui/marketplace_dialog.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/tui/marketplace_layout_engine.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/tui/mcp_dialog.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/tui/miniagents_dialog.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/tui/personality_dialog.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/tui/plugins_dialog.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/tui/publish_dialog.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/tui/quota_dialog.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/tui/quota_redeem_dialog.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/tui/quota_share_dialog.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/tui/resume_dialog.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/tui/rewards_tui.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/tui/skills_dialog.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/tui/subagents_dialog.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/tui/tasks_dialog.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/tui/thinking_display.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/tui/tools_dialog.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/tui/update_dialog.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/update_checker.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/utilities.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/utimmodel.txt +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/vector_memory.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/wheel.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/workspace.py +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli.egg-info/dependency_links.txt +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli.egg-info/entry_points.txt +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli.egg-info/requires.txt +0 -0
- {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli.egg-info/top_level.txt +0 -0
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [2.4.1] - 2026-09-18
|
|
4
|
+
|
|
5
|
+
### Bug Fixes
|
|
6
|
+
- Fixed the `UnboundLocalError: rotator` crash when resolving failover providers.
|
|
7
|
+
- Fixed an Admin UI bug that zeroed out free model upstream prices in the Model Pricing & Provisioning interface.
|
|
8
|
+
|
|
3
9
|
## [2.4.0] - 2026-09-14
|
|
4
10
|
|
|
5
11
|
### ✨ New Features & Enhancements
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: utim-cli
|
|
3
|
-
Version: 2.4.
|
|
3
|
+
Version: 2.4.1
|
|
4
4
|
Summary: UTIM – Universal Terminal Intelligence Manager. A powerful agentic AI coding assistant for your terminal.
|
|
5
5
|
License: Emend AI Proprietary EULA
|
|
6
6
|
Project-URL: Homepage, https://utim.dev
|
|
@@ -14,7 +14,7 @@ Quickstart:
|
|
|
14
14
|
try:
|
|
15
15
|
from utim_cli._version import VERSION as __version__
|
|
16
16
|
except ImportError:
|
|
17
|
-
__version__ = "2.4.
|
|
17
|
+
__version__ = "2.4.1"
|
|
18
18
|
|
|
19
19
|
# Developer-friendly core classes (similar to `import genai` / `import openai`)
|
|
20
20
|
from utim_cli.sdk.session import UtimSession as Session
|
|
@@ -3,6 +3,7 @@ import json
|
|
|
3
3
|
import sys
|
|
4
4
|
import time
|
|
5
5
|
from .tools import get_tools
|
|
6
|
+
from .syntax_fix import attempt_syntax_repair
|
|
6
7
|
from rich.console import Console
|
|
7
8
|
from rich.live import Live
|
|
8
9
|
from rich.spinner import Spinner
|
|
@@ -293,7 +294,20 @@ class ReActAgent:
|
|
|
293
294
|
num_lines = math.ceil(getattr(self, "_last_running_indicator_len", 40) / max(1, width))
|
|
294
295
|
for _ in range(num_lines):
|
|
295
296
|
self.console.print("\033[F\033[K", end="")
|
|
296
|
-
|
|
297
|
+
|
|
298
|
+
# Auto-repair: if the tool failed due to a syntax error in the
|
|
299
|
+
# generated code, attempt an "on the go" syntax fix before
|
|
300
|
+
# reporting the error.
|
|
301
|
+
error_msg = str(e)
|
|
302
|
+
repaired = None
|
|
303
|
+
try:
|
|
304
|
+
repaired = attempt_syntax_repair(error_msg, self.console)
|
|
305
|
+
except Exception:
|
|
306
|
+
repaired = None
|
|
307
|
+
if repaired:
|
|
308
|
+
return repaired
|
|
309
|
+
|
|
310
|
+
return f"Error executing {tool_name}: {error_msg}"
|
|
297
311
|
|
|
298
312
|
|
|
299
313
|
def run(self, max_iterations: int = 500, show_tools: bool = True):
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import re
|
|
3
|
+
import json
|
|
4
|
+
import hashlib
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
import ast
|
|
7
|
+
|
|
8
|
+
def get_project_hash(project_path: str) -> str:
|
|
9
|
+
return hashlib.sha256(os.path.abspath(project_path).encode('utf-8')).hexdigest()[:12]
|
|
10
|
+
|
|
11
|
+
def _ignore_dir(dname: str) -> bool:
|
|
12
|
+
ignored = {'.git', 'node_modules', 'venv', '.venv', '__pycache__', 'dist', 'build', '.next', '.utim'}
|
|
13
|
+
return dname in ignored
|
|
14
|
+
|
|
15
|
+
def index_python_file(filepath: Path) -> dict:
|
|
16
|
+
try:
|
|
17
|
+
content = filepath.read_text(encoding='utf-8', errors='ignore')
|
|
18
|
+
tree = ast.parse(content)
|
|
19
|
+
imports = []
|
|
20
|
+
symbols = []
|
|
21
|
+
lines = content.split('\n')
|
|
22
|
+
|
|
23
|
+
for node in ast.walk(tree):
|
|
24
|
+
if isinstance(node, ast.Import):
|
|
25
|
+
for alias in node.names: imports.append(alias.name)
|
|
26
|
+
elif isinstance(node, ast.ImportFrom):
|
|
27
|
+
if node.module: imports.append(node.module)
|
|
28
|
+
elif isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
|
29
|
+
docstring = ast.get_docstring(node)
|
|
30
|
+
args = [a.arg for a in node.args.args]
|
|
31
|
+
sig = f"({', '.join(args)})"
|
|
32
|
+
start = max(0, node.lineno - 1)
|
|
33
|
+
end = min(len(lines), start + 6)
|
|
34
|
+
snippet = "\n".join(lines[start:end])
|
|
35
|
+
if len(lines) > end: snippet += "\n # ..."
|
|
36
|
+
symbols.append({"type": "function", "name": node.name, "line": node.lineno, "docstring": docstring, "signature": sig, "snippet": snippet})
|
|
37
|
+
elif isinstance(node, ast.ClassDef):
|
|
38
|
+
docstring = ast.get_docstring(node)
|
|
39
|
+
start = max(0, node.lineno - 1)
|
|
40
|
+
end = min(len(lines), start + 5)
|
|
41
|
+
snippet = "\n".join(lines[start:end])
|
|
42
|
+
if len(lines) > end: snippet += "\n # ..."
|
|
43
|
+
symbols.append({"type": "class", "name": node.name, "line": node.lineno, "docstring": docstring, "snippet": snippet})
|
|
44
|
+
return {"imports": list(set(imports)), "symbols": symbols}
|
|
45
|
+
except Exception:
|
|
46
|
+
return {"imports": [], "symbols": []}
|
|
47
|
+
|
|
48
|
+
def index_js_ts_file(filepath: Path) -> dict:
|
|
49
|
+
try:
|
|
50
|
+
content = filepath.read_text(encoding='utf-8', errors='ignore')
|
|
51
|
+
imports = re.findall(r'import\s+.*?from\s+[\'"](.*?)[\'"]', content)
|
|
52
|
+
symbols = []
|
|
53
|
+
lines = content.split('\n')
|
|
54
|
+
for i, line in enumerate(lines):
|
|
55
|
+
match = re.search(r'(?:export\s+)?(?:const|let|var|function|class)\s+([a-zA-Z0-9_]+)', line)
|
|
56
|
+
if match:
|
|
57
|
+
name = match.group(1)
|
|
58
|
+
snippet = "\n".join(lines[i:min(len(lines), i+6)])
|
|
59
|
+
if len(lines) > i+6: snippet += "\n // ..."
|
|
60
|
+
symbols.append({"type": "export", "name": name, "line": i+1, "snippet": snippet, "signature": ""})
|
|
61
|
+
return {"imports": list(set(imports)), "symbols": symbols}
|
|
62
|
+
except Exception:
|
|
63
|
+
return {"imports": [], "symbols": []}
|
|
64
|
+
|
|
65
|
+
def index_generic_file(filepath: Path) -> dict:
|
|
66
|
+
try:
|
|
67
|
+
content = filepath.read_text(encoding='utf-8', errors='ignore')
|
|
68
|
+
symbols = []
|
|
69
|
+
lines = content.split('\n')
|
|
70
|
+
for i, line in enumerate(lines):
|
|
71
|
+
s_line = line.strip()
|
|
72
|
+
name = None
|
|
73
|
+
|
|
74
|
+
# 1. Standard OOP / Procedural / DevOps / Smart Contracts
|
|
75
|
+
m1 = re.search(r'^(?:public |private |protected |export |func |fn |class |struct |interface |def |sub |function |contract |resource |module )\s*([a-zA-Z0-9_]+)\s*(?:\{|\(|:|"|\')', s_line)
|
|
76
|
+
# 2. SQL Data Definition
|
|
77
|
+
m2 = re.search(r'^CREATE\s+(?:TABLE|PROCEDURE|VIEW|FUNCTION|TRIGGER)\s+([a-zA-Z0-9_]+)', s_line, re.IGNORECASE)
|
|
78
|
+
# 3. Bash / Shell scripts
|
|
79
|
+
m3 = re.search(r'^([a-zA-Z0-9_]+)\s*\(\)\s*\{', s_line)
|
|
80
|
+
# 4. Lisp / Clojure / Scheme
|
|
81
|
+
m4 = re.search(r'^\(\s*def[a-zA-Z0-9_]*\s+([a-zA-Z0-9_\-]+)', s_line)
|
|
82
|
+
|
|
83
|
+
if m1: name = m1.group(1)
|
|
84
|
+
elif m2: name = m2.group(1)
|
|
85
|
+
elif m3: name = m3.group(1)
|
|
86
|
+
elif m4: name = m4.group(1)
|
|
87
|
+
|
|
88
|
+
if name and len(name) > 2 and name not in ("if", "for", "while", "switch", "catch", "return", "else", "elif"):
|
|
89
|
+
snippet = "\n".join(lines[i:min(len(lines), i+6)])
|
|
90
|
+
if len(lines) > i+6: snippet += "\n // ..."
|
|
91
|
+
symbols.append({"type": "symbol", "name": name, "line": i+1, "snippet": snippet, "signature": ""})
|
|
92
|
+
return {"imports": [], "symbols": symbols}
|
|
93
|
+
except Exception:
|
|
94
|
+
return {"imports": [], "symbols": []}
|
|
95
|
+
|
|
96
|
+
def architecture_lock(project_path: str = ".") -> str:
|
|
97
|
+
import uuid
|
|
98
|
+
from utim_cli.brain import get_current_resolved_project
|
|
99
|
+
abs_path = os.path.abspath(project_path)
|
|
100
|
+
if not os.path.isdir(abs_path):
|
|
101
|
+
return f"Error: Project path '{abs_path}' does not exist or is not a directory."
|
|
102
|
+
|
|
103
|
+
proj_id, proj_dir = get_current_resolved_project()
|
|
104
|
+
arch_hash = uuid.uuid4().hex[:8]
|
|
105
|
+
arch_dir = proj_dir / f"arch_{arch_hash}"
|
|
106
|
+
arch_dir.mkdir(parents=True, exist_ok=True)
|
|
107
|
+
brain_file = arch_dir / "00_architecture_map.json"
|
|
108
|
+
|
|
109
|
+
inventory = []
|
|
110
|
+
project_type = []
|
|
111
|
+
|
|
112
|
+
# Generic stack identification heuristics
|
|
113
|
+
if (Path(abs_path) / "package.json").exists(): project_type.append("Node.js")
|
|
114
|
+
if (Path(abs_path) / "requirements.txt").exists() or (Path(abs_path) / "pyproject.toml").exists(): project_type.append("Python")
|
|
115
|
+
if (Path(abs_path) / "go.mod").exists(): project_type.append("Go")
|
|
116
|
+
if (Path(abs_path) / "Cargo.toml").exists(): project_type.append("Rust")
|
|
117
|
+
if (Path(abs_path) / "composer.json").exists(): project_type.append("PHP")
|
|
118
|
+
if (Path(abs_path) / "Gemfile").exists(): project_type.append("Ruby")
|
|
119
|
+
|
|
120
|
+
# Known binaries to skip indexing completely
|
|
121
|
+
BINARY_EXTS = {
|
|
122
|
+
".png", ".jpg", ".jpeg", ".gif", ".ico", ".svg", ".mp4", ".mp3", ".wav",
|
|
123
|
+
".zip", ".tar", ".gz", ".rar", ".7z", ".exe", ".dll", ".so", ".dylib",
|
|
124
|
+
".bin", ".dat", ".db", ".sqlite", ".pyc", ".class", ".o", ".a", ".lib",
|
|
125
|
+
".pdf", ".doc", ".docx", ".xls", ".xlsx"
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
for root, dirs, files in os.walk(abs_path):
|
|
129
|
+
dirs[:] = [d for d in dirs if not _ignore_dir(d)]
|
|
130
|
+
for f in files:
|
|
131
|
+
if f.startswith('.'): continue
|
|
132
|
+
fpath = Path(root) / f
|
|
133
|
+
rel_path = str(fpath.relative_to(abs_path)).replace('\\', '/')
|
|
134
|
+
|
|
135
|
+
file_data = {
|
|
136
|
+
"file": rel_path,
|
|
137
|
+
"size": fpath.stat().st_size if fpath.exists() else 0,
|
|
138
|
+
"extension": fpath.suffix,
|
|
139
|
+
"relationships": []
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
# Universal Language Routing
|
|
143
|
+
if fpath.suffix == ".py":
|
|
144
|
+
data = index_python_file(fpath)
|
|
145
|
+
elif fpath.suffix in (".js", ".ts", ".jsx", ".tsx", ".mjs", ".cjs"):
|
|
146
|
+
data = index_js_ts_file(fpath)
|
|
147
|
+
elif fpath.suffix.lower() not in BINARY_EXTS:
|
|
148
|
+
data = index_generic_file(fpath)
|
|
149
|
+
else:
|
|
150
|
+
data = {"imports": [], "symbols": []}
|
|
151
|
+
|
|
152
|
+
file_data["imports"] = data["imports"]
|
|
153
|
+
file_data["symbols"] = data["symbols"]
|
|
154
|
+
inventory.append(file_data)
|
|
155
|
+
|
|
156
|
+
nodes = []
|
|
157
|
+
edges = []
|
|
158
|
+
nodes.append({"id": "PROJECT_ROOT", "type": "project", "path": abs_path})
|
|
159
|
+
|
|
160
|
+
def get_cluster_name(file_path: str) -> str:
|
|
161
|
+
parts = file_path.replace('\\', '/').split('/')
|
|
162
|
+
ignore_dirs = {'src', 'lib', 'app', 'main', 'bin', 'scripts', 'tests', 'test', 'public', 'assets', 'static', 'dist', 'build'}
|
|
163
|
+
|
|
164
|
+
# 1. Find the most meaningful parent directory
|
|
165
|
+
for part in reversed(parts[:-1]):
|
|
166
|
+
clean_part = part.lower().strip()
|
|
167
|
+
if clean_part and clean_part not in ignore_dirs:
|
|
168
|
+
return clean_part
|
|
169
|
+
|
|
170
|
+
# 2. Fallback to filename if it's unique
|
|
171
|
+
filename = parts[-1].split('.')[0].lower()
|
|
172
|
+
if filename not in {'index', 'main', 'app', 'core'}:
|
|
173
|
+
return filename
|
|
174
|
+
|
|
175
|
+
return "core"
|
|
176
|
+
|
|
177
|
+
clusters = {}
|
|
178
|
+
|
|
179
|
+
for item in inventory:
|
|
180
|
+
file_id = item["file"]
|
|
181
|
+
nodes.append({"id": file_id, "type": "file", "extension": item.get("extension"), "size": item.get("size")})
|
|
182
|
+
edges.append({"source": "PROJECT_ROOT", "target": file_id, "relation": "contains"})
|
|
183
|
+
|
|
184
|
+
for imp in item.get("imports", []):
|
|
185
|
+
nodes.append({"id": imp, "type": "module"})
|
|
186
|
+
edges.append({"source": file_id, "target": imp, "relation": "imports"})
|
|
187
|
+
|
|
188
|
+
cluster_name = get_cluster_name(file_id)
|
|
189
|
+
if cluster_name not in clusters:
|
|
190
|
+
clusters[cluster_name] = []
|
|
191
|
+
|
|
192
|
+
try:
|
|
193
|
+
content = (Path(abs_path) / file_id).read_text(encoding='utf-8', errors='ignore')
|
|
194
|
+
content_lower = content.lower()
|
|
195
|
+
file_lower = file_id.lower()
|
|
196
|
+
if "if __name__ ==" in content or "int main(" in content or "func main(" in content or "app.listen" in content_lower:
|
|
197
|
+
clusters[cluster_name].append(f"### File: `{file_id}` (Entrypoint/Server)\n*This file contains application startup or core routing.*\n")
|
|
198
|
+
except:
|
|
199
|
+
pass
|
|
200
|
+
|
|
201
|
+
for sym in item.get("symbols", []):
|
|
202
|
+
sym_id = f"{file_id}::{sym['name']}"
|
|
203
|
+
nodes.append({"id": sym_id, "type": sym["type"], "name": sym["name"], "line": sym.get("line")})
|
|
204
|
+
edges.append({"source": file_id, "target": sym_id, "relation": "defines"})
|
|
205
|
+
|
|
206
|
+
name = sym.get("name", "")
|
|
207
|
+
doc = sym.get("docstring") or ""
|
|
208
|
+
snippet = sym.get("snippet") or ""
|
|
209
|
+
sig = sym.get("signature", "")
|
|
210
|
+
|
|
211
|
+
md_block = f"### `{name}{sig}` in `{file_id}`\n"
|
|
212
|
+
if doc: md_block += f"**Docstring:** {doc}\n"
|
|
213
|
+
md_block += f"```text\n{snippet}\n```\n"
|
|
214
|
+
clusters[cluster_name].append(md_block)
|
|
215
|
+
|
|
216
|
+
# Dynamically generate markdown files based on organic clusters
|
|
217
|
+
generated_mds = []
|
|
218
|
+
for cname, blocks in clusters.items():
|
|
219
|
+
if not blocks: continue
|
|
220
|
+
safe_cname = re.sub(r'[^a-z0-9_]', '_', cname)
|
|
221
|
+
md_content = f"# 🧩 Architecture Component: {cname.title()}\n\n" + "\n".join(blocks)
|
|
222
|
+
md_filename = f"component_{safe_cname}.md"
|
|
223
|
+
(arch_dir / md_filename).write_text(md_content, encoding="utf-8")
|
|
224
|
+
generated_mds.append(md_filename)
|
|
225
|
+
|
|
226
|
+
conventions = [
|
|
227
|
+
"## Innate Project Architecture Rules",
|
|
228
|
+
"- **Organic Component Isolation**: The project is structured organically. Do not bloat central routers or core files.",
|
|
229
|
+
"- **Multifile Designs**: When adding large new features, ALWAYS create new standalone component files based on the existing directory structure.",
|
|
230
|
+
"- **MANDATORY ARCHITECTURE REVIEW**: You MUST use `read_file` on the relevant domain files below BEFORE exploring the source code manually. They contain the exact structural blueprints you need.",
|
|
231
|
+
f"- **Available Domain Files**: {', '.join([f'[file:///{str(arch_dir.absolute() / m).replace(chr(92), "/")}]' for m in generated_mds])}"
|
|
232
|
+
]
|
|
233
|
+
(arch_dir / "conventions_summary.txt").write_text("\n".join(conventions), encoding="utf-8")
|
|
234
|
+
|
|
235
|
+
unique_nodes = {n["id"]: n for n in nodes}.values()
|
|
236
|
+
|
|
237
|
+
graph_data = {
|
|
238
|
+
"project_path": abs_path,
|
|
239
|
+
"project_type": project_type,
|
|
240
|
+
"total_files": len(inventory),
|
|
241
|
+
"nodes": list(unique_nodes),
|
|
242
|
+
"edges": edges,
|
|
243
|
+
"capabilities": ["Project orientation", "Graph traversal", "Symbol resolution"]
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
with open(brain_file, 'w', encoding='utf-8') as bf:
|
|
247
|
+
json.dump(graph_data, bf, indent=2)
|
|
248
|
+
|
|
249
|
+
return (f"Architecture lock successfully generated at: {arch_dir}\n"
|
|
250
|
+
f"Generated 00_architecture_map.json and detailed Markdown component files.\n"
|
|
251
|
+
f"Indexed {len(inventory)} files. Identified stack: {', '.join(project_type) or 'Unknown'}.")
|
|
@@ -37,6 +37,7 @@ def initialize_utim() -> str:
|
|
|
37
37
|
|
|
38
38
|
# 4. Auto-create skills directory and default skills/rules
|
|
39
39
|
_write_skills_and_agents()
|
|
40
|
+
_scaffold_local_ui_skill()
|
|
40
41
|
|
|
41
42
|
# 5. Sync global/local experience database across project folders
|
|
42
43
|
try:
|
|
@@ -51,6 +52,71 @@ def initialize_utim() -> str:
|
|
|
51
52
|
return str(db_path)
|
|
52
53
|
|
|
53
54
|
|
|
55
|
+
def _scaffold_local_ui_skill():
|
|
56
|
+
"""Auto-generate UI design guide in the local workspace if .agents exists or it looks like a project."""
|
|
57
|
+
import os
|
|
58
|
+
from pathlib import Path
|
|
59
|
+
|
|
60
|
+
# Only scaffold if we are in a project directory or .agents already exists
|
|
61
|
+
if not (Path('.git').exists() or Path('.agents').exists() or Path('package.json').exists() or Path('requirements.txt').exists()):
|
|
62
|
+
return
|
|
63
|
+
|
|
64
|
+
try:
|
|
65
|
+
skill_dir = Path('.agents/skills/Ui design Guide')
|
|
66
|
+
skill_dir.mkdir(parents=True, exist_ok=True)
|
|
67
|
+
skill_md = skill_dir / 'SKILL.md'
|
|
68
|
+
|
|
69
|
+
if not skill_md.exists():
|
|
70
|
+
skill_content = """---
|
|
71
|
+
name: Ui design Guide
|
|
72
|
+
description: Directives and best practices for creating beautiful, modern, and highly polished web interfaces.
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
# UI Design Guide
|
|
76
|
+
|
|
77
|
+
When the user asks you to generate something related to design, build a website, or perform a UI overhaul, you MUST apply these modern UI/UX principles to ensure the result is professional, beautiful, and highly polished. Do not settle for default browser styles.
|
|
78
|
+
|
|
79
|
+
## 1. Typography
|
|
80
|
+
- **Fonts**: Use modern, clean sans-serif fonts (e.g., Inter, Roboto, San Francisco, or system-ui).
|
|
81
|
+
- **Hierarchy**: Clearly distinguish between headings (H1, H2) and body text using font weights (e.g., bold headers, regular body) and sizes.
|
|
82
|
+
- **Readability**: Keep body text line-height around `1.5` to `1.6`. Use slightly muted colors for body text (e.g., `#4b5563` on light themes or `#a1a1aa` on dark themes) rather than stark black or pure white.
|
|
83
|
+
|
|
84
|
+
## 2. Layout & Whitespace (Negative Space)
|
|
85
|
+
- **Breathing Room**: Be generous with padding and margins. Do not cram elements together.
|
|
86
|
+
- **Alignment**: Use CSS Flexbox or Grid for perfect alignment. Center things deliberately.
|
|
87
|
+
- **Constraints**: Limit the maximum width of text containers (e.g., `max-w-3xl`) so lines don't stretch too far across wide screens.
|
|
88
|
+
|
|
89
|
+
## 3. Colors & Theme
|
|
90
|
+
- **Backgrounds**: Use soft, off-white (e.g., `#f9fafb`) or rich dark (e.g., `#0f172a`) background colors instead of pure `#ffffff` or `#000000`.
|
|
91
|
+
- **Accents**: Pick a primary brand color (e.g., a vibrant blue or purple) and use it sparingly for primary buttons, active states, and important links.
|
|
92
|
+
- **Gradients & Glassmorphism**: Where appropriate, use subtle background gradients or translucent frosted-glass effects (e.g., `backdrop-filter: blur(10px); background: rgba(255, 255, 255, 0.1);`).
|
|
93
|
+
|
|
94
|
+
## 4. Components & Shapes
|
|
95
|
+
- **Corners**: Use consistent `border-radius` (e.g., `8px` or `12px` for cards/buttons, full pill-shapes for badges).
|
|
96
|
+
- **Shadows**: Add soft, diffuse box-shadows to cards and dropdowns to create depth and elevation. Avoid harsh, dark shadows (e.g., use `box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1)`).
|
|
97
|
+
- **Borders**: If using borders, make them very subtle (e.g., `1px solid rgba(255,255,255,0.1)` or `#e5e7eb`).
|
|
98
|
+
|
|
99
|
+
## 5. Micro-Interactions & States
|
|
100
|
+
- **Transitions**: Apply `transition: all 0.2s ease;` to buttons, links, and interactive elements.
|
|
101
|
+
- **Hover/Active**: Always provide visual feedback when an element is hovered (e.g., slightly lighten/darken the background, or lift the element up by 2px) or clicked.
|
|
102
|
+
- **Focus**: Remove default browser outlines and replace them with custom, aesthetic focus rings.
|
|
103
|
+
|
|
104
|
+
## 6. Responsiveness
|
|
105
|
+
- Ensure all designs are mobile-friendly first.
|
|
106
|
+
- Use responsive units or media queries to collapse grids into single columns on smaller screens.
|
|
107
|
+
- Ensure tap targets (buttons, links) are at least `44px` tall on mobile devices.
|
|
108
|
+
|
|
109
|
+
## Directives
|
|
110
|
+
- **Never use raw unstyled HTML** for a final output unless explicitly requested.
|
|
111
|
+
- **Use modern CSS frameworks** (like Tailwind CSS) if permitted by the project, or write clean, well-structured vanilla CSS using CSS Variables for theming.
|
|
112
|
+
- **Review your design**: Before finalizing the code, ask yourself: "Does this look like a premium modern SaaS product or a 1990s web page?" Adjust accordingly.
|
|
113
|
+
"""
|
|
114
|
+
skill_md.write_text(skill_content, encoding='utf-8')
|
|
115
|
+
|
|
116
|
+
except Exception:
|
|
117
|
+
pass
|
|
118
|
+
|
|
119
|
+
|
|
54
120
|
def _sync_global_experience(api_key: str, current_folder: str):
|
|
55
121
|
"""
|
|
56
122
|
Track which project folder the user is currently in (server-side record only).
|
|
@@ -104,7 +104,9 @@ def _brain_db_path() -> str:
|
|
|
104
104
|
def _project_id(workspace_path: Optional[str] = None) -> str:
|
|
105
105
|
"""Stable short hex ID for the current workspace directory."""
|
|
106
106
|
cwd = workspace_path or os.getcwd()
|
|
107
|
-
|
|
107
|
+
# Normalize path to ensure the same directory always yields the same hash
|
|
108
|
+
normalized = os.path.normcase(os.path.normpath(os.path.abspath(cwd)))
|
|
109
|
+
return "proj_" + hashlib.sha1(normalized.encode('utf-8')).hexdigest()[:10]
|
|
108
110
|
|
|
109
111
|
def _project_dir(project_id: str) -> Path:
|
|
110
112
|
d = _brain_root() / "projects" / project_id
|
|
@@ -904,6 +906,16 @@ def reason_over_memories(user_prompt: str, memories: List[Dict]) -> str:
|
|
|
904
906
|
|
|
905
907
|
# ── Context Injection Helper (called by orchestrator) ─────────────────────────
|
|
906
908
|
|
|
909
|
+
_CURRENT_RESOLVED_PROJ_ID = None
|
|
910
|
+
|
|
911
|
+
def get_current_resolved_project() -> Tuple[str, Path]:
|
|
912
|
+
"""Return the globally resolved project id and path from the last system prompt injection, or fallback to current workspace."""
|
|
913
|
+
global _CURRENT_RESOLVED_PROJ_ID
|
|
914
|
+
if _CURRENT_RESOLVED_PROJ_ID:
|
|
915
|
+
return _CURRENT_RESOLVED_PROJ_ID, _project_dir(_CURRENT_RESOLVED_PROJ_ID)
|
|
916
|
+
pid = _project_id()
|
|
917
|
+
return pid, _project_dir(pid)
|
|
918
|
+
|
|
907
919
|
def find_most_relevant_brain_project(query: str = "") -> Tuple[str, Path]:
|
|
908
920
|
"""
|
|
909
921
|
Find the most relevant brain project folder using local MiniLM vector search.
|
|
@@ -911,16 +923,19 @@ def find_most_relevant_brain_project(query: str = "") -> Tuple[str, Path]:
|
|
|
911
923
|
embeddings table for the project with the highest similarity score.
|
|
912
924
|
Falls back to the current workspace project directory.
|
|
913
925
|
"""
|
|
926
|
+
global _CURRENT_RESOLVED_PROJ_ID
|
|
914
927
|
current_pid = _project_id()
|
|
915
928
|
current_dir = _project_dir(current_pid)
|
|
916
929
|
|
|
917
930
|
if not query or not query.strip():
|
|
931
|
+
_CURRENT_RESOLVED_PROJ_ID = current_pid
|
|
918
932
|
return current_pid, current_dir
|
|
919
933
|
|
|
920
934
|
try:
|
|
921
935
|
brain_root = _brain_root()
|
|
922
936
|
projects_dir = brain_root / "projects"
|
|
923
937
|
if not projects_dir.exists():
|
|
938
|
+
_CURRENT_RESOLVED_PROJ_ID = current_pid
|
|
924
939
|
return current_pid, current_dir
|
|
925
940
|
|
|
926
941
|
# Run MiniLM embedding search against brain_embeddings to score projects
|
|
@@ -931,10 +946,12 @@ def find_most_relevant_brain_project(query: str = "") -> Tuple[str, Path]:
|
|
|
931
946
|
conn.close()
|
|
932
947
|
|
|
933
948
|
if not rows:
|
|
949
|
+
_CURRENT_RESOLVED_PROJ_ID = current_pid
|
|
934
950
|
return current_pid, current_dir
|
|
935
951
|
|
|
936
952
|
q_vecs = _embed([query.strip()])
|
|
937
953
|
if not q_vecs:
|
|
954
|
+
_CURRENT_RESOLVED_PROJ_ID = current_pid
|
|
938
955
|
return current_pid, current_dir
|
|
939
956
|
|
|
940
957
|
q_vec = q_vecs[0]
|
|
@@ -958,11 +975,13 @@ def find_most_relevant_brain_project(query: str = "") -> Tuple[str, Path]:
|
|
|
958
975
|
best_pid = max(project_scores.items(), key=lambda x: x[1])[0]
|
|
959
976
|
best_dir = _project_dir(best_pid)
|
|
960
977
|
if best_dir.exists():
|
|
978
|
+
_CURRENT_RESOLVED_PROJ_ID = best_pid
|
|
961
979
|
return best_pid, best_dir
|
|
962
980
|
|
|
963
981
|
except Exception:
|
|
964
982
|
pass
|
|
965
983
|
|
|
984
|
+
_CURRENT_RESOLVED_PROJ_ID = current_pid
|
|
966
985
|
return current_pid, current_dir
|
|
967
986
|
|
|
968
987
|
|
|
@@ -986,11 +1005,23 @@ def get_brain_context_prompt(user_prompt: str = "") -> str:
|
|
|
986
1005
|
proj_url = str(proj_dir.absolute()).replace("\\", "/")
|
|
987
1006
|
root_url = str(brain_root.absolute()).replace("\\", "/")
|
|
988
1007
|
|
|
1008
|
+
conventions = ""
|
|
1009
|
+
# Find the most recent architecture snapshot and inject conventions
|
|
1010
|
+
try:
|
|
1011
|
+
arch_dirs = sorted(proj_dir.glob("arch_*"), key=os.path.getmtime, reverse=True)
|
|
1012
|
+
if arch_dirs:
|
|
1013
|
+
conv_file = arch_dirs[0] / "conventions_summary.txt"
|
|
1014
|
+
if conv_file.exists():
|
|
1015
|
+
conventions = f"\n\n### PROJECT CONVENTIONS ###\n{conv_file.read_text(encoding='utf-8')}\n"
|
|
1016
|
+
except Exception:
|
|
1017
|
+
pass
|
|
1018
|
+
|
|
989
1019
|
return (
|
|
990
1020
|
f"\n\n### BRAIN MEMORY DIRECTORY ###\n"
|
|
991
1021
|
f"Project brain directory: [brain](file:///{proj_url})\n"
|
|
992
1022
|
f"Global brain directory: [global_brain](file:///{root_url})\n"
|
|
993
|
-
f"Note:
|
|
1023
|
+
f"Note: If you need to know about the project, check the brain files in the directory above - you will get exactly what you need. If something is missing, you can freely check the actual source code files.\n"
|
|
1024
|
+
f"{conventions}"
|
|
994
1025
|
)
|
|
995
1026
|
except Exception:
|
|
996
1027
|
return ""
|