bash-classify 0.2.4__tar.gz → 0.4.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 (195) hide show
  1. {bash_classify-0.2.4 → bash_classify-0.4.0}/.claude-plugin/marketplace.json +3 -3
  2. {bash_classify-0.2.4 → bash_classify-0.4.0}/PKG-INFO +19 -6
  3. {bash_classify-0.2.4 → bash_classify-0.4.0}/README.md +18 -5
  4. {bash_classify-0.2.4 → bash_classify-0.4.0}/SPEC.md +91 -0
  5. {bash_classify-0.2.4 → bash_classify-0.4.0}/coding-agent-plugins/claude-code/.claude-plugin/plugin.json +2 -2
  6. {bash_classify-0.2.4 → bash_classify-0.4.0}/coding-agent-plugins/claude-code/hooks/classify-bash.sh +5 -5
  7. {bash_classify-0.2.4 → bash_classify-0.4.0}/docs/classification-guidance.md +86 -1
  8. {bash_classify-0.2.4 → bash_classify-0.4.0}/schemas/command.schema.json +28 -0
  9. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/__init__.py +2 -1
  10. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/classifier.py +14 -1
  11. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/cli.py +3 -0
  12. bash_classify-0.4.0/src/bash_classify/commands/agent-browser.yaml +9 -0
  13. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/black.yaml +1 -0
  14. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/brew.yaml +4 -0
  15. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/cargo.yaml +14 -0
  16. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/docker.yaml +4 -0
  17. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/git.yaml +28 -10
  18. bash_classify-0.4.0/src/bash_classify/commands/glab-discussion.yaml +21 -0
  19. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/go.yaml +4 -0
  20. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/gradle.yaml +34 -0
  21. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/gradlew.yaml +34 -0
  22. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/gunzip.yaml +1 -0
  23. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/isort.yaml +1 -0
  24. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/mkdir.yaml +1 -0
  25. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/mvn.yaml +30 -0
  26. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/mvnw.yaml +30 -0
  27. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/npm.yaml +4 -0
  28. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/pip.yaml +2 -0
  29. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/pip3.yaml +2 -0
  30. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/pipx.yaml +2 -0
  31. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/poetry.yaml +9 -0
  32. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/ruff.yaml +4 -2
  33. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/tar.yaml +1 -0
  34. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/touch.yaml +1 -0
  35. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/tsc.yaml +1 -0
  36. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/unzip.yaml +1 -0
  37. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/uv.yaml +18 -0
  38. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/yarn.yaml +3 -0
  39. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/zip.yaml +1 -0
  40. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/database.py +20 -1
  41. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/matcher.py +164 -9
  42. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/models.py +42 -0
  43. {bash_classify-0.2.4 → bash_classify-0.4.0}/tests/test_classifier.py +76 -1
  44. {bash_classify-0.2.4 → bash_classify-0.4.0}/tests/test_cli_serialization.py +77 -0
  45. {bash_classify-0.2.4 → bash_classify-0.4.0}/tests/test_matcher.py +202 -1
  46. {bash_classify-0.2.4 → bash_classify-0.4.0}/tests/test_models.py +31 -1
  47. {bash_classify-0.2.4 → bash_classify-0.4.0}/tests/test_real_world.py +32 -0
  48. {bash_classify-0.2.4 → bash_classify-0.4.0}/.claude/rules/command-database.md +0 -0
  49. {bash_classify-0.2.4 → bash_classify-0.4.0}/.editorconfig +0 -0
  50. {bash_classify-0.2.4 → bash_classify-0.4.0}/.github/workflows/ci.yml +0 -0
  51. {bash_classify-0.2.4 → bash_classify-0.4.0}/.github/workflows/publish.yml +0 -0
  52. {bash_classify-0.2.4 → bash_classify-0.4.0}/.gitignore +0 -0
  53. {bash_classify-0.2.4 → bash_classify-0.4.0}/.python-version +0 -0
  54. {bash_classify-0.2.4 → bash_classify-0.4.0}/CLAUDE.md +0 -0
  55. {bash_classify-0.2.4 → bash_classify-0.4.0}/LICENSE +0 -0
  56. {bash_classify-0.2.4 → bash_classify-0.4.0}/coding-agent-plugins/claude-code/hooks/hooks.json +0 -0
  57. {bash_classify-0.2.4 → bash_classify-0.4.0}/pyproject.toml +0 -0
  58. {bash_classify-0.2.4 → bash_classify-0.4.0}/ruff.toml +0 -0
  59. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/__main__.py +0 -0
  60. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/apt.yaml +0 -0
  61. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/awk.yaml +0 -0
  62. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/basename.yaml +0 -0
  63. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/bash-classify.yaml +0 -0
  64. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/bash.yaml +0 -0
  65. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/builtin.yaml +0 -0
  66. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/cat.yaml +0 -0
  67. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/chgrp.yaml +0 -0
  68. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/chmod.yaml +0 -0
  69. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/chown.yaml +0 -0
  70. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/column.yaml +0 -0
  71. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/comm.yaml +0 -0
  72. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/command.yaml +0 -0
  73. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/cp.yaml +0 -0
  74. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/curl.yaml +0 -0
  75. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/cut.yaml +0 -0
  76. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/date.yaml +0 -0
  77. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/df.yaml +0 -0
  78. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/diff.yaml +0 -0
  79. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/dig.yaml +0 -0
  80. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/dirname.yaml +0 -0
  81. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/du.yaml +0 -0
  82. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/echo.yaml +0 -0
  83. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/env.yaml +0 -0
  84. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/eslint.yaml +0 -0
  85. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/expand.yaml +0 -0
  86. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/false.yaml +0 -0
  87. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/file.yaml +0 -0
  88. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/find.yaml +0 -0
  89. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/fmt.yaml +0 -0
  90. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/fold.yaml +0 -0
  91. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/free.yaml +0 -0
  92. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/gawk.yaml +0 -0
  93. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/gcloud.yaml +0 -0
  94. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/gh.yaml +0 -0
  95. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/glab.yaml +0 -0
  96. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/grep.yaml +0 -0
  97. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/groups.yaml +0 -0
  98. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/gzip.yaml +0 -0
  99. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/hadolint.yaml +0 -0
  100. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/head.yaml +0 -0
  101. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/helm.yaml +0 -0
  102. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/hostname.yaml +0 -0
  103. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/id.yaml +0 -0
  104. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/install.yaml +0 -0
  105. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/java.yaml +0 -0
  106. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/javac.yaml +0 -0
  107. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/join.yaml +0 -0
  108. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/journalctl.yaml +0 -0
  109. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/jq.yaml +0 -0
  110. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/kill.yaml +0 -0
  111. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/killall.yaml +0 -0
  112. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/kubectl.yaml +0 -0
  113. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/less.yaml +0 -0
  114. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/ln.yaml +0 -0
  115. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/ls.yaml +0 -0
  116. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/make.yaml +0 -0
  117. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/md5sum.yaml +0 -0
  118. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/more.yaml +0 -0
  119. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/mv.yaml +0 -0
  120. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/mypy.yaml +0 -0
  121. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/netstat.yaml +0 -0
  122. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/nice.yaml +0 -0
  123. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/nl.yaml +0 -0
  124. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/node.yaml +0 -0
  125. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/nohup.yaml +0 -0
  126. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/npx.yaml +0 -0
  127. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/nslookup.yaml +0 -0
  128. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/paste.yaml +0 -0
  129. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/ping.yaml +0 -0
  130. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/pkill.yaml +0 -0
  131. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/prettier.yaml +0 -0
  132. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/printenv.yaml +0 -0
  133. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/printf.yaml +0 -0
  134. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/ps.yaml +0 -0
  135. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/pwd.yaml +0 -0
  136. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/pytest.yaml +0 -0
  137. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/python.yaml +0 -0
  138. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/python3.yaml +0 -0
  139. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/read.yaml +0 -0
  140. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/readlink.yaml +0 -0
  141. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/realpath.yaml +0 -0
  142. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/rev.yaml +0 -0
  143. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/rg.yaml +0 -0
  144. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/rm.yaml +0 -0
  145. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/rsync.yaml +0 -0
  146. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/scp.yaml +0 -0
  147. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/sed.yaml +0 -0
  148. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/seq.yaml +0 -0
  149. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/sh.yaml +0 -0
  150. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/sha1sum.yaml +0 -0
  151. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/sha256sum.yaml +0 -0
  152. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/shellcheck.yaml +0 -0
  153. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/sort.yaml +0 -0
  154. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/ss.yaml +0 -0
  155. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/ssh.yaml +0 -0
  156. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/stat.yaml +0 -0
  157. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/strace.yaml +0 -0
  158. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/sudo.yaml +0 -0
  159. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/systemctl.yaml +0 -0
  160. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/tac.yaml +0 -0
  161. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/tail.yaml +0 -0
  162. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/tee.yaml +0 -0
  163. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/terraform.yaml +0 -0
  164. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/test.yaml +0 -0
  165. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/time.yaml +0 -0
  166. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/timeout.yaml +0 -0
  167. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/top.yaml +0 -0
  168. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/tr.yaml +0 -0
  169. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/traceroute.yaml +0 -0
  170. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/true.yaml +0 -0
  171. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/type.yaml +0 -0
  172. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/uname.yaml +0 -0
  173. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/unexpand.yaml +0 -0
  174. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/uniq.yaml +0 -0
  175. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/uptime.yaml +0 -0
  176. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/wc.yaml +0 -0
  177. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/wget.yaml +0 -0
  178. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/whereis.yaml +0 -0
  179. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/which.yaml +0 -0
  180. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/whoami.yaml +0 -0
  181. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/xargs.yaml +0 -0
  182. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/xmllint.yaml +0 -0
  183. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/yes.yaml +0 -0
  184. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/yq.yaml +0 -0
  185. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/commands/zsh.yaml +0 -0
  186. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/parser.py +0 -0
  187. {bash_classify-0.2.4 → bash_classify-0.4.0}/src/bash_classify/py.typed +0 -0
  188. {bash_classify-0.2.4 → bash_classify-0.4.0}/tests/__init__.py +0 -0
  189. {bash_classify-0.2.4 → bash_classify-0.4.0}/tests/conftest.py +0 -0
  190. {bash_classify-0.2.4 → bash_classify-0.4.0}/tests/test_cli.py +0 -0
  191. {bash_classify-0.2.4 → bash_classify-0.4.0}/tests/test_database.py +0 -0
  192. {bash_classify-0.2.4 → bash_classify-0.4.0}/tests/test_parser.py +0 -0
  193. {bash_classify-0.2.4 → bash_classify-0.4.0}/tests/test_schema.py +0 -0
  194. {bash_classify-0.2.4 → bash_classify-0.4.0}/tests/test_smoke.py +0 -0
  195. {bash_classify-0.2.4 → bash_classify-0.4.0}/uv.lock +0 -0
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "name": "fprochazka-bash-classify",
3
- "description": "Bash command classifier with Claude Code plugin for auto-allowing readonly commands",
3
+ "description": "Bash command classifier with Claude Code plugin for auto-allowing low-risk commands",
4
4
  "owner": {
5
5
  "name": "Filip Procházka"
6
6
  },
