mcp-vector-search 0.7.6__py3-none-any.whl → 0.8.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.

Potentially problematic release.


This version of mcp-vector-search might be problematic. Click here for more details.

@@ -1,7 +1,7 @@
1
1
  """MCP Vector Search - CLI-first semantic code search with MCP integration."""
2
2
 
3
- __version__ = "0.7.6"
4
- __build__ = "28"
3
+ __version__ = "0.8.0"
4
+ __build__ = "29"
5
5
  __author__ = "Robert Matsuoka"
6
6
  __email__ = "bobmatnyc@gmail.com"
7
7
 
@@ -0,0 +1,523 @@
1
+ """Visualization commands for MCP Vector Search."""
2
+
3
+ import asyncio
4
+ import json
5
+ from pathlib import Path
6
+
7
+ import typer
8
+ from loguru import logger
9
+ from rich.console import Console
10
+ from rich.panel import Panel
11
+
12
+ from ...core.database import ChromaVectorDatabase
13
+ from ...core.embeddings import create_embedding_function
14
+ from ...core.project import load_project
15
+
16
+ app = typer.Typer(
17
+ help="Visualize code chunk relationships",
18
+ no_args_is_help=True,
19
+ )
20
+ console = Console()
21
+
22
+
23
+ @app.command()
24
+ def export(
25
+ output: Path = typer.Option(
26
+ Path("chunk-graph.json"),
27
+ "--output",
28
+ "-o",
29
+ help="Output file for chunk relationship data",
30
+ ),
31
+ file_path: str | None = typer.Option(
32
+ None,
33
+ "--file",
34
+ "-f",
35
+ help="Export only chunks from specific file (supports wildcards)",
36
+ ),
37
+ ) -> None:
38
+ """Export chunk relationships as JSON for D3.js visualization.
39
+
40
+ Examples:
41
+ # Export all chunks
42
+ mcp-vector-search visualize export
43
+
44
+ # Export from specific file
45
+ mcp-vector-search visualize export --file src/main.py
46
+
47
+ # Custom output location
48
+ mcp-vector-search visualize export -o graph.json
49
+ """
50
+ asyncio.run(_export_chunks(output, file_path))
51
+
52
+
53
+ async def _export_chunks(output: Path, file_filter: str | None) -> None:
54
+ """Export chunk relationship data."""
55
+ try:
56
+ # Load project
57
+ project_manager, config = load_project(Path.cwd())
58
+
59
+ # Get database
60
+ embedding_function = create_embedding_function(config.embedding_model)
61
+ database = ChromaVectorDatabase(
62
+ persist_directory=config.index_path,
63
+ embedding_function=embedding_function,
64
+ )
65
+ await database.initialize()
66
+
67
+ # Get all chunks with metadata
68
+ console.print("[cyan]Fetching chunks from database...[/cyan]")
69
+
70
+ # Query all chunks (we'll use a dummy search to get all)
71
+ stats = await database.get_stats()
72
+
73
+ if stats.total_chunks == 0:
74
+ console.print("[yellow]No chunks found in index. Run 'mcp-vector-search index' first.[/yellow]")
75
+ raise typer.Exit(1)
76
+
77
+ # Build graph data structure
78
+ nodes = []
79
+ links = []
80
+
81
+ # We need to query the database to get actual chunk data
82
+ # Since there's no "get all chunks" method, we'll work with the stats
83
+ # In a real implementation, you would add a method to get all chunks
84
+
85
+ console.print(f"[yellow]Note: Full chunk export requires database enhancement.[/yellow]")
86
+ console.print(f"[cyan]Creating placeholder graph with {stats.total_chunks} chunks...[/cyan]")
87
+
88
+ # Create sample graph structure
89
+ graph_data = {
90
+ "nodes": [
91
+ {
92
+ "id": f"chunk_{i}",
93
+ "name": f"Chunk {i}",
94
+ "type": "code",
95
+ "file_path": "example.py",
96
+ "start_line": i * 10,
97
+ "end_line": (i + 1) * 10,
98
+ "complexity": 1.0 + (i % 5),
99
+ }
100
+ for i in range(min(stats.total_chunks, 50)) # Limit to 50 for demo
101
+ ],
102
+ "links": [
103
+ {"source": f"chunk_{i}", "target": f"chunk_{i+1}"}
104
+ for i in range(min(stats.total_chunks - 1, 49))
105
+ ],
106
+ "metadata": {
107
+ "total_chunks": stats.total_chunks,
108
+ "total_files": stats.total_files,
109
+ "languages": stats.languages,
110
+ "export_note": "This is a placeholder. Full export requires database enhancement.",
111
+ },
112
+ }
113
+
114
+ # Write to file
115
+ output.parent.mkdir(parents=True, exist_ok=True)
116
+ with open(output, "w") as f:
117
+ json.dump(graph_data, f, indent=2)
118
+
119
+ await database.close()
120
+
121
+ console.print()
122
+ console.print(
123
+ Panel.fit(
124
+ f"[green]✓[/green] Exported graph data to [cyan]{output}[/cyan]\n\n"
125
+ f"Nodes: {len(graph_data['nodes'])}\n"
126
+ f"Links: {len(graph_data['links'])}\n\n"
127
+ f"[dim]Next: Run 'mcp-vector-search visualize serve' to view[/dim]",
128
+ title="Export Complete",
129
+ border_style="green",
130
+ )
131
+ )
132
+
133
+ except Exception as e:
134
+ logger.error(f"Export failed: {e}")
135
+ console.print(f"[red]✗ Export failed: {e}[/red]")
136
+ raise typer.Exit(1)
137
+
138
+
139
+ @app.command()
140
+ def serve(
141
+ port: int = typer.Option(8080, "--port", "-p", help="Port for visualization server"),
142
+ graph_file: Path = typer.Option(
143
+ Path("chunk-graph.json"),
144
+ "--graph",
145
+ "-g",
146
+ help="Graph JSON file to visualize",
147
+ ),
148
+ ) -> None:
149
+ """Start local HTTP server for D3.js visualization.
150
+
151
+ Examples:
152
+ # Start server on default port 8080
153
+ mcp-vector-search visualize serve
154
+
155
+ # Custom port
156
+ mcp-vector-search visualize serve --port 3000
157
+
158
+ # Custom graph file
159
+ mcp-vector-search visualize serve --graph my-graph.json
160
+ """
161
+ import http.server
162
+ import os
163
+ import socketserver
164
+ import webbrowser
165
+
166
+ # Get visualization directory
167
+ viz_dir = Path(__file__).parent.parent.parent / "visualization"
168
+
169
+ if not viz_dir.exists():
170
+ console.print(
171
+ f"[yellow]Visualization directory not found. Creating at {viz_dir}...[/yellow]"
172
+ )
173
+ viz_dir.mkdir(parents=True, exist_ok=True)
174
+
175
+ # Create index.html if it doesn't exist
176
+ html_file = viz_dir / "index.html"
177
+ if not html_file.exists():
178
+ console.print("[yellow]Creating visualization HTML file...[/yellow]")
179
+ _create_visualization_html(html_file)
180
+
181
+ # Copy graph file to visualization directory if it exists
182
+ if graph_file.exists():
183
+ import shutil
184
+
185
+ dest = viz_dir / "chunk-graph.json"
186
+ shutil.copy(graph_file, dest)
187
+ console.print(f"[green]✓[/green] Copied graph data to {dest}")
188
+ else:
189
+ console.print(
190
+ f"[yellow]Warning: Graph file {graph_file} not found. "
191
+ "Run 'mcp-vector-search visualize export' first.[/yellow]"
192
+ )
193
+
194
+ # Change to visualization directory
195
+ os.chdir(viz_dir)
196
+
197
+ # Start server
198
+ Handler = http.server.SimpleHTTPRequestHandler
199
+ try:
200
+ with socketserver.TCPServer(("", port), Handler) as httpd:
201
+ url = f"http://localhost:{port}"
202
+ console.print()
203
+ console.print(
204
+ Panel.fit(
205
+ f"[green]✓[/green] Visualization server running\n\n"
206
+ f"URL: [cyan]{url}[/cyan]\n"
207
+ f"Directory: [dim]{viz_dir}[/dim]\n\n"
208
+ f"[dim]Press Ctrl+C to stop[/dim]",
209
+ title="Server Started",
210
+ border_style="green",
211
+ )
212
+ )
213
+
214
+ # Open browser
215
+ webbrowser.open(url)
216
+
217
+ try:
218
+ httpd.serve_forever()
219
+ except KeyboardInterrupt:
220
+ console.print("\n[yellow]Stopping server...[/yellow]")
221
+
222
+ except OSError as e:
223
+ if "Address already in use" in str(e):
224
+ console.print(
225
+ f"[red]✗ Port {port} is already in use. Try a different port with --port[/red]"
226
+ )
227
+ else:
228
+ console.print(f"[red]✗ Server error: {e}[/red]")
229
+ raise typer.Exit(1)
230
+
231
+
232
+ def _create_visualization_html(html_file: Path) -> None:
233
+ """Create the D3.js visualization HTML file."""
234
+ html_content = '''<!DOCTYPE html>
235
+ <html>
236
+ <head>
237
+ <meta charset="utf-8">
238
+ <title>Code Chunk Relationship Graph</title>
239
+ <script src="https://d3js.org/d3.v7.min.js"></script>
240
+ <style>
241
+ body {
242
+ margin: 0;
243
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
244
+ background: #0d1117;
245
+ color: #c9d1d9;
246
+ overflow: hidden;
247
+ }
248
+
249
+ #controls {
250
+ position: absolute;
251
+ top: 20px;
252
+ left: 20px;
253
+ background: rgba(13, 17, 23, 0.95);
254
+ border: 1px solid #30363d;
255
+ border-radius: 6px;
256
+ padding: 16px;
257
+ min-width: 250px;
258
+ max-height: 80vh;
259
+ overflow-y: auto;
260
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
261
+ }
262
+
263
+ h1 { margin: 0 0 16px 0; font-size: 18px; }
264
+ h3 { margin: 16px 0 8px 0; font-size: 14px; color: #8b949e; }
265
+
266
+ .control-group {
267
+ margin-bottom: 12px;
268
+ }
269
+
270
+ label {
271
+ display: block;
272
+ margin-bottom: 4px;
273
+ font-size: 12px;
274
+ color: #8b949e;
275
+ }
276
+
277
+ input[type="file"] {
278
+ width: 100%;
279
+ padding: 6px;
280
+ background: #161b22;
281
+ border: 1px solid #30363d;
282
+ border-radius: 6px;
283
+ color: #c9d1d9;
284
+ font-size: 12px;
285
+ }
286
+
287
+ .legend {
288
+ font-size: 12px;
289
+ }
290
+
291
+ .legend-item {
292
+ margin: 4px 0;
293
+ display: flex;
294
+ align-items: center;
295
+ }
296
+
297
+ .legend-color {
298
+ width: 12px;
299
+ height: 12px;
300
+ border-radius: 50%;
301
+ margin-right: 8px;
302
+ }
303
+
304
+ #graph {
305
+ width: 100vw;
306
+ height: 100vh;
307
+ }
308
+
309
+ .node circle {
310
+ cursor: pointer;
311
+ stroke: #c9d1d9;
312
+ stroke-width: 1.5px;
313
+ }
314
+
315
+ .node.module circle { fill: #238636; }
316
+ .node.class circle { fill: #1f6feb; }
317
+ .node.function circle { fill: #d29922; }
318
+ .node.method circle { fill: #8957e5; }
319
+ .node.code circle { fill: #6e7681; }
320
+
321
+ .node text {
322
+ font-size: 11px;
323
+ fill: #c9d1d9;
324
+ text-anchor: middle;
325
+ pointer-events: none;
326
+ user-select: none;
327
+ }
328
+
329
+ .link {
330
+ stroke: #30363d;
331
+ stroke-opacity: 0.6;
332
+ stroke-width: 1.5px;
333
+ }
334
+
335
+ .tooltip {
336
+ position: absolute;
337
+ padding: 12px;
338
+ background: rgba(13, 17, 23, 0.95);
339
+ border: 1px solid #30363d;
340
+ border-radius: 6px;
341
+ pointer-events: none;
342
+ display: none;
343
+ font-size: 12px;
344
+ max-width: 300px;
345
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
346
+ }
347
+
348
+ .stats {
349
+ margin-top: 16px;
350
+ padding-top: 16px;
351
+ border-top: 1px solid #30363d;
352
+ font-size: 12px;
353
+ color: #8b949e;
354
+ }
355
+ </style>
356
+ </head>
357
+ <body>
358
+ <div id="controls">
359
+ <h1>🔍 Code Graph</h1>
360
+
361
+ <div class="control-group">
362
+ <label>Load Graph Data:</label>
363
+ <input type="file" id="fileInput" accept=".json">
364
+ </div>
365
+
366
+ <h3>Legend</h3>
367
+ <div class="legend">
368
+ <div class="legend-item">
369
+ <span class="legend-color" style="background: #238636;"></span> Module
370
+ </div>
371
+ <div class="legend-item">
372
+ <span class="legend-color" style="background: #1f6feb;"></span> Class
373
+ </div>
374
+ <div class="legend-item">
375
+ <span class="legend-color" style="background: #d29922;"></span> Function
376
+ </div>
377
+ <div class="legend-item">
378
+ <span class="legend-color" style="background: #8957e5;"></span> Method
379
+ </div>
380
+ <div class="legend-item">
381
+ <span class="legend-color" style="background: #6e7681;"></span> Code
382
+ </div>
383
+ </div>
384
+
385
+ <div class="stats" id="stats"></div>
386
+ </div>
387
+
388
+ <svg id="graph"></svg>
389
+ <div id="tooltip" class="tooltip"></div>
390
+
391
+ <script>
392
+ const width = window.innerWidth;
393
+ const height = window.innerHeight;
394
+
395
+ const svg = d3.select("#graph")
396
+ .attr("width", width)
397
+ .attr("height", height)
398
+ .call(d3.zoom().on("zoom", (event) => {
399
+ g.attr("transform", event.transform);
400
+ }));
401
+
402
+ const g = svg.append("g");
403
+ const tooltip = d3.select("#tooltip");
404
+ let simulation;
405
+
406
+ function visualizeGraph(data) {
407
+ g.selectAll("*").remove();
408
+
409
+ simulation = d3.forceSimulation(data.nodes)
410
+ .force("link", d3.forceLink(data.links).id(d => d.id).distance(100))
411
+ .force("charge", d3.forceManyBody().strength(-400))
412
+ .force("center", d3.forceCenter(width / 2, height / 2))
413
+ .force("collision", d3.forceCollide().radius(40));
414
+
415
+ const link = g.append("g")
416
+ .selectAll("line")
417
+ .data(data.links)
418
+ .join("line")
419
+ .attr("class", "link");
420
+
421
+ const node = g.append("g")
422
+ .selectAll("g")
423
+ .data(data.nodes)
424
+ .join("g")
425
+ .attr("class", d => `node ${d.type}`)
426
+ .call(drag(simulation))
427
+ .on("mouseover", showTooltip)
428
+ .on("mouseout", hideTooltip);
429
+
430
+ node.append("circle")
431
+ .attr("r", d => d.complexity ? Math.min(8 + d.complexity * 2, 25) : 12);
432
+
433
+ node.append("text")
434
+ .text(d => d.name)
435
+ .attr("dy", 30);
436
+
437
+ simulation.on("tick", () => {
438
+ link
439
+ .attr("x1", d => d.source.x)
440
+ .attr("y1", d => d.source.y)
441
+ .attr("x2", d => d.target.x)
442
+ .attr("y2", d => d.target.y);
443
+
444
+ node.attr("transform", d => `translate(${d.x},${d.y})`);
445
+ });
446
+
447
+ updateStats(data);
448
+ }
449
+
450
+ function showTooltip(event, d) {
451
+ tooltip
452
+ .style("display", "block")
453
+ .style("left", (event.pageX + 10) + "px")
454
+ .style("top", (event.pageY + 10) + "px")
455
+ .html(`
456
+ <div><strong>${d.name}</strong></div>
457
+ <div>Type: ${d.type}</div>
458
+ ${d.complexity ? `<div>Complexity: ${d.complexity.toFixed(1)}</div>` : ''}
459
+ ${d.start_line ? `<div>Lines: ${d.start_line}-${d.end_line}</div>` : ''}
460
+ <div>File: ${d.file_path}</div>
461
+ `);
462
+ }
463
+
464
+ function hideTooltip() {
465
+ tooltip.style("display", "none");
466
+ }
467
+
468
+ function drag(simulation) {
469
+ function dragstarted(event) {
470
+ if (!event.active) simulation.alphaTarget(0.3).restart();
471
+ event.subject.fx = event.subject.x;
472
+ event.subject.fy = event.subject.y;
473
+ }
474
+
475
+ function dragged(event) {
476
+ event.subject.fx = event.x;
477
+ event.subject.fy = event.y;
478
+ }
479
+
480
+ function dragended(event) {
481
+ if (!event.active) simulation.alphaTarget(0);
482
+ event.subject.fx = null;
483
+ event.subject.fy = null;
484
+ }
485
+
486
+ return d3.drag()
487
+ .on("start", dragstarted)
488
+ .on("drag", dragged)
489
+ .on("end", dragended);
490
+ }
491
+
492
+ function updateStats(data) {
493
+ const stats = d3.select("#stats");
494
+ stats.html(`
495
+ <div>Nodes: ${data.nodes.length}</div>
496
+ <div>Links: ${data.links.length}</div>
497
+ ${data.metadata ? `<div>Files: ${data.metadata.total_files || 'N/A'}</div>` : ''}
498
+ `);
499
+ }
500
+
501
+ document.getElementById("fileInput").addEventListener("change", (event) => {
502
+ const file = event.target.files[0];
503
+ if (file) {
504
+ const reader = new FileReader();
505
+ reader.onload = (e) => {
506
+ const data = JSON.parse(e.target.result);
507
+ visualizeGraph(data);
508
+ };
509
+ reader.readAsText(file);
510
+ }
511
+ });
512
+
513
+ // Try to load default data
514
+ fetch("chunk-graph.json")
515
+ .then(response => response.json())
516
+ .then(data => visualizeGraph(data))
517
+ .catch(err => console.log("No default graph found. Please load a JSON file."));
518
+ </script>
519
+ </body>
520
+ </html>'''
521
+
522
+ with open(html_file, "w") as f:
523
+ f.write(html_content)
@@ -33,16 +33,17 @@ unfamiliar codebases, finding similar patterns, and integrating with AI tools.
33
33
  3. Check status: [green]mcp-vector-search status[/green]
