@gspothq/cli 0.1.1 → 0.2.0

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 (205) hide show
  1. package/README.md +58 -11
  2. package/configurations/database/postgres/manifest.toml +51 -57
  3. package/configurations/database/postgres/rules/POSTGRES.md +14 -13
  4. package/configurations/framework/astro/manifest.toml +33 -35
  5. package/configurations/framework/astro/rules/ASTRO.md +2 -2
  6. package/configurations/framework/expo/manifest.toml +19 -19
  7. package/configurations/framework/express/manifest.toml +10 -4
  8. package/configurations/framework/express/rules/EXPRESS.md +9 -7
  9. package/configurations/framework/fastapi/manifest.toml +9 -3
  10. package/configurations/framework/fastapi/rules/FASTAPI.md +19 -18
  11. package/configurations/framework/nestjs/manifest.toml +12 -12
  12. package/configurations/framework/nestjs/rules/NESTJS.md +9 -9
  13. package/configurations/framework/nextjs/manifest.toml +39 -79
  14. package/configurations/framework/nextjs/rules/NEXTJS.md +9 -7
  15. package/configurations/framework/nextjs/tool-files/semgrep/nextjs.yml +35 -0
  16. package/configurations/framework/react/manifest.toml +33 -43
  17. package/configurations/framework/react/rules/REACT.md +14 -20
  18. package/configurations/framework/react-dom/manifest.toml +6 -6
  19. package/configurations/framework/react-native/manifest.toml +11 -11
  20. package/configurations/framework/react-native/rules/REACT-NATIVE.md +3 -5
  21. package/configurations/framework/site/manifest.toml +57 -110
  22. package/configurations/framework/site/rules/SITE.md +1 -1
  23. package/configurations/framework/svelte/manifest.toml +29 -31
  24. package/configurations/framework/svelte/rules/SVELTE.md +6 -6
  25. package/configurations/framework/vue/manifest.toml +37 -37
  26. package/configurations/framework/vue/rules/VUE.md +6 -6
  27. package/configurations/general/commits/manifest.toml +20 -84
  28. package/configurations/general/commits/rules/GIT.md +24 -13
  29. package/configurations/general/coverage/manifest.toml +1 -1
  30. package/configurations/general/dependencies/manifest.toml +47 -107
  31. package/configurations/general/dependencies/rules/DEPENDENCIES.md +13 -7
  32. package/configurations/general/docs/manifest.toml +28 -43
  33. package/configurations/general/docs/rules/DOCS.md +16 -9
  34. package/configurations/general/duplication/manifest.toml +12 -14
  35. package/configurations/general/engineering/manifest.toml +6 -2
  36. package/configurations/general/engineering/rules/agent/PLANNING.md +9 -6
  37. package/configurations/general/engineering/rules/agent/SUPPRESSIONS.md +20 -11
  38. package/configurations/general/engineering/rules/agent/TASKS.md +6 -2
  39. package/configurations/general/engineering/rules/agent/WORKING.md +34 -29
  40. package/configurations/general/engineering/rules/code/ACCESSIBILITY.md +9 -7
  41. package/configurations/general/engineering/rules/code/CLI.md +12 -9
  42. package/configurations/general/engineering/rules/code/COMMENTS.md +11 -1
  43. package/configurations/general/engineering/rules/code/CONFIGURATION.md +2 -0
  44. package/configurations/general/engineering/rules/code/ERRORS.md +5 -3
  45. package/configurations/general/engineering/rules/code/HTTP.md +9 -10
  46. package/configurations/general/engineering/rules/code/LOGGING.md +10 -9
  47. package/configurations/general/engineering/rules/code/TESTING.md +18 -11
  48. package/configurations/general/files/manifest.toml +45 -73
  49. package/configurations/general/files/rules/YAML.md +20 -10
  50. package/configurations/general/format/manifest.toml +53 -106
  51. package/configurations/general/gspot/manifest.toml +19 -39
  52. package/configurations/general/gspot/rules/GENERATED.md +14 -4
  53. package/configurations/general/licenses/manifest.toml +12 -14
  54. package/configurations/general/naming/defaults.json +14 -13
  55. package/configurations/general/naming/manifest.toml +17 -17
  56. package/configurations/general/naming/rules/NAMING.md +87 -67
  57. package/configurations/general/prose/manifest.toml +166 -25
  58. package/configurations/general/prose/rules/WRITING.md +6 -2
  59. package/configurations/general/repository/manifest.toml +12 -12
  60. package/configurations/general/secrets/manifest.toml +52 -73
  61. package/configurations/general/secrets/rules/SECRETS.md +3 -1
  62. package/configurations/general/security/manifest.toml +29 -52
  63. package/configurations/general/security/rules/SECURITY.md +8 -6
  64. package/configurations/general/spelling/manifest.toml +15 -25
  65. package/configurations/general/structure/manifest.toml +31 -31
  66. package/configurations/infrastructure/ansible/manifest.toml +15 -15
  67. package/configurations/infrastructure/docker/manifest.toml +55 -67
  68. package/configurations/infrastructure/docker/rules/DOCKER.md +15 -13
  69. package/configurations/infrastructure/github-actions/manifest.toml +21 -41
  70. package/configurations/infrastructure/github-actions/rules/GITHUB-ACTIONS.md +16 -12
  71. package/configurations/infrastructure/github-actions/tool-files/zizmor.yml +5 -0
  72. package/configurations/infrastructure/nginx/manifest.toml +20 -20
  73. package/configurations/infrastructure/nginx/rules/NGINX.md +5 -4
  74. package/configurations/infrastructure/openapi/manifest.toml +20 -20
  75. package/configurations/infrastructure/openapi/rules/OPENAPI.md +3 -1
  76. package/configurations/infrastructure/xcode/manifest.toml +55 -75
  77. package/configurations/infrastructure/xcode/rules/XCODE.md +5 -3
  78. package/configurations/language/bash/manifest.toml +89 -170
  79. package/configurations/language/bash/rules/BASH.md +79 -17
  80. package/configurations/language/bash/rules/LANGUAGE.md +30 -26
  81. package/configurations/language/bash/rules/OPERATIONS.md +10 -8
  82. package/configurations/language/bash/rules/SAFETY.md +39 -35
  83. package/configurations/language/css/manifest.toml +22 -61
  84. package/configurations/language/css/rules/CSS.md +21 -2
  85. package/configurations/language/css/rules/TAILWIND.md +7 -5
  86. package/configurations/language/html/manifest.toml +16 -35
  87. package/configurations/language/html/rules/HTML.md +23 -5
  88. package/configurations/language/javascript/eslint-runtime-names.json +66 -61
  89. package/configurations/language/javascript/eslint-runtime.mjs +118 -0
  90. package/configurations/language/javascript/manifest.toml +135 -207
  91. package/configurations/language/javascript/rules/BUN.md +2 -2
  92. package/configurations/language/javascript/rules/COMPONENTS.md +4 -4
  93. package/configurations/language/javascript/rules/DENO.md +1 -1
  94. package/configurations/language/javascript/rules/JAVASCRIPT.md +30 -17
  95. package/configurations/language/javascript/rules/NODE.md +2 -2
  96. package/configurations/language/javascript/rules/PLAYWRIGHT.md +10 -9
  97. package/configurations/language/markdown/manifest.toml +20 -51
  98. package/configurations/language/python/manifest.toml +231 -342
  99. package/configurations/language/python/rules/DESIGN.md +40 -33
  100. package/configurations/language/python/rules/FLOW.md +8 -6
  101. package/configurations/language/python/rules/PACKAGING.md +14 -7
  102. package/configurations/language/python/rules/PYTHON.md +83 -33
  103. package/configurations/language/python/rules/TYPING.md +25 -23
  104. package/configurations/language/sql/manifest.toml +19 -37
  105. package/configurations/language/sql/rules/SQL.md +90 -2
  106. package/configurations/language/swift/manifest.toml +67 -78
  107. package/configurations/language/swift/rules/SWIFT.md +138 -40
  108. package/configurations/language/swift/rules/SWIFTUI.md +11 -9
  109. package/configurations/language/swift/rules/UIKIT.md +21 -19
  110. package/configurations/language/typescript/manifest.toml +34 -34
  111. package/configurations/language/typescript/rules/TYPESCRIPT.md +62 -10
  112. package/configurations/library/drizzle/manifest.toml +25 -38
  113. package/configurations/library/drizzle/rules/DRIZZLE.md +4 -2
  114. package/configurations/library/react-hook-form/manifest.toml +4 -4
  115. package/configurations/library/react-hook-form/rules/REACT-HOOK-FORM.md +15 -13
  116. package/configurations/library/tanstack-query/manifest.toml +6 -6
  117. package/configurations/library/tanstack-query/rules/TANSTACK-QUERY.md +5 -3
  118. package/configurations/library/testing-library/manifest.toml +6 -6
  119. package/configurations/library/translations/manifest.toml +22 -22
  120. package/configurations/library/translations/rules/NEXT-INTL.md +6 -8
  121. package/configurations/library/translations/rules/TRANSLATIONS.md +22 -23
  122. package/configurations/library/trpc/manifest.toml +3 -3
  123. package/configurations/library/trpc/rules/TRPC.md +8 -6
  124. package/configurations/library/zod/manifest.toml +6 -6
  125. package/configurations/library/zod/rules/ZOD.md +4 -2
  126. package/configurations/library/zustand/manifest.toml +4 -4
  127. package/configurations/library/zustand/rules/ZUSTAND.md +16 -14
  128. package/configurations/platform/cloudflare/manifest.toml +28 -47
  129. package/configurations/platform/cloudflare/rules/CLOUDFLARE.md +11 -6
  130. package/configurations/platform/supabase/manifest.toml +48 -61
  131. package/configurations/platform/supabase/rules/SUPABASE.md +27 -28
  132. package/configurations/testing/jest/manifest.toml +21 -22
  133. package/configurations/testing/pytest/manifest.toml +28 -28
  134. package/configurations/testing/swift-tests/manifest.toml +18 -29
  135. package/configurations/testing/swift-tests/rules/SWIFT-TESTS.md +7 -5
  136. package/configurations/testing/vitest/manifest.toml +32 -32
  137. package/configurations/testing/vitest/rules/VITEST.md +3 -1
  138. package/dist/gspot.js +22297 -24707
  139. package/dist/{linkinator-program.js → linkinator-process.js} +5 -7
  140. package/dist/{purgecss-program.js → purgecss-process.js} +12 -12
  141. package/package.json +7 -9
  142. package/configurations/infrastructure/github-actions/zizmor.yml +0 -3
  143. package/configurations/language/bash/rules/NAMING.md +0 -62
  144. package/configurations/language/css/rules/NAMING.md +0 -17
  145. package/configurations/language/html/rules/NAMING.md +0 -16
  146. package/configurations/language/javascript/rules/NAMING.md +0 -31
  147. package/configurations/language/markdown/markdownlint-cli2.mjs +0 -19
  148. package/configurations/language/python/rules/NAMING.md +0 -53
  149. package/configurations/language/sql/rules/NAMING.md +0 -90
  150. package/configurations/language/swift/rules/NAMING.md +0 -88
  151. package/configurations/language/typescript/rules/NAMING.md +0 -73
  152. package/configurations/library/swift-snapshot-testing/manifest.toml +0 -32
  153. package/dist/THIRD-PARTY-NOTICES.md +0 -25
  154. package/dist/eslint-rule-levels.json +0 -10
  155. package/dist/tool-versions.json +0 -3
  156. package/gspot.schema.json +0 -45991
  157. /package/{dist/LICENSE.md → LICENSE.md} +0 -0
  158. /package/configurations/framework/express/{semgrep → tool-files/semgrep}/express.yml +0 -0
  159. /package/configurations/framework/fastapi/{semgrep → tool-files/semgrep}/fastapi.yml +0 -0
  160. /package/configurations/general/dependencies/{syncpack.json → tool-files/syncpack.json} +0 -0
  161. /package/configurations/general/prose/{vale → tool-files/vale}/styles/LICENSES.md +0 -0
  162. /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/ampm.yml +0 -0
  163. /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/conscious-language.yml +0 -0
  164. /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/contractions.yml +0 -0
  165. /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/currency.yml +0 -0
  166. /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/dashes.yml +0 -0
  167. /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/date-ordinals.yml +0 -0
  168. /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/dates.yml +0 -0
  169. /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/file-paths.yml +0 -0
  170. /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/future.yml +0 -0
  171. /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/hedging.yml +0 -0
  172. /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/history.yml +0 -0
  173. /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/idioms.yml +0 -0
  174. /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/interface-verbs.yml +0 -0
  175. /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/jargon.yml +0 -0
  176. /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/leading-conjunctions.yml +0 -0
  177. /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/link-text.yml +0 -0
  178. /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/locations.yml +0 -0
  179. /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/marketing.yml +0 -0
  180. /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/merge-conflict-markers.yml +0 -0
  181. /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/militaristic-language.yml +0 -0
  182. /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/modals.yml +0 -0
  183. /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/numbered-units.yml +0 -0
  184. /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/obvious-fields.yml +0 -0
  185. /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/ordinal-adverbs.yml +0 -0
  186. /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/percentage-numbers.yml +0 -0
  187. /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/placeholders.yml +0 -0
  188. /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/product-centric-writing.yml +0 -0
  189. /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/punctuated-headings.yml +0 -0
  190. /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/self-reference.yml +0 -0
  191. /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/session-identifiers.yml +0 -0
  192. /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/since.yml +0 -0
  193. /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/straight-quotes.yml +0 -0
  194. /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/suspended-hyphens.yml +0 -0
  195. /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/symbols.yml +0 -0
  196. /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/time-ranges.yml +0 -0
  197. /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/url-prepositions.yml +0 -0
  198. /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/version-range.yml +0 -0
  199. /package/configurations/general/secrets/{gitleaks.toml → tool-files/gitleaks.toml} +0 -0
  200. /package/configurations/infrastructure/docker/{trivyignore → tool-files/trivyignore} +0 -0
  201. /package/configurations/language/bash/{semgrep → tool-files/semgrep}/bash.yml +0 -0
  202. /package/configurations/language/javascript/{semgrep → tool-files/semgrep}/javascript.yml +0 -0
  203. /package/configurations/language/swift/{semgrep → tool-files/semgrep}/swift.yml +0 -0
  204. /package/configurations/platform/cloudflare/{semgrep → tool-files/semgrep}/cloudflare.yml +0 -0
  205. /package/configurations/platform/supabase/{semgrep → tool-files/semgrep}/supabase.yml +0 -0
