@gspothq/cli 0.0.0-stage → 0.1.1

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 (232) hide show
  1. package/README.md +18 -2
  2. package/configurations/database/postgres/manifest.toml +114 -0
  3. package/configurations/database/postgres/rules/POSTGRES.md +78 -0
  4. package/configurations/framework/astro/eslint-presets.json +138 -0
  5. package/configurations/framework/astro/manifest.toml +95 -0
  6. package/configurations/framework/astro/rules/ASTRO.md +39 -0
  7. package/configurations/framework/expo/manifest.toml +43 -0
  8. package/configurations/framework/express/manifest.toml +17 -0
  9. package/configurations/framework/express/rules/EXPRESS.md +73 -0
  10. package/configurations/framework/express/semgrep/express.yml +71 -0
  11. package/configurations/framework/fastapi/manifest.toml +11 -0
  12. package/configurations/framework/fastapi/rules/FASTAPI.md +100 -0
  13. package/configurations/framework/fastapi/semgrep/fastapi.yml +44 -0
  14. package/configurations/framework/nestjs/eslint-presets.json +62 -0
  15. package/configurations/framework/nestjs/manifest.toml +52 -0
  16. package/configurations/framework/nestjs/rules/NESTJS.md +44 -0
  17. package/configurations/framework/nextjs/eslint-presets.json +73 -0
  18. package/configurations/framework/nextjs/manifest.toml +119 -0
  19. package/configurations/framework/nextjs/rules/NEXTJS.md +43 -0
  20. package/configurations/framework/react/eslint-presets.json +77 -0
  21. package/configurations/framework/react/manifest.toml +93 -0
  22. package/configurations/framework/react/rules/REACT.md +108 -0
  23. package/configurations/framework/react-dom/eslint-presets.json +110 -0
  24. package/configurations/framework/react-dom/manifest.toml +24 -0
  25. package/configurations/framework/react-native/manifest.toml +46 -0
  26. package/configurations/framework/react-native/rules/REACT-NATIVE.md +51 -0
  27. package/configurations/framework/site/manifest.toml +170 -0
  28. package/configurations/framework/site/rules/SITE.md +20 -0
  29. package/configurations/framework/svelte/eslint-presets.json +67 -0
  30. package/configurations/framework/svelte/manifest.toml +83 -0
  31. package/configurations/framework/svelte/rules/SVELTE.md +48 -0
  32. package/configurations/framework/vue/eslint-presets.json +180 -0
  33. package/configurations/framework/vue/manifest.toml +83 -0
  34. package/configurations/framework/vue/rules/VUE.md +41 -0
  35. package/configurations/general/commits/manifest.toml +111 -0
  36. package/configurations/general/commits/rules/GIT.md +31 -0
  37. package/configurations/general/coverage/manifest.toml +3 -0
  38. package/configurations/general/dependencies/manifest.toml +159 -0
  39. package/configurations/general/dependencies/rules/DEPENDENCIES.md +30 -0
  40. package/configurations/general/dependencies/syncpack.json +15 -0
  41. package/configurations/general/docs/manifest.toml +79 -0
  42. package/configurations/general/docs/rules/DOCS.md +86 -0
  43. package/configurations/general/duplication/manifest.toml +32 -0
  44. package/configurations/general/engineering/manifest.toml +8 -0
  45. package/configurations/general/engineering/rules/agent/PLANNING.md +42 -0
  46. package/configurations/general/engineering/rules/agent/SUPPRESSIONS.md +16 -0
  47. package/configurations/general/engineering/rules/agent/TALKING.md +3 -0
  48. package/configurations/general/engineering/rules/agent/TASKS.md +19 -0
  49. package/configurations/general/engineering/rules/agent/WORKING.md +83 -0
  50. package/configurations/general/engineering/rules/code/ACCESSIBILITY.md +32 -0
  51. package/configurations/general/engineering/rules/code/CLI.md +37 -0
  52. package/configurations/general/engineering/rules/code/COMMENTS.md +38 -0
  53. package/configurations/general/engineering/rules/code/CONFIGURATION.md +25 -0
  54. package/configurations/general/engineering/rules/code/ERRORS.md +49 -0
  55. package/configurations/general/engineering/rules/code/HTTP.md +46 -0
  56. package/configurations/general/engineering/rules/code/LOGGING.md +55 -0
  57. package/configurations/general/engineering/rules/code/TESTING.md +58 -0
  58. package/configurations/general/files/manifest.toml +141 -0
  59. package/configurations/general/files/rules/YAML.md +19 -0
  60. package/configurations/general/format/manifest.toml +152 -0
  61. package/configurations/general/gspot/manifest.toml +65 -0
  62. package/configurations/general/gspot/rules/GENERATED.md +10 -0
  63. package/configurations/general/licenses/manifest.toml +33 -0
  64. package/configurations/general/naming/defaults.json +423 -0
  65. package/configurations/general/naming/manifest.toml +35 -0
  66. package/configurations/general/naming/rules/NAMING.md +179 -0
  67. package/configurations/general/prose/manifest.toml +86 -0
  68. package/configurations/general/prose/rules/WRITING.md +31 -0
  69. package/configurations/general/prose/vale/styles/LICENSES.md +55 -0
  70. package/configurations/general/prose/vale/styles/gspot/ampm.yml +8 -0
  71. package/configurations/general/prose/vale/styles/gspot/conscious-language.yml +16 -0
  72. package/configurations/general/prose/vale/styles/gspot/contractions.yml +43 -0
  73. package/configurations/general/prose/vale/styles/gspot/currency.yml +8 -0
  74. package/configurations/general/prose/vale/styles/gspot/dashes.yml +8 -0
  75. package/configurations/general/prose/vale/styles/gspot/date-ordinals.yml +41 -0
  76. package/configurations/general/prose/vale/styles/gspot/dates.yml +7 -0
  77. package/configurations/general/prose/vale/styles/gspot/file-paths.yml +7 -0
  78. package/configurations/general/prose/vale/styles/gspot/future.yml +17 -0
  79. package/configurations/general/prose/vale/styles/gspot/hedging.yml +22 -0
  80. package/configurations/general/prose/vale/styles/gspot/history.yml +16 -0
  81. package/configurations/general/prose/vale/styles/gspot/idioms.yml +21 -0
  82. package/configurations/general/prose/vale/styles/gspot/interface-verbs.yml +11 -0
  83. package/configurations/general/prose/vale/styles/gspot/jargon.yml +11 -0
  84. package/configurations/general/prose/vale/styles/gspot/leading-conjunctions.yml +12 -0
  85. package/configurations/general/prose/vale/styles/gspot/link-text.yml +8 -0
  86. package/configurations/general/prose/vale/styles/gspot/locations.yml +14 -0
  87. package/configurations/general/prose/vale/styles/gspot/marketing.yml +26 -0
  88. package/configurations/general/prose/vale/styles/gspot/merge-conflict-markers.yml +13 -0
  89. package/configurations/general/prose/vale/styles/gspot/militaristic-language.yml +22 -0
  90. package/configurations/general/prose/vale/styles/gspot/modals.yml +9 -0
  91. package/configurations/general/prose/vale/styles/gspot/numbered-units.yml +18 -0
  92. package/configurations/general/prose/vale/styles/gspot/obvious-fields.yml +14 -0
  93. package/configurations/general/prose/vale/styles/gspot/ordinal-adverbs.yml +15 -0
  94. package/configurations/general/prose/vale/styles/gspot/percentage-numbers.yml +8 -0
  95. package/configurations/general/prose/vale/styles/gspot/placeholders.yml +7 -0
  96. package/configurations/general/prose/vale/styles/gspot/product-centric-writing.yml +8 -0
  97. package/configurations/general/prose/vale/styles/gspot/punctuated-headings.yml +11 -0
  98. package/configurations/general/prose/vale/styles/gspot/self-reference.yml +15 -0
  99. package/configurations/general/prose/vale/styles/gspot/session-identifiers.yml +11 -0
  100. package/configurations/general/prose/vale/styles/gspot/since.yml +6 -0
  101. package/configurations/general/prose/vale/styles/gspot/straight-quotes.yml +11 -0
  102. package/configurations/general/prose/vale/styles/gspot/suspended-hyphens.yml +8 -0
  103. package/configurations/general/prose/vale/styles/gspot/symbols.yml +8 -0
  104. package/configurations/general/prose/vale/styles/gspot/time-ranges.yml +14 -0
  105. package/configurations/general/prose/vale/styles/gspot/url-prepositions.yml +10 -0
  106. package/configurations/general/prose/vale/styles/gspot/version-range.yml +8 -0
  107. package/configurations/general/repository/manifest.toml +24 -0
  108. package/configurations/general/secrets/gitleaks.toml +2 -0
  109. package/configurations/general/secrets/manifest.toml +125 -0
  110. package/configurations/general/secrets/rules/SECRETS.md +13 -0
  111. package/configurations/general/security/manifest.toml +77 -0
  112. package/configurations/general/security/rules/SECURITY.md +55 -0
  113. package/configurations/general/spelling/manifest.toml +46 -0
  114. package/configurations/general/structure/manifest.toml +70 -0
  115. package/configurations/infrastructure/ansible/manifest.toml +33 -0
  116. package/configurations/infrastructure/docker/manifest.toml +127 -0
  117. package/configurations/infrastructure/docker/rules/DOCKER.md +135 -0
  118. package/configurations/infrastructure/docker/trivyignore +2 -0
  119. package/configurations/infrastructure/github-actions/manifest.toml +76 -0
  120. package/configurations/infrastructure/github-actions/rules/GITHUB-ACTIONS.md +43 -0
  121. package/configurations/infrastructure/github-actions/zizmor.yml +3 -0
  122. package/configurations/infrastructure/nginx/manifest.toml +51 -0
  123. package/configurations/infrastructure/nginx/rules/NGINX.md +38 -0
  124. package/configurations/infrastructure/openapi/manifest.toml +50 -0
  125. package/configurations/infrastructure/openapi/rules/OPENAPI.md +39 -0
  126. package/configurations/infrastructure/xcode/manifest.toml +131 -0
  127. package/configurations/infrastructure/xcode/rules/XCODE.md +34 -0
  128. package/configurations/language/bash/manifest.toml +261 -0
  129. package/configurations/language/bash/rules/BASH.md +106 -0
  130. package/configurations/language/bash/rules/LANGUAGE.md +213 -0
  131. package/configurations/language/bash/rules/NAMING.md +62 -0
  132. package/configurations/language/bash/rules/OPERATIONS.md +77 -0
  133. package/configurations/language/bash/rules/SAFETY.md +222 -0
  134. package/configurations/language/bash/semgrep/bash.yml +30 -0
  135. package/configurations/language/css/manifest.toml +98 -0
  136. package/configurations/language/css/rules/CSS.md +28 -0
  137. package/configurations/language/css/rules/NAMING.md +17 -0
  138. package/configurations/language/css/rules/TAILWIND.md +26 -0
  139. package/configurations/language/css/stylelint-rule-names.json +89 -0
  140. package/configurations/language/html/manifest.toml +59 -0
  141. package/configurations/language/html/rules/HTML.md +27 -0
  142. package/configurations/language/html/rules/NAMING.md +16 -0
  143. package/configurations/language/javascript/eslint-presets.json +997 -0
  144. package/configurations/language/javascript/eslint-rule-names.json +299 -0
  145. package/configurations/language/javascript/eslint-runtime-names.json +61 -0
  146. package/configurations/language/javascript/manifest.toml +393 -0
  147. package/configurations/language/javascript/rules/BUN.md +13 -0
  148. package/configurations/language/javascript/rules/COMPONENTS.md +18 -0
  149. package/configurations/language/javascript/rules/DENO.md +15 -0
  150. package/configurations/language/javascript/rules/JAVASCRIPT.md +60 -0
  151. package/configurations/language/javascript/rules/NAMING.md +31 -0
  152. package/configurations/language/javascript/rules/NODE.md +21 -0
  153. package/configurations/language/javascript/rules/PLAYWRIGHT.md +34 -0
  154. package/configurations/language/javascript/semgrep/javascript.yml +84 -0
  155. package/configurations/language/markdown/manifest.toml +85 -0
  156. package/configurations/language/markdown/markdownlint-cli2.mjs +19 -0
  157. package/configurations/language/python/manifest.toml +621 -0
  158. package/configurations/language/python/rules/DESIGN.md +136 -0
  159. package/configurations/language/python/rules/FLOW.md +124 -0
  160. package/configurations/language/python/rules/NAMING.md +53 -0
  161. package/configurations/language/python/rules/PACKAGING.md +49 -0
  162. package/configurations/language/python/rules/PYTHON.md +136 -0
  163. package/configurations/language/python/rules/TYPING.md +132 -0
  164. package/configurations/language/sql/manifest.toml +68 -0
  165. package/configurations/language/sql/rules/NAMING.md +90 -0
  166. package/configurations/language/sql/rules/SQL.md +9 -0
  167. package/configurations/language/swift/manifest.toml +164 -0
  168. package/configurations/language/swift/rules/NAMING.md +88 -0
  169. package/configurations/language/swift/rules/SWIFT.md +134 -0
  170. package/configurations/language/swift/rules/SWIFTUI.md +61 -0
  171. package/configurations/language/swift/rules/UIKIT.md +99 -0
  172. package/configurations/language/swift/semgrep/swift.yml +126 -0
  173. package/configurations/language/typescript/eslint-presets.json +215 -0
  174. package/configurations/language/typescript/manifest.toml +72 -0
  175. package/configurations/language/typescript/rules/NAMING.md +73 -0
  176. package/configurations/language/typescript/rules/TYPESCRIPT.md +79 -0
  177. package/configurations/library/drizzle/manifest.toml +68 -0
  178. package/configurations/library/drizzle/rules/DRIZZLE.md +44 -0
  179. package/configurations/library/react-hook-form/manifest.toml +15 -0
  180. package/configurations/library/react-hook-form/rules/REACT-HOOK-FORM.md +48 -0
  181. package/configurations/library/swift-snapshot-testing/manifest.toml +32 -0
  182. package/configurations/library/tanstack-query/eslint-presets.json +21 -0
  183. package/configurations/library/tanstack-query/manifest.toml +26 -0
  184. package/configurations/library/tanstack-query/rules/TANSTACK-QUERY.md +41 -0
  185. package/configurations/library/testing-library/eslint-presets.json +191 -0
  186. package/configurations/library/testing-library/manifest.toml +47 -0
  187. package/configurations/library/translations/manifest.toml +40 -0
  188. package/configurations/library/translations/rules/NEXT-INTL.md +33 -0
  189. package/configurations/library/translations/rules/TRANSLATIONS.md +102 -0
  190. package/configurations/library/trpc/manifest.toml +14 -0
  191. package/configurations/library/trpc/rules/TRPC.md +43 -0
  192. package/configurations/library/zod/manifest.toml +25 -0
  193. package/configurations/library/zod/rules/ZOD.md +44 -0
  194. package/configurations/library/zustand/manifest.toml +18 -0
  195. package/configurations/library/zustand/rules/ZUSTAND.md +106 -0
  196. package/configurations/platform/cloudflare/manifest.toml +94 -0
  197. package/configurations/platform/cloudflare/rules/CLOUDFLARE.md +23 -0
  198. package/configurations/platform/cloudflare/semgrep/cloudflare.yml +12 -0
  199. package/configurations/platform/supabase/manifest.toml +112 -0
  200. package/configurations/platform/supabase/rules/SUPABASE.md +129 -0
  201. package/configurations/platform/supabase/semgrep/supabase.yml +60 -0
  202. package/configurations/testing/jest/manifest.toml +54 -0
  203. package/configurations/testing/pytest/manifest.toml +53 -0
  204. package/configurations/testing/swift-tests/manifest.toml +52 -0
  205. package/configurations/testing/swift-tests/rules/SWIFT-TESTS.md +79 -0
  206. package/configurations/testing/vitest/eslint-presets.json +22 -0
  207. package/configurations/testing/vitest/manifest.toml +69 -0
  208. package/configurations/testing/vitest/rules/VITEST.md +18 -0
  209. package/dist/LICENSE.md +201 -0
  210. package/dist/THIRD-PARTY-NOTICES.md +25 -0
  211. package/dist/eslint-rule-levels.json +10 -0
  212. package/dist/gspot.js +30740 -0
  213. package/dist/linkinator-program.js +65 -0
  214. package/dist/purgecss-program.js +51 -0
  215. package/dist/tool-versions.json +3 -0
  216. package/grammars/bash.wasm +0 -0
  217. package/grammars/css.wasm +0 -0
  218. package/grammars/html.wasm +0 -0
  219. package/grammars/javascript.wasm +0 -0
  220. package/grammars/licenses/tree-sitter-bash.txt +21 -0
  221. package/grammars/licenses/tree-sitter-css.txt +21 -0
  222. package/grammars/licenses/tree-sitter-html.txt +21 -0
  223. package/grammars/licenses/tree-sitter-javascript.txt +21 -0
  224. package/grammars/licenses/tree-sitter-python.txt +21 -0
  225. package/grammars/licenses/tree-sitter-swift.txt +21 -0
  226. package/grammars/licenses/tree-sitter-typescript.txt +21 -0
  227. package/grammars/python.wasm +0 -0
  228. package/grammars/swift.wasm +0 -0
  229. package/grammars/tsx.wasm +0 -0
  230. package/grammars/typescript.wasm +0 -0
  231. package/gspot.schema.json +45991 -0
  232. package/package.json +93 -5