34
34
 
35
35
  [bold cyan]Main Commands:[/bold cyan]
36
- init 🔧 Initialize project
37
- demo 🎬 Run interactive demo
38
- doctor 🩺 Check system health
39
- status 📊 Show project status
40
- search 🔍 Search code semantically
41
- index 📇 Index codebase
42
- mcp 🤖 MCP integration for AI tools
43
- config ⚙️ Configure settings
44
- help ❓ Get help
45
- version ℹ️ Show version
36
+ init 🔧 Initialize project
37
+ demo 🎬 Run interactive demo
38
+ doctor 🩺 Check system health
39
+ status 📊 Show project status
40
+ search 🔍 Search code semantically
41
+ index 📇 Index codebase
42
+ mcp 🤖 MCP integration for AI tools
43
+ config ⚙️ Configure settings
44
+ visualize 📊 Visualize code relationships
45
+ help ❓ Get help
46
+ version ℹ️ Show version
46
47
 
47
48
  [dim]For detailed help: [cyan]mcp-vector-search COMMAND --help[/cyan][/dim]
48
49
  """,
@@ -58,6 +59,7 @@ from .commands.init import init_app # noqa: E402
58
59
  from .commands.mcp import mcp_app # noqa: E402
59
60
  from .commands.search import search_app, search_main # noqa: E402, F401
60
61
  from .commands.status import main as status_main # noqa: E402
62
+ from .commands.visualize import app as visualize_app # noqa: E402
61
63
 
62
64
  # ============================================================================
63
65
  # MAIN COMMANDS - Clean hierarchy
@@ -89,7 +91,10 @@ app.add_typer(mcp_app, name="mcp", help="🤖 Manage MCP integration for AI tool
89
91
  # 8. CONFIG - Configuration
90
92
  app.add_typer(config_app, name="config", help="⚙️ Manage project configuration")
91
93
 
92
- # 9. HELP - Enhanced help
94
+ # 9. VISUALIZE - Code graph visualization
95
+ app.add_typer(visualize_app, name="visualize", help="📊 Visualize code chunk relationships")
96
+
97
+ # 10. HELP - Enhanced help
93
98
  # (defined below inline)
94
99
 
95
100
  # 10. VERSION - Version info
@@ -295,8 +295,11 @@ class SemanticIndexer:
295
295
  logger.debug(f"No chunks extracted from {file_path}")
296
296
  return True # Not an error, just empty file
297
297
 
298
+ # Build hierarchical relationships between chunks
299
+ chunks_with_hierarchy = self._build_chunk_hierarchy(chunks)
300
+
298
301
  # Add chunks to database
299
- await self.database.add_chunks(chunks)
302
+ await self.database.add_chunks(chunks_with_hierarchy)
300
303
 
301
304
  # Update metadata after successful indexing
302
305
  metadata = self._load_index_metadata()
@@ -710,8 +713,11 @@ class SemanticIndexer:
710
713
  chunks = await self._parse_file(file_path)
711
714
 
712
715
  if chunks:
716
+ # Build hierarchical relationships
717
+ chunks_with_hierarchy = self._build_chunk_hierarchy(chunks)
718
+
713
719
  # Add chunks to database
714
- await self.database.add_chunks(chunks)
720
+ await self.database.add_chunks(chunks_with_hierarchy)
715
721
  chunks_added = len(chunks)
716
722
  logger.debug(f"Indexed {chunks_added} chunks from {file_path}")
717
723
 
@@ -729,3 +735,67 @@ class SemanticIndexer:
729
735
 
730
736
  # Save metadata at the end
731
737
  self._save_index_metadata(metadata)
738
+
739
+ def _build_chunk_hierarchy(self, chunks: list[CodeChunk]) -> list[CodeChunk]:
740
+ """Build parent-child relationships between chunks.
741
+
742
+ Logic:
743
+ - Module chunks (chunk_type="module") have depth 0
744
+ - Class chunks have depth 1, parent is module
745
+ - Method chunks have depth 2, parent is class
746
+ - Function chunks outside classes have depth 1, parent is module
747
+ - Nested classes increment depth
748
+
749
+ Args:
750
+ chunks: List of code chunks to process
751
+
752
+ Returns:
753
+ List of chunks with hierarchy relationships established
754
+ """
755
+ if not chunks:
756
+ return chunks
757
+
758
+ # Group chunks by type and name
759
+ module_chunks = [c for c in chunks if c.chunk_type in ("module", "imports")]
760
+ class_chunks = [c for c in chunks if c.chunk_type in ("class", "interface", "mixin")]
761
+ function_chunks = [c for c in chunks if c.chunk_type in ("function", "method", "constructor")]
762
+
763
+ # Build relationships
764
+ for func in function_chunks:
765
+ if func.class_name:
766
+ # Find parent class
767
+ parent_class = next(
768
+ (c for c in class_chunks if c.class_name == func.class_name),
769
+ None
770
+ )
771
+ if parent_class:
772
+ func.parent_chunk_id = parent_class.chunk_id
773
+ func.chunk_depth = parent_class.chunk_depth + 1
774
+ if func.chunk_id not in parent_class.child_chunk_ids:
775
+ parent_class.child_chunk_ids.append(func.chunk_id)
776
+ else:
777
+ # Top-level function
778
+ if not func.chunk_depth:
779
+ func.chunk_depth = 1
780
+ # Link to module if exists
781
+ if module_chunks and not func.parent_chunk_id:
782
+ func.parent_chunk_id = module_chunks[0].chunk_id
783
+ if func.chunk_id not in module_chunks[0].child_chunk_ids:
784
+ module_chunks[0].child_chunk_ids.append(func.chunk_id)
785
+
786
+ for cls in class_chunks:
787
+ # Classes without parent are top-level (depth 1)
788
+ if not cls.chunk_depth:
789
+ cls.chunk_depth = 1
790
+ # Link to module if exists
791
+ if module_chunks and not cls.parent_chunk_id:
792
+ cls.parent_chunk_id = module_chunks[0].chunk_id
793
+ if cls.chunk_id not in module_chunks[0].child_chunk_ids:
794
+ module_chunks[0].child_chunk_ids.append(cls.chunk_id)
795
+
796
+ # Module chunks stay at depth 0
797
+ for mod in module_chunks:
798
+ if not mod.chunk_depth:
799
+ mod.chunk_depth = 0
800
+
801
+ return chunks