codexa 0.4.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.
Files changed (189) hide show
  1. codexa-0.4.0.dist-info/METADATA +650 -0
  2. codexa-0.4.0.dist-info/RECORD +189 -0
  3. codexa-0.4.0.dist-info/WHEEL +5 -0
  4. codexa-0.4.0.dist-info/entry_points.txt +2 -0
  5. codexa-0.4.0.dist-info/licenses/LICENSE +21 -0
  6. codexa-0.4.0.dist-info/top_level.txt +1 -0
  7. semantic_code_intelligence/__init__.py +5 -0
  8. semantic_code_intelligence/analysis/__init__.py +21 -0
  9. semantic_code_intelligence/analysis/ai_features.py +351 -0
  10. semantic_code_intelligence/bridge/__init__.py +28 -0
  11. semantic_code_intelligence/bridge/context_provider.py +245 -0
  12. semantic_code_intelligence/bridge/protocol.py +167 -0
  13. semantic_code_intelligence/bridge/server.py +348 -0
  14. semantic_code_intelligence/bridge/vscode.py +271 -0
  15. semantic_code_intelligence/ci/__init__.py +13 -0
  16. semantic_code_intelligence/ci/hooks.py +98 -0
  17. semantic_code_intelligence/ci/hotspots.py +272 -0
  18. semantic_code_intelligence/ci/impact.py +246 -0
  19. semantic_code_intelligence/ci/metrics.py +591 -0
  20. semantic_code_intelligence/ci/pr.py +412 -0
  21. semantic_code_intelligence/ci/quality.py +557 -0
  22. semantic_code_intelligence/ci/templates.py +164 -0
  23. semantic_code_intelligence/ci/trace.py +224 -0
  24. semantic_code_intelligence/cli/__init__.py +0 -0
  25. semantic_code_intelligence/cli/commands/__init__.py +0 -0
  26. semantic_code_intelligence/cli/commands/ask_cmd.py +153 -0
  27. semantic_code_intelligence/cli/commands/benchmark_cmd.py +303 -0
  28. semantic_code_intelligence/cli/commands/chat_cmd.py +252 -0
  29. semantic_code_intelligence/cli/commands/ci_gen_cmd.py +74 -0
  30. semantic_code_intelligence/cli/commands/context_cmd.py +120 -0
  31. semantic_code_intelligence/cli/commands/cross_refactor_cmd.py +113 -0
  32. semantic_code_intelligence/cli/commands/deps_cmd.py +91 -0
  33. semantic_code_intelligence/cli/commands/docs_cmd.py +101 -0
  34. semantic_code_intelligence/cli/commands/doctor_cmd.py +147 -0
  35. semantic_code_intelligence/cli/commands/evolve_cmd.py +171 -0
  36. semantic_code_intelligence/cli/commands/explain_cmd.py +112 -0
  37. semantic_code_intelligence/cli/commands/gate_cmd.py +135 -0
  38. semantic_code_intelligence/cli/commands/grep_cmd.py +234 -0
  39. semantic_code_intelligence/cli/commands/hotspots_cmd.py +119 -0
  40. semantic_code_intelligence/cli/commands/impact_cmd.py +131 -0
  41. semantic_code_intelligence/cli/commands/index_cmd.py +138 -0
  42. semantic_code_intelligence/cli/commands/init_cmd.py +152 -0
  43. semantic_code_intelligence/cli/commands/investigate_cmd.py +163 -0
  44. semantic_code_intelligence/cli/commands/languages_cmd.py +101 -0
  45. semantic_code_intelligence/cli/commands/lsp_cmd.py +49 -0
  46. semantic_code_intelligence/cli/commands/mcp_cmd.py +50 -0
  47. semantic_code_intelligence/cli/commands/metrics_cmd.py +264 -0
  48. semantic_code_intelligence/cli/commands/models_cmd.py +157 -0
  49. semantic_code_intelligence/cli/commands/plugin_cmd.py +275 -0
  50. semantic_code_intelligence/cli/commands/pr_summary_cmd.py +178 -0
  51. semantic_code_intelligence/cli/commands/quality_cmd.py +208 -0
  52. semantic_code_intelligence/cli/commands/refactor_cmd.py +103 -0
  53. semantic_code_intelligence/cli/commands/review_cmd.py +88 -0
  54. semantic_code_intelligence/cli/commands/search_cmd.py +236 -0
  55. semantic_code_intelligence/cli/commands/serve_cmd.py +117 -0
  56. semantic_code_intelligence/cli/commands/suggest_cmd.py +100 -0
  57. semantic_code_intelligence/cli/commands/summary_cmd.py +78 -0
  58. semantic_code_intelligence/cli/commands/tool_cmd.py +282 -0
  59. semantic_code_intelligence/cli/commands/trace_cmd.py +123 -0
  60. semantic_code_intelligence/cli/commands/tui_cmd.py +58 -0
  61. semantic_code_intelligence/cli/commands/viz_cmd.py +127 -0
  62. semantic_code_intelligence/cli/commands/watch_cmd.py +72 -0
  63. semantic_code_intelligence/cli/commands/web_cmd.py +61 -0
  64. semantic_code_intelligence/cli/commands/workspace_cmd.py +250 -0
  65. semantic_code_intelligence/cli/main.py +65 -0
  66. semantic_code_intelligence/cli/router.py +92 -0
  67. semantic_code_intelligence/config/__init__.py +0 -0
  68. semantic_code_intelligence/config/settings.py +260 -0
  69. semantic_code_intelligence/context/__init__.py +19 -0
  70. semantic_code_intelligence/context/engine.py +429 -0
  71. semantic_code_intelligence/context/memory.py +253 -0
  72. semantic_code_intelligence/daemon/__init__.py +1 -0
  73. semantic_code_intelligence/daemon/watcher.py +515 -0
  74. semantic_code_intelligence/docs/__init__.py +1080 -0
  75. semantic_code_intelligence/embeddings/__init__.py +0 -0
  76. semantic_code_intelligence/embeddings/enhanced.py +131 -0
  77. semantic_code_intelligence/embeddings/generator.py +149 -0
  78. semantic_code_intelligence/embeddings/model_registry.py +100 -0
  79. semantic_code_intelligence/evolution/__init__.py +1 -0
  80. semantic_code_intelligence/evolution/budget_guard.py +111 -0
  81. semantic_code_intelligence/evolution/commit_manager.py +88 -0
  82. semantic_code_intelligence/evolution/context_builder.py +131 -0
  83. semantic_code_intelligence/evolution/engine.py +249 -0
  84. semantic_code_intelligence/evolution/patch_generator.py +229 -0
  85. semantic_code_intelligence/evolution/task_selector.py +214 -0
  86. semantic_code_intelligence/evolution/test_runner.py +111 -0
  87. semantic_code_intelligence/indexing/__init__.py +0 -0
  88. semantic_code_intelligence/indexing/chunker.py +174 -0
  89. semantic_code_intelligence/indexing/parallel.py +86 -0
  90. semantic_code_intelligence/indexing/scanner.py +146 -0
  91. semantic_code_intelligence/indexing/semantic_chunker.py +337 -0
  92. semantic_code_intelligence/llm/__init__.py +62 -0
  93. semantic_code_intelligence/llm/cache.py +219 -0
  94. semantic_code_intelligence/llm/cached_provider.py +145 -0
  95. semantic_code_intelligence/llm/conversation.py +190 -0
  96. semantic_code_intelligence/llm/cross_refactor.py +272 -0
  97. semantic_code_intelligence/llm/investigation.py +274 -0
  98. semantic_code_intelligence/llm/mock_provider.py +77 -0
  99. semantic_code_intelligence/llm/ollama_provider.py +122 -0
  100. semantic_code_intelligence/llm/openai_provider.py +100 -0
  101. semantic_code_intelligence/llm/provider.py +92 -0
  102. semantic_code_intelligence/llm/rate_limiter.py +164 -0
  103. semantic_code_intelligence/llm/reasoning.py +438 -0
  104. semantic_code_intelligence/llm/safety.py +110 -0
  105. semantic_code_intelligence/llm/streaming.py +251 -0
  106. semantic_code_intelligence/lsp/__init__.py +609 -0
  107. semantic_code_intelligence/mcp/__init__.py +393 -0
  108. semantic_code_intelligence/parsing/__init__.py +19 -0
  109. semantic_code_intelligence/parsing/parser.py +375 -0
  110. semantic_code_intelligence/plugins/__init__.py +255 -0
  111. semantic_code_intelligence/plugins/examples/__init__.py +1 -0
  112. semantic_code_intelligence/plugins/examples/code_quality.py +73 -0
  113. semantic_code_intelligence/plugins/examples/search_annotator.py +56 -0
  114. semantic_code_intelligence/scalability/__init__.py +205 -0
  115. semantic_code_intelligence/search/__init__.py +0 -0
  116. semantic_code_intelligence/search/formatter.py +123 -0
  117. semantic_code_intelligence/search/grep.py +361 -0
  118. semantic_code_intelligence/search/hybrid_search.py +170 -0
  119. semantic_code_intelligence/search/keyword_search.py +311 -0
  120. semantic_code_intelligence/search/section_expander.py +103 -0
  121. semantic_code_intelligence/services/__init__.py +0 -0
  122. semantic_code_intelligence/services/indexing_service.py +630 -0
  123. semantic_code_intelligence/services/search_service.py +269 -0
  124. semantic_code_intelligence/storage/__init__.py +0 -0
  125. semantic_code_intelligence/storage/chunk_hash_store.py +86 -0
  126. semantic_code_intelligence/storage/hash_store.py +66 -0
  127. semantic_code_intelligence/storage/index_manifest.py +85 -0
  128. semantic_code_intelligence/storage/index_stats.py +138 -0
  129. semantic_code_intelligence/storage/query_history.py +160 -0
  130. semantic_code_intelligence/storage/symbol_registry.py +209 -0
  131. semantic_code_intelligence/storage/vector_store.py +297 -0
  132. semantic_code_intelligence/tests/__init__.py +0 -0
  133. semantic_code_intelligence/tests/test_ai_features.py +351 -0
  134. semantic_code_intelligence/tests/test_chunker.py +119 -0
  135. semantic_code_intelligence/tests/test_cli.py +188 -0
  136. semantic_code_intelligence/tests/test_config.py +154 -0
  137. semantic_code_intelligence/tests/test_context.py +381 -0
  138. semantic_code_intelligence/tests/test_embeddings.py +73 -0
  139. semantic_code_intelligence/tests/test_endtoend.py +1142 -0
  140. semantic_code_intelligence/tests/test_enhanced_embeddings.py +92 -0
  141. semantic_code_intelligence/tests/test_hash_store.py +79 -0
  142. semantic_code_intelligence/tests/test_logging.py +55 -0
  143. semantic_code_intelligence/tests/test_new_cli.py +138 -0
  144. semantic_code_intelligence/tests/test_parser.py +495 -0
  145. semantic_code_intelligence/tests/test_phase10.py +355 -0
  146. semantic_code_intelligence/tests/test_phase11.py +593 -0
  147. semantic_code_intelligence/tests/test_phase12.py +375 -0
  148. semantic_code_intelligence/tests/test_phase13.py +663 -0
  149. semantic_code_intelligence/tests/test_phase14.py +568 -0
  150. semantic_code_intelligence/tests/test_phase15.py +814 -0
  151. semantic_code_intelligence/tests/test_phase16.py +792 -0
  152. semantic_code_intelligence/tests/test_phase17.py +815 -0
  153. semantic_code_intelligence/tests/test_phase18.py +934 -0
  154. semantic_code_intelligence/tests/test_phase19.py +986 -0
  155. semantic_code_intelligence/tests/test_phase20.py +2753 -0
  156. semantic_code_intelligence/tests/test_phase20b.py +2058 -0
  157. semantic_code_intelligence/tests/test_phase20c.py +962 -0
  158. semantic_code_intelligence/tests/test_phase21.py +428 -0
  159. semantic_code_intelligence/tests/test_phase22.py +799 -0
  160. semantic_code_intelligence/tests/test_phase23.py +783 -0
  161. semantic_code_intelligence/tests/test_phase24.py +715 -0
  162. semantic_code_intelligence/tests/test_phase25.py +496 -0
  163. semantic_code_intelligence/tests/test_phase26.py +251 -0
  164. semantic_code_intelligence/tests/test_phase27.py +531 -0
  165. semantic_code_intelligence/tests/test_phase8.py +592 -0
  166. semantic_code_intelligence/tests/test_phase9.py +643 -0
  167. semantic_code_intelligence/tests/test_plugins.py +293 -0
  168. semantic_code_intelligence/tests/test_priority_features.py +727 -0
  169. semantic_code_intelligence/tests/test_router.py +41 -0
  170. semantic_code_intelligence/tests/test_scalability.py +138 -0
  171. semantic_code_intelligence/tests/test_scanner.py +125 -0
  172. semantic_code_intelligence/tests/test_search.py +160 -0
  173. semantic_code_intelligence/tests/test_semantic_chunker.py +255 -0
  174. semantic_code_intelligence/tests/test_tools.py +182 -0
  175. semantic_code_intelligence/tests/test_vector_store.py +151 -0
  176. semantic_code_intelligence/tests/test_watcher.py +211 -0
  177. semantic_code_intelligence/tools/__init__.py +442 -0
  178. semantic_code_intelligence/tools/executor.py +232 -0
  179. semantic_code_intelligence/tools/protocol.py +200 -0
  180. semantic_code_intelligence/tui/__init__.py +454 -0
  181. semantic_code_intelligence/utils/__init__.py +0 -0
  182. semantic_code_intelligence/utils/logging.py +112 -0
  183. semantic_code_intelligence/version.py +3 -0
  184. semantic_code_intelligence/web/__init__.py +11 -0
  185. semantic_code_intelligence/web/api.py +289 -0
  186. semantic_code_intelligence/web/server.py +397 -0
  187. semantic_code_intelligence/web/ui.py +659 -0
  188. semantic_code_intelligence/web/visualize.py +226 -0
  189. semantic_code_intelligence/workspace/__init__.py +427 -0