@@ -1,8 +1,9 @@
1
1
  allowed_words = ["Bash", "ShellCheck", "tree-sitter"]
2
+
2
3
  [configuration]
3
- title = "Bash, zsh, and Bats scripts"
4
+ description = "Shell scripts, hooks, and task files: ShellCheck, shfmt, a syntax check, and the structure rules for shell."
4
5
  suggests = []
5
- description = "Shell scripts, hooks and task files: ShellCheck, shfmt, a syntax pass, and the structure rules for shell."
6
+ title = "Bash, zsh, and Bats scripts"
6
7
 
7
8
  [detect]
8
9
  extensions = [".sh", ".bash", ".zsh", ".bats"]
@@ -12,250 +13,168 @@ shebangs = ["shell"]
12
13
  extensions = [".sh", ".bash", ".zsh", ".bats"]
13
14
  tags = ["shebang:shell"]
14
15
 
16
+ [[naming.override]]
17
+ allowed_names = ["IFS", "OPTARG", "OPTIND", "EUID", "UID", "PPID", "RANDOM", "SECONDS", "LINENO", "BASH_SOURCE", "BASH_REMATCH", "BASH_VERSION", "BASH_VERSINFO", "FUNCNAME", "PIPESTATUS", "REPLY", "CDPATH", "OLDPWD", "SHLVL", "HOSTNAME", "OSTYPE", "MACHTYPE", "COLUMNS", "LINES"]
18
+ categories = ["variables"]
19
+ languages = ["bash"]
20
+ paths = ["**"]
21
+ reason = "The shell defines these variable names."
22
+
23
+ [[naming.override]]
24
+ categories = ["functions", "variables", "constants", "methods", "attributes"]
25
+ ignored_prefix = "^_+"
26
+ languages = ["bash"]
27
+ paths = ["**"]
28
+ reason = "A leading underscore marks a private shell declaration."
29
+
15
30
  [[tool]]
