resolveWorkingDir returns (absPath, isGitRepo, err). When requireGit is true, returns an error if the directory is not a git repo. When false, returns IsGitRepo=false instead of erroring (scan path uses this).
(input string, requireGit bool)
| 82 | // true, returns an error if the directory is not a git repo. When false, |
| 83 | // returns IsGitRepo=false instead of erroring (scan path uses this). |
| 84 | func resolveWorkingDir(input string, requireGit bool) (string, bool, error) { |
| 85 | if input == "" { |
| 86 | wd, err := os.Getwd() |
| 87 | if err != nil { |
| 88 | return "", false, fmt.Errorf("get working directory: %w", err) |
| 89 | } |
| 90 | input = wd |
| 91 | } |
| 92 | absPath, err := filepath.Abs(input) |
| 93 | if err != nil { |
| 94 | return "", false, fmt.Errorf("resolve absolute path: %w", err) |
| 95 | } |
| 96 | if _, statErr := os.Stat(absPath); statErr != nil { |
| 97 | return "", false, fmt.Errorf("stat %s: %w", absPath, statErr) |
| 98 | } |
| 99 | out, err := runGitCmd(absPath, "rev-parse", "--git-dir") |
| 100 | isGit := err == nil && len(out) > 0 |
| 101 | if !isGit && requireGit { |
| 102 | return "", false, fmt.Errorf("%s is not a git repository", absPath) |
| 103 | } |
| 104 | // #287: git reports diff and `git show HEAD:<path>` paths relative to the |
| 105 | // repository root, not the current directory. When `ocr review` runs from a |
| 106 | // subdirectory of a monorepo, anchor RepoDir at the git top-level so those |
| 107 | // root-relative paths resolve for both disk reads and git-show reads. |
| 108 | // requireGit is true only for the review path; scan (requireGit=false) keeps |
| 109 | // the CWD so its `git ls-files` walk stays scoped to the subdirectory. |
| 110 | if isGit && requireGit { |
| 111 | // runGitCmdStdout captures stdout only so git stderr notices can't |
| 112 | // pollute the resolved path. --show-toplevel fails (or is empty) when |
| 113 | // there is no work tree — e.g. a bare repo, where --git-dir succeeds so |
| 114 | // isGit is true. Fail loudly there instead of silently reusing the |
| 115 | // subdir, which would reproduce the #287 root-relative-path bug. |
| 116 | top, topErr := runGitCmdStdout(absPath, "rev-parse", "--show-toplevel") |
| 117 | t := strings.TrimSpace(string(top)) |
| 118 | if topErr != nil || t == "" { |
| 119 | return "", false, fmt.Errorf("%s is a git repository without a work tree (bare repo?); cannot resolve its top level for review", absPath) |
| 120 | } |
| 121 | absPath = t |
| 122 | } |
| 123 | return absPath, isGit, nil |
| 124 | } |
| 125 | |
| 126 | // llmRuntime bundles the LLM-side state both subcommands need once they've |
| 127 | // decided to actually run a session: tool definitions, an app-language |