@@ -0,0 +1,213 @@
1
+ # Bash Language
2
+
3
+ ## Functions
4
+
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:
8
+
9
+ ```bash
10
+ local output
11
+ output="$(some_command)" || return 1
12
+ ```
13
+
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
+
17
+ ```bash
18
+ # report_unreadable_files: Reports each unreadable input and fails if any input is unreadable.
19
+ report_unreadable_files() {
20
+ local path
21
+ local status=0
22
+
23
+ for path in "$@"; do
24
+ if [[ -r ${path} ]]; then
25
+ continue
26
+ fi
27
+ printf 'error: input is unreadable: %s\n' "${path}" >&2
28
+ status=1
29
+ done
30
+ return "${status}"
31
+ }
32
+ ```
33
+
34
+ ### Function conventions
35
+
36
+ <!-- level: all -->
37
+
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
+
41
+ ## Variables and constants
42
+
43
+ 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`.
46
+
47
+ Compute a directory constant with `CDPATH='' cd -- <path> && pwd -P` and carries a failure path, `|| exit 1` in an
48
+ entrypoint and `|| return 1` in a library. Use `$HOME`, not a quoted `~`, inside paths, and
49
+ assign a home-relative value before exporting it. Quote array elements passed to `unset -v`.
50
+
51
+ ```bash
52
+ script_parent="$(dirname -- "${BASH_SOURCE[0]}")" || exit 1
53
+ PROJECT_ROOT="$(CDPATH='' cd -- "${script_parent}/.." && pwd -P)" || exit 1
54
+ readonly PROJECT_ROOT
55
+ export PROJECT_ROOT
56
+ ```
57
+
58
+ ## Quoting and expansion
59
+
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,
63
+ not aliases. Parameter expansion replaces external tools for string work:
64
+
65
+ ```bash
66
+ filename="archive.tar.gz"
67
+ base="${filename%.tar.gz}"
68
+ ```
69
+
70
+ ## Arrays and argument lists
71
+
72
+ 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:
74
+
75
+ ```bash
76
+ declare -a archive_args
77
+ archive_args=(-czf "${archive_path}" -C "${source_directory}" .)
78
+ tar "${archive_args[@]}"
79
+
80
+ while IFS= LC_ALL=C read -r -d '' file; do
81
+ files+=("${file}")
82
+ done < <(find . -type f -name '*.sql' -print0)
83
+ ```
84
+
85
+ Do not use arrays to imitate nested data structures. A need for those structures signals that the work belongs outside Bash.
86
+
87
+ ## Conditionals and arithmetic
88
+
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
92
+ unquoted with `=~`. `-z` and `-n` test for emptiness. Numeric comparisons live in `(( ... ))` after validation of untrusted numbers; `<` and
93
+ `>` inside `[[ ... ]]` compare strings.
94
+
95
+ ```bash
96
+ readonly version_re='^[0-9]+\.[0-9]+\.[0-9]+$'
97
+ if [[ "${version}" =~ ${version_re} ]]; then
98
+ printf 'valid version\n'
99
+ fi
100
+ ```
101
+
102
+ Arithmetic uses `$(( ... ))` and `(( ... ))`. `(( i++ ))` returns false when the expression equals
103
+ zero, so under `errexit` increment with `retry_count=$(( retry_count + 1 ))`. Untrusted
104
+ strings never enter `(( ... ))`, `$(( ... ))`, `[[ value -gt n ]]`, array indices, or
105
+ arithmetic `for` expressions: validate with a pattern first. `10#${value}` converts unsigned
106
+ 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.
109
+
110
+ ```bash
111
+ if [[ ! "${port}" =~ ^[0-9]{1,5}$ ]]; then
112
+ printf 'error: port must be numeric\n' >&2
113
+ return 1
114
+ fi
115
+ if (( 10#${port} < 1 || 10#${port} > 65535 )); then
116
+ printf 'error: port is out of range\n' >&2
117
+ return 1
118
+ fi
119
+ ```
120
+
121
+ ## Loops and delimited data
122
+
123
+ Iterate arguments with `for arg in "$@"; do`, globs directly rather than `ls`, and files with
124
+ `while IFS= read -r line || [[ -n ${line} ]]; do ... done <"${file}"`, which keeps the last
125
+ unterminated line. A pipe into `while` runs the loop in a subshell, so variables set inside it
126
+ vanish; use process substitution or a redirected file. A here-string with command
127
+ substitution collects all output first, strips trailing newlines, and drops `NUL` bytes.
128
+ Neither form propagates the producer's failure; use a checked temporary file when that status
129
+ matters. Counter loops use `for (( index = 0; index < count; index++ ))`, not `seq`.
130
+
131
+ ```bash
132
+ root_dir="$(CDPATH='' cd -- "${1:?Root directory is required}" && pwd -P)" || exit 1
133
+ file_list="$(mktemp)" || exit 1
134
+ trap 'rm -f -- "${file_list}"' EXIT
135
+ find "${root_dir}" -type f -print0 >"${file_list}" || exit 1
136
+
137
+ while IFS= LC_ALL=C read -r -d '' file; do
138
+ printf 'file: %q\n' "${file}"
139
+ done <"${file_list}"
140
+ ```
141
+
142
+ `IFS= read -r` prevents trimming and backslash handling. Do not save and restore `IFS` with
143
+ `old_ifs="${IFS}"`, which loses the distinction between unset and empty; use a function-local
144
+ `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`.
147
+
148
+ ```bash
149
+ # join_path_parts: Joins the arguments with slashes.
150
+ join_path_parts() {
151
+ local IFS='/'
152
+ printf '%s\n' "$*"
153
+ }
154
+ ```
155
+
156
+ ## Paths, globs, and file names
157
+
158
+ Quote paths, put `--` before path arguments where the command supports it, prefer globs with
159
+ an explicit prefix such as `./*.sql`, and never filter filenames with `grep`. Filenames can
160
+ 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`
162
+ when existence matters, and a basename pattern reads `"${path##*/}"`. Handle the no-match
163
+ glob deliberately, either with `[[ -e ${file} ]] || continue` or with `nullglob` scoped to a
164
+ subshell:
165
+
166
+ ```bash
167
+ for file in ./*.sql; do
168
+ [[ -e ${file} ]] || continue
169
+ printf '%s\n' "${file}"
170
+ done
171
+
172
+ if ! cd -- "${target_dir}"; then
173
+ printf 'error: cannot enter target dir: %s\n' "${target_dir}" >&2
174
+ return 1
175
+ fi
176
+ ```
177
+
178
+ ## Command substitution
179
+
180
+ Quote `$(...)`, capture the status at once, and remember that it strips trailing newlines,
181
+ cannot carry binary data, and never builds an argument list. Use `$(<file)` only when
182
+ the operation allows stripping trailing newlines. When trailing newlines matter, keep them with a
183
+ sentinel:
184
+
185
+ ```bash
186
+ commit_sha="$(git rev-parse HEAD)" || exit 1
187
+ content_with_sentinel="$(some_command || exit; printf x)" || return 1
188
+ content="${content_with_sentinel%x}"
189
+ ```
190
+
191
+ ## Pipelines and redirection
192
+
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
196
+ output: write per-job files and combine them, or use a tool that serializes. Check a command
197
+ directly or capture its status in a named variable rather than `(( ! $? )) || die`.
198
+
199
+ ```bash
200
+ tar -cf - ./* | (cd -- "${target_dir}" && tar -xf -)
201
+ statuses=( "${PIPESTATUS[@]}" )
202
+ if (( statuses[0] != 0 || statuses[1] != 0 )); then
203
+ printf 'error: tar copy failed\n' >&2
204
+ return 1
205
+ fi
206
+
207
+ edited_file="$(mktemp "${file}.XXXXXX")" || return 1
208
+ sed 's/foo/bar/g' "${file}" >"${edited_file}" || {
209
+ rm -f -- "${edited_file}"
210
+ return 1
211
+ }
212
+ mv -- "${edited_file}" "${file}"
213
+ ```
@@ -0,0 +1,62 @@
1
+ # Bash Naming
2
+
3
+ Bash naming follows Google shell guidance, with the overrides below for file stems.
4
+
5
+ ## Bash case rules
6
+
7
+ <!-- level: all -->
8
+
9
+ - Executable scripts use `.sh` when invoked through a task runner or build
10
+ rules.
11
+ - Omit the extension on an executable script only when users invoke it
12
+ as a command on `PATH`.
13
+ - Give sourced libraries the `.sh` extension and no executable permission.
14
+ - Numbered pipeline steps use `NN-description.sh`, such as `01-install.sh`; the numeric prefix defines a
15
+ structural prefix the naming check strips before it matches the stem.
16
+ - Functions and mutable variables use `lower_snake_case`.
17
+ - Package-like function prefixes may use `::` only when a script family already
18
+ uses that convention.
19
+
20
+ | Avoid | Prefer | Meaning |
21
+ | ------------------ | -------------------------- | -------------------- |
22
+ | `DeployScript.sh` | `deploy-api.sh` | API deployment. |
23
+ | `deploy_script.sh` | `upload-storage-assets.sh` | Storage upload. |
24
+ | `helpers.sh` | `archive-reports` | A command on `PATH`. |
25
+
26
+ Name a deployment function `deploy_api` and its local target
27
+ `target_environment`.
28
+
29
+ ## Bash variables
30
+
31
+ <!-- level: all -->
32
+
33
+ - Loop variables name each item in the loop.
34
+ - Name a temporary path after what it holds, such as `download_file` or `work_dir`; `tmp` and
35
+ `temp` say nothing.
36
+ - Avoid vague names when the domain provides a name.
37
+ - Avoid shell-reserved and shell-special names for unrelated values.
38
+
39
+ Use `migration_file` when iterating migration paths and `report_output` for
40
+ captured report text. Check the report command's exit status before printing
41
+ the captured output. A loop variable such as `i` does not describe a file.
42
+
43
+ ## Bash functions
44
+
45
+ <!-- level: all -->
46
+
47
+ - Function names use verb phrases when the function has side effects.
48
+ - Name a function that prints to `stdout` for the data it prints.
49
+ - Do not name scripts or functions after shell builtins or common commands.
50
+ - Do not make function names so generic that logs and stack traces lose context.
51
+
52
+ Name a port validator `validate_port`. Avoid `test`, which shadows a shell builtin, and
53
+ generic names such as `run` or `process`.
54
+
55
+ ## Files
56
+
57
+ <!-- level: all -->
58
+
59
+ - Put a related script family in an owning subdirectory and give the contained
60
+ files role names such as `main.sh`, `state.sh`, `query.sh`, or `report.sh`.
61
+ - External hook families may use a dynamic shared prefix when the external
62
+ interface owns those filenames.
@@ -0,0 +1,77 @@
1
+ # Bash Operations
2
+
3
+ ## Module ownership and visibility
4
+
5
+ <!-- level: all -->
6
+
7
+ Each file owns one cohesive responsibility, and the directory supplies the family name.
8
+
9
+ - A sourced public function carries its family name, such as `release_prepare` or
10
+ `cache_read_state`.
11
+ - A function with one caller lives in the caller's file.
12
+
13
+ ## Deployment and publishing pipelines
14
+
15
+ Deployment, publishing, and long-running scripts separate validation, planning, confirmation,
16
+ execution, readiness checks, and cleanup.
17
+
18
+ - Fail before any work when the script lacks a required input, command, file, host, or secret.
19
+ - Name the target explicitly: never infer production, a remote, a host, or an artifact from a
20
+ branch name without a confirmation or a CI contract.
21
+ - A destructive or remote action asks for confirmation unless CI owns the guard.
22
+ - Use idempotent commands and stop at the first failed required step.
23
+ - Check readiness after deploying or publishing, and print diagnostics a person can act on.
24
+ - Keep rollback and cleanup explicit, and delete no artifact or result as a side effect.
25
+ - Log enough to reconstruct what happened, without secrets.
26
+
27
+ ```bash
28
+ # confirm_exact: Requires the user to type the expected value.
29
+ # Arguments:
30
+ # Prompt label.
31
+ # Expected response.
32
+ confirm_exact() {
33
+ local label="$1"
34
+ local expected="$2"
35
+ local response
36
+
37
+ printf '%s Type %s to continue: ' "${label}" "${expected}" >&2
38
+ IFS= read -r response
39
+
40
+ [[ "${response}" == "${expected}" ]]
41
+ }
42
+ ```
43
+
44
+ ## Remote commands
45
+
46
+ Copy a reviewed script to the remote host and invoke it with arguments. SSH joins its arguments
47
+ into one remote shell string, so local quoting does not keep remote argument boundaries. Validate
48
+ host, user, service, and path values before using them, never build a remote command from user
49
+ input, and give every remote call that can hang a timeout.
50
+
51
+ Good, for a remote Bash login shell:
52
+
53
+ ```bash
54
+ printf -v remote_command 'bash -- %q %q %q' "${remote_script}" "${dir}" "${service}"
55
+ ssh -- "${host}" "${remote_command}"
56
+ ```
57
+
58
+ ## Persisted state
59
+
60
+ Never `source` generated state or pass it to `bash -c`. Write state in a fixed data format with an
61
+ exact key schema to a restricted file in the destination folder, and replace the previous state
62
+ with an atomic `mv`. A reader treats a malformed, duplicate, or unknown key as an error.
63
+
64
+ ## Retries
65
+
66
+ Retry only an operation known to converge, such as a network pull or readiness polling, with a
67
+ bounded number of attempts. A retry never masks corrupt data, invalid credentials, a failed
68
+ migration, a syntax error, or a permission problem. Capture the status in the `else` branch,
69
+ because an `if` whose condition fails and that has no `else` returns zero:
70
+
71
+ ```bash
72
+ if "$@"; then
73
+ return 0
74
+ else
75
+ status=$?
76
+ fi
77
+ ```
@@ -0,0 +1,222 @@
1
+ # Bash Safety
2
+
3
+ ## Calling commands
4
+
5
+ Use fixed command names with argument arrays, never strings built from input, and a script
6
+ never passes untrusted input to a shell. Never use `bash -lc` in repository scripts, and a
7
+ function that runs a caller-supplied command takes the command and its arguments after
8
+ `shift` and invokes `"$@"`.
9
+
10
+ Check uncommon commands with `command -v` before a long-running, destructive, publishing, or
11
+ error-handling path depends on them. Prefer builtins and parameter expansion to external
12
+ commands for string and arithmetic work, and pass files directly rather than through
13
+ `cat`. Never run `su -c` without the target user.
14
+
15
+ ```bash
16
+ # require_command: Ensures a command exists on PATH.
17
+ # Arguments:
18
+ # Command name.
19
+ require_command() {
20
+ local command_name="$1"
21
+
22
+ if ! command -v "${command_name}" >/dev/null 2>&1; then
23
+ printf 'error: required command not found: %s\n' "${command_name}" >&2
24
+ return 1
25
+ fi
26
+ }
27
+ ```
28
+
29
+ ## Processes and privilege
30
+
31
+ Treat process names as advisory information, never authorization. Stop and restart paths stop only
32
+ process ids recorded by the owning script family, found through the service manager, a `PID` file, or
33
+ `pgrep -x`, never `ps | grep`. A script that starts background jobs saves each `PID`, waits for
34
+ each, captures each status, and writes per-job output to separate files. It bounds its
35
+ fan-out and kills owned children on `INT`, `TERM`, and `EXIT`. `sudo command > file` redirects
36
+ as the current user, and a glob in `sudo command /path/*` expands before `sudo` runs.
37
+ Privileged writes go through `sudo tee`, and keep a privileged shell string as a fixed literal
38
+ with no input.
39
+
40
+ ```bash
41
+ generate_config | sudo tee /etc/service/config >/dev/null
42
+
43
+ # run_remote_checks: Runs remote checks and returns non-zero on any failure.
44
+ # Arguments:
45
+ # Small, already bounded host list to check.
46
+ run_remote_checks() {
47
+ local host
48
+ local pid
49
+ local status=0
50
+
51
+ trap 'cleanup_children' EXIT
52
+ trap 'cleanup_children; exit 130' INT
53
+ trap 'cleanup_children; exit 143' TERM
54
+
55
+ for host in "$@"; do
56
+ check_host "${host}" >"${work_dir}/${host}.log" 2>&1 &
57
+ child_pids+=( "$!" )
58
+ done
59
+
60
+ for pid in "${child_pids[@]}"; do
61
+ wait "${pid}" || status=1
62
+ done
63
+
64
+ trap - EXIT INT TERM
65
+ return "${status}"
66
+ }
67
+ ```
68
+
69
+ ## Structured data
70
+
71
+ Use parameter expansion for string edits. JSON goes through `jq`, and YAML through `yq` or
72
+ a project-owned parser. Never parse JSON, YAML, XML, HTML, plist, or xcodebuild output
73
+ with ad hoc `grep | sed | awk` unless the script controls the input and the format has no complex structure.
74
+ Prefer machine output modes such as JSON, `NUL`, or explicit format flags and never parse process lists,
75
+ pretty tables, progress bars, or localized text by fixed field numbers. Quote `tr` character
76
+ classes and pin `LC_ALL=C` when converting case. macOS and GNU `sed -i` differ; a committed
77
+ script rewrites through a temporary file unless its documentation restricts it to one platform.
78
+
79
+ ```bash
80
+ # get_service_url: Prints a required nonempty service URL from JSON configuration.
81
+ get_service_url() {
82
+ local config_file="${1:?Configuration file is required}"
83
+ local service_url
84
+
85
+ service_url="$(jq -er '.service.url | select(type == "string" and length > 0)' "${config_file}")" || return 1
86
+ printf '%s\n' "${service_url}"
87
+ }
88
+ ```
89
+
90
+ ## Network
91
+
92
+ Downloads use `curl --fail --show-error --silent --location` into an explicit file, with
93
+ tool-native timeouts such as `--connect-timeout` plus `--max-time`, or SSH `ConnectTimeout`, rather
94
+ than GNU `timeout`, which macOS lacks. Download executable content, verify it against an
95
+ independently obtained checksum or signature, and only then run it.
96
+
97
+ ```bash
98
+ installer="${work_dir}/install.sh"
99
+ curl --fail --show-error --silent --location \
100
+ --connect-timeout 10 \
101
+ --max-time 60 \
102
+ --output "${installer}" \
103
+ "${installer_url}" || exit 1
104
+ printf '%s %s\n' "${expected_sha256}" "${installer}" | shasum -a 256 -c - || exit 1
105
+ bash "${installer}" --version "${tool_version}"
106
+
107
+ ssh -o ConnectTimeout=10 -o ServerAliveInterval=15 -o ServerAliveCountMax=2 \
108
+ -- "${host}" systemctl is-active --quiet "${service_name}"
109
+ ```
110
+
111
+ ## Secrets and environment
112
+
113
+ Keep `set -x` away from secrets. An indirect expansion `${!name}` takes only a validated name.
114
+
115
+ ```bash
116
+ # require_env: Ensures an environment variable is set and non-empty.
117
+ # Arguments:
118
+ # Environment variable name.
119
+ require_env() {
120
+ local name="$1"
121
+
122
+ if [[ ! "${name}" =~ ^[A-Z_][A-Z0-9_]*$ ]]; then
123
+ printf 'error: invalid env var name\n' >&2
124
+ return 1
125
+ fi
126
+ if [[ -z "${!name:-}" ]]; then
127
+ printf 'error: %s is required\n' "${name}" >&2
128
+ return 1
129
+ fi
130
+ }
131
+ ```
132
+
133
+ ## Temporary files, locks, and cleanup
134
+
135
+ Temporary paths come from `mktemp` or `mktemp -d` under the system or an explicit project
136
+ temp directory, never from predictable names. Register cleanup immediately after creation.
137
+ Put downloaded, copied, or generated content in a temporary file, validate it with the real
138
+ parser, and replace the destination with `mv`. The file keeps the restrictive permissions
139
+ `mktemp` gave it unless deployment needs others. Remove failed artifacts unless the script
140
+ documents keeping them. Acquire locks atomically with a `mkdir` lock directory holding
141
+ the owner `PID` or with `noclobber` redirection, never with a `test` followed by a create.
142
+
143
+ Stop commands remove only what the script created. Validate a cleanup path from
144
+ input or persistent state against its permitted root. Reject empty values, `/`,
145
+ the repository root, `$HOME`, symlinks, and anything outside the owner. Keep a `mktemp -d` result
146
+ exactly. Never widen the path to its neighbors. `|| true` never follows a destructive
147
+ command or a required installation, publishing, build, or runtime command.
148
+
149
+ ```bash
150
+ downloaded_file="$(mktemp "${config_file}.XXXXXX")" || exit 1
151
+ trap 'rm -f -- "${downloaded_file}"' EXIT
152
+ curl --fail --show-error --silent --location --output "${downloaded_file}" "${config_url}" || exit 1
153
+ jq empty "${downloaded_file}" >/dev/null || exit 1
154
+ mv -- "${downloaded_file}" "${config_file}" || exit 1
155
+ trap - EXIT
156
+
157
+ if ! mkdir "${lock_dir}"; then
158
+ printf 'error: lock is already held: %s\n' "${lock_dir}" >&2
159
+ return 1
160
+ fi
161
+ printf '%s\n' "$$" >"${lock_dir}/pid" || {
162
+ rmdir "${lock_dir}"
163
+ return 1
164
+ }
165
+ trap 'rm -f -- "${lock_dir}/pid"; rmdir -- "${lock_dir}"' EXIT
166
+ ```
167
+
168
+ Put recursive deletes in one script, and accept that script with an `[[ignore]]` record for `bash/safety`.
169
+ Validate its cleanup path against the owner root:
170
+
171
+ ```bash
172
+ # remove_build_dir: Removes the build child of the approved owner directory.
173
+ remove_build_dir() {
174
+ local owner_root build_dir
175
+
176
+ [[ -n "$1" ]] || return 1
177
+ owner_root="$(cd -- "$1" && pwd -P)" || return 1
178
+ [[ "${owner_root}" != / ]] || return 1
179
+ build_dir="${owner_root}/build"
180
+ [[ -d "${build_dir}" && ! -L "${build_dir}" ]] || return 1
181
+ rm -rf -- "${build_dir}"
182
+ }
183
+ ```
184
+
185
+ ## Portability
186
+
187
+ macOS and BSD
188
+ differ from GNU in `sed`, `date`, `readlink`, `mktemp`, `stat`, `xargs`, and `grep`; keep a
189
+ platform-specific branch with the operation that owns it. Handle `realpath` and `date`
190
+ parsing differences. macOS `/bin/bash` uses an old Bash version,
191
+ Linux-only utilities need checks in macOS-compatible scripts, and CI does not share the
192
+ developer's `PATH`. Symlink-aware absolute paths come from `pwd -P` after `cd`, or from
193
+ product-owned application code.
194
+
195
+ ## Verification and debugging
196
+
197
+ Run the configured syntax, ShellCheck, and shfmt commands. Run destructive or provider tests
198
+ only in a throwaway environment.
199
+
200
+ Production scripts gain no test-only flags, branches, or command replacements. When
201
+ debugging, start from the exact error and line Bash reports, run `bash -n` and ShellCheck,
202
+ reduce to the smallest reproducing block, and expose invisible characters with
203
+ `printf '%q\n'`. Trace with `bash -x` or a local `set -x` block with a `PS4` naming file, line, and function,
204
+ only while diagnosing, never around secrets, and never commit broad tracing, `trap DEBUG`, or stepping
205
+ code. Bash 4.4 supports `BASH_XTRACEFD`. A debug helper preserves the status it diagnoses.
206
+
207
+ Common causes follow. `unexpected EOF` indicates an unmatched quote, an unterminated here document,
208
+ a missing `fi`, `done`, or `esac`, or a CRLF ending hiding the delimiter.
209
+
210
+ `too many arguments` indicates an unquoted expansion inside `[ ... ]`. `event not found` indicates interactive history expansion
211
+ of `!`. Use `type -a` to find an alias, function, builtin, or `PATH` collision when a command behaves differently.
212
+ Failure before the shebang indicates a byte order mark (BOM) or CRLF.
213
+
214
+ ## Refactoring existing scripts
215
+
216
+ Read the whole script and its sourced libraries, identify the caller interface: local, CI,
217
+ remote host, or package script, and preserve behavior before changing style. Fix quoting and
218
+ argument arrays near the touched logic, and add explicit checks around dangerous commands.
219
+ Share a helper across the script family only when it has a real shared contract. Do not
220
+ convert a large script in one pass or change shebangs across a family without verifying the
221
+ runtime. Run the narrow shell lint and format command before the broader checks. Move complex script
222
+ logic into application code and call it directly.
@@ -0,0 +1,30 @@
1
+ rules:
2
+ - id: gspot.bash.curl-pipe-shell
3
+ languages:
4
+ - bash
5
+ severity: ERROR
6
+ message: A script is downloaded and run in one step; nobody can read what runs.
7
+ Download it to a file, check it, then run it.
8
+ pattern-either:
9
+ - pattern: curl ... | sh ...
10
+ - pattern: curl ... | sudo sh ...
11
+ - pattern: sh <(curl ...)
12
+ - pattern: sh -c "$(curl ...)"
13
+ - pattern: curl ... | bash ...
14
+ - pattern: curl ... | sudo bash ...
15
+ - pattern: bash <(curl ...)
16
+ - pattern: bash -c "$(curl ...)"
17
+ - pattern: wget ... | sh ...
18
+ - pattern: wget ... | sudo sh ...
19
+ - pattern: sh <(wget ...)
20
+ - pattern: sh -c "$(wget ...)"
21
+ - pattern: wget ... | bash ...
22
+ - pattern: wget ... | sudo bash ...
23
+ - pattern: bash <(wget ...)
24
+ - pattern: bash -c "$(wget ...)"
25
+ - id: gspot.bash.eval
26
+ languages:
27
+ - bash
28
+ severity: ERROR
29
+ message: eval runs a string as code; build the command as an array instead.
30
+ pattern: eval ...