graph-dependency-analyzer 0.2.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 (86) hide show
  1. graph_dependency_analyzer-0.2.0.dist-info/METADATA +158 -0
  2. graph_dependency_analyzer-0.2.0.dist-info/RECORD +86 -0
  3. graph_dependency_analyzer-0.2.0.dist-info/WHEEL +5 -0
  4. graph_dependency_analyzer-0.2.0.dist-info/entry_points.txt +2 -0
  5. graph_dependency_analyzer-0.2.0.dist-info/top_level.txt +1 -0
  6. src/__init__.py +0 -0
  7. src/application/__init__.py +0 -0
  8. src/application/dtos/__init__.py +0 -0
  9. src/application/dtos/affected_component.py +59 -0
  10. src/application/dtos/batch_processing_result.py +17 -0
  11. src/application/dtos/impact_analysis_response.py +56 -0
  12. src/application/dtos/impact_query_request.py +38 -0
  13. src/application/dtos/index_batch_request.py +53 -0
  14. src/application/dtos/index_batch_response.py +72 -0
  15. src/application/dtos/index_result.py +20 -0
  16. src/application/dtos/repository_index_result.py +79 -0
  17. src/application/exceptions.py +41 -0
  18. src/application/services/__init__.py +0 -0
  19. src/application/services/file_scanner_service.py +258 -0
  20. src/application/services/progress_tracker.py +132 -0
  21. src/application/use_cases/__init__.py +0 -0
  22. src/application/use_cases/clear_all_data_use_case.py +180 -0
  23. src/application/use_cases/index_batch_use_case.py +153 -0
  24. src/application/use_cases/index_repository_use_case.py +683 -0
  25. src/application/use_cases/indexing_orchestrator.py +181 -0
  26. src/application/use_cases/list_repositories_use_case.py +84 -0
  27. src/cli.py +512 -0
  28. src/config/__init__.py +5 -0
  29. src/config/container.py +211 -0
  30. src/config/logging_config.py +123 -0
  31. src/config/settings.py +165 -0
  32. src/domain/__init__.py +0 -0
  33. src/domain/entities/__init__.py +20 -0
  34. src/domain/entities/ast_node.py +32 -0
  35. src/domain/entities/code_chunk.py +45 -0
  36. src/domain/entities/code_file.py +44 -0
  37. src/domain/entities/dependency_graph.py +110 -0
  38. src/domain/entities/repository.py +46 -0
  39. src/domain/exceptions.py +36 -0
  40. src/domain/repositories/__init__.py +22 -0
  41. src/domain/repositories/i_code_parser.py +50 -0
  42. src/domain/repositories/i_dependency_extractor.py +35 -0
  43. src/domain/repositories/i_embedding_provider.py +31 -0
  44. src/domain/repositories/i_graph_repository.py +78 -0
  45. src/domain/repositories/i_vector_repository.py +55 -0
  46. src/domain/services/__init__.py +8 -0
  47. src/domain/services/dependency_extractor.py +962 -0
  48. src/domain/services/semantic_chunk_builder.py +719 -0
  49. src/domain/value_objects/__init__.py +12 -0
  50. src/domain/value_objects/chunk_type.py +24 -0
  51. src/domain/value_objects/dependency_type.py +22 -0
  52. src/domain/value_objects/node_type.py +22 -0
  53. src/domain/value_objects/qualified_name.py +78 -0
  54. src/infrastructure/__init__.py +0 -0
  55. src/infrastructure/exceptions.py +41 -0
  56. src/infrastructure/external_apis/__init__.py +0 -0
  57. src/infrastructure/external_apis/ollama_embedding_provider.py +134 -0
  58. src/infrastructure/external_apis/openai_embedding_provider.py +231 -0
  59. src/infrastructure/parsing/__init__.py +20 -0
  60. src/infrastructure/parsing/config_file_parser.py +347 -0
  61. src/infrastructure/parsing/gradle_dependency_parser.py +159 -0
  62. src/infrastructure/parsing/groovy_parser.py +198 -0
  63. src/infrastructure/parsing/maven_dependency_parser.py +147 -0
  64. src/infrastructure/parsing/microfrontend_config_parser.py +242 -0
  65. src/infrastructure/parsing/npm_package_dependency_parser.py +255 -0
  66. src/infrastructure/parsing/python_parser.py +211 -0
  67. src/infrastructure/parsing/sql_schema_parser.py +658 -0
  68. src/infrastructure/parsing/tree_sitter_java_parser.py +434 -0
  69. src/infrastructure/parsing/tree_sitter_typescript_parser.py +600 -0
  70. src/infrastructure/persistence/__init__.py +0 -0
  71. src/infrastructure/persistence/graph_export_repository.py +443 -0
  72. src/infrastructure/persistence/neo4j_dependency_repository.py +657 -0
  73. src/infrastructure/persistence/qdrant_vector_repository.py +357 -0
  74. src/infrastructure/persistence/repository_relationship_repository.py +1312 -0
  75. src/presentation/__init__.py +0 -0
  76. src/presentation/api/__init__.py +0 -0
  77. src/presentation/api/rest/__init__.py +0 -0
  78. src/presentation/api/rest/exception_handlers.py +174 -0
  79. src/presentation/api/rest/main.py +80 -0
  80. src/presentation/api/rest/routers/__init__.py +5 -0
  81. src/presentation/api/rest/routers/admin.py +103 -0
  82. src/presentation/api/rest/routers/analysis.py +980 -0
  83. src/presentation/api/rest/routers/export.py +442 -0
  84. src/presentation/api/rest/routers/health.py +117 -0
  85. src/presentation/api/rest/routers/indexing.py +148 -0
  86. src/presentation/api/rest/routers/relationships.py +1089 -0