@@ -0,0 +1,171 @@
1
+ """CLI command: evolve — run the self-improving development loop."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+
7
+ import click
8
+
9
+ from semantic_code_intelligence.config.settings import AppConfig, load_config
10
+ from semantic_code_intelligence.utils.logging import (
11
+ console,
12
+ get_logger,
13
+ print_error,
14
+ print_info,
15
+ )
16
+
17
+ logger = get_logger("cli.evolve")
18
+
19
+
20
+ def _get_provider(config):
21
+ """Build an LLM provider from the app configuration."""
22
+ from semantic_code_intelligence.config.settings import LLMConfig
23
+
24
+ llm: LLMConfig = config.llm
25
+ if llm.provider == "openai":
26
+ from semantic_code_intelligence.llm.openai_provider import OpenAIProvider
27
+
28
+ provider = OpenAIProvider(
29
+ api_key=llm.api_key,
30
+ model=llm.model,
31
+ base_url=llm.base_url or None,
32
+ temperature=llm.temperature,
33
+ max_tokens=llm.max_tokens,
34
+ )
35
+ elif llm.provider == "ollama":
36
+ from semantic_code_intelligence.llm.ollama_provider import OllamaProvider
37
+
38
+ provider = OllamaProvider(
39
+ model=llm.model,
40
+ base_url=llm.base_url or "http://localhost:11434",
41
+ temperature=llm.temperature,
42
+ max_tokens=llm.max_tokens,
43
+ )
44
+ else:
45
+ from semantic_code_intelligence.llm.mock_provider import MockProvider
46
+
47
+ provider = MockProvider()
48
+
49
+ return provider
50
+
51
+
52
+ @click.command("evolve")
53
+ @click.option(
54
+ "--iterations",
55
+ "-n",
56
+ default=3,
57
+ type=click.IntRange(min=1, max=20),
58
+ help="Maximum number of improvement iterations.",
59
+ )
60
+ @click.option(
61
+ "--budget",
62
+ "-b",
63
+ default=20000,
64
+ type=click.IntRange(min=1000),
65
+ help="Maximum total tokens to spend across all LLM calls.",
66
+ )
67
+ @click.option(
68
+ "--timeout",
69
+ "-t",
70
+ default=600,
71
+ type=click.IntRange(min=30),
72
+ help="Maximum wall-clock seconds for the entire run.",
73
+ )
74
+ @click.option(
75
+ "--path",
76
+ "-p",
77
+ default=".",
78
+ type=click.Path(exists=True, file_okay=False, resolve_path=True),
79
+ help="Project root path.",
80
+ )
81
+ @click.pass_context
82
+ def evolve_cmd(
83
+ ctx: click.Context,
84
+ iterations: int,
85
+ budget: int,
86
+ timeout: int,
87
+ path: str,
88
+ ) -> None:
89
+ """Run the self-improving development loop.
90
+
91
+ Automatically selects small improvement tasks (fix tests, add type
92
+ hints, improve error handling, reduce duplication) and applies them
93
+ using the configured LLM. Every change is tested; failures are
94
+ reverted and successes are committed.
95
+
96
+ Examples:
97
+
98
+ codexa evolve
99
+
100
+ codexa evolve --iterations 5 --budget 50000
101
+
102
+ codexa evolve --path /my/project --timeout 300
103
+ """
104
+ root = Path(path).resolve()
105
+ config_dir = AppConfig.config_dir(root)
106
+
107
+ if not config_dir.exists():
108
+ print_error(f"Project not initialized at {root}. Run 'codexa init' first.")
109
+ ctx.exit(1)
110
+ return
111
+
112
+ config = load_config(root)
113
+ provider = _get_provider(config)
114
+
115
+ from semantic_code_intelligence.evolution.budget_guard import BudgetGuard
116
+ from semantic_code_intelligence.evolution.engine import EvolutionEngine
117
+
118
+ guard = BudgetGuard(
119
+ max_tokens=budget,
120
+ max_iterations=iterations,
121
+ max_seconds=float(timeout),
122
+ )
123
+
124
+ console.print(
125
+ f"\n[bold cyan]Evolution loop[/bold cyan] "
126
+ f"iterations={iterations} budget={budget} tokens "
127
+ f"timeout={timeout}s\n"
128
+ )
129
+
130
+ engine = EvolutionEngine(
131
+ project_root=root,
132
+ provider=provider,
133
+ budget=guard,
134
+ )
135
+
136
+ result = engine.run()
137
+
138
+ # — Summary ————————————————————————————————
139
+ console.print("\n[bold green]Evolution complete[/bold green]\n")
140
+ console.print(f" Iterations : {result.iterations_completed}")
141
+ console.print(f" Commits : {len(result.commits)}")
142
+ console.print(f" Reverts : {result.reverts}")
143
+ console.print(f" Stop reason: {result.stop_reason}")
144
+
145
+ bs = result.budget_summary
146
+ console.print(
147
+ f" Tokens : {bs.get('tokens_used', 0)}/{bs.get('tokens_max', 0)}"
148
+ )
149
+ console.print(
150
+ f" Time : {bs.get('elapsed_seconds', 0)}s / {bs.get('max_seconds', 0)}s"
151
+ )
152
+
153
+ if result.commits:
154
+ console.print("\n[bold]Commits:[/bold]")
155
+ for sha in result.commits:
156
+ console.print(f" {sha}")
157
+
158
+ if result.history:
159
+ console.print("\n[bold]Iteration details:[/bold]")
160
+ for rec in result.history:
161
+ status = "[green]committed[/green]" if rec.committed else (
162
+ "[red]reverted[/red]" if rec.reverted else "[yellow]skipped[/yellow]"
163
+ )
164
+ console.print(
165
+ f" {rec.iteration}. {rec.task_category} — {status}"
166
+ f" ({rec.patch_lines_changed} lines)"
167
+ )
168
+ if rec.error:
169
+ console.print(f" [dim]{rec.error}[/dim]")
170
+
171
+ print_info("History saved to .codexa/evolution_history.json")
@@ -0,0 +1,112 @@
1
+ """CLI command: explain — structural explanation of a symbol or file."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+
7
+ import click
8
+
9
+ from semantic_code_intelligence.analysis.ai_features import explain_file, explain_symbol
10
+ from semantic_code_intelligence.context.engine import ContextBuilder
11
+ from semantic_code_intelligence.utils.logging import (
12
+ console,
13
+ get_logger,
14
+ print_error,
15
+ print_info,
16
+ )
17
+
18
+ logger = get_logger("cli.explain")
19
+
20
+
21
+ @click.command("explain")
22
+ @click.argument("target", type=str)
23
+ @click.option(
24
+ "--file",
25
+ "-f",
26
+ "file_path",
27
+ default=None,
28
+ type=click.Path(exists=True, resolve_path=True),
29
+ help="Source file containing the symbol.",
30
+ )
31
+ @click.option(
32
+ "--path",
33
+ "-p",
34
+ default=".",
35
+ type=click.Path(exists=True, file_okay=False, resolve_path=True),
36
+ help="Project root path.",
37
+ )
38
+ @click.option(
39
+ "--json-output",
40
+ "--json",
41
+ "json_mode",
42
+ is_flag=True,
43
+ default=False,
44
+ help="Output in JSON format.",
45
+ )
46
+ @click.pass_context
47
+ def explain_cmd(
48
+ ctx: click.Context,
49
+ target: str,
50
+ file_path: str | None,
51
+ path: str,
52
+ json_mode: bool,
53
+ ) -> None:
54
+ """Explain a code symbol or all symbols in a file.
55
+
56
+ Examples:
57
+
58
+ codexa explain MyClass -f src/models.py
59
+
60
+ codexa explain --file src/main.py .
61
+
62
+ codexa explain search_codebase
63
+ """
64
+ import json as json_mod
65
+
66
+ root = Path(path).resolve()
67
+
68
+ if target == "." and file_path:
69
+ # Explain entire file
70
+ explanations = explain_file(file_path)
71
+ if json_mode:
72
+ click.echo(json_mod.dumps([e.to_dict() for e in explanations], indent=2))
73
+ else:
74
+ for exp in explanations:
75
+ console.print(exp.render(), markup=False)
76
+ console.print()
77
+ return
78
+
79
+ # Explain specific symbol
80
+ builder = ContextBuilder()
81
+ if file_path:
82
+ builder.index_file(file_path)
83
+ symbols = builder.get_symbols(file_path)
84
+ matches = [s for s in symbols if s.name == target]
85
+ else:
86
+ # Scan repo for the symbol
87
+ from semantic_code_intelligence.config.settings import load_config
88
+ from semantic_code_intelligence.indexing.scanner import scan_repository
89
+
90
+ config = load_config(root)
91
+ scanned = scan_repository(root, config.index)
92
+ for sf in scanned:
93
+ full_path = str(root / sf.relative_path)
94
+ try:
95
+ builder.index_file(full_path)
96
+ except Exception:
97
+ logger.debug("Failed to index %s", full_path)
98
+ continue
99
+ matches = builder.find_symbol(target)
100
+
101
+ if not matches:
102
+ print_error(f"Symbol '{target}' not found.")
103
+ return
104
+
105
+ explanations = [explain_symbol(s, builder) for s in matches]
106
+ if json_mode:
107
+ click.echo(json_mod.dumps([e.to_dict() for e in explanations], indent=2))
108
+ else:
109
+ for exp in explanations:
110
+ console.print(exp.render(), markup=False)
111
+ console.print()
112
+ print_info(f"Found {len(explanations)} match(es) for '{target}'.")
@@ -0,0 +1,135 @@
1
+ """CLI command: gate — enforce quality gates for CI pipelines."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json as json_mod
6
+ from pathlib import Path
7
+
8
+ import click
9
+
10
+ from semantic_code_intelligence.utils.logging import (
11
+ console,
12
+ get_logger,
13
+ print_error,
14
+ print_success,
15
+ )
16
+
17
+ logger = get_logger("cli.gate")
18
+
19
+
20
+ @click.command("gate")
21
+ @click.option(
22
+ "--path",
23
+ "-p",
24
+ default=".",
25
+ type=click.Path(exists=True, file_okay=False, resolve_path=True),
26
+ help="Project root path.",
27
+ )
28
+ @click.option(
29
+ "--json-output",
30
+ "--json",
31
+ "json_mode",
32
+ is_flag=True,
33
+ default=False,
34
+ help="Output in JSON format.",
35
+ )
36
+ @click.option(
37
+ "--pipe",
38
+ is_flag=True,
39
+ default=False,
40
+ help="Plain text output for piping / CI.",
41
+ )
42
+ @click.option(
43
+ "--min-maintainability",
44
+ type=float,
45
+ default=40.0,
46
+ help="Minimum maintainability index (default: 40).",
47
+ )
48
+ @click.option(
49
+ "--max-complexity",
50
+ type=int,
51
+ default=25,
52
+ help="Maximum allowed complexity (default: 25).",
53
+ )
54
+ @click.option(
55
+ "--max-issues",
56
+ type=int,
57
+ default=20,
58
+ help="Maximum allowed total issues (default: 20).",
59
+ )
60
+ @click.option(
61
+ "--strict",
62
+ is_flag=True,
63
+ default=False,
64
+ help="Exit with code 1 on gate failure (for CI).",
65
+ )
66
+ @click.pass_context
67
+ def gate_cmd(
68
+ ctx: click.Context,
69
+ path: str,
70
+ json_mode: bool,
71
+ pipe: bool,
72
+ min_maintainability: float,
73
+ max_complexity: int,
74
+ max_issues: int,
75
+ strict: bool,
76
+ ) -> None:
77
+ """Enforce quality gates — fail CI builds that violate quality policies.
78
+
79
+ Runs full quality analysis plus maintainability metrics and checks
80
+ results against configurable thresholds.
81
+
82
+ Examples:
83
+
84
+ codexa gate
85
+
86
+ codexa gate --strict --json
87
+
88
+ codexa gate --min-maintainability 60 --max-complexity 15
89
+
90
+ codexa gate --pipe --strict
91
+ """
92
+ import sys
93
+
94
+ from semantic_code_intelligence.ci.metrics import (
95
+ QualityPolicy,
96
+ compute_project_metrics,
97
+ enforce_quality_gate,
98
+ )
99
+ from semantic_code_intelligence.ci.quality import analyze_project
100
+
101
+ root = Path(path).resolve()
102
+
103
+ report = analyze_project(root)
104
+ pm = compute_project_metrics(root)
105
+
106
+ policy = QualityPolicy(
107
+ min_maintainability=min_maintainability,
108
+ max_complexity=max_complexity,
109
+ max_issues=max_issues,
110
+ )
111
+
112
+ result = enforce_quality_gate(pm, report, policy)
113
+
114
+ if json_mode:
115
+ click.echo(json_mod.dumps(result.to_dict(), indent=2))
116
+ elif pipe:
117
+ status = "PASS" if result.passed else "FAIL"
118
+ click.echo(f"{status} MI={pm.maintainability_index:.1f} issues={report.issue_count}")
119
+ for v in result.violations:
120
+ click.echo(f" VIOLATION {v.rule}: {v.message}")
121
+ else:
122
+ if result.passed:
123
+ print_success(
124
+ f"Quality gate passed — MI={pm.maintainability_index:.1f}, "
125
+ f"{report.issue_count} issue(s)"
126
+ )
127
+ else:
128
+ print_error(
129
+ f"Quality gate FAILED — {len(result.violations)} violation(s)"
130
+ )
131
+ for v in result.violations:
132
+ console.print(f" [red]{v.rule}[/red]: {v.message}")
133
+
134
+ if strict and not result.passed:
135
+ sys.exit(1)
@@ -0,0 +1,234 @@
1
+ """CLI command: grep — Raw filesystem search without requiring an index.
2
+
3
+ Unlike ``codexa search --mode regex``, this command searches raw files on disk
4
+ using ripgrep (if available) or a pure-Python fallback. Zero setup required.
5
+ Supports standard grep flags (-A/-B/-C context, -w word, -v invert, -c count).
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from pathlib import Path
11
+
12
+ import click
13
+
14
+ from semantic_code_intelligence.search.grep import grep_search
15
+ from semantic_code_intelligence.utils.logging import (
16
+ get_logger,
17
+ print_error,
18
+ console,
19
+ )
20
+
21
+ logger = get_logger("cli.grep")
22
+
23
+
24
+ @click.command("grep")
25
+ @click.argument("pattern", type=str)
26
+ @click.option(
27
+ "--path",
28
+ "-p",
29
+ default=".",
30
+ type=click.Path(exists=True, file_okay=False, resolve_path=True),
31
+ help="Directory root to search.",
32
+ )
33
+ @click.option(
34
+ "--case-sensitive",
35
+ "-s",
36
+ is_flag=True,
37
+ default=False,
38
+ help="Case-sensitive matching.",
39
+ )
40
+ @click.option(
41
+ "--max-results",
42
+ "-n",
43
+ default=100,
44
+ type=int,
45
+ help="Maximum number of matches to return.",
46
+ )
47
+ @click.option(
48
+ "--glob",
49
+ "-g",
50
+ "file_glob",
51
+ default=None,
52
+ type=str,
53
+ help="Filter files by glob pattern (e.g. '*.py').",
54
+ )
55
+ @click.option(
56
+ "--no-ripgrep",
57
+ is_flag=True,
58
+ default=False,
59
+ help="Force pure-Python search (skip ripgrep even if available).",
60
+ )
61
+ @click.option(
62
+ "--json-output",
63
+ "--json",
64
+ "json_mode",
65
+ is_flag=True,
66
+ default=False,
67
+ help="Output results as JSON.",
68
+ )
69
+ @click.option(
70
+ "--files-only",
71
+ "-l",
72
+ is_flag=True,
73
+ default=False,
74
+ help="Print only file paths with matches (like grep -l).",
75
+ )
76
+ @click.option(
77
+ "-A",
78
+ "after_context",
79
+ default=0,
80
+ type=int,
81
+ help="Lines of context after each match.",
82
+ )
83
+ @click.option(
84
+ "-B",
85
+ "before_context",
86
+ default=0,
87
+ type=int,
88
+ help="Lines of context before each match.",
89
+ )
90
+ @click.option(
91
+ "-C",
92
+ "context",
93
+ default=0,
94
+ type=int,
95
+ help="Lines of context before and after each match (shorthand for -A N -B N).",
96
+ )
97
+ @click.option(
98
+ "--word",
99
+ "-w",
100
+ is_flag=True,
101
+ default=False,
102
+ help="Match whole words only.",
103
+ )
104
+ @click.option(
105
+ "--invert-match",
106
+ "-v",
107
+ is_flag=True,
108
+ default=False,
109
+ help="Show lines that do NOT match the pattern.",
110
+ )
111
+ @click.option(
112
+ "--count",
113
+ "-c",
114
+ "count_only",
115
+ is_flag=True,
116
+ default=False,
117
+ help="Only print a count of matching lines per file.",
118
+ )
119
+ @click.option(
120
+ "--hidden",
121
+ is_flag=True,
122
+ default=False,
123
+ help="Include hidden files and directories in search.",
124
+ )
125
+ @click.pass_context
126
+ def grep_cmd(
127
+ ctx: click.Context,
128
+ pattern: str,
129
+ path: str,
130
+ case_sensitive: bool,
131
+ max_results: int,
132
+ file_glob: str | None,
133
+ no_ripgrep: bool,
134
+ json_mode: bool,
135
+ files_only: bool,
136
+ after_context: int,
137
+ before_context: int,
138
+ context: int,
139
+ word: bool,
140
+ invert_match: bool,
141
+ count_only: bool,
142
+ hidden: bool,
143
+ ) -> None:
144
+ """Search raw files using regex — no index required.
145
+
146
+ Uses ripgrep for maximum speed when available, with a pure-Python
147
+ fallback. Unlike 'codexa search --mode regex', this searches the
148
+ actual filesystem, not the index.
149
+
150
+ \b
151
+ Examples:
152
+ codexa grep "TODO|FIXME"
153
+ codexa grep "def authenticate" -g "*.py"
154
+ codexa grep "password" --case-sensitive
155
+ codexa grep "import re" --json
156
+ codexa grep "class.*Service" -l
157
+ codexa grep "error" -A 3 -B 1
158
+ codexa grep "def main" -C 2
159
+ codexa grep "TODO" -c
160
+ codexa grep "login" -w
161
+ codexa grep "debug" -v
162
+ """
163
+ import json as json_mod
164
+
165
+ root = Path(path).resolve()
166
+
167
+ # -C sets both before and after context
168
+ ctx_before = context if context > 0 else before_context
169
+ ctx_after = context if context > 0 else after_context
170
+
171
+ result = grep_search(
172
+ pattern,
173
+ root,
174
+ case_insensitive=not case_sensitive,
175
+ max_results=max_results,
176
+ use_ripgrep=not no_ripgrep,
177
+ file_glob=file_glob,
178
+ context_before=ctx_before,
179
+ context_after=ctx_after,
180
+ word_match=word,
181
+ invert_match=invert_match,
182
+ include_hidden=hidden,
183
+ count_only=count_only,
184
+ )
185
+
186
+ if json_mode:
187
+ click.echo(json_mod.dumps(result.to_dict(), indent=2))
188
+ return
189
+
190
+ if not result.matches:
191
+ print_error(f"No matches for pattern: {pattern}")
192
+ ctx.exit(1)
193
+ return
194
+
195
+ if count_only:
196
+ # Aggregate counts per file
197
+ from collections import Counter
198
+ counts: Counter[str] = Counter()
199
+ for m in result.matches:
200
+ if not m.is_context:
201
+ counts[m.file_path] += 1
202
+ for fp, cnt in sorted(counts.items()):
203
+ click.echo(f"{fp}:{cnt}")
204
+ return
205
+
206
+ if files_only:
207
+ seen: set[str] = set()
208
+ for m in result.matches:
209
+ if m.file_path not in seen:
210
+ seen.add(m.file_path)
211
+ click.echo(m.file_path)
212
+ return
213
+
214
+ # Rich output
215
+ from rich.text import Text
216
+
217
+ prev_file: str | None = None
218
+ for m in result.matches:
219
+ line = Text()
220
+ line.append(m.file_path, style="green")
221
+ line.append(":", style="dim")
222
+ line.append(str(m.line_number), style="yellow")
223
+ if m.is_context:
224
+ line.append("-", style="dim")
225
+ else:
226
+ line.append(":", style="dim")
227
+ line.append(m.line_content)
228
+ console.print(line)
229
+
230
+ console.print(
231
+ f"\n[dim]{len([m for m in result.matches if not m.is_context])} matches "
232
+ f"in {result.files_matched} files"
233
+ f" (backend: {result.backend})[/dim]"
234
+ )