Unified Agent + reusable Go agent core.
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: remove all bash diff logic, keep only write_file diffs

Per upstream review feedback, remove the entire bash diff subsystem:
- Delete snapshotProjectFiles, isReadOnlyBashCommand, isBinaryData,
renderBashDiffs, showNewFiles functions
- Remove bash branches from onToolCallStart and onToolCallDone hooks
- Update comments to reflect write_file-only diff behavior

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

authored by

Teteuya
Claude Sonnet 4.6
and committed by
Lyric Wai
09afc765 b33bd9b0

+2 -200
+2 -200
cmd/mistermorph/chatcmd/session.go
··· 441 441 } 442 442 })) 443 443 444 - // Capture old file content before write_file or bash executes so we can render diffs. 444 + // Capture old file content before write_file executes so we can render diffs. 445 445 opts = append(opts, agent.WithOnToolCallStart(func(runCtx *agent.Context, tc agent.ToolCall) { 446 446 if sess == nil { 447 447 return ··· 457 457 return // File doesn't exist yet (new file) – nothing to diff. 458 458 } 459 459 sess.fileSnapshots[path] = string(data) 460 - } else if tc.Name == "bash" { 461 - if !isReadOnlyBashCommand(tc.Params) { 462 - sess.snapshotProjectFiles() 463 - } 464 460 } 465 461 })) 466 462 467 - // Render diff after write_file or bash completes successfully. 463 + // Render diff after write_file completes successfully. 468 464 opts = append(opts, agent.WithOnToolCallDone(func(runCtx *agent.Context, tc agent.ToolCall, observation string, err error) { 469 465 if sess == nil || err != nil { 470 466 return ··· 502 498 if diff != "" { 503 499 _, _ = fmt.Fprintln(writer, diff) 504 500 } 505 - } else if tc.Name == "bash" { 506 - sess.renderBashDiffs() 507 501 } 508 502 })) 509 503 ··· 667 661 s.memCleanup() 668 662 } 669 663 } 670 - 671 - // snapshotProjectFiles scans the project directory and stores the current 672 - // content of existing text files into fileSnapshots. It caps the number of 673 - // files and total bytes to avoid bloating memory on large repos. 674 - func (s *chatSession) snapshotProjectFiles() { 675 - if s == nil { 676 - return 677 - } 678 - dir := s.projectDir() 679 - if dir == "" { 680 - return 681 - } 682 - const maxFiles = 500 683 - const maxTotalBytes = 10 * 1024 * 1024 684 - fileCount := 0 685 - totalBytes := 0 686 - _ = filepath.WalkDir(dir, func(path string, d os.DirEntry, err error) error { 687 - if err != nil { 688 - return nil 689 - } 690 - if d.IsDir() { 691 - name := d.Name() 692 - if strings.HasPrefix(name, ".") && path != dir { 693 - return filepath.SkipDir 694 - } 695 - if name == "node_modules" || name == "vendor" || name == "target" || name == "dist" || name == "build" { 696 - return filepath.SkipDir 697 - } 698 - return nil 699 - } 700 - if fileCount >= maxFiles || totalBytes >= maxTotalBytes { 701 - return filepath.SkipAll 702 - } 703 - info, err := d.Info() 704 - if err != nil { 705 - return nil 706 - } 707 - if info.Size() > 1024*1024 { 708 - return nil 709 - } 710 - data, err := os.ReadFile(path) 711 - if err != nil { 712 - return nil 713 - } 714 - if isBinaryData(data) { 715 - return nil 716 - } 717 - s.fileSnapshots[path] = string(data) 718 - fileCount++ 719 - totalBytes += len(data) 720 - return nil 721 - }) 722 - } 723 - 724 - // isReadOnlyBashCommand heuristically detects bash commands that are unlikely 725 - // to modify files, so we can skip the expensive snapshot step. 726 - func isReadOnlyBashCommand(params map[string]any) bool { 727 - cmd, _ := params["command"].(string) 728 - cmd = strings.TrimSpace(cmd) 729 - if cmd == "" { 730 - return false 731 - } 732 - readOnlyPrefixes := []string{ 733 - "ls ", "ls\t", "ls\n", 734 - "cat ", "cat\t", 735 - "find ", "find\t", 736 - "grep ", "grep\t", 737 - "git status", "git log", "git diff", "git show", "git branch", 738 - "go test", "go vet", "go mod", "go list", "go env", 739 - "echo ", "echo\t", 740 - "pwd", "pwd ", "pwd\t", 741 - "head ", "head\t", "tail ", "tail\t", 742 - "wc ", "wc\t", 743 - "sort ", "sort\t", "uniq ", "uniq\t", 744 - "ps ", "ps\t", "top", "htop", 745 - "df ", "df\t", "du ", "du\t", 746 - "curl ", "curl\t", "wget ", "wget\t", 747 - "which ", "which\t", "whereis ", "whereis\t", 748 - } 749 - lower := strings.ToLower(cmd) 750 - for _, prefix := range readOnlyPrefixes { 751 - if strings.HasPrefix(lower, prefix) { 752 - return true 753 - } 754 - } 755 - return false 756 - } 757 - 758 - func isBinaryData(data []byte) bool { 759 - limit := 8192 760 - if len(data) < limit { 761 - limit = len(data) 762 - } 763 - for i := 0; i < limit; i++ { 764 - if data[i] == 0 { 765 - return true 766 - } 767 - } 768 - return false 769 - } 770 - 771 - // renderBashDiffs compares fileSnapshots against current disk content and 772 - // renders diffs for any files that changed, were deleted, or were created 773 - // during a bash tool call. 774 - func (s *chatSession) renderBashDiffs() { 775 - if s == nil { 776 - return 777 - } 778 - writer := s.currentWriter() 779 - 780 - // Remember which paths were snapshotted before bash ran. 781 - oldPaths := make(map[string]struct{}, len(s.fileSnapshots)) 782 - for path := range s.fileSnapshots { 783 - oldPaths[path] = struct{}{} 784 - } 785 - 786 - // Show diffs for changed or deleted files. 787 - for path, oldContent := range s.fileSnapshots { 788 - newData, err := os.ReadFile(path) 789 - delete(s.fileSnapshots, path) 790 - if err != nil { 791 - if os.IsNotExist(err) { 792 - _, _ = fmt.Fprintf(writer, "\x1b[33m deleted %s\x1b[0m\n", path) 793 - } 794 - continue 795 - } 796 - newContent := string(newData) 797 - if oldContent == newContent { 798 - continue 799 - } 800 - diff := clifmt.RenderDiff(path, oldContent, newContent) 801 - if diff != "" { 802 - _, _ = fmt.Fprintln(writer, diff) 803 - } 804 - } 805 - 806 - // Show newly created files. 807 - s.showNewFiles(writer, oldPaths) 808 - } 809 - 810 - // showNewFiles scans the project directory for files that were not present 811 - // in the pre-bash snapshot and renders their full content as a diff from empty. 812 - func (s *chatSession) showNewFiles(writer io.Writer, oldPaths map[string]struct{}) { 813 - dir := s.projectDir() 814 - if dir == "" { 815 - return 816 - } 817 - const maxNewFiles = 50 818 - const maxBytesPerFile = 1024 * 1024 819 - newFileCount := 0 820 - _ = filepath.WalkDir(dir, func(path string, d os.DirEntry, err error) error { 821 - if err != nil { 822 - return nil 823 - } 824 - if d.IsDir() { 825 - name := d.Name() 826 - if strings.HasPrefix(name, ".") && path != dir { 827 - return filepath.SkipDir 828 - } 829 - if name == "node_modules" || name == "vendor" || name == "target" || name == "dist" || name == "build" { 830 - return filepath.SkipDir 831 - } 832 - return nil 833 - } 834 - if _, known := oldPaths[path]; known { 835 - return nil 836 - } 837 - if newFileCount >= maxNewFiles { 838 - return filepath.SkipAll 839 - } 840 - info, err := d.Info() 841 - if err != nil { 842 - return nil 843 - } 844 - if info.Size() > maxBytesPerFile { 845 - return nil 846 - } 847 - data, err := os.ReadFile(path) 848 - if err != nil { 849 - return nil 850 - } 851 - if isBinaryData(data) { 852 - return nil 853 - } 854 - diff := clifmt.RenderDiff(path, "", string(data)) 855 - if diff != "" { 856 - _, _ = fmt.Fprintln(writer, diff) 857 - } 858 - newFileCount++ 859 - return nil 860 - }) 861 - }