bash-classify 0.10.0__tar.gz → 0.11.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (365) hide show
  1. {bash_classify-0.10.0 → bash_classify-0.11.0}/.claude-plugin/marketplace.json +1 -1
  2. {bash_classify-0.10.0 → bash_classify-0.11.0}/PKG-INFO +2 -1
  3. {bash_classify-0.10.0 → bash_classify-0.11.0}/README.md +1 -0
  4. {bash_classify-0.10.0 → bash_classify-0.11.0}/SPEC.md +18 -2
  5. {bash_classify-0.10.0 → bash_classify-0.11.0}/coding-agent-plugins/claude-code/.claude-plugin/plugin.json +1 -1
  6. {bash_classify-0.10.0 → bash_classify-0.11.0}/docs/classification-guidance.md +19 -0
  7. {bash_classify-0.10.0 → bash_classify-0.11.0}/schemas/command.schema.json +7 -0
  8. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/glab.yaml +2 -0
  9. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/database.py +50 -4
  10. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/matcher.py +8 -3
  11. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/models.py +8 -1
  12. bash_classify-0.11.0/tests/fixtures/match/glab-pipeline/block/pipe-get-alias.sh +1 -0
  13. bash_classify-0.11.0/tests/fixtures/match/glab-pipeline/block/pipeline-view-alias.sh +1 -0
  14. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/test_database.py +67 -0
  15. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/test_matcher.py +60 -0
  16. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/test_real_world.py +26 -0
  17. {bash_classify-0.10.0 → bash_classify-0.11.0}/.claude/rules/command-database.md +0 -0
  18. {bash_classify-0.10.0 → bash_classify-0.11.0}/.editorconfig +0 -0
  19. {bash_classify-0.10.0 → bash_classify-0.11.0}/.github/workflows/ci.yml +0 -0
  20. {bash_classify-0.10.0 → bash_classify-0.11.0}/.github/workflows/publish.yml +0 -0
  21. {bash_classify-0.10.0 → bash_classify-0.11.0}/.gitignore +0 -0
  22. {bash_classify-0.10.0 → bash_classify-0.11.0}/.python-version +0 -0
  23. {bash_classify-0.10.0 → bash_classify-0.11.0}/CLAUDE.md +0 -0
  24. {bash_classify-0.10.0 → bash_classify-0.11.0}/LICENSE +0 -0
  25. {bash_classify-0.10.0 → bash_classify-0.11.0}/coding-agent-plugins/claude-code/hooks/hook-permission-request.sh +0 -0
  26. {bash_classify-0.10.0 → bash_classify-0.11.0}/coding-agent-plugins/claude-code/hooks/hook-pre-tool-use.sh +0 -0
  27. {bash_classify-0.10.0 → bash_classify-0.11.0}/coding-agent-plugins/claude-code/hooks/hooks.json +0 -0
  28. {bash_classify-0.10.0 → bash_classify-0.11.0}/pyproject.toml +0 -0
  29. {bash_classify-0.10.0 → bash_classify-0.11.0}/ruff.toml +0 -0
  30. {bash_classify-0.10.0 → bash_classify-0.11.0}/schemas/match-rules.schema.json +0 -0
  31. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/__init__.py +0 -0
  32. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/__main__.py +0 -0
  33. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/classifier.py +0 -0
  34. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/cli.py +0 -0
  35. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/agent-browser.yaml +0 -0
  36. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/apt.yaml +0 -0
  37. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/awk.yaml +0 -0
  38. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/basename.yaml +0 -0
  39. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/bash-classify.yaml +0 -0
  40. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/bash.yaml +0 -0
  41. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/black.yaml +0 -0
  42. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/brew.yaml +0 -0
  43. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/builtin.yaml +0 -0
  44. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/cargo.yaml +0 -0
  45. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/cat.yaml +0 -0
  46. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/chgrp.yaml +0 -0
  47. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/chmod.yaml +0 -0
  48. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/chown.yaml +0 -0
  49. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/column.yaml +0 -0
  50. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/comm.yaml +0 -0
  51. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/command.yaml +0 -0
  52. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/cp.yaml +0 -0
  53. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/curl.yaml +0 -0
  54. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/cut.yaml +0 -0
  55. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/date.yaml +0 -0
  56. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/df.yaml +0 -0
  57. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/diff.yaml +0 -0
  58. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/dig.yaml +0 -0
  59. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/dirname.yaml +0 -0
  60. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/docker.yaml +0 -0
  61. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/du.yaml +0 -0
  62. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/echo.yaml +0 -0
  63. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/env.yaml +0 -0
  64. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/eslint.yaml +0 -0
  65. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/eval.yaml +0 -0
  66. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/exec.yaml +0 -0
  67. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/expand.yaml +0 -0
  68. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/false.yaml +0 -0
  69. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/file.yaml +0 -0
  70. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/find.yaml +0 -0
  71. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/fmt.yaml +0 -0
  72. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/fold.yaml +0 -0
  73. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/free.yaml +0 -0
  74. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/gawk.yaml +0 -0
  75. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/gcloud.yaml +0 -0
  76. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/gh.yaml +0 -0
  77. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/git.yaml +0 -0
  78. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/glab-discussion.yaml +0 -0
  79. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/go.yaml +0 -0
  80. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/gog.yaml +0 -0
  81. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/gradle.yaml +0 -0
  82. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/gradlew.yaml +0 -0
  83. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/grep.yaml +0 -0
  84. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/groups.yaml +0 -0
  85. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/gunzip.yaml +0 -0
  86. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/gws.yaml +0 -0
  87. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/gzip.yaml +0 -0
  88. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/hadolint.yaml +0 -0
  89. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/head.yaml +0 -0
  90. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/helm.yaml +0 -0
  91. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/hostname.yaml +0 -0
  92. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/id.yaml +0 -0
  93. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/install.yaml +0 -0
  94. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/isort.yaml +0 -0
  95. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/java.yaml +0 -0
  96. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/javac.yaml +0 -0
  97. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/join.yaml +0 -0
  98. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/journalctl.yaml +0 -0
  99. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/jq.yaml +0 -0
  100. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/kill.yaml +0 -0
  101. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/killall.yaml +0 -0
  102. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/kubectl.yaml +0 -0
  103. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/less.yaml +0 -0
  104. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/ln.yaml +0 -0
  105. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/ls.yaml +0 -0
  106. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/make.yaml +0 -0
  107. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/md5sum.yaml +0 -0
  108. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/mise.yaml +0 -0
  109. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/mkdir.yaml +0 -0
  110. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/more.yaml +0 -0
  111. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/mv.yaml +0 -0
  112. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/mvn.yaml +0 -0
  113. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/mvnw.yaml +0 -0
  114. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/mypy.yaml +0 -0
  115. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/netstat.yaml +0 -0
  116. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/nice.yaml +0 -0
  117. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/nl.yaml +0 -0
  118. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/node.yaml +0 -0
  119. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/nohup.yaml +0 -0
  120. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/npm.yaml +0 -0
  121. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/npx.yaml +0 -0
  122. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/nslookup.yaml +0 -0
  123. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/paste.yaml +0 -0
  124. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/ping.yaml +0 -0
  125. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/pip.yaml +0 -0
  126. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/pip3.yaml +0 -0
  127. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/pipx.yaml +0 -0
  128. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/pkill.yaml +0 -0
  129. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/pnpm.yaml +0 -0
  130. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/poetry.yaml +0 -0
  131. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/prettier.yaml +0 -0
  132. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/printenv.yaml +0 -0
  133. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/printf.yaml +0 -0
  134. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/ps.yaml +0 -0
  135. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/pup.yaml +0 -0
  136. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/pwd.yaml +0 -0
  137. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/pytest.yaml +0 -0
  138. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/python.yaml +0 -0
  139. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/python3.yaml +0 -0
  140. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/read.yaml +0 -0
  141. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/readlink.yaml +0 -0
  142. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/realpath.yaml +0 -0
  143. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/rev.yaml +0 -0
  144. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/rg.yaml +0 -0
  145. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/rm.yaml +0 -0
  146. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/rsync.yaml +0 -0
  147. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/rtk.yaml +0 -0
  148. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/ruff.yaml +0 -0
  149. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/scp.yaml +0 -0
  150. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/sed.yaml +0 -0
  151. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/seq.yaml +0 -0
  152. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/sh.yaml +0 -0
  153. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/sha1sum.yaml +0 -0
  154. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/sha256sum.yaml +0 -0
  155. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/shellcheck.yaml +0 -0
  156. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/slack.yaml +0 -0
  157. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/sleep.yaml +0 -0
  158. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/snip.yaml +0 -0
  159. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/sort.yaml +0 -0
  160. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/ss.yaml +0 -0
  161. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/ssh.yaml +0 -0
  162. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/stat.yaml +0 -0
  163. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/stdbuf.yaml +0 -0
  164. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/strace.yaml +0 -0
  165. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/strings.yaml +0 -0
  166. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/sudo.yaml +0 -0
  167. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/systemctl.yaml +0 -0
  168. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/tac.yaml +0 -0
  169. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/tail.yaml +0 -0
  170. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/tar.yaml +0 -0
  171. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/tee.yaml +0 -0
  172. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/terraform.yaml +0 -0
  173. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/test.yaml +0 -0
  174. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/time.yaml +0 -0
  175. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/timeout.yaml +0 -0
  176. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/top.yaml +0 -0
  177. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/touch.yaml +0 -0
  178. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/tr.yaml +0 -0
  179. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/traceroute.yaml +0 -0
  180. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/true.yaml +0 -0
  181. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/tsc.yaml +0 -0
  182. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/type.yaml +0 -0
  183. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/uname.yaml +0 -0
  184. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/unexpand.yaml +0 -0
  185. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/uniq.yaml +0 -0
  186. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/unzip.yaml +0 -0
  187. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/uptime.yaml +0 -0
  188. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/uv.yaml +0 -0
  189. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/wait.yaml +0 -0
  190. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/wc.yaml +0 -0
  191. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/wget.yaml +0 -0
  192. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/whereis.yaml +0 -0
  193. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/which.yaml +0 -0
  194. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/whoami.yaml +0 -0
  195. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/xargs.yaml +0 -0
  196. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/xmllint.yaml +0 -0
  197. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/yarn.yaml +0 -0
  198. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/yes.yaml +0 -0
  199. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/yq.yaml +0 -0
  200. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/zip.yaml +0 -0
  201. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/commands/zsh.yaml +0 -0
  202. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/parser.py +0 -0
  203. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/py.typed +0 -0
  204. {bash_classify-0.10.0 → bash_classify-0.11.0}/src/bash_classify/rules.py +0 -0
  205. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/__init__.py +0 -0
  206. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/conftest.py +0 -0
  207. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-discussion/allow/api-merge-request-award-emoji.sh +0 -0
  208. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-discussion/allow/api-merge-request-include-diverged.sh +0 -0
  209. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-discussion/allow/api-merge-request-itself.sh +0 -0
  210. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-discussion/allow/api-merge-request-pipelines-list.sh +0 -0
  211. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-discussion/allow/api-merge-request-update-description.sh +0 -0
  212. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-discussion/allow/api-project-lookup.sh +0 -0
  213. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-discussion/allow/ci-list.sh +0 -0
  214. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-discussion/allow/comment-only-mention.sh +0 -0
  215. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-discussion/allow/echo-mentions-api-endpoint.sh +0 -0
  216. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-discussion/allow/echo-mentions-command.sh +0 -0
  217. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-discussion/allow/git-commit-heredoc-message.sh +0 -0
  218. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-discussion/allow/git-commit-inline-message.sh +0 -0
  219. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-discussion/allow/glab-discussion-read-and-api-mr.sh +0 -0
  220. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-discussion/allow/glab-discussion-read.sh +0 -0
  221. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-discussion/allow/glab-pipeline-inspect.sh +0 -0
  222. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-discussion/allow/grep-pattern-mentions-command.sh +0 -0
  223. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-discussion/allow/grep-recursive-for-command.sh +0 -0
  224. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-discussion/allow/heredoc-brief-2026-09-04.sh +0 -0
  225. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-discussion/allow/heredoc-brief-mentions-blocked-commands.sh +0 -0
  226. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-discussion/allow/heredoc-report-mentions-blocked.sh +0 -0
  227. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-discussion/allow/heredoc-skill-custom-delimiter.sh +0 -0
  228. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-discussion/allow/heredoc-skill-phrase-split-across-lines.sh +0 -0
  229. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-discussion/allow/hook-self-test-json-payload.sh +0 -0
  230. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-discussion/allow/loop-over-quoted-command-strings.sh +0 -0
  231. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-discussion/allow/mr-note-list-output-json.sh +0 -0
  232. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-discussion/allow/mr-note-list.sh +0 -0
  233. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-discussion/allow/mr-update-then-view.sh +0 -0
  234. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-discussion/allow/mr-view-bare.sh +0 -0
  235. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-discussion/allow/mr-view-grep-pipeline.sh +0 -0
  236. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-discussion/allow/mr-view-output-equals-json.sh +0 -0
  237. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-discussion/allow/mr-view-output-json.sh +0 -0
  238. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-discussion/allow/mr-view-output-short-flag.sh +0 -0
  239. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-discussion/allow/printf-mentions-command.sh +0 -0
  240. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-discussion/allow/python-heredoc-triple-quoted-mention.sh +0 -0
  241. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-discussion/allow/ripgrep-pattern-args.sh +0 -0
  242. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-discussion/block/api-absolute-binary-path.sh +0 -0
  243. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-discussion/block/api-delete-note-nested-api.sh +0 -0
  244. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-discussion/block/api-discussions-cd-and-prefix.sh +0 -0
  245. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-discussion/block/api-discussions-export-host-loop.sh +0 -0
  246. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-discussion/block/api-discussions-for-loop.sh +0 -0
  247. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-discussion/block/api-discussions-heredoc-then-call.sh +0 -0
  248. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-discussion/block/api-discussions-hostname-python-consumer.sh +0 -0
  249. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-discussion/block/api-discussions-line-continuation.sh +0 -0
  250. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-discussion/block/api-discussions-method-get-field.sh +0 -0
  251. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-discussion/block/api-discussions-output-ndjson.sh +0 -0
  252. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-discussion/block/api-discussions-paginate-before-path.sh +0 -0
  253. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-discussion/block/api-discussions-paginate.sh +0 -0
  254. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-discussion/block/api-discussions-per-page-query.sh +0 -0
  255. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-discussion/block/api-discussions-quoted-paginate-jq.sh +0 -0
  256. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-discussion/block/api-discussions-redirect-to-file.sh +0 -0
  257. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-discussion/block/api-discussions-repo-flag-first.sh +0 -0
  258. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-discussion/block/api-discussions-var-capture.sh +0 -0
  259. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-discussion/block/api-edit-note-put-heredoc.sh +0 -0
  260. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-discussion/block/api-new-diff-note-input-stdin.sh +0 -0
  261. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-discussion/block/api-new-diff-note-position-fields.sh +0 -0
  262. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-discussion/block/api-note-award-emoji.sh +0 -0
  263. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-discussion/block/api-note-by-id-or-retry.sh +0 -0
  264. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-discussion/block/api-notes-after-allowed-mr-view.sh +0 -0
  265. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-discussion/block/api-notes-query-sort.sh +0 -0
  266. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-discussion/block/api-notes-timeout-prefix.sh +0 -0
  267. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-discussion/block/api-reply-method-post-after-path.sh +0 -0
  268. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-discussion/block/api-reply-post-body-from-file.sh +0 -0
  269. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-discussion/block/api-reply-post-heredoc-body.sh +0 -0
  270. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-discussion/block/api-resolve-loop.sh +0 -0
  271. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-discussion/block/api-resolve-put-field.sh +0 -0
  272. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-discussion/block/api-single-discussion-by-id.sh +0 -0
  273. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-discussion/block/heredoc-then-and-chain-to-blocked.sh +0 -0
  274. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-discussion/block/heredoc-then-pipe-to-blocked.sh +0 -0
  275. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-discussion/block/mr-note-custom-heredoc-delimiter.sh +0 -0
  276. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-discussion/block/mr-note-help.sh +0 -0
  277. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-discussion/block/mr-note-heredoc-body.sh +0 -0
  278. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-discussion/block/mr-note-inline-after-mr-update.sh +0 -0
  279. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-discussion/block/mr-note-long-message-flag.sh +0 -0
  280. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-discussion/block/mr-note-repo-before-message.sh +0 -0
  281. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-discussion/block/mr-note-second-in-pipeline.sh +0 -0
  282. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-discussion/block/mr-note-then-close.sh +0 -0
  283. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-discussion/block/mr-view-comments-bare.sh +0 -0
  284. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-discussion/block/mr-view-comments-loop.sh +0 -0
  285. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-discussion/block/mr-view-comments-no-iid.sh +0 -0
  286. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-discussion/block/mr-view-comments-page-flag.sh +0 -0
  287. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-discussion/block/mr-view-comments-repo-flag.sh +0 -0
  288. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-discussion/block/mr-view-comments-short-cluster.sh +0 -0
  289. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-discussion/block/mr-view-comments-short-flag.sh +0 -0
  290. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-discussion/block/wrapper-bash-c.sh +0 -0
  291. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-discussion/block/wrapper-sudo.sh +0 -0
  292. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-discussion/rules.yaml +0 -0
  293. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-pipeline/allow/api-job-metadata-without-trace.sh +0 -0
  294. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-pipeline/allow/api-job-metadata-xargs.sh +0 -0
  295. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-pipeline/allow/api-merge-request-itself.sh +0 -0
  296. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-pipeline/allow/api-merge-request-pipelines-list-per-page.sh +0 -0
  297. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-pipeline/allow/api-merge-request-pipelines-list.sh +0 -0
  298. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-pipeline/allow/api-pipelines-list-bash-c.sh +0 -0
  299. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-pipeline/allow/api-pipelines-list-ref.sh +0 -0
  300. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-pipeline/allow/api-pipelines-list-single-quoted.sh +0 -0
  301. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-pipeline/allow/ci-lint-local-file.sh +0 -0
  302. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-pipeline/allow/ci-list-per-page.sh +0 -0
  303. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-pipeline/allow/ci-list.sh +0 -0
  304. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-pipeline/allow/ci-status-compact.sh +0 -0
  305. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-pipeline/allow/ci-status-pipeline-id.sh +0 -0
  306. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-pipeline/allow/comment-only-mention.sh +0 -0
  307. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-pipeline/allow/echo-mentions-ci-command.sh +0 -0
  308. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-pipeline/allow/git-commit-heredoc-message.sh +0 -0
  309. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-pipeline/allow/git-commit-inline-message.sh +0 -0
  310. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-pipeline/allow/glab-discussion-read.sh +0 -0
  311. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-pipeline/allow/glab-pipeline-inspect.sh +0 -0
  312. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-pipeline/allow/grep-pattern-mentions-ci.sh +0 -0
  313. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-pipeline/allow/heredoc-brief-mentions-ci-commands.sh +0 -0
  314. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-pipeline/allow/heredoc-skill-mentions-ci.sh +0 -0
  315. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-pipeline/allow/loop-over-quoted-command-strings.sh +0 -0
  316. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-pipeline/allow/printf-mentions-ci-command.sh +0 -0
  317. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-pipeline/allow/ripgrep-pattern-args.sh +0 -0
  318. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-pipeline/block/api-ci-lint-continuation-lines.sh +0 -0
  319. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-pipeline/block/api-ci-lint-input-and-header.sh +0 -0
  320. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-pipeline/block/api-ci-lint-method-post-field.sh +0 -0
  321. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-pipeline/block/api-ci-lint-raw-field-cat.sh +0 -0
  322. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-pipeline/block/api-job-trace-after-allowed-metadata.sh +0 -0
  323. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-pipeline/block/api-job-trace-env-assignment.sh +0 -0
  324. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-pipeline/block/api-job-trace-redirect-to-file.sh +0 -0
  325. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-pipeline/block/api-job-trace-retry-loop.sh +0 -0
  326. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-pipeline/block/api-job-trace-xargs.sh +0 -0
  327. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-pipeline/block/api-pipeline-detail.sh +0 -0
  328. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-pipeline/block/api-pipeline-env-assignment.sh +0 -0
  329. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-pipeline/block/api-pipeline-hostname-after-path.sh +0 -0
  330. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-pipeline/block/api-pipeline-jobs-comment-and-loop.sh +0 -0
  331. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-pipeline/block/api-pipeline-jobs-list.sh +0 -0
  332. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-pipeline/block/api-pipeline-jobs-nested-loop-timeout.sh +0 -0
  333. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-pipeline/block/api-pipeline-jobs-paginate-ndjson.sh +0 -0
  334. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-pipeline/block/api-pipeline-jobs-python-consumer.sh +0 -0
  335. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-pipeline/block/api-pipeline-jobs-scope-failed-loop.sh +0 -0
  336. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-pipeline/block/api-pipeline-status-poll-loop.sh +0 -0
  337. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-pipeline/block/api-pipeline-test-report.sh +0 -0
  338. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-pipeline/block/ci-get-after-allowed-status.sh +0 -0
  339. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-pipeline/block/ci-get-branch-in-echo-chain.sh +0 -0
  340. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-pipeline/block/ci-get-branch-ref-sleep-prefix.sh +0 -0
  341. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-pipeline/block/ci-get-json-jq-sort-uniq.sh +0 -0
  342. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-pipeline/block/ci-get-then-ci-trace.sh +0 -0
  343. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-pipeline/block/ci-get-var-capture-poll-loop.sh +0 -0
  344. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-pipeline/block/ci-get-with-job-details-repo.sh +0 -0
  345. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-pipeline/block/ci-trace-job-name-with-pipeline.sh +0 -0
  346. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-pipeline/block/ci-trace-pipeline-id-before-job-name.sh +0 -0
  347. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-pipeline/block/ci-trace-redirect-to-file.sh +0 -0
  348. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-pipeline/block/ci-trace-repo-before-job-id.sh +0 -0
  349. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-pipeline/block/ci-view-help.sh +0 -0
  350. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-pipeline/block/ci-view-job-web-or-api.sh +0 -0
  351. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-pipeline/block/ci-view-pipeline-id.sh +0 -0
  352. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-pipeline/block/heredoc-then-pipe-to-blocked.sh +0 -0
  353. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-pipeline/block/wrapper-bash-c.sh +0 -0
  354. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-pipeline/block/wrapper-sudo.sh +0 -0
  355. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/fixtures/match/glab-pipeline/rules.yaml +0 -0
  356. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/test_classifier.py +0 -0
  357. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/test_cli.py +0 -0
  358. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/test_cli_serialization.py +0 -0
  359. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/test_match_fixtures.py +0 -0
  360. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/test_models.py +0 -0
  361. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/test_parser.py +0 -0
  362. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/test_rules.py +0 -0
  363. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/test_schema.py +0 -0
  364. {bash_classify-0.10.0 → bash_classify-0.11.0}/tests/test_smoke.py +0 -0
  365. {bash_classify-0.10.0 → bash_classify-0.11.0}/uv.lock +0 -0
