codevira 1.6.0__tar.gz → 1.6.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.
Files changed (117) hide show
  1. {codevira-1.6.0 → codevira-1.6.1}/CHANGELOG.md +51 -22
  2. {codevira-1.6.0/codevira.egg-info → codevira-1.6.1}/PKG-INFO +2 -2
  3. {codevira-1.6.0 → codevira-1.6.1}/README.md +1 -1
  4. {codevira-1.6.0 → codevira-1.6.1/codevira.egg-info}/PKG-INFO +2 -2
  5. {codevira-1.6.0 → codevira-1.6.1}/indexer/index_codebase.py +75 -48
  6. {codevira-1.6.0 → codevira-1.6.1}/mcp_server/cli.py +225 -9
  7. {codevira-1.6.0 → codevira-1.6.1}/mcp_server/ide_inject.py +46 -1
  8. {codevira-1.6.0 → codevira-1.6.1}/mcp_server/server.py +6 -3
  9. {codevira-1.6.0 → codevira-1.6.1}/mcp_server/tools/search.py +39 -10
  10. {codevira-1.6.0 → codevira-1.6.1}/pyproject.toml +1 -1
  11. {codevira-1.6.0 → codevira-1.6.1}/tests/test_index_codebase.py +10 -11
  12. {codevira-1.6.0 → codevira-1.6.1}/tests/test_tools_search.py +29 -34
  13. {codevira-1.6.0 → codevira-1.6.1}/LICENSE +0 -0
  14. {codevira-1.6.0 → codevira-1.6.1}/MANIFEST.in +0 -0
  15. {codevira-1.6.0 → codevira-1.6.1}/agents/builder.md +0 -0
  16. {codevira-1.6.0 → codevira-1.6.1}/agents/developer.md +0 -0
  17. {codevira-1.6.0 → codevira-1.6.1}/agents/documenter.md +0 -0
  18. {codevira-1.6.0 → codevira-1.6.1}/agents/orchestrator.md +0 -0
  19. {codevira-1.6.0 → codevira-1.6.1}/agents/planner.md +0 -0
  20. {codevira-1.6.0 → codevira-1.6.1}/agents/reviewer.md +0 -0
  21. {codevira-1.6.0 → codevira-1.6.1}/agents/tester.md +0 -0
  22. {codevira-1.6.0 → codevira-1.6.1}/codevira.egg-info/SOURCES.txt +0 -0
  23. {codevira-1.6.0 → codevira-1.6.1}/codevira.egg-info/dependency_links.txt +0 -0
  24. {codevira-1.6.0 → codevira-1.6.1}/codevira.egg-info/entry_points.txt +0 -0
  25. {codevira-1.6.0 → codevira-1.6.1}/codevira.egg-info/requires.txt +0 -0
  26. {codevira-1.6.0 → codevira-1.6.1}/codevira.egg-info/top_level.txt +0 -0
  27. {codevira-1.6.0 → codevira-1.6.1}/config.example.yaml +0 -0
  28. {codevira-1.6.0 → codevira-1.6.1}/docs/how-i-built-persistent-memory-for-ai-agents.md +0 -0
  29. {codevira-1.6.0 → codevira-1.6.1}/docs/linkedin-article-ai-agent-memory.md +0 -0
  30. {codevira-1.6.0 → codevira-1.6.1}/docs/linkedin-post-ai-agent-memory.md +0 -0
  31. {codevira-1.6.0 → codevira-1.6.1}/docs/medium-your-ai-coding-agent-has-amnesia.md +0 -0
  32. {codevira-1.6.0 → codevira-1.6.1}/docs/roadmap.md +0 -0
  33. {codevira-1.6.0 → codevira-1.6.1}/graph/_schema.yaml +0 -0
  34. {codevira-1.6.0 → codevira-1.6.1}/indexer/__init__.py +0 -0
  35. {codevira-1.6.0 → codevira-1.6.1}/indexer/chunker.py +0 -0
  36. {codevira-1.6.0 → codevira-1.6.1}/indexer/global_db.py +0 -0
  37. {codevira-1.6.0 → codevira-1.6.1}/indexer/graph_generator.py +0 -0
  38. {codevira-1.6.0 → codevira-1.6.1}/indexer/outcome_tracker.py +0 -0
  39. {codevira-1.6.0 → codevira-1.6.1}/indexer/rule_learner.py +0 -0
  40. {codevira-1.6.0 → codevira-1.6.1}/indexer/sqlite_graph.py +0 -0
  41. {codevira-1.6.0 → codevira-1.6.1}/indexer/treesitter_parser.py +0 -0
  42. {codevira-1.6.0 → codevira-1.6.1}/mcp_server/__init__.py +0 -0
  43. {codevira-1.6.0 → codevira-1.6.1}/mcp_server/__main__.py +0 -0
  44. {codevira-1.6.0 → codevira-1.6.1}/mcp_server/auto_init.py +0 -0
  45. {codevira-1.6.0 → codevira-1.6.1}/mcp_server/crash_logger.py +0 -0
  46. {codevira-1.6.0 → codevira-1.6.1}/mcp_server/data/__init__.py +0 -0
  47. {codevira-1.6.0 → codevira-1.6.1}/mcp_server/data/agents/builder.md +0 -0
  48. {codevira-1.6.0 → codevira-1.6.1}/mcp_server/data/agents/developer.md +0 -0
  49. {codevira-1.6.0 → codevira-1.6.1}/mcp_server/data/agents/documenter.md +0 -0
  50. {codevira-1.6.0 → codevira-1.6.1}/mcp_server/data/agents/orchestrator.md +0 -0
  51. {codevira-1.6.0 → codevira-1.6.1}/mcp_server/data/agents/planner.md +0 -0
  52. {codevira-1.6.0 → codevira-1.6.1}/mcp_server/data/agents/reviewer.md +0 -0
  53. {codevira-1.6.0 → codevira-1.6.1}/mcp_server/data/agents/tester.md +0 -0
  54. {codevira-1.6.0 → codevira-1.6.1}/mcp_server/data/config.example.yaml +0 -0
  55. {codevira-1.6.0 → codevira-1.6.1}/mcp_server/data/rules/coding-standards.md +0 -0
  56. {codevira-1.6.0 → codevira-1.6.1}/mcp_server/data/rules/engineering-excellence.md +0 -0
  57. {codevira-1.6.0 → codevira-1.6.1}/mcp_server/data/rules/git-cicd-governance.md +0 -0
  58. {codevira-1.6.0 → codevira-1.6.1}/mcp_server/data/rules/git_commits.md +0 -0
  59. {codevira-1.6.0 → codevira-1.6.1}/mcp_server/data/rules/incremental-updates.md +0 -0
  60. {codevira-1.6.0 → codevira-1.6.1}/mcp_server/data/rules/master_rule.md +0 -0
  61. {codevira-1.6.0 → codevira-1.6.1}/mcp_server/data/rules/multi-language.md +0 -0
  62. {codevira-1.6.0 → codevira-1.6.1}/mcp_server/data/rules/persistence.md +0 -0
  63. {codevira-1.6.0 → codevira-1.6.1}/mcp_server/data/rules/resilience-observability.md +0 -0
  64. {codevira-1.6.0 → codevira-1.6.1}/mcp_server/data/rules/smoke-testing.md +0 -0
  65. {codevira-1.6.0 → codevira-1.6.1}/mcp_server/data/rules/testing-standards.md +0 -0
  66. {codevira-1.6.0 → codevira-1.6.1}/mcp_server/detect.py +0 -0
  67. {codevira-1.6.0 → codevira-1.6.1}/mcp_server/gitignore.py +0 -0
  68. {codevira-1.6.0 → codevira-1.6.1}/mcp_server/global_sync.py +0 -0
  69. {codevira-1.6.0 → codevira-1.6.1}/mcp_server/http_server.py +0 -0
  70. {codevira-1.6.0 → codevira-1.6.1}/mcp_server/launchd.py +0 -0
  71. {codevira-1.6.0 → codevira-1.6.1}/mcp_server/migrate.py +0 -0
  72. {codevira-1.6.0 → codevira-1.6.1}/mcp_server/paths.py +0 -0
  73. {codevira-1.6.0 → codevira-1.6.1}/mcp_server/prompts.py +0 -0
  74. {codevira-1.6.0 → codevira-1.6.1}/mcp_server/tools/__init__.py +0 -0
  75. {codevira-1.6.0 → codevira-1.6.1}/mcp_server/tools/changesets.py +0 -0
  76. {codevira-1.6.0 → codevira-1.6.1}/mcp_server/tools/code_reader.py +0 -0
  77. {codevira-1.6.0 → codevira-1.6.1}/mcp_server/tools/graph.py +0 -0
  78. {codevira-1.6.0 → codevira-1.6.1}/mcp_server/tools/learning.py +0 -0
  79. {codevira-1.6.0 → codevira-1.6.1}/mcp_server/tools/playbook.py +0 -0
  80. {codevira-1.6.0 → codevira-1.6.1}/mcp_server/tools/roadmap.py +0 -0
  81. {codevira-1.6.0 → codevira-1.6.1}/rules/coding-standards.md +0 -0
  82. {codevira-1.6.0 → codevira-1.6.1}/rules/engineering-excellence.md +0 -0
  83. {codevira-1.6.0 → codevira-1.6.1}/rules/git-cicd-governance.md +0 -0
  84. {codevira-1.6.0 → codevira-1.6.1}/rules/git_commits.md +0 -0
  85. {codevira-1.6.0 → codevira-1.6.1}/rules/incremental-updates.md +0 -0
  86. {codevira-1.6.0 → codevira-1.6.1}/rules/master_rule.md +0 -0
  87. {codevira-1.6.0 → codevira-1.6.1}/rules/persistence.md +0 -0
  88. {codevira-1.6.0 → codevira-1.6.1}/rules/resilience-observability.md +0 -0
  89. {codevira-1.6.0 → codevira-1.6.1}/rules/smoke-testing.md +0 -0
  90. {codevira-1.6.0 → codevira-1.6.1}/rules/testing-standards.md +0 -0
  91. {codevira-1.6.0 → codevira-1.6.1}/setup.cfg +0 -0
  92. {codevira-1.6.0 → codevira-1.6.1}/tests/test_auto_init.py +0 -0
  93. {codevira-1.6.0 → codevira-1.6.1}/tests/test_chunker.py +0 -0
  94. {codevira-1.6.0 → codevira-1.6.1}/tests/test_cli.py +0 -0
  95. {codevira-1.6.0 → codevira-1.6.1}/tests/test_crash_logger.py +0 -0
  96. {codevira-1.6.0 → codevira-1.6.1}/tests/test_detect.py +0 -0
  97. {codevira-1.6.0 → codevira-1.6.1}/tests/test_gitignore.py +0 -0
  98. {codevira-1.6.0 → codevira-1.6.1}/tests/test_global_db.py +0 -0
  99. {codevira-1.6.0 → codevira-1.6.1}/tests/test_global_sync.py +0 -0
  100. {codevira-1.6.0 → codevira-1.6.1}/tests/test_graph_generator.py +0 -0
  101. {codevira-1.6.0 → codevira-1.6.1}/tests/test_http_server.py +0 -0
  102. {codevira-1.6.0 → codevira-1.6.1}/tests/test_ide_inject.py +0 -0
  103. {codevira-1.6.0 → codevira-1.6.1}/tests/test_launchd.py +0 -0
  104. {codevira-1.6.0 → codevira-1.6.1}/tests/test_migrate.py +0 -0
  105. {codevira-1.6.0 → codevira-1.6.1}/tests/test_outcome_tracker.py +0 -0
  106. {codevira-1.6.0 → codevira-1.6.1}/tests/test_paths.py +0 -0
  107. {codevira-1.6.0 → codevira-1.6.1}/tests/test_prompts.py +0 -0
  108. {codevira-1.6.0 → codevira-1.6.1}/tests/test_rule_learner.py +0 -0
  109. {codevira-1.6.0 → codevira-1.6.1}/tests/test_server.py +0 -0
  110. {codevira-1.6.0 → codevira-1.6.1}/tests/test_sqlite_graph.py +0 -0
  111. {codevira-1.6.0 → codevira-1.6.1}/tests/test_tools_changesets.py +0 -0
  112. {codevira-1.6.0 → codevira-1.6.1}/tests/test_tools_code_reader.py +0 -0
  113. {codevira-1.6.0 → codevira-1.6.1}/tests/test_tools_graph.py +0 -0
  114. {codevira-1.6.0 → codevira-1.6.1}/tests/test_tools_learning.py +0 -0
  115. {codevira-1.6.0 → codevira-1.6.1}/tests/test_tools_playbook.py +0 -0
  116. {codevira-1.6.0 → codevira-1.6.1}/tests/test_tools_roadmap.py +0 -0
  117. {codevira-1.6.0 → codevira-1.6.1}/tests/test_treesitter_parser.py +0 -0
