bash-classify 0.1.4__tar.gz → 0.2.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 (173) hide show
  1. {bash_classify-0.1.4 → bash_classify-0.2.0}/.claude-plugin/marketplace.json +1 -1
  2. {bash_classify-0.1.4 → bash_classify-0.2.0}/PKG-INFO +6 -5
  3. {bash_classify-0.1.4 → bash_classify-0.2.0}/README.md +5 -4
  4. {bash_classify-0.1.4 → bash_classify-0.2.0}/SPEC.md +71 -75
  5. {bash_classify-0.1.4 → bash_classify-0.2.0}/coding-agent-plugins/claude-code/.claude-plugin/plugin.json +1 -1
  6. {bash_classify-0.1.4 → bash_classify-0.2.0}/coding-agent-plugins/claude-code/hooks/classify-bash.sh +1 -1
  7. {bash_classify-0.1.4 → bash_classify-0.2.0}/docs/classification-guidance.md +38 -32
  8. {bash_classify-0.1.4 → bash_classify-0.2.0}/schemas/command.schema.json +1 -1
  9. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/classifier.py +3 -3
  10. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/apt.yaml +1 -1
  11. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/brew.yaml +5 -5
  12. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/cargo.yaml +8 -8
  13. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/chgrp.yaml +1 -1
  14. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/chmod.yaml +1 -1
  15. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/chown.yaml +1 -1
  16. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/cp.yaml +1 -1
  17. bash_classify-0.2.0/src/bash_classify/commands/curl.yaml +28 -0
  18. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/docker.yaml +17 -17
  19. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/find.yaml +0 -4
  20. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/git.yaml +88 -88
  21. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/go.yaml +8 -8
  22. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/gunzip.yaml +1 -1
  23. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/gzip.yaml +1 -1
  24. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/helm.yaml +11 -11
  25. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/hostname.yaml +1 -1
  26. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/install.yaml +1 -1
  27. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/journalctl.yaml +3 -3
  28. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/kubectl.yaml +22 -22
  29. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/ln.yaml +1 -1
  30. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/make.yaml +1 -1
  31. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/mkdir.yaml +1 -1
  32. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/mv.yaml +1 -1
  33. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/nice.yaml +1 -1
  34. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/nohup.yaml +1 -1
  35. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/npm.yaml +8 -8
  36. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/pip.yaml +4 -4
  37. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/pip3.yaml +4 -4
  38. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/poetry.yaml +15 -15
  39. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/pytest.yaml +1 -1
  40. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/rsync.yaml +1 -1
  41. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/ruff.yaml +4 -4
  42. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/scp.yaml +1 -1
  43. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/sed.yaml +2 -2
  44. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/sort.yaml +2 -2
  45. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/strace.yaml +1 -1
  46. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/systemctl.yaml +3 -3
  47. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/tar.yaml +1 -1
  48. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/tee.yaml +1 -1
  49. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/terraform.yaml +7 -7
  50. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/touch.yaml +1 -1
  51. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/unzip.yaml +1 -1
  52. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/uv.yaml +21 -21
  53. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/wget.yaml +1 -1
  54. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/yq.yaml +2 -2
  55. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/zip.yaml +1 -1
  56. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/models.py +7 -5
  57. {bash_classify-0.1.4 → bash_classify-0.2.0}/tests/test_classifier.py +32 -18
  58. {bash_classify-0.1.4 → bash_classify-0.2.0}/tests/test_cli.py +1 -1
  59. {bash_classify-0.1.4 → bash_classify-0.2.0}/tests/test_cli_serialization.py +3 -3
  60. {bash_classify-0.1.4 → bash_classify-0.2.0}/tests/test_database.py +3 -3
  61. {bash_classify-0.1.4 → bash_classify-0.2.0}/tests/test_matcher.py +4 -4
  62. {bash_classify-0.1.4 → bash_classify-0.2.0}/tests/test_models.py +9 -8
  63. {bash_classify-0.1.4 → bash_classify-0.2.0}/tests/test_real_world.py +28 -28
  64. bash_classify-0.1.4/src/bash_classify/commands/curl.yaml +0 -28
  65. {bash_classify-0.1.4 → bash_classify-0.2.0}/.claude/rules/command-database.md +0 -0
  66. {bash_classify-0.1.4 → bash_classify-0.2.0}/.editorconfig +0 -0
  67. {bash_classify-0.1.4 → bash_classify-0.2.0}/.github/workflows/ci.yml +0 -0
  68. {bash_classify-0.1.4 → bash_classify-0.2.0}/.github/workflows/publish.yml +0 -0
  69. {bash_classify-0.1.4 → bash_classify-0.2.0}/.gitignore +0 -0
  70. {bash_classify-0.1.4 → bash_classify-0.2.0}/.python-version +0 -0
  71. {bash_classify-0.1.4 → bash_classify-0.2.0}/CLAUDE.md +0 -0
  72. {bash_classify-0.1.4 → bash_classify-0.2.0}/LICENSE +0 -0
  73. {bash_classify-0.1.4 → bash_classify-0.2.0}/coding-agent-plugins/claude-code/hooks/hooks.json +0 -0
  74. {bash_classify-0.1.4 → bash_classify-0.2.0}/pyproject.toml +0 -0
  75. {bash_classify-0.1.4 → bash_classify-0.2.0}/ruff.toml +0 -0
  76. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/__init__.py +0 -0
  77. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/__main__.py +0 -0
  78. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/cli.py +0 -0
  79. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/awk.yaml +0 -0
  80. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/basename.yaml +0 -0
  81. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/bash.yaml +0 -0
  82. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/builtin.yaml +0 -0
  83. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/cat.yaml +0 -0
  84. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/column.yaml +0 -0
  85. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/comm.yaml +0 -0
  86. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/command.yaml +0 -0
  87. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/cut.yaml +0 -0
  88. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/date.yaml +0 -0
  89. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/df.yaml +0 -0
  90. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/diff.yaml +0 -0
  91. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/dig.yaml +0 -0
  92. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/dirname.yaml +0 -0
  93. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/du.yaml +0 -0
  94. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/echo.yaml +0 -0
  95. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/env.yaml +0 -0
  96. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/expand.yaml +0 -0
  97. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/false.yaml +0 -0
  98. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/file.yaml +0 -0
  99. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/fmt.yaml +0 -0
  100. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/fold.yaml +0 -0
  101. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/free.yaml +0 -0
  102. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/gawk.yaml +0 -0
  103. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/grep.yaml +0 -0
  104. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/groups.yaml +0 -0
  105. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/head.yaml +0 -0
  106. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/id.yaml +0 -0
  107. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/join.yaml +0 -0
  108. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/jq.yaml +0 -0
  109. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/kill.yaml +0 -0
  110. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/killall.yaml +0 -0
  111. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/less.yaml +0 -0
  112. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/ls.yaml +0 -0
  113. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/md5sum.yaml +0 -0
  114. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/more.yaml +0 -0
  115. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/netstat.yaml +0 -0
  116. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/nl.yaml +0 -0
  117. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/node.yaml +0 -0
  118. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/nslookup.yaml +0 -0
  119. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/paste.yaml +0 -0
  120. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/ping.yaml +0 -0
  121. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/pkill.yaml +0 -0
  122. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/printenv.yaml +0 -0
  123. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/printf.yaml +0 -0
  124. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/ps.yaml +0 -0
  125. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/pwd.yaml +0 -0
  126. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/python.yaml +0 -0
  127. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/python3.yaml +0 -0
  128. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/read.yaml +0 -0
  129. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/readlink.yaml +0 -0
  130. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/realpath.yaml +0 -0
  131. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/rev.yaml +0 -0
  132. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/rg.yaml +0 -0
  133. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/rm.yaml +0 -0
  134. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/seq.yaml +0 -0
  135. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/sh.yaml +0 -0
  136. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/sha1sum.yaml +0 -0
  137. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/sha256sum.yaml +0 -0
  138. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/ss.yaml +0 -0
  139. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/ssh.yaml +0 -0
  140. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/stat.yaml +0 -0
  141. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/sudo.yaml +0 -0
  142. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/tac.yaml +0 -0
  143. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/tail.yaml +0 -0
  144. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/test.yaml +0 -0
  145. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/time.yaml +0 -0
  146. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/timeout.yaml +0 -0
  147. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/top.yaml +0 -0
  148. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/tr.yaml +0 -0
  149. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/traceroute.yaml +0 -0
  150. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/true.yaml +0 -0
  151. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/type.yaml +0 -0
  152. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/uname.yaml +0 -0
  153. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/unexpand.yaml +0 -0
  154. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/uniq.yaml +0 -0
  155. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/uptime.yaml +0 -0
  156. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/wc.yaml +0 -0
  157. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/whereis.yaml +0 -0
  158. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/which.yaml +0 -0
  159. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/whoami.yaml +0 -0
  160. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/xargs.yaml +0 -0
  161. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/xmllint.yaml +0 -0
  162. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/yes.yaml +0 -0
  163. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/commands/zsh.yaml +0 -0
  164. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/database.py +0 -0
  165. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/matcher.py +0 -0
  166. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/parser.py +0 -0
  167. {bash_classify-0.1.4 → bash_classify-0.2.0}/src/bash_classify/py.typed +0 -0
  168. {bash_classify-0.1.4 → bash_classify-0.2.0}/tests/__init__.py +0 -0
  169. {bash_classify-0.1.4 → bash_classify-0.2.0}/tests/conftest.py +0 -0
  170. {bash_classify-0.1.4 → bash_classify-0.2.0}/tests/test_parser.py +0 -0
  171. {bash_classify-0.1.4 → bash_classify-0.2.0}/tests/test_schema.py +0 -0
  172. {bash_classify-0.1.4 → bash_classify-0.2.0}/tests/test_smoke.py +0 -0
  173. {bash_classify-0.1.4 → bash_classify-0.2.0}/uv.lock +0 -0
