codegraph-gen 1.1.0__tar.gz → 1.3.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. {codegraph_gen-1.1.0 → codegraph_gen-1.3.2}/PKG-INFO +16 -6
  2. {codegraph_gen-1.1.0 → codegraph_gen-1.3.2}/README.md +14 -5
  3. {codegraph_gen-1.1.0 → codegraph_gen-1.3.2}/pyproject.toml +4 -1
  4. {codegraph_gen-1.1.0 → codegraph_gen-1.3.2}/src/codegraph_gen/__main__.py +187 -17
  5. codegraph_gen-1.3.2/src/codegraph_gen/config.py +132 -0
  6. {codegraph_gen-1.1.0 → codegraph_gen-1.3.2}/src/codegraph_gen/detect.py +25 -1
  7. {codegraph_gen-1.1.0 → codegraph_gen-1.3.2}/src/codegraph_gen/engine.py +4 -1
  8. {codegraph_gen-1.1.0 → codegraph_gen-1.3.2}/src/codegraph_gen/parser/base.py +42 -14
  9. {codegraph_gen-1.1.0 → codegraph_gen-1.3.2}/src/codegraph_gen/parser/cpp.py +46 -9
  10. {codegraph_gen-1.1.0 → codegraph_gen-1.3.2}/src/codegraph_gen/parser/go.py +45 -8
  11. {codegraph_gen-1.1.0 → codegraph_gen-1.3.2}/src/codegraph_gen/parser/javascript.py +45 -8
  12. {codegraph_gen-1.1.0 → codegraph_gen-1.3.2}/src/codegraph_gen/parser/kotlin.py +45 -8
  13. codegraph_gen-1.3.2/src/codegraph_gen/parser/ocaml.py +390 -0
  14. {codegraph_gen-1.1.0 → codegraph_gen-1.3.2}/src/codegraph_gen/parser/python.py +44 -7
  15. {codegraph_gen-1.1.0 → codegraph_gen-1.3.2}/src/codegraph_gen/parser/rust.py +45 -8
  16. {codegraph_gen-1.1.0 → codegraph_gen-1.3.2}/src/codegraph_gen/parser/swift.py +45 -8
  17. codegraph_gen-1.3.2/src/codegraph_gen/resolver.py +379 -0
  18. codegraph_gen-1.3.2/src/codegraph_gen/resolver_context.py +118 -0
  19. codegraph_gen-1.3.2/src/codegraph_gen/resolver_steps.py +477 -0
  20. {codegraph_gen-1.1.0 → codegraph_gen-1.3.2}/src/codegraph_gen/resolver_strategy.py +138 -1
  21. codegraph_gen-1.3.2/src/codegraph_gen/scope.py +10 -0
  22. codegraph_gen-1.3.2/src/codegraph_gen/visualizer.py +569 -0
  23. {codegraph_gen-1.1.0 → codegraph_gen-1.3.2}/src/codegraph_gen/writer.py +27 -2
  24. codegraph_gen-1.1.0/src/codegraph_gen/config.py +0 -76
  25. codegraph_gen-1.1.0/src/codegraph_gen/resolver.py +0 -650
  26. {codegraph_gen-1.1.0 → codegraph_gen-1.3.2}/src/codegraph_gen/__init__.py +0 -0
  27. {codegraph_gen-1.1.0 → codegraph_gen-1.3.2}/src/codegraph_gen/ai.py +0 -0
  28. {codegraph_gen-1.1.0 → codegraph_gen-1.3.2}/src/codegraph_gen/analyzer.py +0 -0
  29. {codegraph_gen-1.1.0 → codegraph_gen-1.3.2}/src/codegraph_gen/builder.py +0 -0
  30. {codegraph_gen-1.1.0 → codegraph_gen-1.3.2}/src/codegraph_gen/cluster.py +0 -0
  31. {codegraph_gen-1.1.0 → codegraph_gen-1.3.2}/src/codegraph_gen/parser/__init__.py +0 -0
  32. {codegraph_gen-1.1.0 → codegraph_gen-1.3.2}/src/codegraph_gen/py.typed +0 -0
  33. {codegraph_gen-1.1.0 → codegraph_gen-1.3.2}/src/codegraph_gen/renderer.py +0 -0
  34. {codegraph_gen-1.1.0 → codegraph_gen-1.3.2}/src/codegraph_gen/schema.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: codegraph-gen