@@ -11,32 +11,61 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
11
11
 
12
12
  ## [Unreleased]
13
13
 
14
- ### Performance
15
- - **`get_data_dir()` caching**: Result cached per project root in `_data_dir_cache`.
16
- First call runs the full resolution chain (subprocess + metadata scan); all
17
- subsequent calls for the same root are O(1) dict lookups with zero I/O.
18
- - **`set_project_dir()` cache invalidation**: Changing the project root now clears
19
- the data-dir cache so subsequent `get_data_dir()` calls resolve correctly.
14
+ ---
15
+
16
+ ## [1.6.1] 2026-04-16 Stability, Graceful Degradation & Cleanup
17
+
18
+ ### Added
19
+ - **`codevira clean` command**: One-shot removal of all Codevira data, IDE configs,
20
+ and services. Supports `--all` (per-project artifacts), `--dry-run` (preview),
21
+ and `-y` (skip confirmation).
22
+ - **Google Antigravity global mode**: `codevira register` now includes Antigravity
23
+ with a single global entry — no per-project hardcoded paths.
20
24
 
21
25
  ### Fixed
22
- - **Test isolation**: Autouse fixture now clears `_data_dir_cache` and resets
23
- `_project_dir_override` between every test, preventing cross-test pollution.
24
- - **`auto_init._init_done` renamed to `_init_started`**: The flag is set when the
25
- background thread *starts*, not when it *finishes* — name now matches semantics.
26
- - **Unbounded `join()` in auto-init**: Background semantic indexing thread now has
27
- a 5-minute timeout; if it hangs, the server continues in graph-only mode.
28
- - **`install_launchd()` missing `project_dir`**: Added optional `project_dir`
29
- parameter. When provided, adds `--project-dir` to ProgramArguments and
30
- `WorkingDirectory` to the launchd plist.
31
- - **CLI `project_dir` not forwarded to launchd**: `cmd_serve()` now passes
32
- `project_dir` through to `install_launchd()`.
33
- - **`run_http_server()` ignoring `project_dir`**: Now calls `set_project_dir()`
34
- when `project_dir` is not None, so direct callers get correct path resolution.
26
+ - **Graceful degradation when chromadb not installed**: `refresh_index` and
27
+ `cmd_incremental` now work in graph-only mode instead of crashing with
28
+ ImportError. Background file watcher no longer generates noisy exceptions
29
+ on every file save.
30
+ - **`sys.exit()` crashes eliminated**: `server.py` module-level import failure
31
+ now uses stderr + raise instead of corrupting the MCP stdio protocol.
32
+ `cmd_incremental` no longer kills the MCP server process from background
33
+ watcher threads.
34
+ - **Binary resolution in user-facing output**: `codevira init` "For other AI
35
+ tools" section now shows the resolved `codevira` binary path instead of a
36
+ hardcoded Python interpreter path (e.g. `/opt/homebrew/...`).
37
+ - **Antigravity config path**: Fixed from `~/.gemini/settings/mcp_config.json`
38
+ (wrong) to `~/.gemini/antigravity/mcp_config.json` (correct).
39
+ - **Rich markup escaping**: `codevira[search]` install hints now display
40
+ correctly — Rich no longer strips `[search]` as a style tag.
41
+ - **`codevira status` without chromadb**: Shows "Semantic Search: not installed"
42
+ with install tip instead of crashing. Added graph node count to status.
43
+ - **Git hook uses full binary resolution**: Post-commit hook now uses
44
+ `_resolve_command()` instead of simple `shutil.which`.
45
+
46
+ ### Performance
47
+ - **`get_data_dir()` caching**: Result cached per project root. First call runs
48
+ subprocess + metadata scan; subsequent calls are O(1) dict lookups.
49
+ - **`set_project_dir()` cache invalidation**: Changing the project root now
50
+ clears the data-dir cache automatically.
51
+ - **Unbounded join timeout**: Background semantic indexing thread capped at
52
+ 5 minutes; server continues in graph-only mode if it hangs.
35
53
 