31
+ apt = "shellcheck"
32
+ brew = "shellcheck"
33
+ github = { name = "koalaman/shellcheck", version = "v0.11.0" }
34
+ mise = "shellcheck"
16
35
  name = "shellcheck"
17
36
  refused_options = [
18
37
  { paths = ["enable", "disable"], message = "ShellCheck rule selection comes from level recommended or all and gspot ignore. Remove enable and disable from tools.shellcheck.extra.value." },
19
38
  ]
39
+ replace = [".shellcheckrc"]
20
40
  rule_url = "https://www.shellcheck.net/wiki/{rule}"
41
+ scoop = "shellcheck"
21
42
  suppression = { marker = "\\bshellcheck +disable=", reason = "# ?reason: (?<reason>\\S.*)" }
22
43
  version = "0.11.0"
23
- mise = "shellcheck"
24
- brew = "shellcheck"
25
- apt = "shellcheck"
26
44
  winget = "koalaman.shellcheck"
27
- scoop = "shellcheck"
28
- github = { name = "koalaman/shellcheck", version = "v0.11.0" }
29
- [[tool.replace]]
30
- file = ".shellcheckrc"
31
- shared = false
32
45
 
33
46
  [[tool]]
47
+ brew = "shfmt"
48
+ github = { name = "mvdan/sh", version = "v3.12.0" }
49
+ mise = "shfmt"
34
50
  name = "shfmt"
51
+ scoop = "shfmt"
35
52
  version = "3.12.0"
36
- mise = "shfmt"
37
- brew = "shfmt"
38
53
  winget = "mvdan.shfmt"
39
- scoop = "shfmt"
40
- github = { name = "mvdan/sh", version = "v3.12.0" }
41
-
42
- [[tool]]
43
- name = "bash"
44
- min_version = "4.4"
45
- install_hint = "install Bash 4.4 or newer, such as with brew install bash on macOS"
46
- system = true
47
- version_command = ["--version"]
48
- version_pattern = "version (\\d+\\.\\d+(?:\\.\\d+)?)"
49
54
 
50
55
  [[tool]]
51
56
  name = "zsh"
52
57
  system = true
53
58
 
54
- [[tool]]
55
- name = "bats"
56
- system = true
57
-
58
59
  [[tool_file]]
60
+ per_scope = true
59
61
  target = ".gspot/config/shellcheckrc"
60
62
  tool = "shellcheck"
61
- rule_keys = ["enable", "disable"]
62
- per_scope = true
63
63
 
64
- [[check]]
65
- name = "bash-syntax"
66
- example = "In `greet.sh`, `if then` has no condition. Use a real test: `if [ -f \"$file\" ]; then printf \"%s\\n\" \"$file\"; fi`, then rerun the syntax check."
67
- level = "recommended"
68
- stage = "commit"
69
- command = ["bash", "-n", "{file}"]
70
- files = { tags = ["bash"] }
71
- output = { format = "regex", pattern = "^.*: line (?<line>\\d+): (?<message>.*)$" }
72
- summary = "Checks that Bash scripts parse before running them."
73
- why = "A script with a syntax error stops at the broken line, sometimes after it has already done half its work."
74
- help = "Open the file at the line bash names and fix the quoting, bracket, or keyword it complains about."
64
+ [[tool_file]]
65
+ per_scope = true
66
+ target = ".gspot/config/semgrep/bash.yml"
67
+ tool = "semgrep"
68
+ when = { configuration = "security" }
75
69
 
76
70
  [[check]]
