( command: string, args: string[], spec: CommandSpec | null, )
| 137 | } |
| 138 | |
| 139 | async function calculateDepth( |
| 140 | command: string, |
| 141 | args: string[], |
| 142 | spec: CommandSpec | null, |
| 143 | ): Promise<number> { |
| 144 | // Find first subcommand by skipping flags and their values |
| 145 | const firstSubcommand = findFirstSubcommand(args, spec) |
| 146 | const commandLower = command.toLowerCase() |
| 147 | const key = firstSubcommand |
| 148 | ? `${commandLower} ${firstSubcommand.toLowerCase()}` |
| 149 | : commandLower |
| 150 | if (DEPTH_RULES[key]) return DEPTH_RULES[key] |
| 151 | if (DEPTH_RULES[commandLower]) return DEPTH_RULES[commandLower] |
| 152 | if (!spec) return 2 |
| 153 | |
| 154 | if (spec.options && args.some(arg => arg?.startsWith('-'))) { |
| 155 | for (const arg of args) { |
| 156 | if (!arg?.startsWith('-')) continue |
| 157 | const option = spec.options.find(opt => |
| 158 | Array.isArray(opt.name) ? opt.name.includes(arg) : opt.name === arg, |
| 159 | ) |
| 160 | if ( |
| 161 | option?.args && |
| 162 | toArray(option.args).some(arg => arg?.isCommand || arg?.isModule) |
| 163 | ) |
| 164 | return 3 |
| 165 | } |
| 166 | } |
| 167 | |
| 168 | // Find subcommand spec using the already-found firstSubcommand |
| 169 | if (firstSubcommand && spec.subcommands?.length) { |
| 170 | const firstSubLower = firstSubcommand.toLowerCase() |
| 171 | const subcommand = spec.subcommands.find(sub => |
| 172 | Array.isArray(sub.name) |
| 173 | ? sub.name.some(n => n.toLowerCase() === firstSubLower) |
| 174 | : sub.name.toLowerCase() === firstSubLower, |
| 175 | ) |
| 176 | if (subcommand) { |
| 177 | if (subcommand.args) { |
| 178 | const subArgs = toArray(subcommand.args) |
| 179 | if (subArgs.some(arg => arg?.isCommand)) return 3 |
| 180 | if (subArgs.some(arg => arg?.isVariadic)) return 2 |
| 181 | } |
| 182 | if (subcommand.subcommands?.length) return 4 |
| 183 | // Leaf subcommand with NO args declared (git show, git log, git tag): |
| 184 | // the 3rd word is transient (SHA, ref, tag name) → dead over-specific |
| 185 | // rule like PowerShell(git show 81210f8:*). NOT the isOptional case — |
| 186 | // `git fetch` declares optional remote/branch and `git fetch origin` |
| 187 | // is tested (bash/prefix.test.ts:912) as intentional remote scoping. |
| 188 | if (!subcommand.args) return 2 |
| 189 | return 3 |
| 190 | } |
| 191 | } |
| 192 | |
| 193 | if (spec.args) { |
| 194 | const argsArray = toArray(spec.args) |
| 195 | |
| 196 | if (argsArray.some(arg => arg?.isCommand)) { |
no test coverage detected