36
54
  ### Changed
37
- - **README.md**: Updated for v1.6.0 version badge, Quick Start flow (`register`
38
- instead of `init`), centralized storage diagram, documented `codevira register`,
39
- `--install-service`/`--uninstall-service`, auto-init, and legacy migration.
55
+ - **Package renamed**: `codevira-mcp` `codevira`. Install with `pip install
56
+ codevira`. CLI command is now `codevira` (not `codevira-mcp`).
57
+ - **Removed unused `gitpython` dependency**: CodeVira uses `subprocess` for
58
+ all git operations. Saves ~20MB install weight.
59
+ - **Removed out-of-scope rule files**: REST API standards, SSE/UI events,
60
+ TUI layout/keybinding rules — none apply to an MCP server.
61
+ - **Removed vendor-specific secret patterns from crash logger**: Stripe, AWS,
62
+ GitHub token regexes were irrelevant to CodeVira's scope.
63
+ - **Test isolation hardened**: Autouse fixture clears `_data_dir_cache` and
64
+ resets `_project_dir_override` between every test.
65
+ - **`_init_done` renamed to `_init_started`**: Name matches semantics — the
66
+ flag signals thread launch, not completion.
67
+ - **`install_launchd()` accepts `project_dir`**: Adds `--project-dir` to
68
+ ProgramArguments and `WorkingDirectory` to the plist.
40
69
 