7
7
  "plugins": [
8
8
  {
9
9
  "name": "bash-classify-hook",
10
- "description": "Auto-allows readonly bash commands in Claude Code using bash-classify",
11
- "version": "0.2.4",
10
+ "description": "Auto-allows low-risk bash commands in Claude Code using bash-classify",
11
+ "version": "0.4.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.4
2
2
  Name: bash-classify
3
- Version: 0.2.4
3
+ Version: 0.4.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
@@ -33,9 +33,9 @@ Classify bash commands by their side-effect risk level.
33
33
 
34
34
  ## What it does
35
35
 
36
- bash-classify parses bash expressions using tree-sitter, classifies each command against a database of 120+ known commands, and outputs a structured JSON verdict. Commands are classified into five levels: `READONLY`, `LOCAL_EFFECTS`, `EXTERNAL_EFFECTS`, `DANGEROUS`, and `UNKNOWN`.
36
+ bash-classify parses bash expressions using tree-sitter, classifies each command against a database of 120+ known commands, and outputs a structured JSON verdict. Commands are classified along two axes: **classification** (`READONLY`, `LOCAL_EFFECTS`, `EXTERNAL_EFFECTS`, `DANGEROUS`, `UNKNOWN`) describing what kind of effects a command has, and **risk** (`LOW`, `MEDIUM`, `HIGH`) describing how worried you should be.
37
37
 
38
- Designed primarily as a [Claude Code](https://docs.anthropic.com/en/docs/claude-code) hook to automatically allow safe, read-only commands while flagging risky ones for human review.
38
+ Designed primarily as a [Claude Code](https://docs.anthropic.com/en/docs/claude-code) hook to automatically allow low-risk commands while flagging risky ones for human review.
39
39
 
40
40
  ## Installation
41
41
 
@@ -63,7 +63,7 @@ $ echo 'find . -name "*.pyc" -delete' | bash-classify | jq '.classification'
63
63
 
64
64
  ## Claude Code plugin
65
65
 
66
- The repo includes a Claude Code plugin that auto-allows readonly bash commands via a `PreToolUse` hook.
66
+ The repo includes a Claude Code plugin that auto-allows low-risk bash commands via a `PreToolUse` hook.
67
67
 
68
68
  ```bash
69
69
  # Install the bash-classify CLI
@@ -82,7 +82,7 @@ claude plugin marketplace update fprochazka-bash-classify
82
82
  claude plugin update bash-classify-hook@fprochazka-bash-classify
83
83
  ```
84
84
 
85
- Once installed, any Bash tool call classified as `READONLY` is auto-approved — no permission prompt. Everything else (LOCAL_EFFECTS, EXTERNAL_EFFECTS, DANGEROUS, UNKNOWN) still requires confirmation.
85
+ Once installed, any Bash tool call with `risk: LOW` is auto-approved — no permission prompt. This includes all `READONLY` commands plus safe routine operations like `git add`, `git commit`, `mkdir`, package installs, code formatters, and more. Commands with `MEDIUM` or `HIGH` risk still require confirmation.
86
86
 
87
87
  ## Command database
88
88
 
@@ -95,12 +95,24 @@ The classification database includes 120+ command definitions covering common Un
95
95
 
96
96
  | Level | Description | Examples |
97
97
  |---|---|---|
98
- | `READONLY` | No side effects, auto-approved | `ls`, `cat`, `grep`, `kubectl get` |
98
+ | `READONLY` | No side effects | `ls`, `cat`, `grep`, `kubectl get` |
99
99
  | `LOCAL_EFFECTS` | Modifies local files or state only | `git add`, `git commit`, `cp`, `mkdir`, `pytest` |
100
100
  | `EXTERNAL_EFFECTS` | Interacts with external systems | `git push`, `kubectl apply`, `curl -d` |
101
101
  | `DANGEROUS` | Destructive, system-wide, or irreversible | `rm -rf`, `git push --force`, `chmod` |
102
102
  | `UNKNOWN` | Command not in database | Any unrecognized command |
103
103
 
104
+ ## Risk levels
105
+
106
+ Each command also gets a **risk** rating, orthogonal to classification:
107
+
108
+ | Risk | Description | Examples |
109
+ |---|---|---|
110
+ | `LOW` | Safe, routine operation — auto-approved | `ls`, `git add`, `git commit`, `mkdir`, `ruff format` |
111
+ | `MEDIUM` | Normal caution warranted | `git push`, `cp`, `npm run`, `git rebase` |
112
+ | `HIGH` | Dangerous or unknown — always requires confirmation | `rm -rf`, `git push --force`, unknown commands |
113
+
114
+ Risk defaults are derived from classification (`READONLY`→LOW, `LOCAL_EFFECTS`→MEDIUM, `EXTERNAL_EFFECTS`→MEDIUM, `DANGEROUS`/`UNKNOWN`→HIGH) but can be overridden per command, subcommand, or option in the YAML database.
115
+
104
116
  ## How it works
105
117
 
106
118
  - **Tree-sitter parsing** -- bash expressions are parsed into an AST for accurate command extraction, handling pipes, subshells, and command substitution
@@ -115,6 +127,7 @@ from bash_classify import classify_expression
115
127
 
116
128
  result = classify_expression("kubectl get pods")
117
129
  print(result.classification) # Classification.READONLY
130
+ print(result.risk) # Risk.LOW
118
131
  ```
119
132
 
120
133
  See [SPEC.md](SPEC.md) for the full specification.
@@ -4,9 +4,9 @@ Classify bash commands by their side-effect risk level.
4
4
 
5
5
  ## What it does
6
6
 
7
- bash-classify parses bash expressions using tree-sitter, classifies each command against a database of 120+ known commands, and outputs a structured JSON verdict. Commands are classified into five levels: `READONLY`, `LOCAL_EFFECTS`, `EXTERNAL_EFFECTS`, `DANGEROUS`, and `UNKNOWN`.
7
+ bash-classify parses bash expressions using tree-sitter, classifies each command against a database of 120+ known commands, and outputs a structured JSON verdict. Commands are classified along two axes: **classification** (`READONLY`, `LOCAL_EFFECTS`, `EXTERNAL_EFFECTS`, `DANGEROUS`, `UNKNOWN`) describing what kind of effects a command has, and **risk** (`LOW`, `MEDIUM`, `HIGH`) describing how worried you should be.
8
8
 
9
- Designed primarily as a [Claude Code](https://docs.anthropic.com/en/docs/claude-code) hook to automatically allow safe, read-only commands while flagging risky ones for human review.
9
+ Designed primarily as a [Claude Code](https://docs.anthropic.com/en/docs/claude-code) hook to automatically allow low-risk commands while flagging risky ones for human review.
10
10
 
11
11
  ## Installation
12
12
 
@@ -34,7 +34,7 @@ $ echo 'find . -name "*.pyc" -delete' | bash-classify | jq '.classification'
34
34
 
35
35
  ## Claude Code plugin
36
36
 
37
- The repo includes a Claude Code plugin that auto-allows readonly bash commands via a `PreToolUse` hook.
37
+ The repo includes a Claude Code plugin that auto-allows low-risk bash commands via a `PreToolUse` hook.
38
38
 
39
39
  ```bash
40
40
  # Install the bash-classify CLI
@@ -53,7 +53,7 @@ claude plugin marketplace update fprochazka-bash-classify
53
53
  claude plugin update bash-classify-hook@fprochazka-bash-classify
54
54
  ```
55
55
 
56
- Once installed, any Bash tool call classified as `READONLY` is auto-approved — no permission prompt. Everything else (LOCAL_EFFECTS, EXTERNAL_EFFECTS, DANGEROUS, UNKNOWN) still requires confirmation.
56
+ Once installed, any Bash tool call with `risk: LOW` is auto-approved — no permission prompt. This includes all `READONLY` commands plus safe routine operations like `git add`, `git commit`, `mkdir`, package installs, code formatters, and more. Commands with `MEDIUM` or `HIGH` risk still require confirmation.
57
57
 
58
58
  ## Command database
59
59
 
@@ -66,12 +66,24 @@ The classification database includes 120+ command definitions covering common Un
66
66
 
67
67
  | Level | Description | Examples |
68
68
  |---|---|---|
69
- | `READONLY` | No side effects, auto-approved | `ls`, `cat`, `grep`, `kubectl get` |
69
+ | `READONLY` | No side effects | `ls`, `cat`, `grep`, `kubectl get` |
70
70
  | `LOCAL_EFFECTS` | Modifies local files or state only | `git add`, `git commit`, `cp`, `mkdir`, `pytest` |
71
71
  | `EXTERNAL_EFFECTS` | Interacts with external systems | `git push`, `kubectl apply`, `curl -d` |
72
72
  | `DANGEROUS` | Destructive, system-wide, or irreversible | `rm -rf`, `git push --force`, `chmod` |
73
73
  | `UNKNOWN` | Command not in database | Any unrecognized command |
74
74
 
75
+ ## Risk levels
76
+
77
+ Each command also gets a **risk** rating, orthogonal to classification:
78
+
79
+ | Risk | Description | Examples |
80
+ |---|---|---|
81
+ | `LOW` | Safe, routine operation — auto-approved | `ls`, `git add`, `git commit`, `mkdir`, `ruff format` |
82
+ | `MEDIUM` | Normal caution warranted | `git push`, `cp`, `npm run`, `git rebase` |
83
+ | `HIGH` | Dangerous or unknown — always requires confirmation | `rm -rf`, `git push --force`, unknown commands |
84
+
85
+ Risk defaults are derived from classification (`READONLY`→LOW, `LOCAL_EFFECTS`→MEDIUM, `EXTERNAL_EFFECTS`→MEDIUM, `DANGEROUS`/`UNKNOWN`→HIGH) but can be overridden per command, subcommand, or option in the YAML database.
86
+
75
87
  ## How it works
76
88
 
77
89
  - **Tree-sitter parsing** -- bash expressions are parsed into an AST for accurate command extraction, handling pipes, subshells, and command substitution
@@ -86,6 +98,7 @@ from bash_classify import classify_expression
86
98
 
87
99
  result = classify_expression("kubectl get pods")
88
100
  print(result.classification) # Classification.READONLY
101
+ print(result.risk) # Risk.LOW
89
102
  ```
90
103
 
91
104
  See [SPEC.md](SPEC.md) for the full specification.
@@ -200,6 +200,54 @@ $ echo 'sh -c "ls /tmp | grep log"' | bash-classify
200
200
  | `DANGEROUS` | Destructive, hard to reverse, or affects critical systems |
201
201
  | `UNKNOWN` | Command or subcommand not in database |
202
202
 
203
+ ### Risk levels
204
+
205
+ Each command also receives a risk level, used to distinguish routine operations from high-stakes ones within the same classification tier.
206
+
207
+ | Level | Meaning |
208
+ |---|---|
209
+ | `LOW` | Safe routine operation, suitable for auto-allowing |
210
+ | `MEDIUM` | Standard operation, may deserve review |
211
+ | `HIGH` | High-stakes or irreversible, always requires confirmation |
212
+
213
+ #### Default risk by classification
214
+
215
+ | Classification | Default risk |
216
+ |---|---|
217
+ | `READONLY` | `LOW` |
218
+ | `LOCAL_EFFECTS` | `MEDIUM` |
219
+ | `EXTERNAL_EFFECTS` | `MEDIUM` |
220
+ | `DANGEROUS` | `HIGH` (always, cannot be overridden) |
221
+ | `UNKNOWN` | `HIGH` (always, cannot be overridden) |
222
+
223
+ `DANGEROUS` and `UNKNOWN` commands are always clamped to `HIGH` regardless of any explicit `risk` field in the YAML definition.
224
+
225
+ #### Composite risk
226
+
227
+ The overall `risk` of a full expression is the **maximum** across all commands in the expression:
228
+
229
+ ```
230
+ HIGH > MEDIUM > LOW
231
+ ```
232
+
233
+ #### YAML `risk` field
234
+
235
+ The optional `risk` field can be set on commands, subcommands, and options in the database YAML. It works the same way as classification:
236
+
237
+ - **Command/subcommand level:** Sets the base risk for that command or subcommand.
238
+ - **Option level:** When the option is present, overrides the risk to this level (same semantics as classification `overrides`).
239
+
240
+ When no explicit `risk` is set, the default risk for the resolved classification is used.
241
+
242
+ #### Security elevations
243
+
244
+ Certain expression-level patterns elevate risk to at least `HIGH`:
245
+
246
+ - Writing to system paths (e.g. `/etc`, `/usr`)
247
+ - Redirecting to `/dev/tcp` or `/dev/udp`
248
+ - Backgrounding (`cmd &`)
249
+ - Output redirects to non-`/dev/null` targets
250
+
203
251
  ### Composite classification
204
252
 
205
253
  The overall `classification` of a full expression is the **maximum severity** across all commands in the expression:
@@ -297,6 +345,48 @@ The database defines which options take values, so the parser knows whether `-n
297
345
 
298
346
  If the matched subcommand has `strict: true` (default), any unrecognized option makes the command `UNKNOWN`. If `strict: false`, unrecognized options are ignored (useful for commands with too many harmless flags to enumerate).
299
347
 
348
+ ### Subcommand matching modes
349
+
350
+ The `subcommand_mode` field controls how positional arguments are matched against the subcommand dictionary. There are two modes:
351
+
352
+ #### `hierarchical` (default)
353
+
354
+ The default mode. Subcommands are matched as a greedy chain through nested subcommand dictionaries. Each matched subcommand narrows the context to its own subcommand dict.
355
+
356
+ Example: `kubectl rollout status` matches `kubectl` → `rollout` → `status`, walking the tree.
357
+
358
+ #### `match_all`
359
+
360
+ 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.
361
+
362
+ Example: `mvn clean install` matches both `clean` and `install` independently against `mvn`'s subcommand dict.
363
+
364
+ **Classification/risk aggregation in `match_all` mode:**
365
+
366
+ - The final classification is the **maximum severity** across all matched goals.
367
+ - The final risk is the **maximum** across all matched goals' risks.
368
+ - If any positional argument is **unrecognized** (not in the subcommand dict), the command's base classification and risk are included in the aggregation.
369
+ - If **no goals match** (no positional args or all unrecognized), the command's base classification and risk apply.
370
+ - Option overrides (e.g. `--dry-run`) take precedence over goal aggregation — they replace the classification as usual.
371
+ - The `strict` mode only applies to **options** (flags starting with `-`), not to unrecognized positional arguments. Unrecognized goals do not cause UNKNOWN classification.
372
+
373
+ ```
374
+ argv: ["mvn", "-B", "clean", "deploy"]
375
+
376
+ Step 1: binary lookup → found "mvn" in database (subcommand_mode: match_all)
377
+ Step 2: strip global options → none
378
+ Step 3: subcommand matching (match_all mode)
379
+ - "-B" starts with "-" → passed to option classification
380
+ - "clean" found in subcommands → LOCAL_EFFECTS, risk LOW
381
+ - "deploy" found in subcommands → EXTERNAL_EFFECTS, risk MEDIUM (default)
382
+ - aggregate: classification = EXTERNAL_EFFECTS, risk = MEDIUM
383
+ Step 4: option classification
384
+ - "-B" is a known option (no override)
385
+ Step 5: classification
386
+ - aggregate classification: EXTERNAL_EFFECTS
387
+ - no option overrides → final: EXTERNAL_EFFECTS, risk: MEDIUM
388
+ ```
389
+
300
390
  ## Command Database Format
301
391
 
302
392
  YAML files, one per command or command family. Loaded from a `commands/` directory.
@@ -759,6 +849,7 @@ The `delegates_to` field defines how a command (or option like `find -exec`) han
759
849
  | `subcommands` | `map` | Nested subcommand definitions (recursive structure) |
760
850
  | `options` | `map` | Options that affect classification of this command/subcommand |
761
851
  | `strict` | `bool` | If `true` (default), unrecognized options yield `UNKNOWN` |
852
+ | `subcommand_mode` | `enum` | How subcommands are matched: `hierarchical` (default, chained tree walk) or `match_all` (each positional arg matched independently) |
762
853
  | `delegates_to` | `object` | How this command delegates execution to an inner command (see delegation modes) |
763
854
 
764
855
  #### Option level
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "bash-classify-hook",
3
- "version": "0.2.4",
4
- "description": "Auto-allows readonly bash commands in Claude Code using bash-classify",
3
+ "version": "0.4.0",
4
+ "description": "Auto-allows low-risk bash commands in Claude Code using bash-classify",
5
5
  "author": {
6
6
  "name": "Filip Prochazka"
7
7
  },
@@ -31,14 +31,14 @@ if [[ -z "$classification_output" ]]; then
31
31
  exit 0
32
32
  fi
33
33
 
34
- # Extract the top-level classification
35
- classification=$(echo "$classification_output" | jq -r '.classification // empty')
34
+ # Extract the top-level risk
35
+ risk=$(echo "$classification_output" | jq -r '.risk // empty')
36
36
 
37
- if [[ "$classification" == "READONLY" ]]; then
38
- # Auto-allow readonly commands
37
+ if [[ "$risk" == "LOW" ]]; then
38
+ # Auto-allow low-risk commands
39
39
  echo '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"allow"}}'
40
40
  fi
41
41
 
42
- # For anything else (LOCAL_EFFECTS, EXTERNAL_EFFECTS, DANGEROUS, UNKNOWN), output nothing
42
+ # For anything else (MEDIUM, HIGH), output nothing
43
43
  # — this lets the normal permission flow handle it
44
44
  exit 0
@@ -368,7 +368,92 @@ Commands invoked with a full path (e.g. `/usr/bin/rm`) are resolved to their bas
368
368
 
369
369
  The first line `# $schema: ../../../schemas/command.schema.json` is a convention for IDE support. It is not parsed by bash-classify itself but provides autocomplete and validation when editing YAML files in editors that support JSON Schema.
370
370
 
371
- ## 8. Common Patterns
371
+ ## 8. Risk Levels
372
+
373
+ Each command also has a risk level (`LOW`, `MEDIUM`, `HIGH`) that defaults based on classification. You only need to set `risk` explicitly in YAML when the default does not fit.
374
+
375
+ ### When to set `risk: LOW`
376
+
377
+ Use for safe routine operations that should be auto-allowed, even though their classification is above READONLY:
378
+
379
+ ```yaml
380
+ # git add only stages files -- safe to auto-allow
381
+ subcommands:
382
+ add:
383
+ classification: LOCAL_EFFECTS
384
+ risk: LOW
385
+ ```
386
+
387
+ Other good candidates: `git fetch`, `mkdir`, `touch`, code formatters like `ruff format`, `prettier`.
388
+
389
+ ### When to leave the default (`MEDIUM`)
390
+
391
+ The default `MEDIUM` is appropriate for most `LOCAL_EFFECTS` and `EXTERNAL_EFFECTS` commands. If the command does what its classification says and nothing surprising, don't set `risk` at all.
392
+
393
+ ### When to set `risk: HIGH`
394
+
395
+ Use when a command is more dangerous than its classification default suggests -- e.g. an `EXTERNAL_EFFECTS` command that is particularly destructive or hard to reverse.
396
+
397
+ ### `DANGEROUS` and `UNKNOWN` are always `HIGH`
398
+
399
+ Commands classified as `DANGEROUS` or `UNKNOWN` are automatically clamped to `HIGH` risk regardless of any explicit `risk` field. You never need to set `risk` on these.
400
+
401
+ ### Risk on options
402
+
403
+ Options can override risk the same way they override classification:
404
+
405
+ ```yaml
406
+ subcommands:
407
+ apply:
408
+ classification: EXTERNAL_EFFECTS
409
+ options:
410
+ --dry-run: {takes_value: true, overrides: READONLY, risk: LOW}
411
+ ```
412
+
413
+ ## 9. Subcommand Matching Modes
414
+
415
+ ### `subcommand_mode: hierarchical` (default)
416
+
417
+ The default mode. Subcommands form a tree, and matching walks the tree greedily. This is correct for most CLI tools where subcommands are hierarchical (e.g. `kubectl rollout status`, `git stash list`).
418
+
419
+ ### `subcommand_mode: match_all`
420
+
421
+ Use for build tools that accept multiple goals or tasks as positional arguments in any order. Each positional argument is matched independently against the same subcommand dictionary.
422
+
423
+ ```yaml
424
+ # Maven accepts multiple lifecycle phases: mvn clean install
425
+ command: mvn
426
+ classification: LOCAL_EFFECTS
427
+ strict: false
428
+ subcommand_mode: match_all
429
+ subcommands:
430
+ clean:
431
+ classification: LOCAL_EFFECTS
432
+ risk: LOW
433
+ compile:
434
+ classification: LOCAL_EFFECTS
435
+ risk: LOW
436
+ install:
437
+ classification: LOCAL_EFFECTS
438
+ risk: LOW
439
+ deploy:
440
+ classification: EXTERNAL_EFFECTS
441
+ ```
442
+
443
+ **When to use `match_all`:**
444
+
445
+ - Build tools that accept multiple goals/tasks (Maven, Gradle)
446
+ - Commands where positional args are independent operations, not hierarchical nesting
447
+
448
+ **How classification works:**
449
+
450
+ - The final classification is the maximum severity across all matched goals
451
+ - `mvn clean install` → both LOCAL_EFFECTS → final: LOCAL_EFFECTS
452
+ - `mvn clean deploy` → LOCAL_EFFECTS + EXTERNAL_EFFECTS → final: EXTERNAL_EFFECTS
453
+ - Unrecognized goals (e.g. custom Maven plugin goals like `some-plugin:goal`) fall back to the command's base classification and risk
454
+ - Option overrides (e.g. `--dry-run`) still take precedence over goal aggregation
455
+
456
+ ## 10. Common Patterns
372
457
 
373
458
  | Pattern | Example | Classification |
374
459
  |---------|---------|---------------|
@@ -18,6 +18,10 @@
18
18
  "classification": {
19
19
  "$ref": "#/$defs/Classification"
20
20
  },
21
+ "risk": {
22
+ "$ref": "#/$defs/Risk",
23
+ "description": "Explicit risk level override for this command (default derived from classification)"
24
+ },
21
25
  "strict": {
22
26
  "type": "boolean",
23
27
  "description": "If true (default), unrecognized options yield UNKNOWN"
@@ -34,6 +38,9 @@
34
38
  "$ref": "#/$defs/SubcommandsMap",
35
39
  "description": "Nested subcommand definitions"
36
40
  },
41
+ "subcommand_mode": {
42
+ "$ref": "#/$defs/SubcommandMode"
43
+ },
37
44
  "delegates_to": {
38
45
  "$ref": "#/$defs/DelegationConfig"
39
46
  }
@@ -44,6 +51,16 @@
44
51
  "enum": ["READONLY", "LOCAL_EFFECTS", "EXTERNAL_EFFECTS", "DANGEROUS", "UNKNOWN"],
45
52
  "description": "Classification level for a command or option override"
46
53
  },
54
+ "Risk": {
55
+ "type": "string",
56
+ "enum": ["LOW", "MEDIUM", "HIGH"],
57
+ "description": "Risk level for a command or option override"
58
+ },
59
+ "SubcommandMode": {
60
+ "type": "string",
61
+ "enum": ["hierarchical", "match_all"],
62
+ "description": "How subcommands are matched: hierarchical (chained tree walk, default) or match_all (each positional arg matched independently)"
63
+ },
47
64
  "DelegationConfig": {
48
65
  "type": "object",
49
66
  "required": ["mode"],
@@ -95,6 +112,10 @@
95
112
  "$ref": "#/$defs/Classification",
96
113
  "description": "When this option is present, override the classification to this level"
97
114
  },
115
+ "risk": {
116
+ "$ref": "#/$defs/Risk",
117
+ "description": "When this option is present, override the risk level"
118
+ },
98
119
  "captures_directory": {
99
120
  "type": "boolean",
100
121
  "description": "The value of this option is a working directory"
@@ -118,10 +139,17 @@
118
139
  "classification": {
119
140
  "$ref": "#/$defs/Classification"
120
141
  },
142
+ "risk": {
143
+ "$ref": "#/$defs/Risk",
144
+ "description": "Explicit risk level override for this subcommand (default derived from classification)"
145
+ },
121
146
  "strict": {
122
147
  "type": "boolean",
123
148
  "description": "If true (default), unrecognized options yield UNKNOWN"
124
149
  },
150
+ "subcommand_mode": {
151
+ "$ref": "#/$defs/SubcommandMode"
152
+ },
125
153
  "options": {
126
154
  "$ref": "#/$defs/OptionsMap"
127
155
  },
@@ -2,13 +2,14 @@
2
2
 
3
3
  from bash_classify.classifier import classify_expression
4
4
  from bash_classify.database import CommandDatabase, load_database
5
- from bash_classify.models import Classification, CommandResult, ExpressionResult
5
+ from bash_classify.models import Classification, CommandResult, ExpressionResult, Risk
6
6
 
7
7
  __all__ = [
8
8
  "classify_expression",
9
9
  "load_database",
10
10
  "CommandDatabase",
11
11
  "Classification",
12
+ "Risk",
12
13
  "ExpressionResult",
13
14
  "CommandResult",
14
15
  ]
@@ -14,6 +14,7 @@ from .models import (
14
14
  ExpressionResult,
15
15
  InnerCommandResult,
16
16
  Redirect,
17
+ Risk,
17
18
  )
18
19
  from .parser import parse_expression
19
20
 
@@ -94,6 +95,7 @@ def classify_expression(
94
95
  command=[invocation.argv[0]],
95
96
  argv=list(invocation.argv),
96
97
  classification=Classification.DANGEROUS,
98
+ risk=Risk.HIGH,
97
99
  matched_rule=None,
98
100
  inner_commands=[],
99
101
  classification_reason="variable expansion in command position",
@@ -112,12 +114,15 @@ def classify_expression(
112
114
  if result.classification_reason
113
115
  else "elevated by output redirect"
114
116
  )
117
+ # Elevate risk to at least MEDIUM for output redirects
118
+ result.risk = Risk.max_severity(result.risk, Risk.MEDIUM)
115
119
  # /dev/tcp and /dev/udp redirects are network access -> DANGEROUS
116
120
  if redirect.target.startswith("/dev/tcp/") or redirect.target.startswith("/dev/udp/"):
117
121
  elevated = Classification.max_severity(result.classification, Classification.DANGEROUS)
118
122
  if elevated != result.classification:
119
123
  result.classification = elevated
120
124
  result.classification_reason = "elevated to DANGEROUS: /dev/tcp or /dev/udp access detected"
125
+ result.risk = Risk.HIGH
121
126
 
122
127
  # Step 4b: Check for /dev/tcp and /dev/udp in command arguments
123
128
  for arg in invocation.argv:
@@ -126,6 +131,7 @@ def classify_expression(
126
131
  if elevated != result.classification:
127
132
  result.classification = elevated
128
133
  result.classification_reason = "elevated to DANGEROUS: /dev/tcp or /dev/udp access detected"
134
+ result.risk = Risk.HIGH
129
135
 
130
136
  # Step 5: Apply backgrounding
131
137
  if invocation.is_background:
@@ -137,6 +143,8 @@ def classify_expression(
137
143
  if result.classification_reason
138
144
  else "elevated by backgrounding"
139
145
  )
146
+ # Elevate risk to at least MEDIUM for backgrounding
147
+ result.risk = Risk.max_severity(result.risk, Risk.MEDIUM)
140
148
 
141
149
  # Step 6: Elevate to DANGEROUS when writing to system directories
142
150
  if result.classification.severity() >= Classification.LOCAL_EFFECTS.severity():
@@ -155,6 +163,7 @@ def classify_expression(
155
163
  result.classification_reason = (
156
164
  result.classification_reason or ""
157
165
  ) + f"; elevated to DANGEROUS: system path {system_paths_found[0]}"
166
+ result.risk = Risk.HIGH
158
167
 
159
168
  command_results.append(result)
160
169
  all_redirects.extend(invocation.redirects)
@@ -162,17 +171,21 @@ def classify_expression(
162
171
  # Step 7: Collect directories
163
172
  directories = _collect_directories(command_results)
164
173
 
165
- # Step 8: Compute composite classification
174
+ # Step 8: Compute composite classification and risk
166
175
  if not command_results and parse_warnings:
167
176
  overall = Classification.UNKNOWN
177
+ overall_risk = Risk.HIGH
168
178
  elif command_results:
169
179
  overall = Classification.max_severity(*(r.classification for r in command_results))
180
+ overall_risk = Risk.max_severity(*(r.risk for r in command_results))
170
181
  else:
171
182
  overall = Classification.READONLY # empty input
183
+ overall_risk = Risk.LOW
172
184
 
173
185
  return ExpressionResult(
174
186
  expression=expression,
175
187
  classification=overall,
188
+ risk=overall_risk,
176
189
  directories=directories,
177
190
  commands=command_results,
178
191
  redirects=all_redirects,
@@ -22,6 +22,7 @@ def _inner_command_to_dict(result: InnerCommandResult) -> dict:
22
22
  "command": result.command,
23
23
  "argv": result.argv,
24
24
  "classification": result.classification.value,
25
+ "risk": result.risk.value,
25
26
  "matched_rule": result.matched_rule,
26
27
  }
27
28
 
@@ -43,6 +44,7 @@ def _command_to_dict(result: CommandResult) -> dict:
43
44
  "command": result.command,
44
45
  "argv": result.argv,
45
46
  "classification": result.classification.value,
47
+ "risk": result.risk.value,
46
48
  "matched_rule": result.matched_rule,
47
49
  }
48
50
 
@@ -74,6 +76,7 @@ def _result_to_dict(result: ExpressionResult) -> dict:
74
76
  d: dict = {
75
77
  "expression": result.expression,
76
78
  "classification": result.classification.value,
79
+ "risk": result.risk.value,
77
80
  "directories": result.directories,
78
81
  "commands": [_command_to_dict(cmd) for cmd in result.commands],
79
82
  }
@@ -0,0 +1,9 @@
1
+ # $schema: ../../../schemas/command.schema.json
2
+ command: agent-browser
3
+ description: "Fast browser automation CLI for AI agents"
4
+ classification: EXTERNAL_EFFECTS
5
+ strict: false
6
+ global_options:
7
+ --help: {overrides: READONLY}
8
+ --version: {overrides: READONLY}
9
+ -V: {overrides: READONLY}
@@ -2,6 +2,7 @@
2
2
  command: black
3
3
  description: "Python code formatter"
4
4
  classification: LOCAL_EFFECTS
5
+ risk: LOW
5
6
  strict: false
6
7
  global_options:
7
8
  --help: {overrides: READONLY}
@@ -13,14 +13,18 @@ subcommands:
13
13
  classification: READONLY
14
14
  install:
15
15
  classification: LOCAL_EFFECTS
16
+ risk: LOW
16
17
  uninstall:
17
18
  classification: LOCAL_EFFECTS
19
+ risk: LOW
18
20
  update:
19
21
  classification: LOCAL_EFFECTS
22
+ risk: LOW
20
23
  upgrade:
21
24
  classification: LOCAL_EFFECTS
22
25
  cleanup:
23
26
  classification: LOCAL_EFFECTS
27
+ risk: LOW
24
28
  doctor:
25
29
  classification: READONLY
26
30
  config: