@pmaddire/gcie 0.1.4 → 0.1.6

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.
package/cli/app.py CHANGED
@@ -1,163 +1,199 @@
1
- """Typer entrypoint for GCIE CLI."""
2
-
3
- from __future__ import annotations
4
-
5
- import json
6
- import re
7
-
8
- import typer
9
-
10
- from .commands.cache import cache_status, clear_cache, warm_cache
11
- from .commands.context import run_context
12
- from .commands.context_slices import run_context_slices
13
- from .commands.debug import run_debug
14
- from .commands.index import run_index
15
- from .commands.query import run_query
16
- from .commands.setup import run_setup
17
-
18
- app = typer.Typer(help="GraphCode Intelligence Engine CLI")
19
-
20
-
21
- def _query_tokens(query: str) -> tuple[str, ...]:
22
- return tuple(re.findall(r"[a-zA-Z_./{}-][a-zA-Z0-9_./{}-]*", query.lower()))
23
-
24
-
25
- def _auto_context_budget(query: str, intent: str | None) -> int | None:
26
- tokens = _query_tokens(query)
27
- lowered = query.lower()
28
- effective_intent = intent or "explore"
29
-
30
- file_terms = [token for token in tokens if "." in token or "/" in token or "{" in token]
31
- explicit_files = [token for token in file_terms if token.endswith((".py", ".jsx", ".js", ".tsx", ".ts", ".html"))]
32
- symbol_terms = [token for token in tokens if any(ch in token for ch in ("_", "/", ".", "{", "}"))]
33
-
34
- has_frontend = any(token.startswith(("frontend/", "frontend\\")) for token in file_terms)
35
- has_backend = any(
36
- token.endswith(".py") or token.startswith(("backend/", "server/", "api/"))
37
- for token in file_terms
38
- )
39
- cross_layer = has_frontend and has_backend
40
-
41
- stage_pipeline = any(term in lowered for term in ("stage", "pipeline", "planner", "plan", "build", "orchestr"))
42
- backend_config = any(term in lowered for term in ("backend", "config", "openai", "api_key", "llm", "no_ai", "backend_info"))
43
- ai_chain = any(term in lowered for term in ("openai", "llm", "model", "agent")) and has_backend
44
- same_layer_backend_pair = len([token for token in explicit_files if token.endswith(".py")]) >= 2 and not has_frontend
45
- has_api = "/api/" in lowered or any("/api/" in token for token in file_terms)
46
-
47
- if effective_intent in {"edit", "debug", "refactor"} and cross_layer and len(symbol_terms) >= 4:
48
- return 1200 if has_api else 1150
49
- if stage_pipeline and len(explicit_files) >= 2:
50
- return 1400
51
- if same_layer_backend_pair and (backend_config or ai_chain):
52
- return 1100
53
- if len(explicit_files) >= 3 and effective_intent in {"edit", "debug", "refactor"}:
54
- return 1200
55
- if effective_intent in {"edit", "debug"} and len(explicit_files) >= 2:
56
- return 1000
57
- if effective_intent == "refactor" and len(explicit_files) >= 2:
58
- return 1000
59
- return None
60
-
61
-
62
- @app.command("index")
63
- def index_cmd(path: str = typer.Argument(".")) -> None:
64
- result = run_index(path)
65
- typer.echo(json.dumps(result, indent=2))
66
-
67
-
68
- @app.command("query")
69
- def query_cmd(path: str, query: str, max_hops: int = 2) -> None:
70
- result = run_query(path, query, max_hops=max_hops)
71
- typer.echo(json.dumps(result, indent=2))
72
-
73
-
74
- @app.command("debug")
75
- def debug_cmd(path: str, query: str) -> None:
76
- result = run_debug(path, query)
77
- typer.echo(json.dumps(result, indent=2))
78
-
79
-
80
- @app.command("context")
81
- def context_cmd(
82
- path: str,
83
- query: str,
84
- budget: str = typer.Option("auto", "--budget"),
85
- intent: str | None = typer.Option(None, "--intent"),
86
- ) -> None:
87
- if budget == "auto":
88
- budget_val = _auto_context_budget(query, intent)
89
- else:
90
- budget_val = int(budget)
91
-
92
- result = run_context(path, query, budget=budget_val, intent=intent)
93
- typer.echo(json.dumps(result, indent=2))
94
-
95
-
96
- @app.command("context-slices")
97
- def context_slices_cmd(
98
- repo: str,
99
- query: str,
100
- profile: str | None = typer.Option("recall", "--profile"),
101
- stage_a_budget: int = typer.Option(400, "--stage-a"),
102
- stage_b_budget: int = typer.Option(800, "--stage-b"),
103
- max_total: int = typer.Option(1200, "--max-total"),
104
- intent: str | None = typer.Option(None, "--intent"),
105
- pin: str | None = typer.Option(None, "--pin"),
106
- pin_budget: int = typer.Option(300, "--pin-budget"),
107
- include_tests: bool = typer.Option(False, "--include-tests"),
108
- ) -> None:
109
- result = run_context_slices(
110
- repo,
111
- query,
112
- stage_a_budget=stage_a_budget,
113
- stage_b_budget=stage_b_budget,
114
- max_total=max_total,
115
- intent=intent,
116
- pin=pin,
117
- pin_budget=pin_budget,
118
- include_tests=include_tests,
119
- profile=profile,
120
- )
121
- typer.echo(json.dumps(result, indent=2))
122
-
123
-
124
-
125
-
126
- @app.command("setup")
127
- def setup_cmd(
128
- path: str = typer.Argument("."),
129
- force: bool = typer.Option(False, "--force", help="Overwrite existing setup files"),
130
- no_agent_usage: bool = typer.Option(False, "--no-agent-usage", help="Do not copy GCIE_USAGE.md"),
131
- no_setup_doc: bool = typer.Option(False, "--no-setup-doc", help="Do not copy SETUP_ANY_REPO.md"),
132
- no_index: bool = typer.Option(False, "--no-index", help="Skip initial indexing pass"),
133
- ) -> None:
134
- result = run_setup(
135
- path,
136
- force=force,
137
- include_agent_usage=not no_agent_usage,
138
- include_setup_doc=not no_setup_doc,
139
- run_index_pass=not no_index,
140
- )
141
- typer.echo(json.dumps(result, indent=2))
142
-
143
- @app.command("cache-clear")
144
- def cache_clear_cmd(path: str = typer.Argument(".")) -> None:
145
- result = clear_cache(path)
146
- typer.echo(json.dumps(result, indent=2))
147
-
148
-
149
- @app.command("cache-status")
150
- def cache_status_cmd(path: str = typer.Argument(".")) -> None:
151
- result = cache_status(path)
152
- typer.echo(json.dumps(result, indent=2))
153
-
154
-
155
- @app.command("cache-warm")
156
- def cache_warm_cmd(path: str = typer.Argument(".")) -> None:
157
- result = warm_cache(path)
158
- typer.echo(json.dumps(result, indent=2))
159
-
160
-
161
- if __name__ == "__main__":
1
+ """Typer entrypoint for GCIE CLI."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import re
7
+
8
+ import typer
9
+
10
+ from .commands.adaptation import run_post_init_adaptation
11
+ from .commands.cache import cache_status, clear_cache, warm_cache
12
+ from .commands.context import run_context, run_context_basic
13
+ from .commands.context_slices import adaptive_profile_summary, clear_adaptive_profile, run_context_slices
14
+ from .commands.debug import run_debug
15
+ from .commands.index import run_index
16
+ from .commands.query import run_query
17
+ from .commands.setup import run_setup
18
+
19
+ app = typer.Typer(help="GraphCode Intelligence Engine CLI")
20
+
21
+
22
+ def _query_tokens(query: str) -> tuple[str, ...]:
23
+ return tuple(re.findall(r"[a-zA-Z_./{}-][a-zA-Z0-9_./{}-]*", query.lower()))
24
+
25
+
26
+ def _auto_context_budget(query: str, intent: str | None) -> int | None:
27
+ tokens = _query_tokens(query)
28
+ lowered = query.lower()
29
+ effective_intent = intent or "explore"
30
+
31
+ file_terms = [token for token in tokens if "." in token or "/" in token or "{" in token]
32
+ explicit_files = [token for token in file_terms if token.endswith((".py", ".jsx", ".js", ".tsx", ".ts", ".html"))]
33
+ symbol_terms = [token for token in tokens if any(ch in token for ch in ("_", "/", ".", "{", "}"))]
34
+
35
+ has_frontend = any(token.startswith(("frontend/", "frontend\\")) for token in file_terms)
36
+ has_backend = any(
37
+ token.endswith(".py") or token.startswith(("backend/", "server/", "api/"))
38
+ for token in file_terms
39
+ )
40
+ cross_layer = has_frontend and has_backend
41
+
42
+ stage_pipeline = any(term in lowered for term in ("stage", "pipeline", "planner", "plan", "build", "orchestr"))
43
+ backend_config = any(term in lowered for term in ("backend", "config", "openai", "api_key", "llm", "no_ai", "backend_info"))
44
+ ai_chain = any(term in lowered for term in ("openai", "llm", "model", "agent")) and has_backend
45
+ same_layer_backend_pair = len([token for token in explicit_files if token.endswith(".py")]) >= 2 and not has_frontend
46
+ has_api = "/api/" in lowered or any("/api/" in token for token in file_terms)
47
+
48
+ if effective_intent in {"edit", "debug", "refactor"} and cross_layer and len(symbol_terms) >= 4:
49
+ return 1200 if has_api else 1150
50
+ if stage_pipeline and len(explicit_files) >= 2:
51
+ return 1400
52
+ if same_layer_backend_pair and (backend_config or ai_chain):
53
+ return 1100
54
+ if len(explicit_files) >= 3 and effective_intent in {"edit", "debug", "refactor"}:
55
+ return 1200
56
+ if effective_intent in {"edit", "debug"} and len(explicit_files) >= 2:
57
+ return 1000
58
+ if effective_intent == "refactor" and len(explicit_files) >= 2:
59
+ return 1000
60
+ return None
61
+
62
+
63
+ @app.command("index")
64
+ def index_cmd(path: str = typer.Argument(".")) -> None:
65
+ result = run_index(path)
66
+ typer.echo(json.dumps(result, indent=2))
67
+
68
+
69
+ @app.command("query")
70
+ def query_cmd(path: str, query: str, max_hops: int = 2) -> None:
71
+ result = run_query(path, query, max_hops=max_hops)
72
+ typer.echo(json.dumps(result, indent=2))
73
+
74
+
75
+ @app.command("debug")
76
+ def debug_cmd(path: str, query: str) -> None:
77
+ result = run_debug(path, query)
78
+ typer.echo(json.dumps(result, indent=2))
79
+
80
+
81
+ @app.command("context")
82
+ def context_cmd(
83
+ path: str,
84
+ query: str,
85
+ budget: str = typer.Option("auto", "--budget"),
86
+ intent: str | None = typer.Option(None, "--intent"),
87
+ mode: str = typer.Option("basic", "--mode", help="context mode: basic or adaptive"),
88
+ ) -> None:
89
+ if budget == "auto":
90
+ budget_val = _auto_context_budget(query, intent)
91
+ else:
92
+ budget_val = int(budget)
93
+
94
+ if mode == "basic":
95
+ result = run_context_basic(path, query, budget=budget_val, intent=intent)
96
+ elif mode == "adaptive":
97
+ result = run_context(path, query, budget=budget_val, intent=intent)
98
+ else:
99
+ raise typer.BadParameter("--mode must be 'basic' or 'adaptive'")
100
+ typer.echo(json.dumps(result, indent=2))
101
+
102
+
103
+ @app.command("context-slices")
104
+ def context_slices_cmd(
105
+ repo: str,
106
+ query: str,
107
+ profile: str | None = typer.Option("recall", "--profile"),
108
+ stage_a_budget: int = typer.Option(400, "--stage-a"),
109
+ stage_b_budget: int = typer.Option(800, "--stage-b"),
110
+ max_total: int = typer.Option(1200, "--max-total"),
111
+ intent: str | None = typer.Option(None, "--intent"),
112
+ pin: str | None = typer.Option(None, "--pin"),
113
+ pin_budget: int = typer.Option(300, "--pin-budget"),
114
+ include_tests: bool = typer.Option(False, "--include-tests"),
115
+ ) -> None:
116
+ result = run_context_slices(
117
+ repo,
118
+ query,
119
+ stage_a_budget=stage_a_budget,
120
+ stage_b_budget=stage_b_budget,
121
+ max_total=max_total,
122
+ intent=intent,
123
+ pin=pin,
124
+ pin_budget=pin_budget,
125
+ include_tests=include_tests,
126
+ profile=profile,
127
+ )
128
+ typer.echo(json.dumps(result, indent=2))
129
+
130
+
131
+ @app.command("adaptive-profile")
132
+ def adaptive_profile_cmd(
133
+ repo: str = typer.Argument("."),
134
+ clear: bool = typer.Option(False, "--clear", help="Clear learned adaptive profile"),
135
+ ) -> None:
136
+ result = clear_adaptive_profile(repo) if clear else adaptive_profile_summary(repo)
137
+ typer.echo(json.dumps(result, indent=2))
138
+
139
+
140
+ @app.command("adapt")
141
+ def adapt_cmd(
142
+ repo: str = typer.Argument("."),
143
+ benchmark_size: int = typer.Option(10, "--benchmark-size"),
144
+ efficiency_iterations: int = typer.Option(5, "--efficiency-iterations"),
145
+ clear_profile: bool = typer.Option(False, "--clear-profile"),
146
+ ) -> None:
147
+ result = run_post_init_adaptation(
148
+ repo,
149
+ benchmark_size=benchmark_size,
150
+ efficiency_iterations=efficiency_iterations,
151
+ clear_profile=clear_profile,
152
+ )
153
+ typer.echo(json.dumps(result, indent=2))
154
+
155
+
156
+ @app.command("setup")
157
+ def setup_cmd(
158
+ path: str = typer.Argument("."),
159
+ force: bool = typer.Option(False, "--force", help="Overwrite existing setup files"),
160
+ no_agent_usage: bool = typer.Option(False, "--no-agent-usage", help="Do not copy GCIE_USAGE.md"),
161
+ no_setup_doc: bool = typer.Option(False, "--no-setup-doc", help="Do not copy SETUP_ANY_REPO.md"),
162
+ no_index: bool = typer.Option(False, "--no-index", help="Skip initial indexing pass"),
163
+ adapt: bool = typer.Option(False, "--adapt", help="Run post-init adaptation pipeline after setup"),
164
+ adaptation_benchmark_size: int = typer.Option(10, "--adapt-benchmark-size"),
165
+ adaptation_efficiency_iterations: int = typer.Option(5, "--adapt-efficiency-iterations"),
166
+ ) -> None:
167
+ result = run_setup(
168
+ path,
169
+ force=force,
170
+ include_agent_usage=not no_agent_usage,
171
+ include_setup_doc=not no_setup_doc,
172
+ run_index_pass=not no_index,
173
+ run_adaptation_pass=adapt,
174
+ adaptation_benchmark_size=adaptation_benchmark_size,
175
+ adaptation_efficiency_iterations=adaptation_efficiency_iterations,
176
+ )
177
+ typer.echo(json.dumps(result, indent=2))
178
+
179
+
180
+ @app.command("cache-clear")
181
+ def cache_clear_cmd(path: str = typer.Argument(".")) -> None:
182
+ result = clear_cache(path)
183
+ typer.echo(json.dumps(result, indent=2))
184
+
185
+
186
+ @app.command("cache-status")
187
+ def cache_status_cmd(path: str = typer.Argument(".")) -> None:
188
+ result = cache_status(path)
189
+ typer.echo(json.dumps(result, indent=2))
190
+
191
+
192
+ @app.command("cache-warm")
193
+ def cache_warm_cmd(path: str = typer.Argument(".")) -> None:
194
+ result = warm_cache(path)
195
+ typer.echo(json.dumps(result, indent=2))
196
+
197
+
198
+ if __name__ == "__main__":
162
199
  app()
163
-