diff --git a/cmd/hooks.go b/cmd/hooks.go index de10223..1e4c1fa 100644 --- a/cmd/hooks.go +++ b/cmd/hooks.go @@ -94,13 +94,53 @@ func hookSessionStart(root string) error { fmt.Println("📍 Project Context:") fmt.Println() - // Run codemap to show full tree structure + // IMPORTANT: Hook output goes directly into Claude's "Messages" context, not system prompt. + // This means hook output competes with conversation history for the ~200k token limit. + // A 1.3MB output (like a full tree of a 10k file repo) = ~500k tokens = instant context overflow. + // + // We enforce two limits: + // 1. Adaptive depth: larger repos get shallower trees (depth 2-4 based on file count) + // 2. Hard cap: 60KB max output (~15k tokens, <10% of context window) + // + // Future: Consider structured output that Claude Code can format/truncate intelligently. exe, err := os.Executable() if err == nil { - cmd := exec.Command(exe, root) - cmd.Stdout = os.Stdout + // Count files to determine appropriate depth + fileCount := 0 + if state := watch.ReadState(root); state != nil { + fileCount = state.FileCount + } + + // Adaptive depth: large repos get shallower trees + depth := "4" + if fileCount > 5000 { + depth = "2" + } else if fileCount > 2000 { + depth = "3" + } + + cmd := exec.Command(exe, "--depth", depth, root) + + // Capture output to enforce size limit + var buf strings.Builder + cmd.Stdout = &buf cmd.Stderr = os.Stderr cmd.Run() + + output := buf.String() + const maxBytes = 60000 // ~15k tokens, <10% of 200k context + + if len(output) > maxBytes { + // Truncate and add warning + output = output[:maxBytes] + // Find last newline to avoid cutting mid-line + if idx := strings.LastIndex(output, "\n"); idx > maxBytes-1000 { + output = output[:idx] + } + output += "\n\n... (truncated - repo has " + fmt.Sprintf("%d", fileCount) + " files, use `codemap .` for full tree)\n" + } + + fmt.Print(output) fmt.Println() } diff --git a/mcp/main.go b/mcp/main.go index 85d836d..a00b33c 100644 --- a/mcp/main.go +++ b/mcp/main.go @@ -188,6 +188,19 @@ func handleGetStructure(ctx context.Context, req *mcp.CallToolRequest, input Pat render.Tree(&buf, project) output := stripANSI(buf.String()) + // IMPORTANT: MCP tool output contributes to Claude's context window. + // Large repos can produce megabytes of tree output, causing instant context overflow. + // Cap at 60KB (~15k tokens) to stay under 10% of typical 200k context limit. + const maxBytes = 60000 + if len(output) > maxBytes { + output = output[:maxBytes] + // Find last newline to avoid cutting mid-line + if idx := strings.LastIndex(output, "\n"); idx > maxBytes-1000 { + output = output[:idx] + } + output += fmt.Sprintf("\n\n... (truncated - repo has %d files, use `codemap --depth N` for full tree)\n", len(files)) + } + // Add hub file summary fg, err := scanner.BuildFileGraph(input.Path) if err == nil {