| 215 | } |
| 216 | |
| 217 | func handleGetStructure(ctx context.Context, req *mcp.CallToolRequest, input PathInput) (*mcp.CallToolResult, any, error) { |
| 218 | absRoot, err := filepath.Abs(input.Path) |
| 219 | if err != nil { |
| 220 | return errorResult("Invalid path: " + err.Error()), nil, nil |
| 221 | } |
| 222 | |
| 223 | gitCache := scanner.NewGitIgnoreCache(input.Path) |
| 224 | files, err := scanner.ScanFiles(input.Path, gitCache, nil, nil) |
| 225 | if err != nil { |
| 226 | return errorResult("Scan error: " + err.Error()), nil, nil |
| 227 | } |
| 228 | fileCount := len(files) |
| 229 | depth := input.Depth |
| 230 | if depth <= 0 { |
| 231 | depth = limits.AdaptiveDepth(fileCount) |
| 232 | } |
| 233 | |
| 234 | project := scanner.Project{ |
| 235 | Root: absRoot, |
| 236 | Mode: "tree", |
| 237 | Depth: depth, |
| 238 | Files: files, |
| 239 | } |
| 240 | |
| 241 | var buf bytes.Buffer |
| 242 | render.Tree(&buf, project) |
| 243 | output := stripANSI(buf.String()) |
| 244 | |
| 245 | // IMPORTANT: MCP tool output contributes directly to context window usage. |
| 246 | if len(output) > limits.MaxStructureOutputBytes { |
| 247 | output = limits.TruncateAtLineBoundary( |
| 248 | output, |
| 249 | limits.MaxStructureOutputBytes, |
| 250 | fmt.Sprintf("\n\n... (truncated - repo has %d files, use `codemap --depth N` for full tree)\n", fileCount), |
| 251 | ) |
| 252 | } |
| 253 | |
| 254 | // Add hub file summary. Prefer daemon cache; avoid expensive graph builds on |
| 255 | // very large repos. |
| 256 | var hubs []string |
| 257 | var importers map[string][]string |
| 258 | if state := watch.ReadState(absRoot); state != nil && (len(state.Importers) > 0 || len(state.Hubs) > 0) { |
| 259 | hubs = append(hubs, state.Hubs...) |
| 260 | importers = state.Importers |
| 261 | } else if fileCount <= limits.LargeRepoFileCount { |
| 262 | fg, err := scanner.BuildFileGraph(absRoot) |
| 263 | if err == nil { |
| 264 | hubs = fg.HubFiles() |
| 265 | importers = fg.Importers |
| 266 | } |
| 267 | } else { |
| 268 | output += "\nℹ️ Hub analysis skipped for large repo (request get_hubs for explicit analysis)\n" |
| 269 | } |
| 270 | |
| 271 | if len(hubs) > 0 { |
| 272 | output += "\n⚠️ HUB FILES (high-impact, 3+ dependents):\n" |
| 273 | sort.Slice(hubs, func(i, j int) bool { |
| 274 | return len(importers[hubs[i]]) > len(importers[hubs[j]]) |