3
- Version: 1.1.0
3
+ Version: 1.3.2
4
4
  Summary: AST-based codebase knowledge graph generator in Markdown
5
5
  Keywords: knowledge-graph,ast,codebase,markdown,tree-sitter,visualization,static-analysis,ai-agent,obsidian
6
6
  Author: twn39
@@ -28,6 +28,7 @@ Requires-Dist: pydantic>=2.0.0
28
28
  Requires-Dist: tree-sitter-c>=0.24.2
29
29
  Requires-Dist: tree-sitter-cpp>=0.23.4
30
30
  Requires-Dist: tree-sitter-kotlin>=1.1.0
31
+ Requires-Dist: tree-sitter-ocaml>=0.25.0
31
32
  Requires-Python: >=3.11
32
33
  Project-URL: Homepage, https://github.com/twn39/codegraph
33
34
  Project-URL: Repository, https://github.com/twn39/codegraph
@@ -51,7 +52,7 @@ Description-Content-Type: text/markdown
51
52
 
52
53
  ## 🚀 核心特性
53
54
 
54
- - **多语言 AST 解析**:基于 `tree-sitter`,原生支持 **Python, JavaScript, TypeScript, Kotlin, Go, Rust, Swift**。
55
+ - **多语言 AST 解析**:基于 `tree-sitter`,原生支持 **Python, JavaScript, TypeScript, Kotlin, Go, Rust, Swift, C, C++**。
55
56
  - **语义边解析与绑定**:静态解析跨文件的函数/方法调用(`calls`)、类型继承/接口实现(`inherits`/`implements`)以及文件导入关系(`imports`)。
56
57
  - **逻辑组件自动聚类**:利用贪心模块度社区发现算法(Louvain Modularity Clustering)将紧密耦合的文件和符号自动聚类为 **Component(逻辑组件)**,并根据组件核心节点智能命名。
57
58
  - **架构脆弱性分析**:自动识别 **God Nodes(度数最高的核心抽象)**,并静态检测文件级别的 **循环导入依赖(Circular Imports)**。
@@ -61,7 +62,16 @@ Description-Content-Type: text/markdown
61
62
 
62
63
  ## 📦 架构概览
63
64
 