77
- name = "zsh-syntax"
78
- example = "In a Zsh script, `if then` has no condition. Use a real test: `if [[ -f \"$file\" ]]; then print -r -- \"$file\"; fi`, then rerun with Zsh installed."
79
- level = "recommended"
80
- stage = "commit"
81
71
  command = ["zsh", "-n", "{file}"]
72
+ example = "In a Zsh script, `if then` has no condition. Write a real test, such as `if [[ -f \"$file\" ]]; then print -r -- \"$file\"; fi`."
82
73
  files = { tags = ["zsh"] }
83
- output = { format = "regex", pattern = "^(?<file>[^:]+):(?<line>\\d+):\\s*(?<message>.*)$" }
84
- summary = "Checks Zsh syntax with the Zsh interpreter."
85
- why = "Zsh syntax differs from Bash, so a Bash parser rejects valid Zsh programs."
86
- help = "Fix the syntax at the line Zsh reports. Install zsh when the command is missing."
87
-
88
- [[check]]
89
- name = "bats-syntax"
90
- example = "In a `.bats` file, an unterminated `@test \"greeting\" {` block fails parsing. Complete the test body and close it with `}` before rerunning with Bats installed."
74
+ help = "Fix the syntax at the line Zsh reports. Install zsh when the system lacks it."
91
75
  level = "recommended"
76
+ name = "zsh-syntax"
77
+ output = { format = "regex", pattern = "^(?<file>[^:]+):(?<line>\\d+):\\s*(?<message>.*)$" }
92
78
  stage = "commit"
93
- command = ["bats", "--count", "{file}"]
94
- # Bats parses the file with the bash on PATH, and under Bash 3.2 it passes a file that does not parse.
95
- tool = ["bats", "bash"]
96
- files = { tags = ["bats"] }
97
- output = { format = "regex", pattern = "^# (?<file>[^:]+): line (?<line>\\d+): (?<message>.*)$" }
98
- summary = "Parses Bats test files while counting their test cases."
99
- why = "Bats test declarations need the Bats parser before Bash can read them."
100
- help = "Fix the syntax reported by Bats. Install bats-core when the command is missing."
79
+ summary = "Checks the syntax of Zsh scripts with `zsh -n`."
80
+ why = "Zsh syntax differs from Bash, so a Bash parser rejects valid Zsh programs."
101
81
 
102
82
  [[check]]
103
- name = "shellcheck"
104
- example = "In a Bash script, `printf \"%s\\n\" $name` reports SC2086 because the expansion permits word splitting and globbing. Use `printf \"%s\\n\" \"$name\"` to pass the value as one argument."
105
- level = "recommended"
106
- stage = "commit"
107
- files = { tags = ["bash", "sh", "bats"] }
108
83
  command = ["shellcheck", "--rcfile", "{tool_file:shellcheckrc}", "--severity=style", "--check-sourced", "--format=gcc", "{files}"]
84
+ example = "In a Bash script, `printf \"%s\\n\" $name` reports SC2086 because the expansion permits word splitting and globbing. Use `printf \"%s\\n\" \"$name\"` to pass the value as one argument."
109
85
  exit_codes = [1]
86
+ files = { tags = ["bash", "sh", "bats"] }
87
+ help = "Read the SC code in the finding; `gspot explain shellcheck/SC2086` links to its page. Turn one code off with `gspot ignore bash/shellcheck --rule SC2086 --reason \"<why>\"`."
88
+ level = "recommended"
89
+ name = "shellcheck"
110
90
  output = { format = "regex", pattern = "^(?<file>[^:]+):(?<line>\\d+):(?<column>\\d+): (?:note|warning|error|style): (?<message>.*?) \\[(?<rule>SC\\d+)\\]$" }
111
- summary = "Runs ShellCheck over shell scripts and sourced files, adding stable optional ShellCheck rules at level all."
112
- why = "ShellCheck catches the quoting, globbing and exit-status mistakes that make shell scripts fail on the one input nobody tested."
113
- help = "Read the SC code in the finding; gspot explain shellcheck/SC2086 links to its page. Turn one code off with gspot ignore bash/shellcheck --rule SC2086 --reason \"`<why>`\"."
91
+ stage = "commit"
92
+ summary = "Runs ShellCheck over shell scripts and sourced files, with its stable optional rules at level all."
93
+ why = "ShellCheck catches the quoting, globbing, and exit-status mistakes that make a script fail on the one input nobody tested."
114
94
 
115
95
  [[check]]
116
- name = "shfmt"
117
- example = "In a script, `if true;then echo hello;fi` differs from shfmt output. Run the configured formatter to add the required spacing and block layout, then rerun the check."
118
- level = "recommended"
119
- stage = "commit"
120
- files = { tags = ["bash", "sh", "bats"] }
121
96
  command = ["shfmt", "-d", "-i", "{indent}", "-ci", "-s", "{files}"]
97
+ example = "`if true;then echo hello;fi` differs from the shfmt output. Run `gspot check --fix` to add the spacing and line breaks."
98
+ files = { tags = ["bash", "sh", "bats"] }
122
99
  fix = ["shfmt", "-w", "-i", "{indent}", "-ci", "-s", "{files}"]
100
+ help = "Run `gspot check --fix` to format every script, or `shfmt -w` on one file."
101
+ level = "recommended"
102
+ name = "shfmt"
123
103
  output = { format = "regex", pattern = "^(?:--- )?(?<file>.+?)(?:\\.orig$|:(?<line>\\d+):(?<column>\\d+): (?<message>.+)$)", message = "This script is not formatted the way shfmt formats it.", fixable = "^--- " }
124
- summary = "Checks that every shell script is formatted the way shfmt formats it."
104
+ stage = "commit"
105
+ summary = "Checks that every shell script matches the shfmt output."
125
106
  why = "One formatter means diffs show what changed, not how someone's editor indents."
126
- help = "Run gspot check --fix to format every script, or shfmt -w on one file."
127
107
 
128
108
  [[check]]
129
- name = "script-layout"
130
- example = "An executable Bash script without `set -euo pipefail` reports a strict-mode finding. Put strict mode before the first command and, when the script defines functions, call the documented `main` function last."
109
+ example = "An executable Bash script without `set -euo pipefail` reports a finding. Put that line before the first command, and when the script defines functions, call `main` last."
110
+ help = "Open with the shebang and run `set -euo pipefail` first. When an executable defines functions, end it with a call to main."
131
111
  level = "all"
112
+ name = "script-layout"
132
113
  stage = "commit"