41
70
  ---
42
71
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: codevira
3
- Version: 1.6.0
3
+ Version: 1.6.1
4
4
  Summary: Persistent adaptive memory for AI coding agents — MCP server with context graph, semantic search, adaptive learning, roadmap tracking, and cross-tool continuity.
5
5
  Author-email: Sachin Shelke <sachin.worldnet@gmail.com>
6
6
  License: MIT
@@ -46,7 +46,7 @@ Requires-Dist: sentence-transformers>=2.7.0; extra == "all"
46
46
  [![Python](https://img.shields.io/badge/python-3.10%2B-blue)](https://www.python.org/)
47
47
  [![License: MIT](https://img.shields.io/badge/license-MIT-green)](LICENSE)
48
48
  [![MCP](https://img.shields.io/badge/protocol-MCP-purple)](https://modelcontextprotocol.io)
49
- [![Version](https://img.shields.io/badge/version-1.6.0-orange)](CHANGELOG.md)
49
+ [![Version](https://img.shields.io/badge/version-1.6.1-orange)](CHANGELOG.md)
50
50
  [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen)](CONTRIBUTING.md)
51
51
 
52
52
  **Works with:** Claude Code · Claude Desktop · Cursor · Windsurf · Google Antigravity · any MCP-compatible AI tool
@@ -5,7 +5,7 @@
5
5
  [![Python](https://img.shields.io/badge/python-3.10%2B-blue)](https://www.python.org/)
6
6
  [![License: MIT](https://img.shields.io/badge/license-MIT-green)](LICENSE)
7
7
  [![MCP](https://img.shields.io/badge/protocol-MCP-purple)](https://modelcontextprotocol.io)
8
- [![Version](https://img.shields.io/badge/version-1.6.0-orange)](CHANGELOG.md)
8
+ [![Version](https://img.shields.io/badge/version-1.6.1-orange)](CHANGELOG.md)
9
9
  [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen)](CONTRIBUTING.md)
10
10
 
11
11
  **Works with:** Claude Code · Claude Desktop · Cursor · Windsurf · Google Antigravity · any MCP-compatible AI tool
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: codevira
3
- Version: 1.6.0
3
+ Version: 1.6.1
4
4
  Summary: Persistent adaptive memory for AI coding agents — MCP server with context graph, semantic search, adaptive learning, roadmap tracking, and cross-tool continuity.
5
5
  Author-email: Sachin Shelke <sachin.worldnet@gmail.com>
6
6
  License: MIT
@@ -46,7 +46,7 @@ Requires-Dist: sentence-transformers>=2.7.0; extra == "all"
46
46
  [![Python](https://img.shields.io/badge/python-3.10%2B-blue)](https://www.python.org/)
47
47
  [![License: MIT](https://img.shields.io/badge/license-MIT-green)](LICENSE)
48
48
  [![MCP](https://img.shields.io/badge/protocol-MCP-purple)](https://modelcontextprotocol.io)
49
- [![Version](https://img.shields.io/badge/version-1.6.0-orange)](CHANGELOG.md)
49
+ [![Version](https://img.shields.io/badge/version-1.6.1-orange)](CHANGELOG.md)
50
50
  [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen)](CONTRIBUTING.md)
51
51
 
52
52
  **Works with:** Claude Code · Claude Desktop · Cursor · Windsurf · Google Antigravity · any MCP-compatible AI tool
@@ -61,9 +61,10 @@ def _get_chroma_client():
61
61
  try:
62
62
  import chromadb
63
63
  except ImportError:
64
- print("ERROR: semantic search requires chromadb.")
65
- print(" Install it with: pip install 'codevira[search]'")
66
- sys.exit(1)
64
+ raise ImportError(
65
+ "Semantic search requires chromadb. "
66
+ "Install it with: pip install 'codevira[search]'"
67
+ )
67
68
  db_dir = str(_index_dir())
68
69
  return chromadb.PersistentClient(path=db_dir)
69
70
 
@@ -72,9 +73,10 @@ def _get_embedding_fn():
72
73
  from chromadb.utils import embedding_functions
73
74
  return embedding_functions.SentenceTransformerEmbeddingFunction(model_name="all-MiniLM-L6-v2")
74
75
  except ImportError:
75
- print("ERROR: semantic search requires sentence-transformers.")
76
- print(" Install it with: pip install 'codevira[search]'")
77
- sys.exit(1)
76
+ raise ImportError(
77
+ "Semantic search requires sentence-transformers. "
78
+ "Install it with: pip install 'codevira[search]'"
79
+ )
78
80
 
79
81
  def _compute_hash(file_path: Path) -> str:
80
82
  hasher = hashlib.sha256()
@@ -177,7 +179,7 @@ def cmd_full_rebuild():
177
179
  db = SQLiteGraph(get_data_dir() / "graph" / "graph.db")
178
180
 
179
181
  if not _check_search_deps():
180
- console.print("[yellow]⚠[/yellow] Semantic search skipped — install with: [bold]pip install 'codevira\\[search\\]'[/bold]")
182
+ console.print("[yellow]⚠[/yellow] Semantic search skipped — install with: [bold]pip install 'codevira\\[search]'[/bold]")
181
183
  # Still build the graph even without search deps
182
184
  from indexer.graph_generator import generate_graph_sqlite
183
185
  result = generate_graph_sqlite(str(_project_root()), str(get_data_dir() / "graph" / "graph.db"))
@@ -272,50 +274,62 @@ def cmd_incremental(quiet: bool = False, file_paths: list[str] | None = None):
272
274
  file_label = "requested file(s)" if explicit_files else "changed file(s)"
273
275
  console.print(f"[bold cyan]Incremental update:[/bold cyan] {len(changed_items)} {file_label}")
274
276
 
275
- client = _get_chroma_client()
276
- embed_fn = _get_embedding_fn()
277
- try:
278
- collection = client.get_collection(COLLECTION_NAME, embedding_function=embed_fn)
279
- except Exception:
280
- console.print("[red]No existing index found.[/red] Run codevira index --full first.")
281
- db.close()
282
- sys.exit(1)
277
+ # Check if semantic search deps are available
278
+ has_search = _check_search_deps()
279
+ collection = None
280
+
281
+ if has_search:
282
+ try:
283
+ client = _get_chroma_client()
284
+ embed_fn = _get_embedding_fn()
285
+ collection = client.get_collection(COLLECTION_NAME, embedding_function=embed_fn)
286
+ except Exception:
287
+ # No existing collection — skip semantic indexing, still update graph
288
+ collection = None
289
+ if not quiet:
290
+ console.print("[yellow]⚠[/yellow] No semantic index found — updating graph only.")
283
291
 
284
292
  indexed_any = False
285
- # Acquire ChromaDB write lock to prevent concurrent writes with the
286
- # background watcher or background full-index thread.
287
- with _chroma_write_lock:
288
- for fpath, fhash in changed_items:
289
- try:
290
- collection.delete(where={"file_path": fpath})
291
- except Exception as e:
292
- try:
293
- from mcp_server.crash_logger import log_crash
294
- log_crash(e, context=f"incremental index: delete old chunks for {fpath}")
295
- except Exception: pass
296
293
 
297
- try:
298
- chunks = chunk_file(str(_project_root() / fpath), str(_project_root()))
299
- if chunks:
300
- ids, docs, metas = [], [], []
301
- for chunk in chunks:
302
- doc_id, doc, meta = _chunk_to_document(chunk)
303
- ids.append(doc_id)
304
- docs.append(doc)
305
- metas.append(meta)
306
- collection.add(ids=ids, documents=docs, metadatas=metas)
307
-
308
- db.update_file_hash(fpath, fhash)
309
- indexed_any = True
310
- console.print(f" [green]+[/green] Re-indexed {len(chunks)} chunks for {fpath}")
294
+ if collection is not None:
295
+ # Semantic search + graph update
296
+ with _chroma_write_lock:
297
+ for fpath, fhash in changed_items:
298
+ try:
299
+ collection.delete(where={"file_path": fpath})
300
+ except Exception as e:
301
+ try:
302
+ from mcp_server.crash_logger import log_crash
303
+ log_crash(e, context=f"incremental index: delete old chunks for {fpath}")
304
+ except Exception: pass
311
305
 
312
- except Exception as e:
313
- console.print(f"[red]Error indexing {fpath}: {e}[/red]")
314
306
  try:
315
- from mcp_server.crash_logger import log_crash
316
- log_crash(e, context=f"incremental index: indexing {fpath}")
317
- except Exception: pass
318
- continue
307
+ chunks = chunk_file(str(_project_root() / fpath), str(_project_root()))
308
+ if chunks:
309
+ ids, docs, metas = [], [], []
310
+ for chunk in chunks:
311
+ doc_id, doc, meta = _chunk_to_document(chunk)
312
+ ids.append(doc_id)
313
+ docs.append(doc)
314
+ metas.append(meta)
315
+ collection.add(ids=ids, documents=docs, metadatas=metas)
316
+
317
+ db.update_file_hash(fpath, fhash)
318
+ indexed_any = True
319
+ console.print(f" [green]+[/green] Re-indexed {len(chunks)} chunks for {fpath}")
320
+
321
+ except Exception as e:
322
+ console.print(f"[red]Error indexing {fpath}: {e}[/red]")
323
+ try:
324
+ from mcp_server.crash_logger import log_crash
325
+ log_crash(e, context=f"incremental index: indexing {fpath}")
326
+ except Exception: pass
327
+ continue
328
+ else:
329
+ # Graph-only mode: update file hashes without semantic indexing
330
+ for fpath, fhash in changed_items:
331
+ db.update_file_hash(fpath, fhash)
332
+ indexed_any = True
319
333
 
320
334
  if indexed_any:
321
335
  generate_graph_sqlite(str(_project_root()), str(db.db_path))
@@ -516,8 +530,18 @@ def cmd_status():
516
530
 
517
531
  stale_files = _get_changed_files(db)
518
532
 
533
+ # Count graph nodes
534
+ try:
535
+ nodes = db.conn.execute("SELECT COUNT(*) FROM nodes").fetchone()[0]
536
+ except Exception:
537
+ nodes = 0
538
+
519
539
  table = Table(show_header=False, box=None)
520
- table.add_row("[cyan]ChromaDB Chunks:[/cyan]", str(chunk_count))
540
+ table.add_row("[cyan]Graph Nodes:[/cyan]", str(nodes))
541
+ if search_available:
542
+ table.add_row("[cyan]ChromaDB Chunks:[/cyan]", str(chunk_count))
543
+ else:
544
+ table.add_row("[cyan]Semantic Search:[/cyan]", "[dim]not installed[/dim]")
521
545
  table.add_row("[cyan]Outdated Files:[/cyan]", str(len(stale_files)))
522
546
 
523
547
  panel = Panel(
@@ -528,13 +552,16 @@ def cmd_status():
528
552
  )
529
553
  console.print(panel)
530
554
 
555
+ if not search_available:
556
+ console.print("\n[dim] Tip: pip install 'codevira\\[search]' to enable semantic code search[/dim]")
557
+
531
558
  if stale_files:
532
559
  console.print("\n[yellow]Files requiring re-indexing:[/yellow]")
533
560
  for fp, _ in stale_files[:10]:
534
561
  console.print(f" - {fp}")
535
562
  if len(stale_files) > 10:
536
563
  console.print(f" ... and {len(stale_files) - 10} more.")
537
-
564
+
538
565
  db.close()
539
566
 
540
567
  def cmd_generate_graph():
@@ -236,9 +236,12 @@ def cmd_init() -> None:
236
236
  hooks_dir.mkdir(exist_ok=True)
237
237
  hook_path = hooks_dir / "post-commit"
238
238
 
239
- # Find codevira executable path
240
- import shutil as _shutil
241
- cmd_path = _shutil.which("codevira") or "codevira"
239
+ # Find codevira executable path using full resolution chain
240
+ from mcp_server.ide_inject import _resolve_command
241
+ resolved_cmd, _py = _resolve_command()
242
+ # For git hooks, use the resolved binary if found; otherwise bare name
243
+ # (git hooks inherit the user's shell PATH)
244
+ cmd_path = resolved_cmd if resolved_cmd != _py else "codevira"
242
245
 
243
246
  hook_content = (
244
247
  "#!/bin/sh\n"
@@ -311,20 +314,25 @@ def cmd_init() -> None:
311
314
  log_crash(e, context="codevira init: global memory register", project_path=str(cwd))
312
315
  except Exception: pass
313
316
 
314
- # Print fallback for undetected tools
315
- import shutil as _shutil
316
- import sys as _sys
317
- python_exe = _sys.executable
317
+ # Print config for undetected tools — use the resolved binary path,
318
+ # not the Python interpreter, so users get a clean command.
319
+ from mcp_server.ide_inject import _resolve_command
320
+ cmd_path, python_exe = _resolve_command()
318
321
  project_path = str(cwd)
319
322
 
323
+ is_python_fallback = (cmd_path == python_exe)
320
324
  print()
321
325
  print(" For other AI tools, add this to their MCP config:")
322
326
  print()
323
327
  print(' {')
324
328
  print(' "mcpServers": {')
325
329
  print(' "codevira": {')
326
- print(f' "command": "{python_exe}",')
327
- print(f' "args": ["-m", "mcp_server", "--project-dir", "{project_path}"]')
330
+ if is_python_fallback:
331
+ print(f' "command": "{python_exe}",')
332
+ print(f' "args": ["-m", "mcp_server", "--project-dir", "{project_path}"]')
333
+ else:
334
+ print(f' "command": "{cmd_path}",')
335
+ print(f' "args": ["--project-dir", "{project_path}"]')
328
336
  print(' }')
329
337
  print(' }')
330
338
  print(' }')
@@ -473,6 +481,11 @@ def cmd_register(
473
481
  path = _inject_claude_desktop(project_root, cmd_path, python_exe)
474
482
  if path:
475
483
  results["Claude Desktop"] = path
484
+ elif ide == "antigravity":
485
+ from mcp_server.ide_inject import inject_global_antigravity
486
+ path = inject_global_antigravity(cmd_path, python_exe)
487
+ if path:
488
+ results["Antigravity (global)"] = path
476
489
  except Exception as e:
477
490
  print(f" Warning: could not configure {ide}: {e}")
478
491
 
@@ -575,6 +588,24 @@ def main() -> None:
575
588
  help="Inject an HTTP URL config into Claude Code (e.g. https://localhost:7443/mcp)",
576
589
  )
577
590
 
591
+ # clean
592
+ clean_parser = subparsers.add_parser(
593
+ "clean",
594
+ help="Remove all Codevira data, IDE configs, and services",
595
+ )
596
+ clean_parser.add_argument(
597
+ "--all", action="store_true",
598
+ help="Also clean per-project artifacts (legacy .codevira/, git hooks, per-project IDE configs)",
599
+ )
600
+ clean_parser.add_argument(
601
+ "--dry-run", action="store_true",
602
+ help="Show what would be removed without deleting anything",
603
+ )
604
+ clean_parser.add_argument(
605
+ "-y", "--yes", action="store_true",
606
+ help="Skip confirmation prompt",
607
+ )
608
+
578
609
  args = parser.parse_args(raw_args)
579
610
 
580
611
  if args.command == "init":
@@ -613,10 +644,195 @@ def main() -> None:
613
644
  claude_desktop=getattr(args, "claude_desktop", False),
614
645
  http_url=getattr(args, "http_url", None),
615
646
  )
647
+ elif args.command == "clean":
648
+ cmd_clean(
649
+ clean_all=getattr(args, "all", False),
650
+ dry_run=getattr(args, "dry_run", False),
651
+ yes=getattr(args, "yes", False),
652
+ )
616
653
  else:
617
654
  # No subcommand → start MCP server (stdio transport)
618
655
  cmd_server()
619
656
 
620
657
 
658
+ # ---------------------------------------------------------------------------
659
+ # cmd_clean
660
+ # ---------------------------------------------------------------------------
661
+
662
+ def cmd_clean(clean_all: bool = False, dry_run: bool = False, yes: bool = False) -> None:
663
+ """Remove all Codevira data, IDE configs, and services."""
664
+ import shutil
665
+
666
+ from mcp_server.paths import get_global_home
667
+ from mcp_server.ide_inject import (
668
+ _claude_global_config_path,
669
+ _claude_desktop_config_path,
670
+ _cursor_global_config_path,
671
+ _windsurf_global_config_path,
672
+ _antigravity_config_path,
673
+ remove_codevira_from_config,
674
+ )
675
+
676
+ actions: list[tuple[str, callable]] = []
677
+ print()
678
+ print(" Codevira — Clean Setup")
679
+ print(" " + "─" * 40)
680
+ print()
681
+
682
+ # 1. Global data directory
683
+ global_home = get_global_home()
684
+ if global_home.exists():
685
+ # Count projects and size
686
+ projects_dir = global_home / "projects"
687
+ project_count = len(list(projects_dir.iterdir())) if projects_dir.exists() else 0
688
+ try:
689
+ total_size = sum(f.stat().st_size for f in global_home.rglob("*") if f.is_file())
690
+ size_str = f"{total_size / 1024 / 1024:.1f} MB"
691
+ except Exception:
692
+ size_str = "unknown size"
693
+ print(f" • ~/.codevira/ ({project_count} projects, {size_str})")
694
+ actions.append(("Removed ~/.codevira/", lambda: shutil.rmtree(global_home, ignore_errors=True)))
695
+
696
+ # 2. IDE configs
697
+ ide_configs = [
698
+ ("Claude Code global", _claude_global_config_path()),
699
+ ("Claude Desktop", _claude_desktop_config_path()),
700
+ ("Cursor global", _cursor_global_config_path()),
701
+ ("Windsurf global", _windsurf_global_config_path()),
702
+ ("Antigravity", _antigravity_config_path()),
703
+ ]
704
+ for ide_name, config_path in ide_configs:
705
+ if config_path.exists():
706
+ from mcp_server.ide_inject import _read_json_safe
707
+ data = _read_json_safe(config_path)
708
+ servers = data.get("mcpServers", {})
709
+ has_codevira = any(k == "codevira" or k.startswith("codevira-") for k in servers)
710
+ if has_codevira:
711
+ print(f" • {ide_name} config (mcpServers.codevira)")
712
+ actions.append(
713
+ (f"Removed codevira from {ide_name}",
714
+ lambda p=config_path: remove_codevira_from_config(p))
715
+ )
716
+
717
+ # 3. Launchd service
718
+ plist_path = Path.home() / "Library" / "LaunchAgents" / "com.codevira.mcp-serve.plist"
719
+ if plist_path.exists():
720
+ print(" • Launchd service (com.codevira.mcp-serve)")
721
+ def _unload_launchd():
722
+ try:
723
+ from mcp_server.launchd import uninstall_launchd
724
+ uninstall_launchd()
725
+ except Exception:
726
+ plist_path.unlink(missing_ok=True)
727
+ actions.append(("Unloaded launchd service", _unload_launchd))
728
+
729
+ # 4. Server log
730
+ log_path = Path.home() / "Library" / "Logs" / "codevira.log"
731
+ if log_path.exists():
732
+ print(f" • ~/Library/Logs/codevira.log")
733
+ actions.append(("Removed server log", lambda: log_path.unlink(missing_ok=True)))
734
+
735
+ # 5. Per-project artifacts (only with --all)
736
+ if clean_all and global_home.exists():
737
+ projects_dir = global_home / "projects"
738
+ if projects_dir.exists():
739
+ for meta_file in projects_dir.glob("*/metadata.json"):
740
+ try:
741
+ import json
742
+ meta = json.loads(meta_file.read_text())
743
+ project_path = Path(meta.get("original_path", ""))
744
+ if project_path.exists():
745
+ _collect_project_cleanup(project_path, actions)
746
+ except Exception:
747
+ continue
748
+
749
+ if not actions:
750
+ print(" Nothing to clean — Codevira is not installed.")
751
+ print()
752
+ return
753
+
754
+ print()
755
+
756
+ # Confirmation
757
+ if dry_run:
758
+ print(" [dry-run] No changes made.")
759
+ print()
760
+ return
761
+
762
+ if not yes:
763
+ answer = input(" Remove all of the above? [y/N] ").strip().lower()
764
+ if answer != "y":
765
+ print(" Aborted.")
766
+ print()
767
+ return
768
+
769
+ print()
770
+ for label, action in actions:
771
+ try:
772
+ action()
773
+ print(f" {label:<45} done")
774
+ except Exception as e:
775
+ print(f" {label:<45} FAILED ({e})")
776
+
777
+ print()
778
+ print(" ✓ Codevira fully removed.")
779
+ print(" To reinstall: pipx install codevira && codevira register")
780
+ print()
781
+
782
+
783
+ def _collect_project_cleanup(project_path: Path, actions: list) -> None:
784
+ """Collect per-project cleanup actions."""
785
+ import shutil
786
+ from mcp_server.ide_inject import remove_codevira_from_config
787
+
788
+ name = project_path.name
789
+
790
+ # Legacy .codevira/ dir
791
+ legacy = project_path / ".codevira"
792
+ if legacy.exists():
793
+ print(f" • {name}/.codevira/")
794
+ actions.append((f"Removed {name}/.codevira/", lambda p=legacy: shutil.rmtree(p, ignore_errors=True)))
795
+
796
+ # Migration backup
797
+ migrated = project_path / ".codevira.migrated"
798
+ if migrated.exists():
799
+ print(f" • {name}/.codevira.migrated/")
800
+ actions.append((f"Removed {name}/.codevira.migrated/", lambda p=migrated: shutil.rmtree(p, ignore_errors=True)))
801
+
802
+ # Git hook
803
+ hook = project_path / ".git" / "hooks" / "post-commit"
804
+ if hook.exists():
805
+ try:
806
+ content = hook.read_text()
807
+ if "codevira" in content.lower() or "Codevira" in content:
808
+ print(f" • {name}/.git/hooks/post-commit")
809
+ # Check if hook has backup
810
+ backup = hook.with_suffix(".bak")
811
+ if backup.exists():
812
+ actions.append((f"Restored {name} git hook from backup",
813
+ lambda h=hook, b=backup: b.rename(h)))
814
+ else:
815
+ actions.append((f"Removed {name} git hook",
816
+ lambda h=hook: h.unlink(missing_ok=True)))
817
+ except Exception:
818
+ pass
819
+
820
+ # Per-project IDE configs
821
+ for ide_name, config_path in [
822
+ ("claude", project_path / ".claude" / "settings.json"),
823
+ ("cursor", project_path / ".cursor" / "mcp.json"),
824
+ ("windsurf", project_path / ".windsurf" / "mcp.json"),
825
+ ]:
826
+ if config_path.exists():
827
+ from mcp_server.ide_inject import _read_json_safe
828
+ data = _read_json_safe(config_path)
829
+ if "codevira" in data.get("mcpServers", {}):
830
+ print(f" • {name}/.{ide_name} config")
831
+ actions.append(
832
+ (f"Removed codevira from {name}/{ide_name}",
833
+ lambda p=config_path: remove_codevira_from_config(p))
834
+ )
835
+
836
+
621
837
  if __name__ == "__main__":
622
838
  main()
@@ -93,7 +93,7 @@ def _windsurf_global_config_path() -> Path:
93
93
  return Path.home() / ".windsurf" / "mcp_config.json"
94
94
 
95
95
  def _antigravity_config_path() -> Path:
96
- return Path.home() / ".gemini" / "settings" / "mcp_config.json"
96
+ return Path.home() / ".gemini" / "antigravity" / "mcp_config.json"
97
97
 
98
98
 
99
99
  # ---------------------------------------------------------------------------
@@ -128,6 +128,36 @@ def _merge_mcp_config(existing: dict, server_name: str, server_config: dict) ->
128
128
  return result
129
129
 
130
130
 
131
+ def remove_codevira_from_config(config_path: Path, key_prefix: str = "codevira") -> bool:
132
+ """Remove all codevira entries from an IDE config file.
133
+
134
+ Deletes keys from mcpServers that match `key_prefix` exactly or start
135
+ with `key_prefix-` (for Antigravity per-project entries like codevira-udap).
136
+
137
+ Returns True if any keys were removed, False if nothing to do.
138
+ """
139
+ if not config_path.exists():
140
+ return False
141
+
142
+ data = _read_json_safe(config_path)
143
+ servers = data.get("mcpServers", {})
144
+ if not servers:
145
+ return False
146
+
147
+ keys_to_remove = [
148
+ k for k in servers
149
+ if k == key_prefix or k.startswith(f"{key_prefix}-")
150
+ ]
151
+ if not keys_to_remove:
152
+ return False
153
+
154
+ for k in keys_to_remove:
155
+ del servers[k]
156
+
157
+ _write_json_safe(config_path, data)
158
+ return True
159
+
160
+
131
161
  # ---------------------------------------------------------------------------
132
162
  # Resolve the best command to run codevira
133
163
  # ---------------------------------------------------------------------------
@@ -340,6 +370,21 @@ def inject_global_windsurf(cmd_path: str, python_exe: str) -> str | None:
340
370
  return str(config_path)
341
371
 
342
372
 
373
+ def inject_global_antigravity(cmd_path: str, python_exe: str) -> str | None:
374
+ """Inject global codevira config into Google Antigravity.
375
+
376
+ Uses a single 'codevira' entry with no project path. Antigravity
377
+ sets the working directory when it starts the MCP server process.
378
+ """
379
+ config_path = _antigravity_config_path()
380
+ existing = _read_json_safe(config_path)
381
+ base_config = _build_global_server_config(cmd_path, python_exe)
382
+ server_config = {"$typeName": "exa.cascade_plugins_pb.CascadePluginCommandTemplate", **base_config}
383
+ merged = _merge_mcp_config(existing, "codevira", server_config)
384
+ _write_json_safe(config_path, merged)
385
+ return str(config_path)
386
+
387
+
343
388
  def inject_claude_http_url(url: str) -> str | None:
344
389
  """Inject HTTP URL config into Claude Code global settings.
345
390