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.
- codexa-0.4.0.dist-info/METADATA +650 -0
- codexa-0.4.0.dist-info/RECORD +189 -0
- codexa-0.4.0.dist-info/WHEEL +5 -0
- codexa-0.4.0.dist-info/entry_points.txt +2 -0
- codexa-0.4.0.dist-info/licenses/LICENSE +21 -0
- codexa-0.4.0.dist-info/top_level.txt +1 -0
- semantic_code_intelligence/__init__.py +5 -0
- semantic_code_intelligence/analysis/__init__.py +21 -0
- semantic_code_intelligence/analysis/ai_features.py +351 -0
- semantic_code_intelligence/bridge/__init__.py +28 -0
- semantic_code_intelligence/bridge/context_provider.py +245 -0
- semantic_code_intelligence/bridge/protocol.py +167 -0
- semantic_code_intelligence/bridge/server.py +348 -0
- semantic_code_intelligence/bridge/vscode.py +271 -0
- semantic_code_intelligence/ci/__init__.py +13 -0
- semantic_code_intelligence/ci/hooks.py +98 -0
- semantic_code_intelligence/ci/hotspots.py +272 -0
- semantic_code_intelligence/ci/impact.py +246 -0
- semantic_code_intelligence/ci/metrics.py +591 -0
- semantic_code_intelligence/ci/pr.py +412 -0
- semantic_code_intelligence/ci/quality.py +557 -0
- semantic_code_intelligence/ci/templates.py +164 -0
- semantic_code_intelligence/ci/trace.py +224 -0
- semantic_code_intelligence/cli/__init__.py +0 -0
- semantic_code_intelligence/cli/commands/__init__.py +0 -0
- semantic_code_intelligence/cli/commands/ask_cmd.py +153 -0
- semantic_code_intelligence/cli/commands/benchmark_cmd.py +303 -0
- semantic_code_intelligence/cli/commands/chat_cmd.py +252 -0
- semantic_code_intelligence/cli/commands/ci_gen_cmd.py +74 -0
- semantic_code_intelligence/cli/commands/context_cmd.py +120 -0
- semantic_code_intelligence/cli/commands/cross_refactor_cmd.py +113 -0
- semantic_code_intelligence/cli/commands/deps_cmd.py +91 -0
- semantic_code_intelligence/cli/commands/docs_cmd.py +101 -0
- semantic_code_intelligence/cli/commands/doctor_cmd.py +147 -0
- semantic_code_intelligence/cli/commands/evolve_cmd.py +171 -0
- semantic_code_intelligence/cli/commands/explain_cmd.py +112 -0
- semantic_code_intelligence/cli/commands/gate_cmd.py +135 -0
- semantic_code_intelligence/cli/commands/grep_cmd.py +234 -0
- semantic_code_intelligence/cli/commands/hotspots_cmd.py +119 -0
- semantic_code_intelligence/cli/commands/impact_cmd.py +131 -0
- semantic_code_intelligence/cli/commands/index_cmd.py +138 -0
- semantic_code_intelligence/cli/commands/init_cmd.py +152 -0
- semantic_code_intelligence/cli/commands/investigate_cmd.py +163 -0
- semantic_code_intelligence/cli/commands/languages_cmd.py +101 -0
- semantic_code_intelligence/cli/commands/lsp_cmd.py +49 -0
- semantic_code_intelligence/cli/commands/mcp_cmd.py +50 -0
- semantic_code_intelligence/cli/commands/metrics_cmd.py +264 -0
- semantic_code_intelligence/cli/commands/models_cmd.py +157 -0
- semantic_code_intelligence/cli/commands/plugin_cmd.py +275 -0
- semantic_code_intelligence/cli/commands/pr_summary_cmd.py +178 -0
- semantic_code_intelligence/cli/commands/quality_cmd.py +208 -0
- semantic_code_intelligence/cli/commands/refactor_cmd.py +103 -0
- semantic_code_intelligence/cli/commands/review_cmd.py +88 -0
- semantic_code_intelligence/cli/commands/search_cmd.py +236 -0
- semantic_code_intelligence/cli/commands/serve_cmd.py +117 -0
- semantic_code_intelligence/cli/commands/suggest_cmd.py +100 -0
- semantic_code_intelligence/cli/commands/summary_cmd.py +78 -0
- semantic_code_intelligence/cli/commands/tool_cmd.py +282 -0
- semantic_code_intelligence/cli/commands/trace_cmd.py +123 -0
- semantic_code_intelligence/cli/commands/tui_cmd.py +58 -0
- semantic_code_intelligence/cli/commands/viz_cmd.py +127 -0
- semantic_code_intelligence/cli/commands/watch_cmd.py +72 -0
- semantic_code_intelligence/cli/commands/web_cmd.py +61 -0
- semantic_code_intelligence/cli/commands/workspace_cmd.py +250 -0
- semantic_code_intelligence/cli/main.py +65 -0
- semantic_code_intelligence/cli/router.py +92 -0
- semantic_code_intelligence/config/__init__.py +0 -0
- semantic_code_intelligence/config/settings.py +260 -0
- semantic_code_intelligence/context/__init__.py +19 -0
- semantic_code_intelligence/context/engine.py +429 -0
- semantic_code_intelligence/context/memory.py +253 -0
- semantic_code_intelligence/daemon/__init__.py +1 -0
- semantic_code_intelligence/daemon/watcher.py +515 -0
- semantic_code_intelligence/docs/__init__.py +1080 -0
- semantic_code_intelligence/embeddings/__init__.py +0 -0
- semantic_code_intelligence/embeddings/enhanced.py +131 -0
- semantic_code_intelligence/embeddings/generator.py +149 -0
- semantic_code_intelligence/embeddings/model_registry.py +100 -0
- semantic_code_intelligence/evolution/__init__.py +1 -0
- semantic_code_intelligence/evolution/budget_guard.py +111 -0
- semantic_code_intelligence/evolution/commit_manager.py +88 -0
- semantic_code_intelligence/evolution/context_builder.py +131 -0
- semantic_code_intelligence/evolution/engine.py +249 -0
- semantic_code_intelligence/evolution/patch_generator.py +229 -0
- semantic_code_intelligence/evolution/task_selector.py +214 -0
- semantic_code_intelligence/evolution/test_runner.py +111 -0
- semantic_code_intelligence/indexing/__init__.py +0 -0
- semantic_code_intelligence/indexing/chunker.py +174 -0
- semantic_code_intelligence/indexing/parallel.py +86 -0
- semantic_code_intelligence/indexing/scanner.py +146 -0
- semantic_code_intelligence/indexing/semantic_chunker.py +337 -0
- semantic_code_intelligence/llm/__init__.py +62 -0
- semantic_code_intelligence/llm/cache.py +219 -0
- semantic_code_intelligence/llm/cached_provider.py +145 -0
- semantic_code_intelligence/llm/conversation.py +190 -0
- semantic_code_intelligence/llm/cross_refactor.py +272 -0
- semantic_code_intelligence/llm/investigation.py +274 -0
- semantic_code_intelligence/llm/mock_provider.py +77 -0
- semantic_code_intelligence/llm/ollama_provider.py +122 -0
- semantic_code_intelligence/llm/openai_provider.py +100 -0
- semantic_code_intelligence/llm/provider.py +92 -0
- semantic_code_intelligence/llm/rate_limiter.py +164 -0
- semantic_code_intelligence/llm/reasoning.py +438 -0
- semantic_code_intelligence/llm/safety.py +110 -0
- semantic_code_intelligence/llm/streaming.py +251 -0
- semantic_code_intelligence/lsp/__init__.py +609 -0
- semantic_code_intelligence/mcp/__init__.py +393 -0
- semantic_code_intelligence/parsing/__init__.py +19 -0
- semantic_code_intelligence/parsing/parser.py +375 -0
- semantic_code_intelligence/plugins/__init__.py +255 -0
- semantic_code_intelligence/plugins/examples/__init__.py +1 -0
- semantic_code_intelligence/plugins/examples/code_quality.py +73 -0
- semantic_code_intelligence/plugins/examples/search_annotator.py +56 -0
- semantic_code_intelligence/scalability/__init__.py +205 -0
- semantic_code_intelligence/search/__init__.py +0 -0
- semantic_code_intelligence/search/formatter.py +123 -0
- semantic_code_intelligence/search/grep.py +361 -0
- semantic_code_intelligence/search/hybrid_search.py +170 -0
- semantic_code_intelligence/search/keyword_search.py +311 -0
- semantic_code_intelligence/search/section_expander.py +103 -0
- semantic_code_intelligence/services/__init__.py +0 -0
- semantic_code_intelligence/services/indexing_service.py +630 -0
- semantic_code_intelligence/services/search_service.py +269 -0
- semantic_code_intelligence/storage/__init__.py +0 -0
- semantic_code_intelligence/storage/chunk_hash_store.py +86 -0
- semantic_code_intelligence/storage/hash_store.py +66 -0
- semantic_code_intelligence/storage/index_manifest.py +85 -0
- semantic_code_intelligence/storage/index_stats.py +138 -0
- semantic_code_intelligence/storage/query_history.py +160 -0
- semantic_code_intelligence/storage/symbol_registry.py +209 -0
- semantic_code_intelligence/storage/vector_store.py +297 -0
- semantic_code_intelligence/tests/__init__.py +0 -0
- semantic_code_intelligence/tests/test_ai_features.py +351 -0
- semantic_code_intelligence/tests/test_chunker.py +119 -0
- semantic_code_intelligence/tests/test_cli.py +188 -0
- semantic_code_intelligence/tests/test_config.py +154 -0
- semantic_code_intelligence/tests/test_context.py +381 -0
- semantic_code_intelligence/tests/test_embeddings.py +73 -0
- semantic_code_intelligence/tests/test_endtoend.py +1142 -0
- semantic_code_intelligence/tests/test_enhanced_embeddings.py +92 -0
- semantic_code_intelligence/tests/test_hash_store.py +79 -0
- semantic_code_intelligence/tests/test_logging.py +55 -0
- semantic_code_intelligence/tests/test_new_cli.py +138 -0
- semantic_code_intelligence/tests/test_parser.py +495 -0
- semantic_code_intelligence/tests/test_phase10.py +355 -0
- semantic_code_intelligence/tests/test_phase11.py +593 -0
- semantic_code_intelligence/tests/test_phase12.py +375 -0
- semantic_code_intelligence/tests/test_phase13.py +663 -0
- semantic_code_intelligence/tests/test_phase14.py +568 -0
- semantic_code_intelligence/tests/test_phase15.py +814 -0
- semantic_code_intelligence/tests/test_phase16.py +792 -0
- semantic_code_intelligence/tests/test_phase17.py +815 -0
- semantic_code_intelligence/tests/test_phase18.py +934 -0
- semantic_code_intelligence/tests/test_phase19.py +986 -0
- semantic_code_intelligence/tests/test_phase20.py +2753 -0
- semantic_code_intelligence/tests/test_phase20b.py +2058 -0
- semantic_code_intelligence/tests/test_phase20c.py +962 -0
- semantic_code_intelligence/tests/test_phase21.py +428 -0
- semantic_code_intelligence/tests/test_phase22.py +799 -0
- semantic_code_intelligence/tests/test_phase23.py +783 -0
- semantic_code_intelligence/tests/test_phase24.py +715 -0
- semantic_code_intelligence/tests/test_phase25.py +496 -0
- semantic_code_intelligence/tests/test_phase26.py +251 -0
- semantic_code_intelligence/tests/test_phase27.py +531 -0
- semantic_code_intelligence/tests/test_phase8.py +592 -0
- semantic_code_intelligence/tests/test_phase9.py +643 -0
- semantic_code_intelligence/tests/test_plugins.py +293 -0
- semantic_code_intelligence/tests/test_priority_features.py +727 -0
- semantic_code_intelligence/tests/test_router.py +41 -0
- semantic_code_intelligence/tests/test_scalability.py +138 -0
- semantic_code_intelligence/tests/test_scanner.py +125 -0
- semantic_code_intelligence/tests/test_search.py +160 -0
- semantic_code_intelligence/tests/test_semantic_chunker.py +255 -0
- semantic_code_intelligence/tests/test_tools.py +182 -0
- semantic_code_intelligence/tests/test_vector_store.py +151 -0
- semantic_code_intelligence/tests/test_watcher.py +211 -0
- semantic_code_intelligence/tools/__init__.py +442 -0
- semantic_code_intelligence/tools/executor.py +232 -0
- semantic_code_intelligence/tools/protocol.py +200 -0
- semantic_code_intelligence/tui/__init__.py +454 -0
- semantic_code_intelligence/utils/__init__.py +0 -0
- semantic_code_intelligence/utils/logging.py +112 -0
- semantic_code_intelligence/version.py +3 -0
- semantic_code_intelligence/web/__init__.py +11 -0
- semantic_code_intelligence/web/api.py +289 -0
- semantic_code_intelligence/web/server.py +397 -0
- semantic_code_intelligence/web/ui.py +659 -0
- semantic_code_intelligence/web/visualize.py +226 -0
- 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
|
+
)
|