@@ -8,7 +8,7 @@
8
8
  {
9
9
  "name": "bash-classify-hook",
10
10
  "description": "Auto-allows readonly bash commands in Claude Code using bash-classify",
11
- "version": "0.1.4",
11
+ "version": "0.2.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.1.4
3
+ Version: 0.2.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,7 +33,7 @@ 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 four levels: `READONLY`, `WRITE`, `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 into five levels: `READONLY`, `LOCAL_EFFECTS`, `EXTERNAL_EFFECTS`, `DANGEROUS`, and `UNKNOWN`.
37
37
 
38
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.
39
39
 
@@ -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 (WRITE, DANGEROUS, UNKNOWN) still requires confirmation.
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.
86
86
 
87
87
  ## Command database
88
88
 
@@ -95,8 +95,9 @@ The classification database includes 120+ command definitions covering common Un
95
95
 
96
96
  | Level | Description | Examples |
97
97
  |---|---|---|
98
- | `READONLY` | No side effects, safe to auto-approve | `ls`, `cat`, `grep`, `kubectl get` |
99
- | `WRITE` | Modifies local files or state | `mkdir`, `cp`, `git commit` |
98
+ | `READONLY` | No side effects, auto-approved | `ls`, `cat`, `grep`, `kubectl get` |
99
+ | `LOCAL_EFFECTS` | Modifies local files or state only | `git add`, `git commit`, `cp`, `mkdir`, `pytest` |
100
+ | `EXTERNAL_EFFECTS` | Interacts with external systems | `git push`, `kubectl apply`, `curl -d` |
100
101
  | `DANGEROUS` | Destructive, system-wide, or irreversible | `rm -rf`, `git push --force`, `chmod` |
101
102
  | `UNKNOWN` | Command not in database | Any unrecognized command |
102
103
 
@@ -4,7 +4,7 @@ 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 four levels: `READONLY`, `WRITE`, `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 into five levels: `READONLY`, `LOCAL_EFFECTS`, `EXTERNAL_EFFECTS`, `DANGEROUS`, and `UNKNOWN`.
8
8
 
9
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.
10
10
 
@@ -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 (WRITE, DANGEROUS, UNKNOWN) still requires confirmation.
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.
57
57
 
58
58
  ## Command database
59
59
 
@@ -66,8 +66,9 @@ The classification database includes 120+ command definitions covering common Un
66
66
 
67
67
  | Level | Description | Examples |
68
68
  |---|---|---|
69
- | `READONLY` | No side effects, safe to auto-approve | `ls`, `cat`, `grep`, `kubectl get` |
70
- | `WRITE` | Modifies local files or state | `mkdir`, `cp`, `git commit` |
69
+ | `READONLY` | No side effects, auto-approved | `ls`, `cat`, `grep`, `kubectl get` |
70
+ | `LOCAL_EFFECTS` | Modifies local files or state only | `git add`, `git commit`, `cp`, `mkdir`, `pytest` |
71
+ | `EXTERNAL_EFFECTS` | Interacts with external systems | `git push`, `kubectl apply`, `curl -d` |
71
72
  | `DANGEROUS` | Destructive, system-wide, or irreversible | `rm -rf`, `git push --force`, `chmod` |
72
73
  | `UNKNOWN` | Command not in database | Any unrecognized command |
73
74
 
@@ -93,15 +93,14 @@ $ echo 'find . -name "*.tmp" -exec rm -f {} \;' | bash-classify
93
93
  "argv": ["find", ".", "-name", "*.tmp", "-exec", "rm", "-f", "{}", ";"],
94
94
  "classification": "DANGEROUS",
95
95
  "matched_rule": "find",
96
- "overriding_option": "-exec",
97
96
  "inner_commands": [
98
97
  {
99
98
  "delegation_mode": "terminated_argv",
100
99
  "delegation_source": "-exec",
101
100
  "command": ["rm"],
102
101
  "argv": ["rm", "-f"],
103
- "classification": "UNKNOWN",
104
- "matched_rule": null,
102
+ "classification": "DANGEROUS",
103
+ "matched_rule": "rm",
105
104
  "inner_commands": []
106
105
  }
107
106
  ]
@@ -196,7 +195,8 @@ $ echo 'sh -c "ls /tmp | grep log"' | bash-classify
196
195
  | Level | Meaning |
197
196
  |---|---|
198
197
  | `READONLY` | Only reads data, no side effects |
199
- | `WRITE` | Creates, modifies, or deletes data in a controlled way |
198
+ | `LOCAL_EFFECTS` | Modifies local files or state only |
199
+ | `EXTERNAL_EFFECTS` | Interacts with external systems or network |
200
200
  | `DANGEROUS` | Destructive, hard to reverse, or affects critical systems |
201
201
  | `UNKNOWN` | Command or subcommand not in database |
202
202
 
@@ -205,10 +205,10 @@ $ echo 'sh -c "ls /tmp | grep log"' | bash-classify
205
205
  The overall `classification` of a full expression is the **maximum severity** across all commands in the expression:
206
206
 
207
207
  ```
208
- DANGEROUS > UNKNOWN > WRITE > READONLY
208
+ DANGEROUS > UNKNOWN > EXTERNAL_EFFECTS > LOCAL_EFFECTS > READONLY
209
209
  ```
210
210
 
211
- `UNKNOWN` is ranked above `WRITE` because an unrecognized command should not be silently trusted — it must be reviewed.
211
+ `UNKNOWN` is ranked above `EXTERNAL_EFFECTS` because an unrecognized command should not be silently trusted — it must be reviewed.
212
212
 
213
213
  ## Bash Parsing
214
214
 
@@ -237,7 +237,7 @@ The parser walks the CST and extracts a list of `CommandInvocation` objects, eac
237
237
  | Subshells `(cmd)` | Inner command extracted recursively |
238
238
  | Heredocs `<<EOF` | Content captured but not parsed as commands |
239
239
  | Variable assignments `X=1 cmd` | Prefix assignments stripped, `cmd` extracted |
240
- | Backgrounding `cmd &` | Classified as WRITE (side effect: background process) |
240
+ | Backgrounding `cmd &` | Classified as EXTERNAL_EFFECTS (side effect: background process) |
241
241
 
242
242
  ### Constructs that force DANGEROUS
243
243
 
@@ -352,15 +352,15 @@ subcommands:
352
352
  can-i:
353
353
  classification: READONLY
354
354
  reconcile:
355
- classification: WRITE
355
+ classification: EXTERNAL_EFFECTS
356
356
 
357
357
  apply:
358
- classification: WRITE
358
+ classification: EXTERNAL_EFFECTS
359
359
  options:
360
360
  --dry-run: {overrides: READONLY}
361
361
 
362
362
  create:
363
- classification: WRITE
363
+ classification: EXTERNAL_EFFECTS
364
364
 
365
365
  delete:
366
366
  classification: DANGEROUS
@@ -368,13 +368,13 @@ subcommands:
368
368
  --dry-run: {overrides: READONLY}
369
369
 
370
370
  edit:
371
- classification: WRITE
371
+ classification: EXTERNAL_EFFECTS
372
372
 
373
373
  patch:
374
- classification: WRITE
374
+ classification: EXTERNAL_EFFECTS
375
375
 
376
376
  scale:
377
- classification: WRITE
377
+ classification: EXTERNAL_EFFECTS
378
378
 
379
379
  rollout:
380
380
  subcommands:
@@ -383,40 +383,40 @@ subcommands:
383
383
  history:
384
384
  classification: READONLY
385
385
  restart:
386
- classification: WRITE
386
+ classification: EXTERNAL_EFFECTS
387
387
  undo:
388
388
  classification: DANGEROUS
389
389
  pause:
390
- classification: WRITE
390
+ classification: EXTERNAL_EFFECTS
391
391
  resume:
392
- classification: WRITE
392
+ classification: EXTERNAL_EFFECTS
393
393
 
394
394
  exec:
395
395
  classification: DANGEROUS
396
396
 
397
397
  port-forward:
398
- classification: WRITE
398
+ classification: EXTERNAL_EFFECTS
399
399
 
400
400
  cp:
401
- classification: WRITE
401
+ classification: EXTERNAL_EFFECTS
402
402
 
403
403
  drain:
404
404
  classification: DANGEROUS
405
405
 
406
406
  cordon:
407
- classification: WRITE
407
+ classification: EXTERNAL_EFFECTS
408
408
 
409
409
  uncordon:
410
- classification: WRITE
410
+ classification: EXTERNAL_EFFECTS
411
411
 
412
412
  taint:
413
- classification: WRITE
413
+ classification: EXTERNAL_EFFECTS
414
414
 
415
415
  label:
416
- classification: WRITE
416
+ classification: EXTERNAL_EFFECTS
417
417
 
418
418
  annotate:
419
- classification: WRITE
419
+ classification: EXTERNAL_EFFECTS
420
420
 
421
421
  config:
422
422
  subcommands:
@@ -427,15 +427,15 @@ subcommands:
427
427
  current-context:
428
428
  classification: READONLY
429
429
  use-context:
430
- classification: WRITE
430
+ classification: EXTERNAL_EFFECTS
431
431
  set:
432
- classification: WRITE
432
+ classification: EXTERNAL_EFFECTS
433
433
  set-context:
434
- classification: WRITE
434
+ classification: EXTERNAL_EFFECTS
435
435
  set-cluster:
436
- classification: WRITE
436
+ classification: EXTERNAL_EFFECTS
437
437
  set-credentials:
438
- classification: WRITE
438
+ classification: EXTERNAL_EFFECTS
439
439
  delete-context:
440
440
  classification: DANGEROUS
441
441
  delete-cluster:
@@ -471,34 +471,34 @@ subcommands:
471
471
  branch:
472
472
  classification: READONLY
473
473
  options:
474
- -d: {overrides: WRITE}
474
+ -d: {overrides: EXTERNAL_EFFECTS}
475
475
  -D: {overrides: DANGEROUS}
476
- --delete: {overrides: WRITE}
477
- --move: {overrides: WRITE}
478
- --copy: {overrides: WRITE}
479
- --edit-description: {overrides: WRITE}
480
- --set-upstream-to: {overrides: WRITE, takes_value: true}
481
- --unset-upstream: {overrides: WRITE}
476
+ --delete: {overrides: EXTERNAL_EFFECTS}
477
+ --move: {overrides: EXTERNAL_EFFECTS}
478
+ --copy: {overrides: EXTERNAL_EFFECTS}
479
+ --edit-description: {overrides: EXTERNAL_EFFECTS}
480
+ --set-upstream-to: {overrides: EXTERNAL_EFFECTS, takes_value: true}
481
+ --unset-upstream: {overrides: EXTERNAL_EFFECTS}
482
482
  remote:
483
483
  classification: READONLY
484
484
  subcommands:
485
485
  add:
486
- classification: WRITE
486
+ classification: EXTERNAL_EFFECTS
487
487
  remove:
488
- classification: WRITE
488
+ classification: EXTERNAL_EFFECTS
489
489
  rename:
490
- classification: WRITE
490
+ classification: EXTERNAL_EFFECTS
491
491
  set-url:
492
- classification: WRITE
492
+ classification: EXTERNAL_EFFECTS
493
493
  prune:
494
- classification: WRITE
494
+ classification: EXTERNAL_EFFECTS
495
495
  tag:
496
496
  classification: READONLY
497
497
  options:
498
- -d: {overrides: WRITE}
499
- --delete: {overrides: WRITE}
500
- -a: {overrides: WRITE}
501
- -s: {overrides: WRITE}
498
+ -d: {overrides: EXTERNAL_EFFECTS}
499
+ --delete: {overrides: EXTERNAL_EFFECTS}
500
+ -a: {overrides: EXTERNAL_EFFECTS}
501
+ -s: {overrides: EXTERNAL_EFFECTS}
502
502
  blame:
503
503
  classification: READONLY
504
504
  shortlog:
@@ -519,49 +519,49 @@ subcommands:
519
519
  classification: READONLY
520
520
 
521
521
  add:
522
- classification: WRITE
522
+ classification: EXTERNAL_EFFECTS
523
523
  commit:
524
- classification: WRITE
524
+ classification: EXTERNAL_EFFECTS
525
525
  merge:
526
- classification: WRITE
526
+ classification: EXTERNAL_EFFECTS
527
527
  options:
528
528
  --abort: {overrides: DANGEROUS}
529
529
  rebase:
530
- classification: WRITE
530
+ classification: EXTERNAL_EFFECTS
531
531
  options:
532
532
  --abort: {overrides: DANGEROUS}
533
533
  cherry-pick:
534
- classification: WRITE
534
+ classification: EXTERNAL_EFFECTS
535
535
  options:
536
536
  --abort: {overrides: DANGEROUS}
537
537
 
538
538
  push:
539
- classification: WRITE
539
+ classification: EXTERNAL_EFFECTS
540
540
  options:
541
541
  --force: {overrides: DANGEROUS}
542
542
  -f: {overrides: DANGEROUS}
543
- --force-with-lease: {overrides: WRITE}
543
+ --force-with-lease: {overrides: EXTERNAL_EFFECTS}
544
544
  --delete: {overrides: DANGEROUS}
545
545
 
546
546
  pull:
547
- classification: WRITE
547
+ classification: EXTERNAL_EFFECTS
548
548
 
549
549
  fetch:
550
- classification: WRITE
550
+ classification: EXTERNAL_EFFECTS
551
551
  options:
552
- --prune: {overrides: WRITE}
552
+ --prune: {overrides: EXTERNAL_EFFECTS}
553
553
 
554
554
  checkout:
555
- classification: WRITE
555
+ classification: EXTERNAL_EFFECTS
556
556
 
557
557
  switch:
558
- classification: WRITE
558
+ classification: EXTERNAL_EFFECTS
559
559
 
560
560
  restore:
561
- classification: WRITE
561
+ classification: EXTERNAL_EFFECTS
562
562
 
563
563
  stash:
564
- classification: WRITE
564
+ classification: EXTERNAL_EFFECTS
565
565
  subcommands:
566
566
  list:
567
567
  classification: READONLY
@@ -573,18 +573,18 @@ subcommands:
573
573
  classification: DANGEROUS
574
574
 
575
575
  reset:
576
- classification: WRITE
576
+ classification: EXTERNAL_EFFECTS
577
577
  options:
578
578
  --hard: {overrides: DANGEROUS}
579
579
 
580
580
  revert:
581
- classification: WRITE
581
+ classification: EXTERNAL_EFFECTS
582
582
 
583
583
  clean:
584
584
  classification: DANGEROUS
585
585
 
586
586
  rm:
587
- classification: WRITE
587
+ classification: EXTERNAL_EFFECTS
588
588
  options:
589
589
  -r: {overrides: DANGEROUS}
590
590
  ```
@@ -598,22 +598,18 @@ strict: false # find has too many predicates to enumerate
598
598
  options:
599
599
  -delete: {overrides: DANGEROUS}
600
600
  -exec:
601
- overrides: DANGEROUS
602
601
  delegates_to:
603
602
  mode: terminated_argv
604
603
  terminator: ";" # tokens between -exec and \; are the inner command
605
604
  -execdir:
606
- overrides: DANGEROUS
607
605
  delegates_to:
608
606
  mode: terminated_argv
609
607
  terminator: ";"
610
608
  -ok:
611
- overrides: WRITE
612
609
  delegates_to:
613
610
  mode: terminated_argv
614
611
  terminator: ";"
615
612
  -okdir:
616
- overrides: WRITE
617
613
  delegates_to:
618
614
  mode: terminated_argv
619
615
  terminator: ";"
@@ -668,10 +664,10 @@ options:
668
664
 
669
665
  ```yaml
670
666
  command: sudo
671
- classification: WRITE # sudo itself elevates privileges
667
+ classification: EXTERNAL_EFFECTS # sudo itself elevates privileges
672
668
  delegates_to:
673
669
  mode: rest_are_argv
674
- min_classification: WRITE # inner command is at least WRITE regardless of its own classification
670
+ min_classification: EXTERNAL_EFFECTS # inner command is at least EXTERNAL_EFFECTS regardless of its own classification
675
671
  options:
676
672
  -u: {takes_value: true}
677
673
  --user: {takes_value: true, aliases: [-u]}
@@ -749,7 +745,7 @@ The `delegates_to` field defines how a command (or option like `find -exec`) han
749
745
  | `terminator` | `string` | For `terminated_argv`: the token that ends the inner argv |
750
746
  | `flag` | `string` | For `flag_value_is_expression`: which flag's value to parse |
751
747
  | `strip_assignments` | `bool` | For `rest_are_argv`: strip leading `KEY=VALUE` tokens before the inner command |
752
- | `min_classification` | `enum` | Floor classification for the inner command (e.g. `sudo` forces at least `WRITE`) |
748
+ | `min_classification` | `enum` | Floor classification for the inner command (e.g. `sudo` forces at least `EXTERNAL_EFFECTS`) |
753
749
 
754
750
  ### Database field reference
755
751
 
@@ -781,13 +777,13 @@ Output redirects affect the overall classification:
781
777
 
782
778
  | Redirect | Effect |
783
779
  |---|---|
784
- | `> file` | Elevates to at least `WRITE` |
785
- | `>> file` | Elevates to at least `WRITE` |
780
+ | `> file` | Elevates to at least `EXTERNAL_EFFECTS` |
781
+ | `>> file` | Elevates to at least `EXTERNAL_EFFECTS` |
786
782
  | `> /dev/null` | No effect (discarding output is not a write) |
787
- | `2> file` / `2>> file` | Elevates to at least `WRITE` (unless `/dev/null`) |
788
- | `&> file` | Elevates to at least `WRITE` (unless `/dev/null`) |
783
+ | `2> file` / `2>> file` | Elevates to at least `EXTERNAL_EFFECTS` (unless `/dev/null`) |
784
+ | `&> file` | Elevates to at least `EXTERNAL_EFFECTS` (unless `/dev/null`) |
789
785
  | `< file` | No effect (input redirect is reading) |
790
- | `\| tee file` | `tee` is classified as `WRITE` via its own database entry |
786
+ | `\| tee file` | `tee` is classified as `EXTERNAL_EFFECTS` via its own database entry |
791
787
 
792
788
  ## Directory Detection
793
789
 
@@ -829,13 +825,13 @@ The `commands` list is recursive — any command entry can contain `inner_comman
829
825
  ```json
830
826
  {
831
827
  "expression": "string — the original input",
832
- "classification": "READONLY | WRITE | DANGEROUS | UNKNOWN",
828
+ "classification": "READONLY | LOCAL_EFFECTS | EXTERNAL_EFFECTS | DANGEROUS | UNKNOWN",
833
829
  "directories": ["string — detected directories"],
834
830
  "commands": [
835
831
  {
836
832
  "command": ["string — binary + subcommand chain (without inner command tokens)"],
837
833
  "argv": ["string — full original argv for this command"],
838
- "classification": "READONLY | WRITE | DANGEROUS | UNKNOWN",
834
+ "classification": "READONLY | LOCAL_EFFECTS | EXTERNAL_EFFECTS | DANGEROUS | UNKNOWN",
839
835
  "matched_rule": "string — dotted path in database, or null",
840
836
  "ignored_options": ["string — global options that were stripped"],
841
837
  "remaining_options": ["string — options that were not in database"],
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bash-classify-hook",
3
- "version": "0.1.4",
3
+ "version": "0.2.0",
4
4
  "description": "Auto-allows readonly bash commands in Claude Code using bash-classify",
5
5
  "author": {
6
6
  "name": "Filip Prochazka"
@@ -39,6 +39,6 @@ if [[ "$classification" == "READONLY" ]]; then
39
39
  echo '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"allow"}}'
40
40
  fi
41
41
 
42
- # For anything else (WRITE, DANGEROUS, UNKNOWN), output nothing
42
+ # For anything else (LOCAL_EFFECTS, EXTERNAL_EFFECTS, DANGEROUS, UNKNOWN), output nothing
43
43
  # — this lets the normal permission flow handle it
44
44
  exit 0
@@ -16,11 +16,17 @@ No side effects. Safe to auto-allow without user confirmation.
16
16
 
17
17
  Examples: `ls`, `cat`, `grep`, `kubectl get`, `git status`, `git log`, `docker ps`, `terraform plan`, `curl https://example.com`
18
18
 
19
- ### WRITE
19
+ ### LOCAL_EFFECTS
20
20
 
21
- Creates, modifies, or deletes data in a controlled way. Requires user confirmation.
21
+ Modifies local state only (files, git index, local config). No network or external system interaction. Requires user confirmation.
22
22
 
23
- Examples: `git commit`, `cp`, `touch`, `mkdir`, `kubectl apply`, `docker build`, `git push`, `curl -d '...'`
23
+ Examples: `git add`, `git commit`, `cp`, `touch`, `mkdir`, `sed -i`, `chmod`
24
+
25
+ ### EXTERNAL_EFFECTS
26
+
27
+ Creates, modifies, or deletes data beyond the local machine, or interacts with external systems. Requires user confirmation.
28
+
29
+ Examples: `git push`, `kubectl apply`, `docker build`, `curl -d '...'`, `npm publish`
24
30
 
25
31
  ### DANGEROUS
26
32
 
@@ -35,10 +41,10 @@ Command or subcommand not in the database. Treated as requiring confirmation.
35
41
  ### Severity Ordering
36
42
 
37
43
  ```
38
- DANGEROUS > UNKNOWN > WRITE > READONLY
44
+ DANGEROUS > UNKNOWN > EXTERNAL_EFFECTS > LOCAL_EFFECTS > READONLY
39
45
  ```
40
46
 
41
- UNKNOWN is ranked **above WRITE** because an unrecognized command should not be silently trusted -- it must be reviewed. A known WRITE command (like `git commit`) is predictable; an unknown command could do anything.
47
+ UNKNOWN is ranked **above EXTERNAL_EFFECTS** because an unrecognized command should not be silently trusted -- it must be reviewed. A known EXTERNAL_EFFECTS command (like `git push`) is predictable; an unknown command could do anything.
42
48
 
43
49
  The overall classification of a full expression (e.g. a pipeline) is the **maximum severity** across all commands in the expression.
44
50
 
@@ -55,8 +61,8 @@ description: "Stream editor" # (optional) short one-liner
55
61
  classification: READONLY # (optional, default READONLY) base classification
56
62
  strict: false # (optional, default true) unrecognized options -> UNKNOWN?
57
63
  options: # options that affect classification
58
- -i: {overrides: WRITE} # -i changes classification to WRITE
59
- --in-place: {overrides: WRITE, aliases: [-i]}
64
+ -i: {overrides: EXTERNAL_EFFECTS} # -i changes classification to EXTERNAL_EFFECTS
65
+ --in-place: {overrides: EXTERNAL_EFFECTS, aliases: [-i]}
60
66
  -e: {takes_value: true} # -e consumes the next token as its value
61
67
  --expression: {takes_value: true, aliases: [-e]}
62
68
  -f: {takes_value: true}
@@ -101,7 +107,7 @@ Add `# $schema: ../../../schemas/command.schema.json` as the first line of every
101
107
  - The command is a pure filter/transformer (`grep`, `awk`, `sed` without `-i`, `jq`, `sort` without `-o`)
102
108
  - The command queries a remote system without changing it (`kubectl get`, `curl` without `-d`/`-F`/`-o`, `dig`, `ping`)
103
109
 
104
- ### Use WRITE when:
110
+ ### Use EXTERNAL_EFFECTS when:
105
111
 
106
112
  - The command creates or modifies files (`touch`, `cp`, `mv`, `mkdir`, `tee`)
107
113
  - The command modifies local state (`git commit`, `git checkout`, `kubectl apply`)
@@ -149,21 +155,21 @@ There are two distinct mechanisms for controlling classification — don't confu
149
155
  Each subcommand has its **own** `classification` field. It does not inherit from or override the parent — it simply IS the classification for that subcommand. No special keyword is needed.
150
156
 
151
157
  ```yaml
152
- # git.worktree is WRITE, but git.worktree.list is READONLY
158
+ # git.worktree is EXTERNAL_EFFECTS, but git.worktree.list is READONLY
153
159
  # No "overrides" keyword — list has its own classification
154
160
  subcommands:
155
161
  worktree:
156
- classification: WRITE
162
+ classification: EXTERNAL_EFFECTS
157
163
  subcommands:
158
164
  list:
159
165
  classification: READONLY # independent, not an override
160
166
  add:
161
- classification: WRITE
167
+ classification: EXTERNAL_EFFECTS
162
168
  remove:
163
- classification: WRITE
169
+ classification: EXTERNAL_EFFECTS
164
170
  ```
165
171
 
166
- The parent's classification (`WRITE`) is used only when **no subcommand matches** — e.g., bare `git worktree` or `git worktree unknown-thing`.
172
+ The parent's classification (`EXTERNAL_EFFECTS`) is used only when **no subcommand matches** — e.g., bare `git worktree` or `git worktree unknown-thing`.
167
173
 
168
174
  ### Option overrides: replace the matched subcommand's classification
169
175
 
@@ -174,15 +180,15 @@ The `overrides` keyword is for **options** (flags) that change the classificatio
174
180
  A flag that makes a normally safe command destructive:
175
181
 
176
182
  ```yaml
177
- # git push is WRITE, but --force makes it DANGEROUS
183
+ # git push is EXTERNAL_EFFECTS, but --force makes it DANGEROUS
178
184
  command: git
179
185
  subcommands:
180
186
  push:
181
- classification: WRITE
187
+ classification: EXTERNAL_EFFECTS
182
188
  options:
183
189
  --force: {overrides: DANGEROUS}
184
190
  -f: {overrides: DANGEROUS}
185
- --force-with-lease: {overrides: WRITE} # safer force push stays WRITE
191
+ --force-with-lease: {overrides: EXTERNAL_EFFECTS} # safer force push stays EXTERNAL_EFFECTS
186
192
  ```
187
193
 
188
194
  ```yaml
@@ -190,7 +196,7 @@ subcommands:
190
196
  command: sed
191
197
  classification: READONLY
192
198
  options:
193
- -i: {overrides: WRITE}
199
+ -i: {overrides: EXTERNAL_EFFECTS}
194
200
  ```
195
201
 
196
202
  ### Lowering classification
@@ -198,28 +204,28 @@ options:
198
204
  A flag that makes a normally writing command safe to auto-allow:
199
205
 
200
206
  ```yaml
201
- # kubectl apply is WRITE, but --dry-run only prints what would happen
207
+ # kubectl apply is EXTERNAL_EFFECTS, but --dry-run only prints what would happen
202
208
  subcommands:
203
209
  apply:
204
- classification: WRITE
210
+ classification: EXTERNAL_EFFECTS
205
211
  options:
206
212
  --dry-run: {takes_value: true, overrides: READONLY}
207
213
  ```
208
214
 
209
215
  ```yaml
210
- # tar is WRITE (creates/extracts archives), but -t only lists contents
216
+ # tar is EXTERNAL_EFFECTS (creates/extracts archives), but -t only lists contents
211
217
  command: tar
212
- classification: WRITE
218
+ classification: EXTERNAL_EFFECTS
213
219
  options:
214
220
  -t: {overrides: READONLY}
215
221
  --list: {overrides: READONLY, aliases: [-t]}
216
222
  ```
217
223
 
218
224
  ```yaml
219
- # git branch is WRITE (creates branches), but -l only lists them
225
+ # git branch is EXTERNAL_EFFECTS (creates branches), but -l only lists them
220
226
  subcommands:
221
227
  branch:
222
- classification: WRITE
228
+ classification: EXTERNAL_EFFECTS
223
229
  options:
224
230
  -l: {overrides: READONLY}
225
231
  --list: {overrides: READONLY, aliases: [-l]}
@@ -251,10 +257,10 @@ options:
251
257
 
252
258
  ```yaml
253
259
  command: sudo
254
- classification: WRITE
260
+ classification: EXTERNAL_EFFECTS
255
261
  delegates_to:
256
262
  mode: rest_are_argv
257
- min_classification: WRITE # inner command is at least WRITE
263
+ min_classification: EXTERNAL_EFFECTS # inner command is at least EXTERNAL_EFFECTS
258
264
  ```
259
265
 
260
266
  **env:** `env FOO=bar BAZ=1 python script.py` -- strips `FOO=bar BAZ=1`, inner command is `["python", "script.py"]`
@@ -331,7 +337,7 @@ options:
331
337
 
332
338
  ### `min_classification`
333
339
 
334
- Forces the inner command to be classified at least at the given level. sudo uses this to ensure that even `sudo ls` is at least WRITE -- because running anything under elevated privileges is not a no-op.
340
+ Forces the inner command to be classified at least at the given level. sudo uses this to ensure that even `sudo ls` is at least EXTERNAL_EFFECTS -- because running anything under elevated privileges is not a no-op.
335
341
 
336
342
  ## 7. Special Cases
337
343
 
@@ -339,7 +345,7 @@ Forces the inner command to be classified at least at the given level. sudo uses
339
345
 
340
346
  Some commands default to a higher classification when used without a recognized subcommand:
341
347
 
342
- - **kubectl** -- base `WRITE` (bare `kubectl` without a known subcommand should not be auto-allowed)
348
+ - **kubectl** -- base `EXTERNAL_EFFECTS` (bare `kubectl` without a known subcommand should not be auto-allowed)
343
349
  - **terraform** -- base `DANGEROUS` (unknown terraform subcommands could modify infrastructure)
344
350
  - **docker** -- no explicit base classification, so commands like `docker unknown-thing` fall through as UNKNOWN
345
351
 
@@ -367,13 +373,13 @@ The first line `# $schema: ../../../schemas/command.schema.json` is a convention
367
373
  | Pattern | Example | Classification |
368
374
  |---------|---------|---------------|
369
375
  | Pure reader | `cat`, `grep`, `ls` | READONLY |
370
- | Filter with in-place mode | `sed` base, `sed -i` | READONLY / WRITE |
371
- | File creator/modifier | `cp`, `mv`, `touch` | WRITE |
376
+ | Filter with in-place mode | `sed` base, `sed -i` | READONLY / EXTERNAL_EFFECTS |
377
+ | File creator/modifier | `cp`, `mv`, `touch` | EXTERNAL_EFFECTS |
372
378
  | Subcommand-driven | `git`, `kubectl` | Per subcommand |
373
379
  | Arbitrary code executor | `python`, `sh`, `eval` | DANGEROUS |
374
380
  | Wrapper/delegator | `sudo`, `xargs`, `env` | Delegation-based |
375
- | Lister with create mode | `git branch`, `git tag` | WRITE base, `-l` overrides to READONLY |
376
- | Dry-run capable | `make`, `kubectl apply` | WRITE base, `--dry-run` overrides to READONLY |
381
+ | Lister with create mode | `git branch`, `git tag` | EXTERNAL_EFFECTS base, `-l` overrides to READONLY |
382
+ | Dry-run capable | `make`, `kubectl apply` | EXTERNAL_EFFECTS base, `--dry-run` overrides to READONLY |
377
383
  | Network tool (read) | `curl`, `ping`, `dig` | READONLY |
378
- | Network tool (write) | `curl -d`, `wget` | WRITE |
384
+ | Network tool (write) | `curl -d`, `wget` | EXTERNAL_EFFECTS |
379
385
  | System admin | `systemctl`, `kill` | DANGEROUS |
@@ -41,7 +41,7 @@
41
41
  "$defs": {
42
42
  "Classification": {
43
43
  "type": "string",
44
- "enum": ["READONLY", "WRITE", "DANGEROUS", "UNKNOWN"],
44
+ "enum": ["READONLY", "LOCAL_EFFECTS", "EXTERNAL_EFFECTS", "DANGEROUS", "UNKNOWN"],
45
45
  "description": "Classification level for a command or option override"
46
46
  },
47
47
  "DelegationConfig": {