133
- summary = "Checks the contract every Bash script keeps: the shebang, strict mode, a main called last when functions are defined, declarative libraries, cleaned-up temporary files."
134
- why = "A script that skips strict mode or hides its runtime fails on the machine where it matters, silently."
135
- help = "Open with the shebang and run set -euo pipefail first. When an executable defines functions, end it with a call to main."
114
+ summary = "Checks each Bash script for a shebang, strict mode, a final `main` call when it defines functions, declarative libraries, and removed temporary files."
115
+ why = "A script that skips strict mode or hides its runtime fails silently on the machine that matters."
136
116
 
137
117
  [[check]]
138
- name = "doc-comments"
139
- example = "A function `_quiet() { printf \"%s\\n\" \"Hello\"; }` without a preceding description reports a missing comment. Add `# _quiet: prints a greeting.` immediately above the function. Other structural checks still apply."
118
+ example = "`_quiet() { printf \"%s\\n\" \"Hello\"; }` with no comment above it reports a finding. Add `# _quiet: prints a greeting.` on the line above."
119
+ help = "Write \"# name: what it does\" above the function; when it carries doc sections, keep Globals, Arguments, Outputs, and Returns in that order."
140
120
  level = "all"
121
+ name = "doc-comments"
141
122
  stage = "commit"
142
- summary = "Checks that every shell function announces itself in a comment above it that says more than its name."
143
- why = "A function with no summary makes the reader run it in their head; a vague summary is a summary nobody will trust."
144
- help = "Write \"# name: what it does\" above the function; when it carries doc sections, keep Globals, Arguments, Outputs, Returns in that order."
123
+ summary = "Checks that each shell function has a comment above it that says more than its name."
124
+ why = "Without a summary, the reader must trace the function in their head, and nobody trusts a vague one."
145
125
 
146
126
  [[check]]
147
- name = "unused-functions"
148
- example = "A private shell function `_orphan` that no script calls reports an unused-function finding. Delete the unused definition. If an external hook requires the function, record that contract with `gspot ignore` and a reason."
127
+ example = "A private function `_orphan` that no script calls reports a finding. Delete it, or record a hook that needs it with `gspot ignore` and a reason."
128
+ help = "Delete the function. For a required hook entry point, run `gspot ignore bash/unused-functions --paths <file> --reason <why>`."
149
129
  level = "recommended"
150
- stage = "commit"
130
+ name = "unused-functions"
151
131
  runs = "scope"
132
+ stage = "commit"
152
133
  summary = "Finds shell functions no script in the scope calls."
153
- why = "ShellCheck checks one file; this check follows calls across every script in the scope."
154
- help = "Delete the function. For a required hook entry point, run `gspot ignore bash/unused-functions --paths <file> --reason <why>`."
134
+ why = "ShellCheck works on one file at a time, while this check follows calls across the whole scope."
155
135
 
156
136
  [[check]]
157
- name = "unread-arguments"
158
- example = "A call `_deaf one two` whose function never reads positional arguments reports dead parameters. If both arguments are unnecessary, remove them from the call. Otherwise read and use the arguments in the function."
137
+ example = "`_deaf one two` reports dead parameters when `_deaf` never reads its arguments. Drop them from the call, or use them in the function."
138
+ help = "Read the argument in the function, or drop it from every call."
159
139
  level = "recommended"
160
- stage = "commit"
140
+ name = "unread-arguments"
161
141
  runs = "scope"
162
- summary = "Finds shell functions called with arguments they never read."
163
- why = "A caller that passes an argument expects it to affect the result. An unread argument misleads the caller."
164
- help = "Read the argument in the function, or drop it from every call."
165
-
166
- [[check]]
167
- name = "source-order"
168
- example = "A block of `source` lines that is not ordered shortest first reports the sorted order. Reorder the lines."
169
- level = "all"
170
142
  stage = "commit"
171
- summary = "Checks that the source statements of a block go shortest first, alphabetically among lines of one length."
172
- why = "One order for every block means a reader never searches it."
173
- help = "Reorder the source statements shortest first."
143
+ summary = "Finds shell functions called with arguments they never read."
144
+ why = "An unread argument misleads the caller, who expects it to change the result."
174
145
 
175
146
  [[check]]
176
- name = "private-prefix"
177
- example = "A function `build_it` called only from its own script reports a missing private prefix. Rename it `_build_it` and update each local call. A function called from another script keeps its public name."
147
+ example = "A function over `limits.bash.function_lines` reports `function-lines` with the count and the limit. Split it by job."
148
+ help = "Split the script or the function, or raise the named limits.bash setting with a reason."
178
149
  level = "all"
179
- stage = "commit"
180
- runs = "scope"
181
- summary = "Checks that a shell function no other file calls starts with an underscore, and that an underscore function is not called from outside."
182
- why = "The underscore tells a reader what the file publishes without reading every caller."
183
- help = "Rename the function with a leading underscore, or drop the underscore when another file calls it."
184
-
185
- [[check]]
186
150
  name = "function-size"
187
- example = "A function over `limits.bash.function_lines`, `limits.bash.branches`, `limits.bash.nesting`, or `limits.bash.assignments` reports the count and the ceiling under `function-lines`, `branches`, `nesting`, or `assignments`. Split the function by the job, then rerun."
188
- level = "all"
189
151
  stage = "commit"
190
152
  summary = "Checks every shell function against the size ceilings: code lines, branches, nesting, and assignments."
191
- why = "A long script holds more than one job, and a function with many branches or assignments is several functions with one name."
192
- help = "Split the script or the function, or raise the named limits.bash setting with a reason."
153
+ why = "A long script holds more than one job, and many branches or assignments hide several functions under one name."
193
154
 
194
155
  [[check]]
195
- name = "wrappers"
196
- example = "A short launcher containing only `exec bash scripts/build.sh` reports a forwarding-wrapper finding. Invoke `scripts/build.sh` directly and remove the redundant launcher."
156
+ example = "A launcher that holds only `exec bash scripts/build.sh` reports a finding. Call `scripts/build.sh` directly, and delete the launcher."
157
+ help = "Call the target script directly and delete the redundant launcher or alias."
197
158
  level = "all"
159
+ name = "forwarders"
198
160
  stage = "commit"
199
161
  summary = "Finds forwarding wrappers and compatibility aliases in shell scripts."
200
- why = "A wrapper is a second entry point that drifts from the first."
201
- help = "Call the target script directly and delete the redundant launcher or alias."
162
+ why = "A wrapper adds a second entry point, and the two drift apart."
202
163
 
203
164
  [[check]]
204
- name = "inline-scripts"
205
165
  example = "A shell heredoc containing a Python program reports embedded code. Move the program into a `.py` file and invoke that file from the shell script."
206
- level = "all"
207
- stage = "commit"
208
- summary = "Finds inline Python, Node, and generated-script heredocs in shell scripts."
209
- why = "Code inside a heredoc is checked by nothing."
210
166
  help = "Move the embedded code into its own file and run that."