src/cli.py ADDED
@@ -0,0 +1,512 @@
1
+ """
2
+ graph-explorer CLI — Knowledge Graph Dependency Analyzer
3
+
4
+ Usage:
5
+ graph-explorer init Setup project (guides through .env, Docker, Ollama)
6
+ graph-explorer start Start the API server
7
+ graph-explorer index <repos> Index repositories into the graph
8
+ graph-explorer link Connect cross-repo dependencies
9
+ graph-explorer status Show indexing status
10
+ graph-explorer open Open the graph in browser
11
+ """
12
+ from __future__ import annotations
13
+
14
+ import os
15
+ import shutil
16
+ import subprocess
17
+ import sys
18
+ import time
19
+ from pathlib import Path
20
+
21
+ import click
22
+ import httpx
23
+
24
+ # ── Constants ──────────────────────────────────────────────────────────────
25
+
26
+ API_URL = "http://localhost:8000"
27
+ PACKAGE_ROOT = Path(__file__).parent.parent # project root when installed
28
+
29
+ _BANNER = """
30
+ ╔══════════════════════════════════════════════════════════╗
31
+ ā•‘ Knowledge Graph — Dependency Analyzer ā•‘
32
+ ā•‘ graph-explorer CLI ā•‘
33
+ ā•šā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•
34
+ """
35
+
36
+
37
+ # ── Helpers ────────────────────────────────────────────────────────────────
38
+
39
+ def _api_running() -> bool:
40
+ try:
41
+ r = httpx.get(f"{API_URL}/health", timeout=3)
42
+ return r.status_code == 200
43
+ except Exception:
44
+ return False
45
+
46
+
47
+ def _docker_running() -> bool:
48
+ try:
49
+ result = subprocess.run(
50
+ ["docker", "info"], capture_output=True, timeout=5
51
+ )
52
+ return result.returncode == 0
53
+ except Exception:
54
+ return False
55
+
56
+
57
+ def _ollama_running() -> bool:
58
+ try:
59
+ r = httpx.get("http://localhost:11434/api/tags", timeout=3)
60
+ return r.status_code == 200
61
+ except Exception:
62
+ return False
63
+
64
+
65
+ def _ollama_model_installed(model: str) -> bool:
66
+ try:
67
+ r = httpx.get("http://localhost:11434/api/tags", timeout=3)
68
+ if r.status_code == 200:
69
+ models = [m["name"] for m in r.json().get("models", [])]
70
+ return any(model in m for m in models)
71
+ except Exception:
72
+ pass
73
+ return False
74
+
75
+
76
+ def _find_project_root() -> Path:
77
+ """Find the project root — works both in dev and when pip-installed."""
78
+ # When pip installed, look for docker-compose.yml in CWD or parent
79
+ cwd = Path.cwd()
80
+ for p in [cwd, *cwd.parents]:
81
+ if (p / "docker-compose.yml").exists() and (p / ".env").exists():
82
+ return p
83
+ # Fallback: package root
84
+ return PACKAGE_ROOT
85
+
86
+
87
+ def _check_env_file(project_root: Path) -> bool:
88
+ return (project_root / ".env").exists()
89
+
90
+
91
+ # ── CLI Group ──────────────────────────────────────────────────────────────
92
+
93
+ @click.group()
94
+ def cli():
95
+ """Knowledge Graph Dependency Analyzer — explore cross-repo dependencies."""
96
+ pass
97
+
98
+
99
+ # ── init ──────────────────────────────────────────────────────────────────
100
+
101
+ @cli.command()
102
+ @click.option("--skip-docker", is_flag=True, help="Skip Docker setup check")
103
+ @click.option("--skip-ollama", is_flag=True, help="Skip Ollama setup check")
104
+ def init(skip_docker: bool, skip_ollama: bool):
105
+ """
106
+ First-time setup wizard.
107
+
108
+ Guides through:\n
109
+ 1. Creating .env from template\n
110
+ 2. Starting Docker infrastructure (Neo4j + Qdrant)\n
111
+ 3. Setting up Ollama (optional, for local embeddings)\n
112
+ 4. Installing Claude Code skills
113
+ """
114
+ click.echo(_BANNER)
115
+ click.echo("Welcome! Let's get you set up.\n")
116
+
117
+ project_root = _find_project_root()
118
+ env_file = project_root / ".env"
119
+ env_example = project_root / ".env.example"
120
+
121
+ # ── Step 1: .env ───────────────────────────────────────────────────
122
+ click.echo("šŸ“‹ Step 1: Environment configuration")
123
+
124
+ if env_file.exists():
125
+ click.echo(" āœ… .env already exists")
126
+ else:
127
+ if env_example.exists():
128
+ shutil.copy(env_example, env_file)
129
+ click.echo(" āœ… Created .env from .env.example")
130
+ else:
131
+ click.echo(" āš ļø .env.example not found — creating minimal .env")
132
+ env_file.write_text(_MINIMAL_ENV)
133
+
134
+ click.echo("\n āš ļø Please edit .env before continuing:")
135
+ click.echo(f" {env_file}")
136
+ click.echo("\n Required settings:")
137
+ click.echo(" REPOSITORIES_PATH=/path/to/your/repos")
138
+ click.echo(" EMBEDDING_PROVIDER=ollama (or 'flow' if using CI&T Flow)")
139
+ click.echo()
140
+ if not click.confirm(" Have you configured .env?", default=False):
141
+ click.echo("\n Please edit .env and run 'graph-explorer init' again.")
142
+ return
143
+
144
+ # ── Step 2: Docker ─────────────────────────────────────────────────
145
+ click.echo("\n🐳 Step 2: Docker infrastructure (Neo4j + Qdrant)")
146
+
147
+ if skip_docker:
148
+ click.echo(" ā­ļø Skipped (--skip-docker)")
149
+ elif not _docker_running():
150
+ click.echo(" āŒ Docker is not running.")
151
+ click.echo(" Please start Docker Desktop and run 'graph-explorer init' again.")
152
+ click.echo(" Download: https://docs.docker.com/get-docker/")
153
+ return
154
+ else:
155
+ compose_file = project_root / "docker-compose.yml"
156
+ if not compose_file.exists():
157
+ click.echo(" āŒ docker-compose.yml not found")
158
+ return
159
+
160
+ click.echo(" šŸ”„ Starting containers...")
161
+ result = subprocess.run(
162
+ ["docker", "compose", "up", "-d"],
163
+ cwd=str(project_root),
164
+ capture_output=True, text=True
165
+ )
166
+ if result.returncode != 0:
167
+ click.echo(f" āŒ Docker failed:\n{result.stderr}")
168
+ return
169
+
170
+ click.echo(" ā³ Waiting for Neo4j and Qdrant to be ready (~15s)...")
171
+ time.sleep(15)
172
+ click.echo(" āœ… Containers running")
173
+ click.echo(" Neo4j browser: http://localhost:7474")
174
+ click.echo(" Qdrant: http://localhost:6333")
175
+
176
+ # ── Step 3: Ollama ─────────────────────────────────────────────────
177
+ click.echo("\nšŸ¦™ Step 3: Ollama (local AI embeddings — no rate limits)")
178
+
179
+ if skip_ollama:
180
+ click.echo(" ā­ļø Skipped (--skip-ollama)")
181
+ else:
182
+ # Check if .env uses ollama
183
+ env_content = env_file.read_text()
184
+ uses_ollama = "EMBEDDING_PROVIDER=ollama" in env_content
185
+
186
+ if not uses_ollama:
187
+ click.echo(" ā„¹ļø EMBEDDING_PROVIDER is not 'ollama' in .env — skipping")
188
+ click.echo(" (Using Flow/OpenAI for embeddings)")
189
+ elif not shutil.which("ollama"):
190
+ click.echo(" āŒ Ollama is not installed.")
191
+ click.echo("\n Install options:")
192
+ click.echo(" Mac: brew install ollama")
193
+ click.echo(" Other: https://ollama.com/download")
194
+ click.echo("\n After installing:")
195
+ click.echo(" ollama serve &")
196
+ click.echo(" ollama pull nomic-embed-text")
197
+ click.echo("\n Then run 'graph-explorer init' again.")
198
+ return
199
+ elif not _ollama_running():
200
+ click.echo(" āš ļø Ollama is installed but not running.")
201
+ click.echo(" Start it with: ollama serve")
202
+ if click.confirm(" Start Ollama now?", default=True):
203
+ subprocess.Popen(["ollama", "serve"],
204
+ stdout=subprocess.DEVNULL,
205
+ stderr=subprocess.DEVNULL)
206
+ time.sleep(3)
207
+ click.echo(" āœ… Ollama started")
208
+ else:
209
+ click.echo(" āš ļø Ollama not running — embeddings will fail")
210
+ else:
211
+ click.echo(" āœ… Ollama is running")
212
+
213
+ if _ollama_running():
214
+ model = "nomic-embed-text"
215
+ if _ollama_model_installed(model):
216
+ click.echo(f" āœ… Model '{model}' already installed")
217
+ else:
218
+ click.echo(f" šŸ“„ Pulling model '{model}' (~274MB)...")
219
+ subprocess.run(["ollama", "pull", model])
220
+ click.echo(f" āœ… Model '{model}' ready")
221
+
222
+ # ── Step 4: Claude skills ──────────────────────────────────────────
223
+ click.echo("\nšŸ¤– Step 4: Claude Code skills")
224
+ install_script = project_root / "install-graph-skills.sh"
225
+ if install_script.exists():
226
+ skills_dir = Path.home() / ".claude" / "skills" / "graph-index"
227
+ if skills_dir.exists():
228
+ click.echo(" āœ… Skills already installed globally")
229
+ else:
230
+ if click.confirm(" Install /graph-index, /graph-link, /graph-impact skills globally?", default=True):
231
+ result = subprocess.run(
232
+ ["bash", str(install_script)],
233
+ capture_output=True, text=True
234
+ )
235
+ if result.returncode == 0:
236
+ click.echo(" āœ… Skills installed — use /graph-index in Claude Code")
237
+ else:
238
+ click.echo(f" āš ļø {result.stderr[:100]}")
239
+ else:
240
+ click.echo(" ā­ļø install-graph-skills.sh not found — skipping")
241
+
242
+ # ── Done ───────────────────────────────────────────────────────────
243
+ click.echo("\n" + "─" * 58)
244
+ click.echo("āœ… Setup complete! Next steps:")
245
+ click.echo()
246
+ click.echo(" 1. Start the API: graph-explorer start")
247
+ click.echo(" 2. Index your repos: graph-explorer index <repo1> <repo2>")
248
+ click.echo(" 3. Connect links: graph-explorer link")
249
+ click.echo(" 4. Open the graph: graph-explorer open")
250
+ click.echo()
251
+
252
+
253
+ # ── start ──────────────────────────────────────────────────────────────────
254
+
255
+ @cli.command()
256
+ @click.option("--port", default=8000, show_default=True, help="API port")
257
+ @click.option("--host", default="0.0.0.0", show_default=True, help="API host")
258
+ @click.option("--no-reload", is_flag=True, help="Disable auto-reload")
259
+ def start(port: int, host: str, no_reload: bool):
260
+ """Start the Knowledge Graph API server."""
261
+ click.echo(_BANNER)
262
+
263
+ if _api_running():
264
+ click.echo(f"āœ… API already running at http://localhost:{port}")
265
+ click.echo(f" Docs: http://localhost:{port}/docs")
266
+ return
267
+
268
+ # Verify Docker infra
269
+ if not _api_running():
270
+ click.echo("šŸ”„ Checking infrastructure...")
271
+
272
+ reload_flag = [] if no_reload else ["--reload"]
273
+ cmd = [
274
+ sys.executable, "-m", "uvicorn",
275
+ "src.presentation.api.rest.main:app",
276
+ "--host", host,
277
+ "--port", str(port),
278
+ *reload_flag,
279
+ ]
280
+
281
+ click.echo(f"šŸš€ Starting API on http://{host}:{port}")
282
+ click.echo(f" Docs: http://localhost:{port}/docs")
283
+ click.echo(f" Graph: http://localhost:{port}/api/analysis/repository-relationships/graph.html")
284
+ click.echo("\n Press Ctrl+C to stop\n")
285
+
286
+ project_root = _find_project_root()
287
+ os.chdir(str(project_root))
288
+ os.execvp(sys.executable, cmd)
289
+
290
+
291
+ # ── index ──────────────────────────────────────────────────────────────────
292
+
293
+ @cli.command()
294
+ @click.argument("repos", nargs=-1, required=True)
295
+ @click.option("--parallel", "-p", default=None, type=int,
296
+ help="Parallel limit (auto-detected if not set)")
297
+ @click.option("--api-url", default=API_URL, show_default=True)
298
+ def index(repos: tuple, parallel: int | None, api_url: str):
299
+ """
300
+ Index repositories into the Knowledge Graph.
301
+
302
+ Examples:\n
303
+ graph-explorer index wdc-packing-list wdc-ui\n
304
+ graph-explorer index wms-oracle --parallel 1
305
+ """
306
+ if not _api_running():
307
+ click.echo("āŒ API is not running. Start it first:")
308
+ click.echo(" graph-explorer start")
309
+ raise SystemExit(1)
310
+
311
+ # Auto-detect parallel limit
312
+ large_repos = {"wmsportal", "wms-oracle", "wms-oracle-fork"}
313
+ if parallel is None:
314
+ parallel = 1 if any(r in large_repos for r in repos) else 3
315
+
316
+ click.echo(f"šŸ“¦ Indexing {len(repos)} repo(s) with parallel={parallel}...\n")
317
+
318
+ payload = {
319
+ "repositories": list(repos),
320
+ "parallel": parallel,
321
+ "background": False,
322
+ }
323
+
324
+ try:
325
+ with httpx.Client(timeout=3600) as client:
326
+ r = client.post(f"{api_url}/api/indexing/index/batch", json=payload)
327
+ r.raise_for_status()
328
+ data = r.json()
329
+ except httpx.ConnectError:
330
+ click.echo("āŒ Cannot connect to API. Is it running?")
331
+ click.echo(" graph-explorer start")
332
+ raise SystemExit(1)
333
+ except Exception as e:
334
+ click.echo(f"āŒ Error: {e}")
335
+ raise SystemExit(1)
336
+
337
+ results = data.get("results", [])
338
+ success = data.get("success_count", 0)
339
+ total = data.get("total_processed", 0)
340
+
341
+ # Table
342
+ click.echo(f"{'Repository':<40} {'Status':<8} {'Files':>6} {'Nodes':>7} {'Chunks':>7}")
343
+ click.echo("─" * 72)
344
+ for r in results:
345
+ name = (r.get("repository_name") or "")[:38]
346
+ ok = r.get("status") == "success"
347
+ status = "āœ… ok" if ok else "āŒ err"
348
+ files = r.get("files_processed", 0) or 0
349
+ nodes = r.get("nodes_created", 0) or 0
350
+ chunks = r.get("chunks_created", 0) or 0
351
+ err = (r.get("error_message") or "")[:40]
352
+ click.echo(f"{name:<40} {status:<8} {files:>6} {nodes:>7} {chunks:>7} {err}")
353
+
354
+ click.echo("─" * 72)
355
+ click.echo(f"{'TOTAL':<40} {success}/{total} ok\n")
356
+
357
+ # Has SQL repos?
358
+ sql_repos = {"wms-oracle", "wms-oracle-fork"}
359
+ if any(r in sql_repos for r in repos):
360
+ click.echo("šŸ’” SQL/Oracle repos indexed → run: graph-explorer link --db-only")
361
+ else:
362
+ click.echo("šŸ’” Run 'graph-explorer link' to connect cross-repo dependencies")
363
+
364
+ click.echo(f"\n🌐 View: {api_url}/api/analysis/repository-relationships/graph.html")
365
+
366
+
367
+ # ── link ──────────────────────────────────────────────────────────────────
368
+
369
+ @cli.command()
370
+ @click.option("--http-only", is_flag=True, help="Only link HTTP calls")
371
+ @click.option("--db-only", is_flag=True, help="Only resolve DB refs")
372
+ @click.option("--api-url", default=API_URL, show_default=True)
373
+ def link(http_only: bool, db_only: bool, api_url: str):
374
+ """
375
+ Connect cross-repository dependencies.
376
+
377
+ Runs:\n
378
+ 1. cross-language-link — matches HTTP calls to backend endpoints\n
379
+ 2. resolve-db-refs — connects Oracle/SQL procedures to tables
380
+ """
381
+ if not _api_running():
382
+ click.echo("āŒ API is not running. Start it first: graph-explorer start")
383
+ raise SystemExit(1)
384
+
385
+ with httpx.Client(timeout=120) as client:
386
+
387
+ if not db_only:
388
+ click.echo("šŸ”— Linking HTTP calls (frontend → backend)...")
389
+ r = client.post(f"{api_url}/api/analysis/impact/cross-language-link")
390
+ d = r.json()
391
+ created = d.get("created", 0)
392
+ click.echo(f" āœ… {created} CALLS_BACKEND edges created")
393
+
394
+ if not http_only:
395
+ click.echo("šŸ—„ļø Resolving DB cross-references (~30s)...")
396
+ r = client.post(f"{api_url}/api/analysis/impact/resolve-db-refs",
397
+ timeout=120)
398
+ d = r.json()
399
+ new_edges = d.get("total_new_edges", 0)
400
+ total = d.get("total_resolved_edges_in_graph", 0)
401
+ click.echo(f" āœ… {new_edges} new DB edges — {total} total resolved")
402
+
403
+ click.echo(f"\n🌐 View: {api_url}/api/analysis/repository-relationships/graph.html")
404
+
405
+
406
+ # ── status ─────────────────────────────────────────────────────────────────
407
+
408
+ @cli.command()
409
+ @click.option("--api-url", default=API_URL, show_default=True)
410
+ def status(api_url: str):
411
+ """Show current indexing status and graph stats."""
412
+ click.echo("šŸ“Š Knowledge Graph Status\n")
413
+
414
+ # API
415
+ if _api_running():
416
+ click.echo(" āœ… API running")
417
+ try:
418
+ r = httpx.get(f"{api_url}/health", timeout=3)
419
+ for check in r.json().get("checks", []):
420
+ icon = "āœ…" if check.get("status") == "healthy" else "āŒ"
421
+ click.echo(f" {icon} {check['service']}: {check.get('message','')}")
422
+ except Exception:
423
+ pass
424
+ else:
425
+ click.echo(" āŒ API not running → graph-explorer start")
426
+
427
+ # Docker
428
+ click.echo(f" {'āœ…' if _docker_running() else 'āŒ'} Docker")
429
+
430
+ # Ollama
431
+ if _ollama_running():
432
+ click.echo(" āœ… Ollama running")
433
+ if _ollama_model_installed("nomic-embed-text"):
434
+ click.echo(" āœ… nomic-embed-text installed")
435
+ else:
436
+ click.echo(" ⬜ Ollama not running (ok if using Flow/OpenAI)")
437
+
438
+ # Graph stats
439
+ if _api_running():
440
+ try:
441
+ r = httpx.get(f"{api_url}/api/analysis/repository-relationships", timeout=10)
442
+ d = r.json()
443
+ repos = d.get("repositories", [])
444
+ rels = d.get("relationships", [])
445
+ meta = d.get("meta", {})
446
+ click.echo(f"\n šŸ“ˆ Graph: {len(repos)} repos | {meta.get('total_after_dedup',len(rels))} relationships")
447
+ if repos:
448
+ click.echo(f" Indexed repos: {', '.join(repos[:10])}")
449
+ if len(repos) > 10:
450
+ click.echo(f" ... and {len(repos)-10} more")
451
+ except Exception:
452
+ pass
453
+
454
+ click.echo()
455
+
456
+
457
+ # ── open ───────────────────────────────────────────────────────────────────
458
+
459
+ @cli.command("open")
460
+ @click.option("--repo", "-r", default=None, help="Open specific repo graph")
461
+ @click.option("--api-url", default=API_URL, show_default=True)
462
+ def open_graph(repo: str | None, api_url: str):
463
+ """Open the dependency graph in your browser."""
464
+ import webbrowser
465
+
466
+ if repo:
467
+ url = f"{api_url}/api/export/graph.html?repository={repo}"
468
+ else:
469
+ url = f"{api_url}/api/analysis/repository-relationships/graph.html"
470
+
471
+ click.echo(f"🌐 Opening: {url}")
472
+ webbrowser.open(url)
473
+
474
+
475
+ # ── Minimal .env template ──────────────────────────────────────────────────
476
+
477
+ _MINIMAL_ENV = """\
478
+ # ── Neo4j ────────────────────────────────────────────────
479
+ NEO4J_URI=bolt://localhost:7687
480
+ NEO4J_USER=neo4j
481
+ NEO4J_PASSWORD=medline2024
482
+
483
+ # ── Qdrant ───────────────────────────────────────────────
484
+ QDRANT_URL=http://localhost:6333
485
+ QDRANT_COLLECTION=code_chunks
486
+ QDRANT_VECTOR_SIZE=768
487
+
488
+ # ── Repositories ─────────────────────────────────────────
489
+ REPOSITORIES_PATH=/path/to/your/repositories
490
+
491
+ # ── Embedding (ollama | flow | openai) ───────────────────
492
+ EMBEDDING_PROVIDER=ollama
493
+ OLLAMA_BASE_URL=http://localhost:11434
494
+ OLLAMA_EMBEDDING_MODEL=nomic-embed-text
495
+ OLLAMA_EMBEDDING_DIM=768
496
+
497
+ # Flow (CI&T) — set EMBEDDING_PROVIDER=flow to use
498
+ # OPENAI_API_KEY=your-flow-token
499
+ # OPENAI_BASE_URL=https://flow.ciandt.com/ai-orchestration-api
500
+ # OPENAI_EMBEDDING_MODEL=text-embedding-3-small
501
+ # QDRANT_VECTOR_SIZE=1536
502
+ """
503
+
504
+
505
+ # ── Entry point ────────────────────────────────────────────────────────────
506
+
507
+ def main():
508
+ cli()
509
+
510
+
511
+ if __name__ == "__main__":
512
+ main()
src/config/__init__.py ADDED
@@ -0,0 +1,5 @@
1
+ """Configuration package."""
2
+ from src.config.settings import Settings, get_settings
3
+ from src.config import logging_config
4
+
5
+ __all__ = ["Settings", "get_settings", "logging_config"]