64
- - **工作区源码 Workspace** -> **detect: 语言识别与过滤** -> **parser: Tree-Sitter AST 符号提取** -> **builder: NetworkX 语义图组装与绑定** -> **cluster: 社区模块度聚类命名** -> **analyze: 上帝节点与循环导入分析** -> **export: 导出至 .codegraph/** -> **生成 AGENT_PROMPT.md / AGENTS.md / README.md / nodes / components**
65
+ ```mermaid
66
+ flowchart LR
67
+ Workspace["工作区源码 Workspace"] --> Detect["detect: 语言识别与过滤"]
68
+ Detect --> Parser["parser: Tree-Sitter AST 符号提取"]
69
+ Parser --> Builder["builder: NetworkX 语义图组装与绑定"]
70
+ Builder --> Cluster["cluster: 社区模块度聚类命名"]
71
+ Cluster --> Analyze["analyze: 上帝节点与循环导入分析"]
72
+ Analyze --> Export["export: 导出至 .codegraph/"]
73
+ Export --> Generate["生成 AGENT_PROMPT.md / AGENTS.md / README.md / nodes / components"]
74
+ ```
65
75
 
66
76
  ---
67
77
 
@@ -83,11 +93,11 @@ uv pip install codegraph-gen
83
93
 
84
94
  ### 注册 AI Agent 斜杠命令
85
95
 
86
- `codegraph-gen` 支持一键将 `/codegraph` 自定义斜杠命令注册到您的 AI Agent(如 Codex 或 Antigravity)的全局配置中:
96
+ `codegraph-gen` 支持一键将 `/codegraph` 自定义斜杠命令注册到您的 AI Agent(如 Codex、AntigravityCrush)的全局配置中:
87
97
 
88
98
  ```bash
89
- # 为 Codex / Antigravity 注入 /codegraph 全局斜杠命令
90
- codegraph install --platform codex
99
+ # 为 Codex / Antigravity / Crush 注入 /codegraph 全局斜杠命令
100
+ codegraph install --platform crush
91
101
  ```
92
102
 
93
103
  注册完成后,在对应的 Agent 终端中,您只需输入 `/codegraph` 即可全自动运行整个图谱的提取、分析与回写流程。
@@ -15,7 +15,7 @@
15
15
 
16
16
  ## 🚀 核心特性
17
17
 
18
- - **多语言 AST 解析**:基于 `tree-sitter`,原生支持 **Python, JavaScript, TypeScript, Kotlin, Go, Rust, Swift**。
18
+ - **多语言 AST 解析**:基于 `tree-sitter`,原生支持 **Python, JavaScript, TypeScript, Kotlin, Go, Rust, Swift, C, C++**。
19
19
  - **语义边解析与绑定**:静态解析跨文件的函数/方法调用(`calls`)、类型继承/接口实现(`inherits`/`implements`)以及文件导入关系(`imports`)。
20
20
  - **逻辑组件自动聚类**:利用贪心模块度社区发现算法(Louvain Modularity Clustering)将紧密耦合的文件和符号自动聚类为 **Component(逻辑组件)**,并根据组件核心节点智能命名。
21
21
  - **架构脆弱性分析**:自动识别 **God Nodes(度数最高的核心抽象)**,并静态检测文件级别的 **循环导入依赖(Circular Imports)**。
@@ -25,7 +25,16 @@
25
25
 
26
26
  ## 📦 架构概览
27
27
 
28
- - **工作区源码 Workspace** -> **detect: 语言识别与过滤** -> **parser: Tree-Sitter AST 符号提取** -> **builder: NetworkX 语义图组装与绑定** -> **cluster: 社区模块度聚类命名** -> **analyze: 上帝节点与循环导入分析** -> **export: 导出至 .codegraph/** -> **生成 AGENT_PROMPT.md / AGENTS.md / README.md / nodes / components**
28
+ ```mermaid
29
+ flowchart LR
30
+ Workspace["工作区源码 Workspace"] --> Detect["detect: 语言识别与过滤"]
31
+ Detect --> Parser["parser: Tree-Sitter AST 符号提取"]
32
+ Parser --> Builder["builder: NetworkX 语义图组装与绑定"]
33
+ Builder --> Cluster["cluster: 社区模块度聚类命名"]
34
+ Cluster --> Analyze["analyze: 上帝节点与循环导入分析"]
35
+ Analyze --> Export["export: 导出至 .codegraph/"]
36
+ Export --> Generate["生成 AGENT_PROMPT.md / AGENTS.md / README.md / nodes / components"]
37
+ ```
29
38
 
30
39
  ---
31
40
 
@@ -47,11 +56,11 @@ uv pip install codegraph-gen
47
56
 
48
57
  ### 注册 AI Agent 斜杠命令
49
58
 
50
- `codegraph-gen` 支持一键将 `/codegraph` 自定义斜杠命令注册到您的 AI Agent(如 Codex 或 Antigravity)的全局配置中:
59
+ `codegraph-gen` 支持一键将 `/codegraph` 自定义斜杠命令注册到您的 AI Agent(如 Codex、AntigravityCrush)的全局配置中:
51
60
 
52
61
  ```bash
53
- # 为 Codex / Antigravity 注入 /codegraph 全局斜杠命令
54
- codegraph install --platform codex
62
+ # 为 Codex / Antigravity / Crush 注入 /codegraph 全局斜杠命令
63
+ codegraph install --platform crush
55
64
  ```
56
65
 
57
66
  注册完成后,在对应的 Agent 终端中,您只需输入 `/codegraph` 即可全自动运行整个图谱的提取、分析与回写流程。
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "codegraph-gen"
3
- version = "1.1.0"
3
+ version = "1.3.2"
4
4
  description = "AST-based codebase knowledge graph generator in Markdown"
5
5
  readme = "README.md"
6
6
  authors = [
@@ -44,6 +44,7 @@ dependencies = [
44
44
  "tree-sitter-c>=0.24.2",
45
45
  "tree-sitter-cpp>=0.23.4",
46
46
  "tree-sitter-kotlin>=1.1.0",
47
+ "tree-sitter-ocaml>=0.25.0",
47
48
  ]
48
49
 
49
50
  [dependency-groups]
@@ -51,6 +52,8 @@ dev = [
51
52
  "pytest>=8.0.0",
52
53
  "ruff>=0.15.16",
53
54
  "ty>=0.0.44",
55
+ "plotly>=5.0.0",
56
+ "numpy>=1.20.0",
54
57
  ]
55
58
 
56
59
  [project.urls]
@@ -10,12 +10,26 @@ from rich.progress import (
10
10
  MofNCompleteColumn,
11
11
  )
12
12
 
13
- from codegraph_gen.config import CodegraphConfig, DEFAULT_EXCLUSIONS
13
+ from codegraph_gen.config import (
14
+ CodegraphConfig,
15
+ DEFAULT_EXCLUSIONS,
16
+ LANGUAGE_EXTENSIONS,
17
+ load_project_config,
18
+ PROJECT_CONFIG_FILE,
19
+ )
14
20
 
15
21
  console = Console()
16
22
 
23
+ try:
24
+ from importlib.metadata import version
25
+
26
+ __version__ = version("codegraph-gen")
27
+ except Exception:
28
+ __version__ = "1.3.2"
29
+
17
30
 
18
31
  @click.group()
32
+ @click.version_option(version=__version__, prog_name="codegraph")
19
33
  def cli():
20
34
  """codegraph - Build a Markdown knowledge graph of your codebase for AI analysis."""
21
35
  pass
@@ -69,26 +83,99 @@ def build(
69
83
  """Parses the codebase in SRC_DIR and exports the Markdown graph vault."""
70
84
  console.print("[bold blue]Starting codegraph analysis...[/bold blue]")
71
85
 
72
- # 1. Prepare configuration
86
+ workspace = src_dir.resolve()
87
+
88
+ # ── Load project config file (.codegraphrc) ────────────────────────────
89
+ project_cfg = load_project_config(workspace)
90
+
91
+ if project_cfg is not None:
92
+ console.print(
93
+ f"[dim]📄 Found project config: [underline]{workspace / PROJECT_CONFIG_FILE}[/underline][/dim]"
94
+ )
95
+
96
+ # ── Merge: CLI args take priority over .codegraphrc ────────────────────
97
+ # Exclusions: default set + .codegraphrc extras + CLI --exclude (cumulative)
73
98
  exclusions = set(DEFAULT_EXCLUSIONS)
99
+ if project_cfg and project_cfg.exclude:
100
+ exclusions.update(project_cfg.exclude)
74
101
  if exclude:
75
102
  exclusions.update(exclude)
76
103
 
104
+ # Output directory: CLI --output > .codegraphrc output > default
105
+ if output != Path(".codegraph"):
106
+ # User explicitly passed --output on CLI
107
+ resolved_output = output.resolve()
108
+ elif project_cfg and project_cfg.output != ".codegraph":
109
+ resolved_output = (workspace / project_cfg.output).resolve()
110
+ else:
111
+ resolved_output = (workspace / ".codegraph").resolve()
112
+
113
+ # Languages: CLI has no language filter option yet; use .codegraphrc if present
114
+ all_languages = set(LANGUAGE_EXTENSIONS.keys())
115
+ if project_cfg and project_cfg.languages:
116
+ valid = {lang for lang in project_cfg.languages if lang in all_languages}
117
+ invalid = set(project_cfg.languages) - valid
118
+ if invalid:
119
+ console.print(
120
+ f"[yellow]⚠ .codegraphrc: unknown languages ignored: {', '.join(sorted(invalid))}[/yellow]"
121
+ )
122
+ languages = valid if valid else all_languages
123
+ else:
124
+ languages = all_languages
125
+
126
+ # Workers: CLI --workers > .codegraphrc workers > CPU count
77
127
  import os
78
128
 
79
129
  if not parallel:
80
130
  max_workers = 1
81
131
  elif workers is not None:
82
132
  max_workers = workers
133
+ elif project_cfg and project_cfg.workers is not None:
134
+ max_workers = project_cfg.workers
83
135
  else:
84
136
  max_workers = os.cpu_count() or 4
85
137
 
138
+ # Cache: CLI --cache/--no-cache flag always applies; .codegraphrc only when CLI default
139
+ # (click default for cache is True, so we trust project_cfg when CLI wasn't explicitly set)
140
+ effective_cache = (
141
+ cache if cache is not None else (project_cfg.cache if project_cfg else True)
142
+ )
143
+
144
+ # Include dirs: from .codegraphrc include whitelist (no CLI equivalent)
145
+ include_dirs = None
146
+ if project_cfg and project_cfg.include:
147
+ include_dirs = []
148
+ for subdir in project_cfg.include:
149
+ resolved = (workspace / subdir).resolve()
150
+ if not resolved.exists():
151
+ console.print(
152
+ f"[yellow]⚠ .codegraphrc include '{subdir}' does not exist, skipping.[/yellow]"
153
+ )
154
+ else:
155
+ include_dirs.append(resolved)
156
+ if not include_dirs:
157
+ include_dirs = None # All entries invalid → fall back to full scan
158
+
159
+ # Print effective config summary when a project config was loaded
160
+ if project_cfg is not None:
161
+ parts = []
162
+ if include_dirs:
163
+ parts.append(f"include: {', '.join(p.name for p in include_dirs)}")
164
+ if project_cfg.exclude:
165
+ parts.append(f"extra exclude: {', '.join(project_cfg.exclude)}")
166
+ if project_cfg.languages:
167
+ parts.append(f"languages: {', '.join(sorted(languages))}")
168
+ if parts:
169
+ console.print(f"[dim] {' | '.join(parts)}[/dim]")
170
+
86
171
  config = CodegraphConfig(
87
- workspace_dir=src_dir.resolve(),
88
- output_dir=output.resolve(),
172
+ workspace_dir=workspace,
173
+ output_dir=resolved_output,
89
174
  exclusions=exclusions,
175
+ languages=languages,
90
176
  max_workers=max_workers,
91
- use_cache=cache,
177
+ use_cache=effective_cache,
178
+ include_dirs=include_dirs,
92
179
  )
93
180
 
94
181
  from codegraph_gen.engine import CodegraphEngine, PipelineStage
@@ -176,7 +263,7 @@ def build(
176
263
  "--platform",
177
264
  "-p",
178
265
  default="codex",
179
- type=click.Choice(["codex", "antigravity"]),
266
+ type=click.Choice(["codex", "antigravity", "crush"]),
180
267
  help="The AI agent platform to integrate with.",
181
268
  )
182
269
  def install(platform: str):
@@ -190,6 +277,8 @@ def install(platform: str):
190
277
  skills_dir = Path.home() / ".codex" / "skills" / "codegraph"
191
278
  elif platform == "antigravity":
192
279
  skills_dir = Path.home() / ".gemini" / "config" / "skills" / "codegraph"
280
+ elif platform == "crush":
281
+ skills_dir = Path.home() / ".config" / "crush" / "skills" / "codegraph"
193
282
  else:
194
283
  skills_dir = Path.home() / ".codex" / "skills" / "codegraph"
195
284
 
@@ -212,9 +301,41 @@ Build a codebase knowledge graph using `codegraph` for any folder, cluster symbo
212
301
  /codegraph --exclude <pattern> # Build and exclude specific folders/patterns
213
302
  ```
214
303
 
304
+ ### Project Configuration File (`.codegraphrc`)
305
+
306
+ Projects can place a `.codegraphrc` JSON file in their root directory to persist build settings. When present, `codegraph build` automatically loads it — no extra flags needed.
307
+
308
+ ```json
309
+ {
310
+ "include": ["src", "tests"],
311
+ "exclude": ["dist", "third_party"],
312
+ "output": ".codegraph",
313
+ "languages": ["python", "typescript"],
314
+ "workers": 4,
315
+ "cache": true
316
+ }
317
+ ```
318
+
319
+ | Field | Type | Description |
320
+ |---|---|---|
321
+ | `include` | `string[]` | Subdirectory whitelist — only these dirs are scanned. Omit to scan the entire workspace. |
322
+ | `exclude` | `string[]` | Extra directory names to exclude (appended to built-in defaults). |
323
+ | `output` | `string` | Output directory, relative to workspace root. Default: `.codegraph` |
324
+ | `languages` | `string[]` | Language whitelist. Omit to include all supported languages. |
325
+ | `workers` | `int` | Number of parallel worker processes. |
326
+ | `cache` | `bool` | Enable incremental parse cache. Default: `true` |
327
+
328
+ CLI flags always take priority over `.codegraphrc` values. `--exclude` is cumulative (appends to the config file list).
329
+
215
330
  ## What You Must Do When Invoked
216
331
 
217
- If the user invoked `/codegraph` with no path, do not ask the user for a path. Instead of scanning the entire project root directory `.` (which may include non-essential scripts, docs, or huge subfolders), you MUST prioritize targeting the primary source directory (e.g. `src/`, `lib/`, `app/`) and test directory (e.g. `tests/`, `test/`).
332
+ ### Check for a project config file first
333
+
334
+ Before deciding which directory or flags to use, check if a `.codegraphrc` file exists in the project root:
335
+ - If it exists, read it to understand which directories the project wants scanned (`include`) and excluded (`exclude`). Honor those settings — do NOT override them with your own guesses.
336
+ - If it does NOT exist, follow the fallback rules below.
337
+
338
+ If the user invoked `/codegraph` with no path and there is NO `.codegraphrc`, do not ask the user for a path. Instead, prioritize targeting the primary source directory (e.g. `src/`, `lib/`, `app/`) and test directory (e.g. `tests/`, `test/`).
218
339
  - If specific source or test folders are found, run the build targeting those folders, or build the root `.` but exclude other non-code/non-test directories (e.g., `docs/`, `scripts/`, `examples/`) using the `--exclude` flag to keep the graph focused on code and tests.
219
340
  - Otherwise, default to `.` (current directory).
220
341
 
@@ -234,7 +355,7 @@ elif [ -f "venv/bin/codegraph" ]; then
234
355
  CODEGRAPH_BIN="venv/bin/codegraph"
235
356
  else
236
357
  if ! command -v codegraph >/dev/null 2>&1; then
237
- uv tool install codegraph
358
+ uv tool install codegraph-gen
238
359
  fi
239
360
  CODEGRAPH_BIN="codegraph"
240
361
  fi
@@ -243,7 +364,7 @@ echo "Using codegraph binary: $CODEGRAPH_BIN"
243
364
 
244
365
  ### Step 2 - Build the Knowledge Graph
245
366
 
246
- Run the resolved `$CODEGRAPH_BIN` on the specified directory:
367
+ Run the resolved `$CODEGRAPH_BIN` on the specified directory. If a `.codegraphrc` is present in the project root, simply run without extra flags — the config file is picked up automatically:
247
368
  ```bash
248
369
  $CODEGRAPH_BIN build INPUT_PATH
249
370
  # Or with additional exclude arguments if provided by the user
@@ -289,17 +410,66 @@ Finally, reply to the user in English, summarizing:
289
410
 
290
411
 
291
412
  @cli.command()
292
- def info():
293
- """Prints tool info and supported languages."""
413
+ @click.argument(
414
+ "src_dir",
415
+ type=click.Path(exists=True, file_okay=False, path_type=Path),
416
+ default=".",
417
+ )
418
+ @click.option(
419
+ "--output",
420
+ "-o",
421
+ type=click.Path(path_type=Path),
422
+ default=Path(".codegraph"),
423
+ help="Directory where the Markdown vault was written.",
424
+ )
425
+ @click.option(
426
+ "--open/--no-open",
427
+ "open_browser",
428
+ default=True,
429
+ help="Automatically open the interactive HTML page in the browser.",
430
+ )
431
+ def visualize(src_dir: Path, output: Path, open_browser: bool):
432
+ """Generates an interactive Plotly-based HTML visualization of the graph."""
433
+ import sys
434
+
435
+ console.print(
436
+ "[bold blue]Generating interactive graph visualization...[/bold blue]"
437
+ )
438
+
439
+ workspace = src_dir.resolve()
440
+
441
+ # Resolve output directory using same logic as build
442
+ project_cfg = load_project_config(workspace)
443
+ if output != Path(".codegraph"):
444
+ resolved_output = output.resolve()
445
+ elif project_cfg and project_cfg.output != ".codegraph":
446
+ resolved_output = (workspace / project_cfg.output).resolve()
447
+ else:
448
+ resolved_output = (workspace / ".codegraph").resolve()
449
+
294
450
  try:
295
- from importlib.metadata import version
451
+ from codegraph_gen.visualizer import generate_visualization
296
452
 
297
- ver = version("codegraph-gen")
298
- except Exception:
299
- ver = "1.1.0"
300
- console.print(f"[bold]codegraph v{ver}[/bold]")
453
+ html_path = generate_visualization(
454
+ workspace, resolved_output, open_browser=open_browser
455
+ )
456
+ console.print(
457
+ f"[bold green]Success![/bold green] Interactive graph exported to: [bold underline]{html_path}[/bold underline]"
458
+ )
459
+ except ImportError as e:
460
+ console.print(f"[bold red]Error: {e}[/bold red]")
461
+ sys.exit(1)
462
+ except Exception as e:
463
+ console.print(f"[bold red]Error generating visualization: {e}[/bold red]")
464
+ sys.exit(1)
465
+
466
+
467
+ @cli.command()
468
+ def info():
469
+ """Prints tool info and supported languages."""
470
+ console.print(f"[bold]codegraph v{__version__}[/bold]")
301
471
  console.print(
302
- "Supported languages: Python, JavaScript, TypeScript, Kotlin, Go, Rust, Swift"
472
+ "Supported languages: Python, JavaScript, TypeScript, Kotlin, Go, Rust, Swift, C, C++"
303
473
  )
304
474
 
305
475
 
@@ -0,0 +1,132 @@
1
+ import json
2
+ import logging
3
+ import os
4
+ from pathlib import Path
5
+ from typing import Optional
6
+ from pydantic import BaseModel, Field
7
+ from codegraph_gen.schema import ExtractionResult
8
+
9
+ logger = logging.getLogger(__name__)
10
+
11
+ PROJECT_CONFIG_FILE = ".codegraphrc"
12
+
13
+ # Default exclusions for files and directories we want to ignore
14
+ DEFAULT_EXCLUSIONS = {
15
+ ".git",
16
+ ".venv",
17
+ "venv",
18
+ "node_modules",
19
+ "third_party",
20
+ "dist",
21
+ "build",
22
+ ".build",
23
+ "__pycache__",
24
+ ".pytest_cache",
25
+ ".codegraph",
26
+ ".idea",
27
+ ".vscode",
28
+ "target",
29
+ "out",
30
+ "bin",
31
+ "obj",
32
+ "vendor",
33
+ "Pods",
34
+ "Carthage",
35
+ "DerivedData",
36
+ "build_output",
37
+ ".next",
38
+ ".nuxt",
39
+ ".cache",
40
+ "build_mac",
41
+ "build_ios",
42
+ "build_ios_sim",
43
+ }
44
+
45
+
46
+ # Mapping of supported languages to file extensions
47
+ LANGUAGE_EXTENSIONS = {
48
+ "python": {".py"},
49
+ "javascript": {".js", ".mjs", ".cjs"},
50
+ "typescript": {".ts", ".tsx"},
51
+ "kotlin": {".kt", ".kts"},
52
+ "go": {".go"},
53
+ "rust": {".rs"},
54
+ "swift": {".swift"},
55
+ "c": {".c", ".h"},
56
+ "cpp": {".cpp", ".cc", ".cxx", ".hpp", ".hxx"},
57
+ "ocaml": {".ml", ".mli"},
58
+ }
59
+
60
+ ALL_EXTENSIONS = {ext for exts in LANGUAGE_EXTENSIONS.values() for ext in exts}
61
+
62
+
63
+ class CacheEntry(BaseModel):
64
+ mtime: float
65
+ size: int
66
+ hash: str
67
+ result: ExtractionResult
68
+
69
+
70
+ class ProjectConfig(BaseModel):
71
+ """Schema for the .codegraphrc project-level configuration file."""
72
+
73
+ include: Optional[list[str]] = None
74
+ """Subdirectory whitelist (relative to workspace root). None = scan entire workspace."""
75
+
76
+ exclude: list[str] = []
77
+ """Extra directory names/patterns to exclude (appended to DEFAULT_EXCLUSIONS)."""
78
+
79
+ output: str = ".codegraph"
80
+ """Output directory path (relative to workspace root)."""
81
+
82
+ languages: Optional[list[str]] = None
83
+ """Language whitelist. None = all supported languages."""
84
+
85
+ workers: Optional[int] = None
86
+ """Number of parallel worker processes. None = use CPU count."""
87
+
88
+ cache: bool = True
89
+ """Enable incremental parse cache."""
90
+
91
+
92
+ def load_project_config(workspace_dir: Path) -> Optional[ProjectConfig]:
93
+ """
94
+ Loads .codegraphrc from the workspace root directory.
95
+ Returns None (silently) if the file does not exist.
96
+ Returns None (with a warning) if the file is malformed.
97
+ No upward traversal — the file must be in workspace_dir itself.
98
+ """
99
+ config_path = workspace_dir / PROJECT_CONFIG_FILE
100
+ if not config_path.is_file():
101
+ return None
102
+ try:
103
+ data = json.loads(config_path.read_text(encoding="utf-8"))
104
+ cfg = ProjectConfig.model_validate(data)
105
+ logger.info(f"Loaded project config from {config_path}")
106
+ return cfg
107
+ except json.JSONDecodeError as e:
108
+ logger.warning(
109
+ f"{PROJECT_CONFIG_FILE}: JSON parse error — {e}. Using defaults."
110
+ )
111
+ except Exception as e:
112
+ logger.warning(f"{PROJECT_CONFIG_FILE}: Failed to load — {e}. Using defaults.")
113
+ return None
114
+
115
+
116
+ class CodegraphConfig(BaseModel):
117
+ """Configuration class for codegraph parsing and exporting."""
118
+
119
+ workspace_dir: Path
120
+ output_dir: Path = Field(default_factory=lambda: Path(".codegraph"))
121
+ exclusions: set[str] = Field(default_factory=lambda: DEFAULT_EXCLUSIONS)
122
+ languages: set[str] = Field(default_factory=lambda: set(LANGUAGE_EXTENSIONS.keys()))
123
+ max_workers: int = Field(default_factory=lambda: os.cpu_count() or 4)
124
+ use_cache: bool = Field(default=True)
125
+ include_dirs: Optional[list[Path]] = Field(default=None)
126
+ """Absolute paths of subdirectories to scan. None = scan entire workspace_dir."""
127
+
128
+ @property
129
+ def absolute_output_dir(self) -> Path:
130
+ if self.output_dir.is_absolute():
131
+ return self.output_dir
132
+ return self.workspace_dir / self.output_dir
@@ -9,11 +9,20 @@ def discover_files(
9
9
  workspace_dir: Path,
10
10
  languages: set[str],
11
11
  exclusions: set[str],
12
+ include_dirs: list[Path] | None = None,
12
13
  ) -> list[tuple[Path, str]]:
13
14
  """
14
15
  Recursively discovers source files in the workspace directory.
15
16
  Filters by allowed languages and ignores files/directories in exclusions.
16
17
 
18
+ Args:
19
+ workspace_dir: Root of the workspace (used to compute relative paths / node IDs).
20
+ languages: Set of language names to include.
21
+ exclusions: Directory names/patterns to exclude.
22
+ include_dirs: Optional whitelist of absolute directories to scan.
23
+ When provided, only these directories are scanned.
24
+ When None, the entire workspace_dir is scanned.
25
+
17
26
  Returns:
18
27
  List of tuples: (absolute_file_path, language_name)
19
28
  """
@@ -59,5 +68,20 @@ def discover_files(
59
68
  except Exception as e:
60
69
  logger.error(f"Error scanning {directory}: {e}")
61
70
 
62
- scan_dir(workspace)
71
+ # Determine which root directories to scan
72
+ if include_dirs:
73
+ for root in include_dirs:
74
+ root = root.resolve()
75
+ if not root.exists():
76
+ logger.warning(f"include_dirs entry does not exist, skipping: {root}")
77
+ continue
78
+ if not root.is_dir():
79
+ logger.warning(
80
+ f"include_dirs entry is not a directory, skipping: {root}"
81
+ )
82
+ continue
83
+ scan_dir(root)
84
+ else:
85
+ scan_dir(workspace)
86
+
63
87
  return found_files
@@ -100,7 +100,10 @@ class CodegraphEngine:
100
100
  if progress_callback:
101
101
  progress_callback(PipelineStage.DISCOVERING, None, 0, 0)
102
102
  files = discover_files(
103
- config.workspace_dir, config.languages, config.exclusions
103
+ config.workspace_dir,
104
+ config.languages,
105
+ config.exclusions,
106
+ config.include_dirs,
104
107
  )
105
108
  if not files:
106
109
  logger.warning("No supported files found.")