211
-
212
- [[check]]
213
- name = "ssh-blocks"
214
- example = "An anonymous SSH heredoc reports a remote-block finding. Move the remote operation into a named function with a comment that explains its purpose, then invoke that function through the supported remote call."
215
- level = "all"
216
- stage = "commit"
217
- summary = "Checks that ssh heredocs are documented."
218
- why = "A remote command block with no name is the hardest thing in a script to test or reuse."
219
- help = "Move the block into a named function and write \"# name: what it does\" above it."
220
-
221
- [[check]]
222
- name = "variable-defaults"
223
- example = "A default such as `${PORT:-8080}` outside a declared configuration owner reports a misplaced default. Set the default in a path listed in `architecture.roles.env` and consume the configured value elsewhere."
224
- level = "all"
225
- stage = "commit"
226
- summary = "Finds variable defaults of the form name:-value outside the configuration owners."
227
- why = "A default set in three places is three settings with one name."
228
- help = "Set the default in a configuration owner named under architecture.roles.env and read the variable everywhere else."
229
-
230
- [[check]]
231
- name = "include-guards"
232
- example = "A sourced configuration owner without a load guard reports a guard finding. Begin it with `[[ -n ${APP_CONFIG_READY:-} ]] && return 0`, followed by `readonly APP_CONFIG_READY=1`, before declaring its configuration."
233
167
  level = "all"
168
+ name = "inline-scripts"
234
169
  stage = "commit"
235
- runs = "scope"
236
- summary = "Checks that each script in architecture.roles.env starts with an include guard of its own."
237
- why = "A configuration file sourced twice runs its side effects twice."
238
- help = "Open the owner with [[ -n ${NAME_READY:-} ]] && return 0 and then readonly NAME_READY=1, with a guard name no other owner uses."
170
+ summary = "Finds inline Python, Node, and generated-script heredocs in shell scripts."
171
+ why = "No check reads code inside a heredoc."
239
172
 
240
173
  [[check]]
241
- name = "safety"
242
- example = "A `killall worker` command reports broad-kill. Target the specific process, or ignore that rule for the owning script with a reason."
174
+ example = "`killall worker` reports `broad-kill`. Stop the one process by its ID, or ignore the rule for that script with a reason."
175
+ help = "Handle the failure. For required cleanup, use `gspot ignore bash/safety` with `--rule`, `--paths`, and `--reason`."
243
176
  level = "recommended"
177
+ name = "safety"
244
178
  stage = "commit"
245
- summary = "Finds broad process kills and recursive deletes, except traps that remove mktemp paths. At all, it also reports discarded failures."
246
- why = "Each of these is a way a script destroys something it did not mean to and says nothing."
247
- help = "Handle the failure. For required cleanup, use gspot ignore bash/safety with --rule, --paths and --reason."
248
-
249
- [[naming.override]]
250
- paths = ["**"]
251
- languages = ["bash"]
252
- categories = ["variables"]
253
- allowed_names = ["IFS", "OPTARG", "OPTIND", "EUID", "UID", "PPID", "RANDOM", "SECONDS", "LINENO", "BASH_SOURCE", "BASH_REMATCH", "BASH_VERSION", "BASH_VERSINFO", "FUNCNAME", "PIPESTATUS", "REPLY", "CDPATH", "OLDPWD", "SHLVL", "HOSTNAME", "OSTYPE", "MACHTYPE", "COLUMNS", "LINES"]
254
- reason = "The shell defines these variable names."
255
-
256
- [[naming.override]]
257
- paths = ["**"]
258
- languages = ["bash"]
259
- categories = ["functions", "variables", "constants", "methods", "attributes"]
260
- ignored_prefix = "^_+"
261
- reason = "A leading underscore marks a private shell declaration."
179
+ summary = "Finds broad process kills and recursive deletes, except traps that remove mktemp paths. At level all, also reports discarded failures."
180
+ why = "Each of these lets a script silently destroy something by mistake."
@@ -1,17 +1,18 @@
1
1
  # Bash
2
2
 
3
+ Read this rule when you write or change a shell script.
4
+
3
5
  ## Bash scope
4
6
 
5
7
  <!-- level: all -->
6
8
 
7
- Bash orchestrates commands as glue code and does not build application logic. Prefer
8
- small scripts with explicit inputs, explicit outputs, and clear failure behavior.
9
+ Use Bash as glue code that runs commands, not for application logic. Prefer
10
+ small scripts with explicit inputs and outputs and clear failure behavior.
9
11
  Treat every path, argument, environment value, command output, and user input as unsafe until
10
12
  quoted, validated, or parsed by a structured tool. A script that builds, deploys, deletes, or
11
13
  publishes artifacts makes its inputs, effects, and failure behavior explicit.
12
14
 
13
- `set -euo pipefail` does not replace checks around dangerous commands. Keep CI YAML and package
14
- scripts short. Put longer logic in a script file the checks can lint.
15
+ `set -euo pipefail` does not replace checks around dangerous commands.
15
16
 
16
17
  ## Bash tasks
17
18
 
@@ -20,7 +21,7 @@ scripts short. Put longer logic in a script file the checks can lint.
20
21
  Use Bash when the script mostly calls other command-line tools or wires together
21
22
  installation, lint, build, runtime, publication, or cleanup steps. Use it to validate the
22
23
  environment and dispatch to project commands, or for file movement, process checks,
23
- and retry loops. Do not use Bash for business logic or complex text parsing. Do not use it
24
+ and retry loops. Do not use Bash for complex text parsing. Do not use it
24
25
  for JSON, YAML, XML, or HTML transformations beyond extraction with a dedicated
25
26
  parser. Large mutable data structures, long-lived daemons, high-performance work,
26
27
  security-sensitive parsing of untrusted input, and behavior that needs typed contracts belong
@@ -40,22 +41,22 @@ for cleanup that can run safely on the relevant exit path.
40
41
 
41
42
  <!-- level: all -->
42
43
 
43
- An executable script finishes with a meaningful status: a final diagnostic command, a false
44
- condition, or an optional cleanup check must not become the script status by accident. Use
44
+ End an executable script with a meaningful status. A final message, a false condition, or an
45
+ optional cleanup check must not set the exit status by accident. Use
45
46
  an explicit `exit 0` only when the final command's status differs from the program result and
46
47
  prior checks establish success.
47
48
 
48
49
  ## Options
49
50
 
50
- Use `errexit` as a backstop rather than control flow: it has exceptions in
51
- conditionals, pipelines, command substitutions, subshells, and functions, so `cd`, `rm`,
52
- check builds, publishing, uploads, and destructive commands explicitly. Do not toggle
51
+ Use `errexit` as a backstop, not as control flow. It does not stop the script inside conditionals, pipelines,
52
+ command substitutions, subshells, and some functions. Check the status of `cd`, `rm`, builds, publishing, uploads,
53
+ and destructive commands explicitly. Do not toggle
53
54
  options around a small operation without restoring the prior state. Do not change `IFS` as a