@@ -8,7 +8,7 @@
8
8
  {
9
9
  "name": "bash-classify-hook",
10
10
  "description": "Auto-allows low-risk bash commands in Claude Code using bash-classify",
11
- "version": "0.10.0",
11
+ "version": "0.11.0",
12
12
  "author": {
13
13
  "name": "Filip Procházka",
14
14
  "url": "https://github.com/fprochazka"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: bash-classify
3
- Version: 0.10.0
3
+ Version: 0.11.0
4
4
  Summary: Classify bash commands as READONLY, WRITE, DANGEROUS, or UNKNOWN
5
5
  Project-URL: Homepage, https://github.com/fprochazka/bash-classify
6
6
  Project-URL: Repository, https://github.com/fprochazka/bash-classify
@@ -180,6 +180,7 @@ Risk defaults are derived from classification (`READONLY`→LOW, `LOCAL_EFFECTS`
180
180
  - **Tree-sitter parsing** -- bash expressions are parsed into an AST for accurate command extraction, handling pipes, subshells, and command substitution
181
181
  - **YAML command database** -- each command has classification rules with subcommand and option matching
182
182
  - **Subcommand matching** -- `kubectl get` and `kubectl delete` can have different classifications
183
+ - **Subcommand aliases** -- a subcommand can declare other names for itself, so `glab pipeline view` resolves to `glab ci view` and any rule written for the canonical name catches the aliased spelling
183
184
  - **Multi-goal build tools** -- `subcommand_mode: match_all` handles commands like `mvn clean install` and `gradle clean build test` where multiple goals can be combined in any order
184
185
  - **Delegation for wrappers** -- commands like `xargs`, `sudo`, and `env` delegate classification to the inner command
185
186
  - **File path detection** -- redirect operators (`>`, `>>`, `<`) are parsed into `write_paths`/`read_paths` in the output; writes to `/tmp` and `/var/tmp` stay at LOW risk
@@ -151,6 +151,7 @@ Risk defaults are derived from classification (`READONLY`→LOW, `LOCAL_EFFECTS`
151
151
  - **Tree-sitter parsing** -- bash expressions are parsed into an AST for accurate command extraction, handling pipes, subshells, and command substitution
152
152
  - **YAML command database** -- each command has classification rules with subcommand and option matching
153
153
  - **Subcommand matching** -- `kubectl get` and `kubectl delete` can have different classifications
154
+ - **Subcommand aliases** -- a subcommand can declare other names for itself, so `glab pipeline view` resolves to `glab ci view` and any rule written for the canonical name catches the aliased spelling
154
155
  - **Multi-goal build tools** -- `subcommand_mode: match_all` handles commands like `mvn clean install` and `gradle clean build test` where multiple goals can be combined in any order
155
156
  - **Delegation for wrappers** -- commands like `xargs`, `sudo`, and `env` delegate classification to the inner command
156
157
  - **File path detection** -- redirect operators (`>`, `>>`, `<`) are parsed into `write_paths`/`read_paths` in the output; writes to `/tmp` and `/var/tmp` stay at LOW risk
@@ -398,7 +398,7 @@ This is the core of the tool. Given an `argv` like `["kubectl", "--context=prod"
398
398
 
399
399
  1. **Look up the binary** (`kubectl`) in the database
400
400
  2. **Separate global options** from the rest of `argv`, using the database definition of which options are global and whether they take a value
401
- 3. **Match the subcommand chain** from the remaining tokens (e.g. `get` → resolve to `kubectl.get`)
401
+ 3. **Match the subcommand chain** from the remaining tokens (e.g. `get` → resolve to `kubectl.get`), resolving any declared subcommand alias to its canonical name
402
402
  4. **Classify remaining options** against the matched subcommand's option definitions
403
403
  5. **Determine the final classification** based on subcommand default + any option overrides
404
404
 
@@ -451,6 +451,14 @@ The default mode. Subcommands are matched as a greedy chain through nested subco
451
451
 
452
452
  Example: `kubectl rollout status` matches `kubectl` → `rollout` → `status`, walking the tree.
453
453
 
454
+ #### Subcommand aliases
455
+
456
+ A subcommand may declare `aliases`. Every alias is another name for the same definition, so matching a word against a subcommand dictionary tries the canonical names and the aliases alike. What is reported is always the canonical name: `glab pipeline view 1000` yields `command: ["glab", "ci", "view"]` and `matched_rule: "glab.ci.view"`, while `argv` keeps `["glab", "pipeline", "view", "1000"]` — the word that was actually typed. Classification, options and `strict` behaviour are the canonical subcommand's, since it is literally the same definition.
457
+
458
+ This matters to `match` mode: a rule written as `command: [glab, ci, view]` catches the aliased spelling too, without listing it.
459
+
460
+ Aliases live in one namespace with their siblings. An alias that names a sibling subcommand, or that two siblings both claim, is rejected when the database file is loaded — the error names the file and the alias.
461
+
454
462
  #### `match_all`
455
463
 
456
464
  Each positional argument is matched independently against the **same** top-level subcommand dictionary. This is designed for build tools like Maven and Gradle where multiple goals/tasks appear as positional arguments in any order.
@@ -952,6 +960,14 @@ When command-level delegation resolves at least one inner command (and no option
952
960
  | `subcommand_mode` | `enum` | How subcommands are matched: `hierarchical` (default, chained tree walk) or `match_all` (each positional arg matched independently) |
953
961
  | `delegates_to` | `object` | How this command delegates execution to an inner command (see delegation modes) |
954
962
 
963
+ #### Subcommand level
964
+
965
+ A subcommand entry takes the same fields as the command level, minus `command` and `global_options` — its name is the key it is filed under — plus:
966
+
967
+ | Field | Type | Description |
968
+ |---|---|---|
969
+ | `aliases` | `list[str]` | Alternative names for this subcommand (e.g. `pipe` and `pipeline` for `glab ci`). Each alias resolves to this same definition; the resolved `command` path and `matched_rule` name the canonical subcommand, while `argv` keeps the word that was typed. An alias that collides with a sibling subcommand, or that two siblings both claim, is a load-time error. |
970
+
955
971
  #### Option level
956
972
 
957
973
  | Field | Type | Description |
@@ -1129,7 +1145,7 @@ builtins, an unknown binary, an empty argv — carry `null` internally and seria
1129
1145
  - **Not a sandbox.** This is a classifier, not an enforcer. It does not execute or block anything. `match` mode is no exception: it reports which declared command shapes an expression invokes, and the caller decides what to do about it.
1130
1146
  - **No awk/sed/perl script analysis.** These are classified as a whole command; their embedded programs are opaque. They should simply not be in the READONLY allowlist.
1131
1147
  - **No variable resolution.** `$DIR`, `$(cmd)` in command position → UNKNOWN. We classify what we can see statically.
1132
- - **No alias/function resolution.** We classify the literal command name as written.
1148
+ - **No shell alias/function resolution.** We classify the literal command name as written.
1133
1149
 
1134
1150
  ## Future extensions
1135
1151
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bash-classify-hook",
3
- "version": "0.10.0",
3
+ "version": "0.11.0",
4
4
  "description": "Auto-allows low-risk bash commands in Claude Code using bash-classify",
5
5
  "author": {
6
6
  "name": "Filip Prochazka"
@@ -84,6 +84,25 @@ options: # options that affect classification
84
84
  | `subcommands` | map | -- | Nested subcommand definitions (recursive) |
85
85
  | `delegates_to` | object | -- | How the command hands off to an inner command |
86
86
 
87
+ #### Subcommand fields
88
+
89
+ A subcommand takes the same fields as the top level, minus `command`, `description` and `global_options`, plus:
90
+
91
+ | Field | Type | Description |
92
+ |-------|------|-------------|
93
+ | `aliases` | list | Alternative names for this subcommand (e.g. `pipe` and `pipeline` for `glab ci`) |
94
+
95
+ An alias is another name for the same definition, not a copy of it: `glab pipeline view` classifies exactly as `glab ci view` and is reported as `["glab", "ci", "view"]`, while `argv` keeps the word that was typed. Declare aliases only for names the tool itself accepts (`glab ci --help` lists `pipe` and `pipeline`), and never a name a sibling subcommand already uses — that is rejected when the file loads.
96
+
97
+ ```yaml
98
+ subcommands:
99
+ ci:
100
+ aliases: [pipe, pipeline]
101
+ subcommands:
102
+ list:
103
+ classification: READONLY
104
+ ```
105
+
87
106
  #### Option fields
88
107
 
89
108
  | Field | Type | Description |
@@ -174,6 +174,13 @@
174
174
  "type": "object",
175
175
  "additionalProperties": false,
176
176
  "properties": {
177
+ "aliases": {
178
+ "type": "array",
179
+ "items": {
180
+ "type": "string"
181
+ },
182
+ "description": "Alternative names for this subcommand (e.g. `pipe` and `pipeline` for `glab ci`). A match through an alias resolves to this definition and reports the canonical name in the command path. An alias must not collide with a sibling subcommand or with another sibling's alias."
183
+ },
177
184
  "classification": {
178
185
  "$ref": "#/$defs/Classification"
179
186
  },
@@ -251,6 +251,8 @@ subcommands:
251
251
 
252
252
  # ── CI/CD: Pipelines ──────────────────────────────────────────
253
253
  ci:
254
+ # `pipe` and `pipeline` are glab's own deprecated aliases for `ci`
255
+ aliases: [pipe, pipeline]
254
256
  subcommands:
255
257
  list:
256
258
  classification: READONLY
@@ -181,8 +181,19 @@ _ALIAS_FORBIDDEN_KEYS = frozenset(
181
181
  )
182
182
 
183
183
 
184
- def _parse_command_def(data: dict, command_name: str) -> CommandDef:
185
- """Parse a raw YAML dict into a CommandDef structure."""
184
+ def _parse_command_def(data: dict, command_name: str, is_subcommand: bool = False) -> CommandDef:
185
+ """Parse a raw YAML dict into a CommandDef structure.
186
+
187
+ `is_subcommand` distinguishes an entry under a `subcommands` map from the top level of
188
+ a command file. Only the former may carry `aliases`; a command file names itself by its
189
+ filename, so a second name for it is a separate `alias_of` file.
190
+ """
191
+ if "aliases" in data and not is_subcommand:
192
+ raise ValueError(
193
+ f"command '{command_name}': 'aliases' is only valid on subcommands; "
194
+ f"use an alias_of file for a command-level alias"
195
+ )
196
+
186
197
  alias_of = data.get("alias_of")
187
198
  if alias_of is not None:
188
199
  conflicting = sorted(_ALIAS_FORBIDDEN_KEYS & data.keys())
@@ -205,6 +216,7 @@ def _parse_command_def(data: dict, command_name: str) -> CommandDef:
205
216
 
206
217
  return CommandDef(
207
218
  command=command_name,
219
+ aliases=_parse_aliases(data.get("aliases"), command_name),
208
220
  classification=classification,
209
221
  risk=risk,
210
222
  global_options=global_options,
@@ -267,8 +279,23 @@ def _parse_options(raw: dict | None) -> dict[str, OptionDef]:
267
279
  return options
268
280
 
269
281
 
282
+ def _parse_aliases(raw: object, command_name: str) -> list[str]:
283
+ """Parse the `aliases` list of a subcommand definition."""
284
+ if raw is None:
285
+ return []
286
+ if not isinstance(raw, list):
287
+ raise ValueError(f"subcommand '{command_name}': 'aliases' must be a list of strings")
288
+ return [_yaml_str(alias) for alias in raw]
289
+
290
+
270
291
  def _parse_subcommands(raw: dict | None) -> dict[str, CommandDef]:
271
- """Parse a subcommands map recursively."""
292
+ """Parse a subcommands map recursively.
293
+
294
+ Each alias is registered in the same map as the definition it belongs to, pointing at
295
+ that very object, so `_match_subcommand` finds it by the typed word and still reports
296
+ the canonical name. An alias that would shadow a real sibling subcommand, or that two
297
+ siblings claim, is rejected here rather than silently resolving to one of them.
298
+ """
272
299
  if not raw:
273
300
  return {}
274
301
 
@@ -277,7 +304,26 @@ def _parse_subcommands(raw: dict | None) -> dict[str, CommandDef]:
277
304
  for name, props in raw.items():
278
305
  if props is None:
279
306
  props = {}
280
- subcommands[name] = _parse_command_def(props, name)
307
+ canonical = _yaml_str(name)
308
+ subcommands[canonical] = _parse_command_def(props, canonical, is_subcommand=True)
309
+
310
+ aliased_by: dict[str, str] = {}
311
+ for canonical, definition in list(subcommands.items()):
312
+ for alias in definition.aliases:
313
+ if alias in subcommands:
314
+ raise ValueError(
315
+ f"subcommand '{canonical}' declares alias '{alias}', "
316
+ f"which is already a subcommand of the same parent"
317
+ )
318
+ if alias in aliased_by:
319
+ raise ValueError(
320
+ f"subcommand '{canonical}' declares alias '{alias}', "
321
+ f"which is already an alias of subcommand '{aliased_by[alias]}'"
322
+ )
323
+ aliased_by[alias] = canonical
324
+
325
+ for alias, canonical in aliased_by.items():
326
+ subcommands[alias] = subcommands[canonical]
281
327
 
282
328
  return subcommands
283
329
 
@@ -452,6 +452,10 @@ def _match_subcommand(
452
452
  ) -> tuple[CommandDef, list[str], list[str]]:
453
453
  """Match subcommand chain from argv.
454
454
 
455
+ The chain holds canonical subcommand names: a word typed as a declared alias
456
+ (`glab pipeline view` for `glab ci view`) resolves to the definition it names, and it
457
+ is that definition's own name that lands in the chain. `argv` keeps the typed word.
458
+
455
459
  Returns (matched_def, command_chain, remaining_argv).
456
460
  """
457
461
  matched_def = command_def
@@ -466,8 +470,8 @@ def _match_subcommand(
466
470
  break
467
471
 
468
472
  if token in matched_def.subcommands:
469
- command_chain.append(token)
470
473
  matched_def = matched_def.subcommands[token]
474
+ command_chain.append(matched_def.command)
471
475
  remaining = remaining[1:]
472
476
  else:
473
477
  break
@@ -496,8 +500,9 @@ def _match_all_subcommands(
496
500
  # Options pass through for _classify_options later
497
501
  remaining.append(token)
498
502
  elif token in command_def.subcommands:
499
- matched_names.append(token)
500
- matched_defs.append(command_def.subcommands[token])
503
+ matched = command_def.subcommands[token]
504
+ matched_names.append(matched.command)
505
+ matched_defs.append(matched)
501
506
  else:
502
507
  # Unrecognized goal — pass through as positional
503
508
  remaining.append(token)
@@ -134,10 +134,17 @@ class OptionDef:
134
134
 
135
135
  @dataclass
136
136
  class CommandDef:
137
- """Definition of a command (or subcommand) from the database."""
137
+ """Definition of a command (or subcommand) from the database.
138
+
139
+ ``command`` is the canonical name. A subcommand may additionally be reachable under
140
+ the names in ``aliases``: the parent's ``subcommands`` map holds the very same
141
+ definition object under each of them, so a matched alias still reports the canonical
142
+ name in the resolved command path.
143
+ """
138
144
 
139
145
  command: str
140
146
  alias_of: str | None = None
147
+ aliases: list[str] = field(default_factory=list)
141
148
  classification: Classification | None = None
142
149
  risk: Risk | None = None
143
150
  global_options: dict[str, OptionDef] = field(default_factory=dict)
@@ -0,0 +1 @@
1
+ glab pipe get -p 1000 --with-job-details -F json 2>/dev/null | jq -r '.jobs[] | "\(.status)\t\(.name)"'
@@ -0,0 +1 @@
1
+ glab pipeline view 1000 2>&1 | head -50
@@ -238,6 +238,73 @@ class TestAliasOf:
238
238
  assert db["myalias"].alias_of == "target"
239
239
 
240
240
 
241
+ class TestSubcommandAliases:
242
+ def test_alias_maps_to_the_same_definition_object(self, database: dict[str, CommandDef]) -> None:
243
+ """`glab pipe` and `glab pipeline` are glab's own deprecated names for `glab ci`."""
244
+ glab = database["glab"]
245
+ ci = glab.subcommands["ci"]
246
+ assert glab.subcommands["pipe"] is ci
247
+ assert glab.subcommands["pipeline"] is ci
248
+
249
+ def test_alias_keeps_the_canonical_name(self, database: dict[str, CommandDef]) -> None:
250
+ glab = database["glab"]
251
+ assert glab.subcommands["pipeline"].command == "ci"
252
+ assert glab.subcommands["ci"].aliases == ["pipe", "pipeline"]
253
+
254
+ def test_subcommand_without_aliases_has_an_empty_list(self, database: dict[str, CommandDef]) -> None:
255
+ assert database["glab"].subcommands["mr"].aliases == []
256
+
257
+ def test_nested_alias_is_registered(self, tmp_path: Path) -> None:
258
+ """Aliases work at any depth, not only on the first level."""
259
+ (tmp_path / "tool.yaml").write_text(
260
+ "command: tool\n"
261
+ "subcommands:\n"
262
+ " remote:\n"
263
+ " subcommands:\n"
264
+ " list:\n"
265
+ " aliases: [ls]\n"
266
+ " classification: READONLY\n"
267
+ )
268
+ db = load_database(tmp_path)
269
+ remote = db["tool"].subcommands["remote"]
270
+ assert remote.subcommands["ls"] is remote.subcommands["list"]
271
+
272
+ def test_alias_colliding_with_a_sibling_subcommand_is_rejected(self, tmp_path: Path) -> None:
273
+ (tmp_path / "tool.yaml").write_text(
274
+ "command: tool\nsubcommands:\n ci:\n aliases: [status]\n status:\n classification: READONLY\n"
275
+ )
276
+ db = load_database(tmp_path)
277
+ with pytest.raises(ValueError, match="tool.yaml.*alias 'status'.*already a subcommand"):
278
+ db["tool"]
279
+
280
+ def test_alias_claimed_by_two_siblings_is_rejected(self, tmp_path: Path) -> None:
281
+ (tmp_path / "tool.yaml").write_text(
282
+ "command: tool\nsubcommands:\n ci:\n aliases: [p]\n publish:\n aliases: [p]\n"
283
+ )
284
+ db = load_database(tmp_path)
285
+ with pytest.raises(ValueError, match="tool.yaml.*alias 'p'.*already an alias of subcommand 'ci'"):
286
+ db["tool"]
287
+
288
+ def test_alias_repeating_its_own_subcommand_name_is_rejected(self, tmp_path: Path) -> None:
289
+ (tmp_path / "tool.yaml").write_text("command: tool\nsubcommands:\n ci:\n aliases: [ci]\n")
290
+ db = load_database(tmp_path)
291
+ with pytest.raises(ValueError, match="tool.yaml.*alias 'ci'.*already a subcommand"):
292
+ db["tool"]
293
+
294
+ def test_top_level_aliases_key_is_rejected(self, tmp_path: Path) -> None:
295
+ """A command file names itself by its filename; a second name is an alias_of file."""
296
+ (tmp_path / "tool.yaml").write_text("command: tool\naliases: [t]\nclassification: READONLY\n")
297
+ db = load_database(tmp_path)
298
+ with pytest.raises(ValueError, match="tool.yaml.*'aliases' is only valid on subcommands"):
299
+ db["tool"]
300
+
301
+ def test_aliases_must_be_a_list(self, tmp_path: Path) -> None:
302
+ (tmp_path / "tool.yaml").write_text("command: tool\nsubcommands:\n ci:\n aliases: pipe\n")
303
+ db = load_database(tmp_path)
304
+ with pytest.raises(ValueError, match="tool.yaml.*'aliases' must be a list"):
305
+ db["tool"]
306
+
307
+
241
308
  class TestUserCommandsDir:
242
309
  def test_user_override_replaces_builtin(self, tmp_path: Path) -> None:
243
310
  """User YAML overrides built-in command definition."""
@@ -837,6 +837,66 @@ class TestRiskOptionOverrides:
837
837
  assert result.risk == Risk.HIGH
838
838
 
839
839
 
840
+ class TestSubcommandAliases:
841
+ """A subcommand reached through an alias reports the canonical name.
842
+
843
+ `command` and `matched_rule` are what a rule or an allowlist is written against, so
844
+ they must name the subcommand the database defines. `argv` is what the user typed and
845
+ stays untouched.
846
+ """
847
+
848
+ def test_alias_resolves_to_the_canonical_command_path(self, database: dict[str, CommandDef]) -> None:
849
+ result = match_command(_make_invocation(["glab", "pipeline", "view", "1000"]), database)
850
+ assert result.command == ["glab", "ci", "view"]
851
+ assert result.argv == ["glab", "pipeline", "view", "1000"]
852
+ assert result.matched_rule == "glab.ci.view"
853
+
854
+ def test_alias_classifies_exactly_as_the_canonical_subcommand(self, database: dict[str, CommandDef]) -> None:
855
+ typed = match_command(_make_invocation(["glab", "pipe", "get", "-p", "1000", "-F", "json"]), database)
856
+ canonical = match_command(_make_invocation(["glab", "ci", "get", "-p", "1000", "-F", "json"]), database)
857
+ assert typed.command == canonical.command
858
+ assert typed.classification == canonical.classification
859
+ assert typed.risk == canonical.risk
860
+ assert typed.options == canonical.options
861
+ assert typed.positionals == canonical.positionals
862
+
863
+ def test_alias_only_shadows_at_its_own_level(self, database: dict[str, CommandDef]) -> None:
864
+ """`pipeline` is an alias of `glab ci`, not a word that means `ci` everywhere."""
865
+ result = match_command(_make_invocation(["glab", "mr", "pipeline"]), database)
866
+ assert result.command == ["glab", "mr"]
867
+
868
+ def test_nested_alias_reports_the_canonical_path(self) -> None:
869
+ inner = CommandDef(command="list", aliases=["ls"], classification=Classification.READONLY)
870
+ outer = CommandDef(command="remote", subcommands={"list": inner, "ls": inner})
871
+ database = {"tool": CommandDef(command="tool", subcommands={"remote": outer})}
872
+ result = match_command(_make_invocation(["tool", "remote", "ls", "origin"]), database)
873
+ assert result.command == ["tool", "remote", "list"]
874
+ assert result.argv == ["tool", "remote", "ls", "origin"]
875
+ assert result.matched_rule == "tool.remote.list"
876
+ assert result.classification == Classification.READONLY
877
+
878
+ def test_alias_inherits_strict_mode(self) -> None:
879
+ """The alias is the same definition, so an unknown option is UNKNOWN either way."""
880
+ sub = CommandDef(command="build", aliases=["b"], classification=Classification.LOCAL_EFFECTS, strict=True)
881
+ database = {"tool": CommandDef(command="tool", subcommands={"build": sub, "b": sub})}
882
+ result = match_command(_make_invocation(["tool", "b", "--no-such-flag"]), database)
883
+ assert result.command == ["tool", "build"]
884
+ assert result.classification == Classification.UNKNOWN
885
+
886
+ def test_alias_in_match_all_mode_reports_the_canonical_goal(self) -> None:
887
+ clean = CommandDef(command="clean", aliases=["clobber"], classification=Classification.LOCAL_EFFECTS)
888
+ database = {
889
+ "build-tool": CommandDef(
890
+ command="build-tool",
891
+ subcommand_mode=SubcommandMode.MATCH_ALL,
892
+ subcommands={"clean": clean, "clobber": clean},
893
+ )
894
+ }
895
+ result = match_command(_make_invocation(["build-tool", "clobber"]), database)
896
+ assert result.command == ["build-tool", "clean"]
897
+ assert result.classification == Classification.LOCAL_EFFECTS
898
+
899
+
840
900
  class TestRiskExplicitOverrideInCommandDef:
841
901
  def test_explicit_risk_in_command_def(self) -> None:
842
902
  """A command with explicit risk: LOW should use that risk."""
@@ -599,6 +599,32 @@ class TestGlabOptions:
599
599
  assert command.options == ["--per-page"]
600
600
  assert command.positionals == []
601
601
 
602
+ def test_ci_pipeline_alias(self, database):
603
+ """`glab pipeline` is glab's own deprecated spelling of `glab ci`."""
604
+ result = classify_expression("glab pipeline view 1000 --web", database)
605
+ assert result.classification == Classification.READONLY
606
+ command = result.commands[0]
607
+ assert command.command == ["glab", "ci", "view"]
608
+ assert command.argv == ["glab", "pipeline", "view", "1000", "--web"]
609
+ assert command.matched_rule == "glab.ci.view"
610
+ assert command.options == ["--web"]
611
+ assert command.positionals == ["1000"]
612
+
613
+ def test_ci_pipe_alias(self, database):
614
+ result = classify_expression("glab pipe get -p 1000 --with-job-details -F json", database)
615
+ assert result.classification == Classification.READONLY
616
+ command = result.commands[0]
617
+ assert command.command == ["glab", "ci", "get"]
618
+ assert command.argv[1] == "pipe"
619
+ assert command.options == ["-p", "--with-job-details", "-F"]
620
+
621
+ def test_ci_alias_inside_a_wrapper(self, database):
622
+ """The alias resolves the same way in a delegated inner command."""
623
+ result = classify_expression("timeout 60 glab pipeline trace deploy-test -p 1000", database)
624
+ inner = result.commands[0].inner_commands[0]
625
+ assert inner.command == ["glab", "ci", "trace"]
626
+ assert inner.argv == ["glab", "pipeline", "trace", "deploy-test", "-p", "1000"]
627
+
602
628
  def test_glab_classifications_unchanged(self, database):
603
629
  """Declaring options must not move any classification or risk."""
604
630
  assert classify_expression("glab mr view 42", database).classification == Classification.READONLY
File without changes
File without changes
File without changes