54
55
  strict-mode ritual; set it locally where reading or joining data requires it. Keep `set -x`
55
56
  out of committed code.
56
57
 
57
- A function called in
58
- `if`, `while`, `&&`, or `||` writes its checks inside because `errexit` does not stop it there:
58
+ Put explicit checks inside a function called in `if`, `while`, `&&`, or `||`, because `errexit`
59
+ does not stop it there:
59
60
 
60
61
  ```bash
61
62
  # cleanup: Removes the contents of the target directory.
@@ -67,16 +68,16 @@ cleanup() {
67
68
  }
68
69
  ```
69
70
 
70
- Process substitution hides producer failures; when the producer matters, write to a checked
71
- temporary file, use `PIPESTATUS`, or call the producer separately. Under `pipefail`, short
72
- readers such as `head` and `grep -q` can make the producer see SIGPIPE and fail the pipeline;
73
- consume input predictably or handle the known status. Under `nounset`, use `${name:-}` where
71
+ Process substitution hides producer failures. When the producer status matters, write to a checked
72
+ temporary file, use `PIPESTATUS`, or call it separately. Under `pipefail`, short
73
+ readers such as `head` and `grep -q` can make the producer receive SIGPIPE and fail the pipeline.
74
+ Consume input predictably or handle the known status. Under `nounset`, use `${name:-}` where
74
75
  the public interface accepts an unset variable and `${name:?message}` for a required value at a clear
75
76
  boundary. Use `${1:-}` where callers can omit an argument.
76
77
 
77
78
  ## Output, logging, and errors
78
79
 
79
- Write diagnostics with `printf`, not `echo`. Return or exit with the intended status: printing
80
+ Write messages with `printf`, not `echo`. Return or exit with the intended status: printing
80
81
  an error does not make a command fail.
81
82
 
82
83
  ```bash
@@ -104,3 +105,64 @@ log_status() {
104
105
 
105
106
  A public, sourced, complex, or risky function carries the full header with `Globals`,
106
107
  `Arguments`, `Outputs`, and `Returns` sections.
108
+
109
+ ## Naming
110
+
111
+ ### Bash case rules
112
+
113
+ <!-- level: all -->
114
+
115
+ - Executable scripts use `.sh` when invoked through a task runner or build
116
+ rules.
117
+ - Omit the extension on an executable script only when users invoke it
118
+ as a command on `PATH`.
119
+ - Give sourced libraries the `.sh` extension and no executable permission.
120
+ - Numbered pipeline steps use `NN-description.sh`, such as `01-install.sh`; the naming check strips
121
+ the numeric prefix before it matches the stem.
122
+ - Functions and mutable variables use `lower_snake_case`.
123
+ - Use `::` in package-like function prefixes only when a script family already
124
+ follows that convention.
125
+
126
+ | Avoid | Prefer | Meaning |
127
+ | ------------------ | -------------------------- | -------------------- |
128
+ | `DeployScript.sh` | `deploy-api.sh` | API deployment. |
129
+ | `deploy_script.sh` | `upload-storage-assets.sh` | Storage upload. |
130
+ | `helpers.sh` | `archive-reports` | A command on `PATH`. |
131
+
132
+ Name a deployment function `deploy_api` and its local target
133
+ `target_environment`.
134
+
135
+ ### Bash variables
136
+
137
+ <!-- level: all -->
138
+
139
+ - Name each loop variable after its item.
140
+ - Name a temporary path after what it holds, such as `download_file` or `work_dir`; `tmp` and
141
+ `temp` say nothing.
142
+ - Avoid vague names when the domain has a term.
143
+ - Avoid reserved and special shell names for unrelated values.
144
+
145
+ Use `migration_file` when iterating migration paths and `report_output` for
146
+ captured report text. Check the report command's exit status before printing
147
+ the captured output. A loop variable such as `i` does not describe a file.
148
+
149
+ ### Bash functions
150
+
151
+ <!-- level: all -->
152
+
153
+ - Give a function with side effects a verb-phrase name.
154
+ - Name a function that writes to `stdout` for the data it prints.
155
+ - Do not name scripts or functions after shell builtins or common commands.
156
+ - Do not make function names so generic that logs and stack traces lose context.
157
+
158
+ Name a port validator `validate_port`. Avoid `test`, which shadows a shell builtin, and
159
+ generic names such as `run` or `process`.
160
+
161
+ ### Files
162
+
163
+ <!-- level: all -->
164
+
165
+ - Put a related script family in its own subfolder, and give its files role names such as
166
+ `main.sh`, `state.sh`, `query.sh`, or `report.sh`.
167
+ - Hook families may use a dynamic shared prefix when an external interface owns those
168
+ filenames.
@@ -1,18 +1,20 @@
1
1
  # Bash Language
2
2
 
3
+ Read this rule when you write Bash functions, variables, quoting, or loops.
4
+
3
5
  ## Functions
4
6
 
5
- Declare function-local variables with `local`, and separate the declaration from a command
6
- substitution assignment when the exit code matters, because `local`, `declare`, `readonly`,
7
- and `export` report their own status rather than the substitution's:
7
+ Declare function-local variables with `local`. When the exit code of a command substitution
8
+ matters, assign it apart from the declaration: `local`, `declare`, `readonly`, and `export`
9
+ report their own status:
8
10
 
9
11
  ```bash
10
12
  local output
11
13
  output="$(some_command)" || return 1
12
14
  ```
13
15
 
14
- Preserve the documented status contract: a predicate may use status 1 for false, kept
15
- distinct from an execution error. At level `all`, every executable defines `main` and calls it last.
16
+ Preserve the documented status contract: a predicate may return 1 for false, distinct from an
17
+ execution error.
16
18
 
17
19
  ```bash
18
20
  # report_unreadable_files: Reports each unreadable input and fails if any input is unreadable.
@@ -35,16 +37,18 @@ report_unreadable_files() {
35
37
 
36
38
  <!-- level: all -->
37
39
 
38
- Use `name() { ... }` for a function that runs in the caller's shell, and a subshell body,
39
- `name() ( ... )`, for deliberate isolation of the caller's state and traps.
40
+ Use `name() { ... }` for a function that runs in the current shell, and a subshell body,
41
+ `name() ( ... )`, to isolate state and traps from the caller on purpose.
42
+
43
+ Every executable defines `main` and calls it last.
40
44
 
41
45
  ## Variables and constants
42
46
 
43
47
  Mark constants `readonly` immediately after assignment, and
44
- `export` only what child processes need. Never casually overwrite `PATH`, `HOME`, `IFS`,
45
- `CDPATH`, `SHELL`, `PWD`, or `BASH_ENV`, and never export `CDPATH`.
48
+ `export` only what child processes need. Do not overwrite `PATH`, `HOME`, `IFS`, `CDPATH`,
49
+ `SHELL`, `PWD`, or `BASH_ENV` without a clear need. Never export `CDPATH`.
46
50
 
47
- Compute a directory constant with `CDPATH='' cd -- <path> && pwd -P` and carries a failure path, `|| exit 1` in an
51
+ Compute a folder constant with `CDPATH='' cd -- <path> && pwd -P` and add a failure path: `|| exit 1` in an
48
52
  entrypoint and `|| return 1` in a library. Use `$HOME`, not a quoted `~`, inside paths, and
49
53
  assign a home-relative value before exporting it. Quote array elements passed to `unset -v`.
50
54
 
@@ -57,9 +61,9 @@ export PROJECT_ROOT
57
61
 
58
62
  ## Quoting and expansion
59
63
 
60
- Forward arguments with `"$@"`, and use `$*` only to join arguments into one string on
61
- purpose. Single quotes hold literals and double quotes hold expansions. Command substitution
62
- output never supplies an unquoted argument list, and word splitting never parses data. Use functions,
64
+ Forward arguments with `"$@"`, and use `$*` only to join them into one string on
65
+ purpose. Use single quotes for literals, double for expansions. Command substitution
66
+ output never supplies an unquoted argument list. Word splitting never parses data. Use functions,
63
67
  not aliases. Parameter expansion replaces external tools for string work:
64
68
 
65
69
  ```bash
@@ -70,7 +74,7 @@ base="${filename%.tar.gz}"
70
74
  ## Arrays and argument lists
71
75
 
72
76
  Command arguments live in arrays, expanded with `"${array[@]}"` and never populated from raw
73
- `$(...)`. Read lines with a `while read` loop or `readarray`, and read filenames from `NUL`-delimited streams:
77
+ `$(...)`. Read lines with a `while read` loop or `readarray`, and filenames from `NUL`-delimited streams:
74
78
 
75
79
  ```bash
76
80
  declare -a archive_args
@@ -86,9 +90,9 @@ Do not use arrays to imitate nested data structures. A need for those structures
86
90
 
87
91
  ## Conditionals and arithmetic
88
92
 
89
- `[[ ... ]]` holds string, pattern, and file tests. `==` compares for equality with a quoted
90
- right-hand side when the value may hold glob characters, and an unquoted right-hand side only
91
- for pattern matching. Store a regular expression in a variable and use it
93
+ `[[ ... ]]` holds string, pattern, and file tests. With `==`, quote the right-hand side to
94
+ compare equality when the value may hold glob characters, and leave it unquoted only for
95
+ pattern matching. Store a regular expression in a variable and use it
92
96
  unquoted with `=~`. `-z` and `-n` test for emptiness. Numeric comparisons live in `(( ... ))` after validation of untrusted numbers; `<` and
93
97
  `>` inside `[[ ... ]]` compare strings.
94
98
 
@@ -104,8 +108,8 @@ zero, so under `errexit` increment with `retry_count=$(( retry_count + 1 ))`. Un
104
108
  strings never enter `(( ... ))`, `$(( ... ))`, `[[ value -gt n ]]`, array indices, or
105
109
  arithmetic `for` expressions: validate with a pattern first. `10#${value}` converts unsigned
106
110
  base-10 strings; a signed value needs `$(( ${value%%[!+-]*}10#${value#[-+]} ))`. Call `date`
107
- once when several fields describe one instant, and compute a redirection path before the
108
- command when the path expression mutates a variable.
111
+ once when several fields describe one instant. Compute a redirection path before the command
112
+ when its expression mutates a variable.
109
113
 
110
114
  ```bash
111
115
  if [[ ! "${port}" =~ ^[0-9]{1,5}$ ]]; then
@@ -142,8 +146,8 @@ done <"${file_list}"
142
146
  `IFS= read -r` prevents trimming and backslash handling. Do not save and restore `IFS` with
143
147
  `old_ifs="${IFS}"`, which loses the distinction between unset and empty; use a function-local
144
148
  `IFS` or a subshell. `read` treats `IFS` as a terminator, so a controlled delimited line
145
- keeps its trailing empty field only with an appended delimiter. Parse general CSV with
146
- application code with a real parser, never `IFS=, read`.
149
+ keeps its trailing empty field only with an appended delimiter. Parse general CSV in
150
+ application code with a real parser, never with `IFS=, read`.
147
151
 
148
152
  ```bash
149
153
  # join_path_parts: Joins the arguments with slashes.
@@ -155,10 +159,10 @@ join_path_parts() {
155
159
 
156
160
  ## Paths, globs, and file names
157
161
 
158
- Quote paths, put `--` before path arguments where the command supports it, prefer globs with
162
+ Quote paths, put `--` before them where the command supports it, prefer globs with
159
163
  an explicit prefix such as `./*.sql`, and never filter filenames with `grep`. Filenames can
160
164
  hold spaces, newlines, quotes, brackets, and leading dashes. Never assume the current
161
- directory; compute it, and check `cd` explicitly. A broken symlink needs `-L` beside `-e`
165
+ folder; compute it, and check `cd` explicitly. A broken symlink needs `-L` beside `-e`
162
166
  when existence matters, and a basename pattern reads `"${path##*/}"`. Handle the no-match
163
167
  glob deliberately, either with `[[ -e ${file} ]] || continue` or with `nullglob` scoped to a
164
168
  subshell:
@@ -190,9 +194,9 @@ content="${content_with_sentinel%x}"
190
194
 
191
195
  ## Pipelines and redirection
192
196
 
193
- Read `PIPESTATUS` immediately when individual statuses matter. Order redirections to show the intent, `>file 2>&1`, and never close a standard descriptor as a shortcut for `/dev/null`. A
194
- pipeline never reads from and writes to the same file; rewrite a file through a
195
- temporary file and an atomic rename. Parallel `xargs` jobs do not produce ordered, unmixed
197
+ Read `PIPESTATUS` immediately when individual statuses matter. Order redirections to show the intent, `>file 2>&1`, and never close a standard descriptor as a shortcut for `/dev/null`. Never
198
+ read from and write to the same file in one pipeline. Rewrite it through a temporary file
199
+ and an atomic rename. Parallel `xargs` jobs do not produce ordered, unmixed
196
200
  output: write per-job files and combine them, or use a tool that serializes. Check a command
197
201
  directly or capture its status in a named variable rather than `(( ! $? )) || die`.
198
202