docopslab-dev 0.1.0 → 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 (99) hide show
  1. checksums.yaml +4 -4
  2. data/README.adoc +645 -318
  3. data/docopslab-dev.gemspec +2 -3
  4. data/docs/agent/index.md +4 -8
  5. data/docs/agent/misc/bash-styles.md +470 -0
  6. data/docs/agent/missions/conduct-release.md +161 -87
  7. data/docs/agent/missions/setup-new-project.md +228 -134
  8. data/docs/agent/roles/devops-release-engineer.md +60 -17
  9. data/docs/agent/roles/docops-engineer.md +84 -20
  10. data/docs/agent/roles/planner-architect.md +22 -0
  11. data/docs/agent/roles/product-engineer.md +63 -15
  12. data/docs/agent/roles/product-manager.md +57 -24
  13. data/docs/agent/roles/project-manager.md +48 -12
  14. data/docs/agent/roles/qa-testing-engineer.md +48 -14
  15. data/docs/agent/roles/tech-docs-manager.md +63 -17
  16. data/docs/agent/roles/tech-writer.md +68 -14
  17. data/docs/agent/skills/asciidoc.md +65 -238
  18. data/docs/agent/skills/bash-cli-dev.md +135 -0
  19. data/docs/agent/skills/code-commenting.md +143 -106
  20. data/docs/agent/skills/fix-broken-links.md +145 -100
  21. data/docs/agent/skills/fix-jekyll-asciidoc-build-errors.md +1 -10
  22. data/docs/agent/skills/fix-spelling-issues.md +0 -3
  23. data/docs/agent/skills/git.md +69 -34
  24. data/docs/agent/skills/github-issues.md +110 -71
  25. data/docs/agent/skills/rake-cli-dev.md +1 -1
  26. data/docs/agent/skills/readme-driven-dev.md +1 -0
  27. data/docs/agent/skills/release-history.md +1 -7
  28. data/docs/agent/skills/ruby.md +18 -7
  29. data/docs/agent/skills/schemagraphy-sgyml.md +3 -0
  30. data/docs/agent/skills/tests-running.md +22 -14
  31. data/docs/agent/skills/tests-writing.md +51 -28
  32. data/docs/agent/skills/write-the-docs.md +71 -9
  33. data/docs/agent/topics/common-project-paths.md +122 -70
  34. data/docs/agent/topics/dev-tooling-usage.md +70 -77
  35. data/docs/agent/topics/devops-ci-cd.md +3 -1
  36. data/docs/agent/topics/product-docs-deployment.md +18 -12
  37. data/docs/library-readme.adoc +39 -0
  38. data/lib/docopslab/dev/cast_ops.rb +199 -0
  39. data/lib/docopslab/dev/config_manager.rb +6 -6
  40. data/lib/docopslab/dev/data_utils.rb +42 -0
  41. data/lib/docopslab/dev/file_utils.rb +18 -7
  42. data/lib/docopslab/dev/git_branch.rb +201 -0
  43. data/lib/docopslab/dev/git_hooks.rb +17 -11
  44. data/lib/docopslab/dev/initializer.rb +13 -4
  45. data/lib/docopslab/dev/library/cache.rb +167 -0
  46. data/lib/docopslab/dev/library/fetch.rb +209 -0
  47. data/lib/docopslab/dev/library.rb +328 -0
  48. data/lib/docopslab/dev/linters.rb +63 -12
  49. data/lib/docopslab/dev/manifest.rb +28 -0
  50. data/lib/docopslab/dev/paths.rb +0 -17
  51. data/lib/docopslab/dev/script_manager.rb +12 -6
  52. data/lib/docopslab/dev/skim.rb +109 -0
  53. data/lib/docopslab/dev/spell_check.rb +2 -2
  54. data/lib/docopslab/dev/sync_ops.rb +94 -33
  55. data/lib/docopslab/dev/tasks.rb +58 -18
  56. data/lib/docopslab/dev/version.rb +1 -1
  57. data/lib/docopslab/dev.rb +75 -35
  58. data/specs/data/default-manifest.yml +15 -5
  59. data/specs/data/library-index.yml +22 -0
  60. data/specs/data/manifest-schema.yaml +142 -4
  61. data/specs/data/tasks-def.yml +122 -10
  62. metadata +28 -39
  63. data/assets/config-packs/actionlint/base.yml +0 -13
  64. data/assets/config-packs/actionlint/project.yml +0 -13
  65. data/assets/config-packs/htmlproofer/base.yml +0 -27
  66. data/assets/config-packs/htmlproofer/project.yml +0 -25
  67. data/assets/config-packs/rubocop/base.yml +0 -130
  68. data/assets/config-packs/rubocop/project.yml +0 -8
  69. data/assets/config-packs/shellcheck/base.shellcheckrc +0 -14
  70. data/assets/config-packs/subtxt/ai-asciidoc-antipatterns.sub.txt +0 -11
  71. data/assets/config-packs/vale/asciidoc/ExplicitSectionIDs.yml +0 -8
  72. data/assets/config-packs/vale/asciidoc/ExtraLineBeforeLevel1.yml +0 -7
  73. data/assets/config-packs/vale/asciidoc/OneSentencePerLine.yml +0 -8
  74. data/assets/config-packs/vale/asciidoc/PreferSourceBlocks.yml +0 -8
  75. data/assets/config-packs/vale/asciidoc/ProperAdmonitions.yml +0 -8
  76. data/assets/config-packs/vale/asciidoc/ProperDLs.yml +0 -7
  77. data/assets/config-packs/vale/asciidoc/UncleanListStart.yml +0 -8
  78. data/assets/config-packs/vale/authoring/ButParagraph.yml +0 -8
  79. data/assets/config-packs/vale/authoring/ExNotEg.yml +0 -8
  80. data/assets/config-packs/vale/authoring/LiteralTerms.yml +0 -20
  81. data/assets/config-packs/vale/authoring/Spelling.yml +0 -679
  82. data/assets/config-packs/vale/base.ini +0 -38
  83. data/assets/config-packs/vale/config/scripts/ExplicitSectionIDs.tengo +0 -56
  84. data/assets/config-packs/vale/config/scripts/ExtraLineBeforeLevel1.tengo +0 -121
  85. data/assets/config-packs/vale/config/scripts/OneSentencePerLine.tengo +0 -53
  86. data/assets/config-packs/vale/project.ini +0 -5
  87. data/assets/hooks/pre-commit +0 -63
  88. data/assets/hooks/pre-push +0 -72
  89. data/assets/scripts/adoc_section_ids.rb +0 -50
  90. data/assets/scripts/build-common.sh +0 -193
  91. data/assets/scripts/build-docker.sh +0 -64
  92. data/assets/scripts/build.sh +0 -56
  93. data/assets/scripts/parse_jekyll_asciidoc_logs.rb +0 -467
  94. data/assets/templates/Gemfile +0 -7
  95. data/assets/templates/Rakefile +0 -3
  96. data/assets/templates/gitignore +0 -69
  97. data/assets/templates/jekyll-asciidoc-fix.prompt.yml +0 -17
  98. data/assets/templates/spellcheck.prompt.yml +0 -16
  99. data/docs/agent/AGENTS.md +0 -229
@@ -20,12 +20,10 @@ Gem::Specification.new do |spec|
20
20
  spec.metadata['changelog_uri'] = 'https://github.com/DocOps/lab/blob/main/gems/docopslab-dev/README.adoc'
21
21
  spec.metadata['rubygems_mfa_required'] = 'true'
22
22
 
23
- spec.files = Dir.glob('{lib,config-packs,hooks,docs,assets}/**/*') +
23
+ spec.files = Dir.glob('{lib,docs}/**/*') +
24
24
  %w[README.adoc LICENSE docopslab-dev.gemspec] +
25
25
  Dir.glob('specs/data/*')
26
26
 
27
- spec.bindir = 'exe'
28
- spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
29
27
  spec.require_paths = ['lib']
30
28
 
31
29
  # Core runtime dependencies
@@ -34,6 +32,7 @@ Gem::Specification.new do |spec|
34
32
  spec.add_dependency 'yaml', '~> 0.2'
35
33
 
36
34
  # Code quality and linting
35
+ spec.add_dependency 'asciisourcerer', '~> 0.2'
37
36
  spec.add_dependency 'debride', '~> 1.13'
38
37
  spec.add_dependency 'fasterer', '~> 0.11'
39
38
  spec.add_dependency 'flog', '~> 4.8'
data/docs/agent/index.md CHANGED
@@ -1,7 +1,5 @@
1
1
  # DocOps Lab AI Agent Documentation
2
2
 
3
- <section class="docs-group"></section>
4
-
5
3
  ## Roles
6
4
 
7
5
  #### [AGENT ROLE: Assistant Planner / Project Architect](/docs/agent/planner-architect/)
@@ -22,8 +20,6 @@
22
20
 
23
21
  #### [AGENT ROLE: Technical Writer](/docs/agent/tech-writer/)
24
22
 
25
- <section class="docs-group"></section>
26
-
27
23
  ## Skills
28
24
 
29
25
  #### [AI Agent Instructions for Git Operations](/docs/agent/git/)
@@ -32,6 +28,10 @@
32
28
 
33
29
  #### [Agent Rake CLI Guide](/docs/agent/rake-cli-dev/)
34
30
 
31
+ #### [Bash CLI Development for Agents](/docs/agent/bash-cli-dev/)
32
+
33
+ #### [Bash Coding for Agents](/docs/agent/bash-styles/)
34
+
35
35
  #### [Code Commenting](/docs/agent/code-commenting/)
36
36
 
37
37
  #### [Documenting Product Changes](/docs/agent/write-the-docs/)
@@ -58,8 +58,6 @@
58
58
 
59
59
  #### [Writing Tests for DocOps Lab Projects](/docs/agent/tests-writing/)
60
60
 
61
- <section class="docs-group"></section>
62
-
63
61
  ## Topics
64
62
 
65
63
  #### [AI Agent Instructions for In-house Dev-Tooling Usage](/docs/agent/dev-tooling-usage/)
@@ -70,8 +68,6 @@
70
68
 
71
69
  #### [Product Documentation Deployment](/docs/agent/product-docs-deployment/)
72
70
 
73
- <section class="docs-group"></section>
74
-
75
71
  ## Missions
76
72
 
77
73
  #### [MISSION: Conduct a Product Release](/docs/agent/conduct-release/)
@@ -0,0 +1,470 @@
1
+ # Bash Coding for Agents
2
+
3
+ This document is intended for AI agents operating within a DocOps Lab environment.
4
+
5
+ ## File and Script Structure
6
+
7
+ ### Bash Version (4.0)
8
+
9
+ Use Bash 4.0 or later to take advantage of modern features like associative arrays and improved string manipulation.
10
+
11
+ ### Shebang
12
+
13
+ Always start scripts with this canonical shebang.
14
+
15
+ ```bash
16
+ #!/usr/bin/env bash
17
+ ```
18
+
19
+ ### Script Header
20
+
21
+ Follow the shebang with a brief inline comment block covering the script’s purpose and dependencies. Keep it compact: one line per thought, no visual padding.
22
+
23
+ ```bash
24
+ #!/usr/bin/env bash
25
+ # script-name: brief description of what the script does.
26
+ # Depends on: curl, jq
27
+ ```
28
+
29
+ ### Indentation
30
+
31
+ Indent with 2 spaces anywhere indentation is called for.
32
+
33
+ Do not use 4 spaces or tabs.
34
+
35
+ ### Code Organization
36
+
37
+ Structure your script into logical sections to improve readability.
38
+
39
+ ```bash
40
+ #!/usr/bin/env bash
41
+ # script-name: brief description.
42
+ # Depends on: curl, jq
43
+ set -euo pipefail
44
+
45
+ # CONSTANTS
46
+ readonly SCRIPT_VERSION="1.0.0"
47
+
48
+ # HELPERS
49
+ _validate_input() {
50
+ # ...
51
+ }
52
+
53
+ # OPERATIONS
54
+ process_data() {
55
+ # ...
56
+ }
57
+
58
+ # COMMANDS
59
+ cmd_run() {
60
+ _validate_input "$1"
61
+ process_data "$1"
62
+ }
63
+
64
+ # DISPATCH
65
+ case "${1:-}" in
66
+ run) shift; cmd_run "$@" ;;
67
+ *) printf 'Usage: script-name run\n' >&2; exit 1 ;;
68
+ esac
69
+ ```
70
+
71
+ ### Comment Style
72
+
73
+ Avoid em dashes or en dashes in comments.
74
+
75
+ Use a colon (`:`) to separate a name from its description.
76
+
77
+ Use a semicolon (`;`) to combine phrases into a single line when they are closely related.
78
+
79
+ Example comment styles
80
+
81
+ ```bash
82
+ # clobber.sh: A script to overwrite files; use with caution.
83
+
84
+ # Write config file (first run only; do not clobber user edits)
85
+
86
+ # path set by init --local; sync uses it instead of git pull
87
+ ```
88
+
89
+ ### Section Comments
90
+
91
+ Mark major sections with plain uppercase `#` labels. No decorative dashes or borders needed; the label is sufficient.
92
+
93
+ ```bash
94
+ # CONSTANTS
95
+ # HELPERS
96
+ # COMMANDS
97
+ # DISPATCH
98
+ ```
99
+
100
+ In longer scripts (several hundred lines or more), a horizontal rule comment may precede a major section label to add visual weight. Use these sparingly.
101
+
102
+ ```bash
103
+ # ---------------------------------------------------------------------------
104
+ # COMMANDS
105
+ # ---------------------------------------------------------------------------
106
+ ```
107
+
108
+ ## Naming Conventions
109
+
110
+ ### Variables
111
+
112
+ <dl>
113
+ <dt class="hdlist1">Global variables and constants</dt>
114
+ <dd>
115
+ Use `SCREAMING_SNAKE_CASE`. Use `readonly` for constants.
116
+
117
+ - `readonly MAX_RETRIES=5`
118
+
119
+ - `APP_CONFIG_PATH=".env"`
120
+ </dd>
121
+ <dt class="hdlist1">Local variables</dt>
122
+ <dd>
123
+ Use `snake_case` and `local` declaration.
124
+
125
+ - `local user_name="$1"`
126
+ </dd>
127
+ </dl>
128
+
129
+ ### Functions
130
+
131
+ <dl>
132
+ <dt class="hdlist1">Operation functions</dt>
133
+ <dd>
134
+ The substantive work of a script; what `cmd_` functions orchestrate, and what sourced libraries export as their callable API. Use unprefixed `snake_case`.
135
+
136
+ - `evaluate_system()`, `build_docker_image()`, `get_current_version()`
137
+ </dd>
138
+ <dt class="hdlist1">Helper functions</dt>
139
+ <dd>
140
+ Prefix internal utility functions with ``. This applies in both standalone scripts and sourced library files. In a sourced library, the `` prefix signals that these functions are implementation details and reduces the risk of collisions in the calling script’s namespace.
141
+
142
+ - `_bold()`, `_check_help()`, `_resolve_slug()`, `_check_project_root()`
143
+ </dd>
144
+ <dt class="hdlist1">Subcommand handlers</dt>
145
+ <dd>
146
+ Prefix functions that implement top-level subcommands with `cmd_`. The dispatch `case` at the bottom of the script maps argument strings to these functions unambiguously.
147
+
148
+ - `cmd_init()`, `cmd_run()`, `cmd_check()`
149
+ </dd>
150
+ </dl>
151
+
152
+ ## Variables and Data
153
+
154
+ ### Declaration and Scoping
155
+
156
+ Always use `local` to declare variables inside functions. This prevents polluting the global scope and avoids unintended side effects.
157
+
158
+ ```bash
159
+ _some_action() {
160
+ local file_path="$1" # Good: variable is local to the function
161
+ count=0 # Avoid: variable is global by default
162
+ }
163
+ ```
164
+
165
+ ### Quoting
166
+
167
+ Always quote variable expansions (`"$variable"`) and command substitutions (`"$(command)"`) to prevent issues with word splitting and unexpected filename expansion (globbing).
168
+
169
+ ```bash
170
+ # Good: handles spaces and special characters in filenames
171
+ echo "$file_name"
172
+ touch "$new_file"
173
+
174
+ # Avoid: will fail if file_name contains spaces
175
+ echo $file_name
176
+ touch $new_file
177
+ ```
178
+
179
+ > **NOTE:** Names of files created by DocOps Lab should never include spaces, but this habit is important for dealing with user input or external data. Always remember that many of our users come from Windows, where spaces in filenames are common.
180
+
181
+ ### Arrays
182
+
183
+ Use standard indexed arrays for lists of items.
184
+
185
+ Use associative arrays (`declare -A`) for key-value pairs (i.e., maps).
186
+
187
+ ```bash
188
+ # Indexed array
189
+ local -a packages=("git" "curl" "jq")
190
+ echo "First package is: ${packages[0]}"
191
+
192
+ # Associative array
193
+ declare -A user_details
194
+ user_details["name"]="John Doe"
195
+ user_details["email"]="john.doe@example.com"
196
+ echo "User email: ${user_details["email"]}"
197
+ ```
198
+
199
+ ## Functions
200
+
201
+ ### Syntax
202
+
203
+ Use the `name() { }` syntax. Do not use the `function` keyword; it is redundant in Bash and creates visual inconsistency when mixed with bare definitions.
204
+
205
+ ```bash
206
+ # Good
207
+ some_action() {
208
+ local arg="$1"
209
+ # ...
210
+ }
211
+
212
+ # Avoid
213
+ function some_action() {
214
+ local arg="$1"
215
+ # ...
216
+ }
217
+ ```
218
+
219
+ Single-line form is acceptable for very short utility functions:
220
+
221
+ ```bash
222
+ _bold() { printf '\033[1m%s\033[0m' "$*"; }
223
+ ```
224
+
225
+ ### Arguments
226
+
227
+ Access arguments using positional parameters (`$1`, `$2`, etc.). Use `"$@"` to forward all arguments.
228
+
229
+ ```bash
230
+ _log_message() {
231
+ local level="$1"
232
+ local message="$2"
233
+ echo "[$level] $message"
234
+ }
235
+
236
+ _log_message "INFO" "Process complete."
237
+ ```
238
+
239
+ ### Returning Values
240
+
241
+ **To return a string or data** , use `echo` or `printf` and capture the output using command substitution.
242
+
243
+ ```bash
244
+ _get_user_home() {
245
+ local user="$1"
246
+ # ... logic to find home directory ...
247
+ echo "/home/$user" # Returns string via stdout
248
+ }
249
+ ```
250
+
251
+ **To return a status** , use `return` with a numeric code. `0` means success, and any non-zero value (`1-255`) indicates failure.
252
+
253
+ ```bash
254
+ _check_file_exists() {
255
+ if [[-f "$1"]]; then
256
+ return 0 # Success
257
+ else
258
+ return 1 # Failure
259
+ fi
260
+ }
261
+ ```
262
+
263
+ ## Conditionals
264
+
265
+ Use `[[…​]]` for conditional tests. It is more powerful, prevents many common errors, and is easier to use than the older `[…​]` or `test` builtins.
266
+
267
+ ```bash
268
+ # Good
269
+ if [["$name" == "admin" && -f "$config_file"]]; then
270
+ # ...
271
+ fi
272
+
273
+ # Avoid
274
+ if ["$name" = "admin" -a -f "$config_file"]; then
275
+ # ...
276
+ fi
277
+ ```
278
+
279
+ For dispatching based on a command or option, `case` statements are often cleaner than long `if/elif/else` chains.
280
+
281
+ ```bash
282
+ case "$command" in
283
+ build) cmd_build
284
+ ;;
285
+ run) cmd_run
286
+ ;;
287
+ *)
288
+ printf 'Error: unknown command: %s\n' "$command" >&2
289
+ exit 1
290
+ ;;
291
+ esac
292
+ ```
293
+
294
+ ## Error Handling
295
+
296
+ Use `set -euo pipefail` at the top of every script.
297
+
298
+ <dl>
299
+ <dt class="hdlist1">`e`</dt>
300
+ <dd>
301
+ Exit immediately when any command returns a non-zero status.
302
+ </dd>
303
+ <dt class="hdlist1">`u`</dt>
304
+ <dd>
305
+ Treat unset variables as an error, catching silent bugs from empty references.
306
+ </dd>
307
+ <dt class="hdlist1">`o pipefail`</dt>
308
+ <dd>
309
+ Fail a pipeline if any command within it fails, not just the last one.
310
+ </dd>
311
+ </dl>
312
+
313
+ Print error messages to standard error (`stderr`) and exit with a non-zero status.
314
+
315
+ ```bash
316
+ #!/usr/bin/env bash
317
+ set -euo pipefail
318
+
319
+ printf 'Error: something went wrong.\n' >&2
320
+ exit 1
321
+ ```
322
+
323
+ > **NOTE:** Some scripts warrant more selective error handling. A container entrypoint running as PID 1, or a script that sources untrusted config, may use `set -e` alone. Document any deviations and the reason for them.
324
+
325
+ ### Cleanup Traps
326
+
327
+ For scripts that create temporary resources or modify system state, register a cleanup function with `trap` so those resources are removed whether the script exits normally, fails under `set -e`, or is interrupted.
328
+
329
+ ```bash
330
+ _cleanup() {
331
+ [[-n "${tmp_file:-}"]] && rm -f "$tmp_file"
332
+ }
333
+ trap _cleanup EXIT INT TERM
334
+
335
+ tmp_file="$(mktemp)"
336
+ # ... work with $tmp_file ...
337
+ ```
338
+
339
+ > **TIP:** The `EXIT` pseudo-signal fires on both normal exit and on `set -e` termination. Adding `INT` and `TERM` ensures cleanup even when the user presses Ctrl+C or the process is sent SIGTERM.
340
+
341
+ ### Sourced Libraries
342
+
343
+ Do not place `set -euo pipefail` inside a sourced library file. The calling script owns the error mode. The library’s functions will execute under whatever error mode the caller established.
344
+
345
+ ```bash
346
+ #!/usr/bin/env bash
347
+ # my-lib.sh; shared helpers sourced by build scripts.
348
+ # Do NOT add set -euo pipefail here.
349
+
350
+ _do_something() {
351
+ # ...
352
+ }
353
+ ```
354
+
355
+ For file-level variables that a sourced library exports for its callers to read, ShellCheck will emit `SC2034` ("variable appears unused"). Suppress it inline on those specific declarations rather than disabling the check globally.
356
+
357
+ ```bash
358
+ # shellcheck disable=SC2034 # exported; read by callers
359
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
360
+ ```
361
+
362
+ ### Intentional Word-splitting
363
+
364
+ When a variable genuinely needs to be word-split (ex: an options string passed to a command), suppress the ShellCheck warning inline on that specific line. Add a comment explaining the intent.
365
+
366
+ ```bash
367
+ # shellcheck disable=SC2086 # intentional: $docker_args may contain multiple flags
368
+ docker build ${docker_args} -t "${image}" .
369
+ ```
370
+
371
+ ## Output
372
+
373
+ Prefer `printf` over `echo` for all script output.
374
+
375
+ - `printf` is predictable, portable across Bash scripts, and supports format strings.
376
+
377
+ - `echo` behaviour varies across shells and platforms, particularly with `-e` and `-n`.
378
+
379
+ - Never use `echo -e`.
380
+
381
+ - Use `printf` with `\n`, or a heredoc.
382
+
383
+ ```bash
384
+ # Good
385
+ printf 'Error: %s not found.\n' "$name" >&2
386
+
387
+ # Capture heredoc as a local var
388
+ read -r -d '' error_message <<'ERRMSG'
389
+ Error: Something went wrong.
390
+ Please check your configuration and try again.
391
+ ERRMSG
392
+ printf '%s\n' "$error_message"
393
+
394
+ # Avoid
395
+ echo -e "Error: $name not found.\n"
396
+ ```
397
+
398
+ > **TIP:** Note the use of semantic heredoc delimiters (`ERRMSG`) instead of generic `EOF` or `HEREDOC`.
399
+
400
+ For output intended for the user (status ticks, warnings, separators), use the shared style helpers from the centrally maintained [`universals.sh`](https://github.com/DocOps/lab/blob/main/gems/docopslab-dev/assets/templates/universals.sh) rather than writing raw ANSI codes inline. See the `universal-style-helpers` tagged segment for the canonical set.
401
+
402
+ Universal style helpers common to all DocOps Lab scripts
403
+
404
+ ```bash
405
+ _bold() { printf '\033[1m%s\033[0m' "$*"; }
406
+ _green() { printf '\033[32m%s\033[0m' "$*"; }
407
+ _yellow() { printf '\033[33m%s\033[0m' "$*"; }
408
+ _red() { printf '\033[31m%s\033[0m' "$*"; }
409
+ _tick() { printf '%s %s\n' "$(_green '✓')" "$*"; }
410
+ _warn() { printf '%s %s\n' "$(_yellow '⚠')" "$*"; }
411
+ _fail() { printf '%s %s\n' "$(_red '✗')" "$*"; }
412
+ _info() { printf ' %s\n' "$*"; }
413
+ _sep() { printf '%s\n' "────────────────────────────────────────────────"; }
414
+ _run_echo() { printf '\n%s %s\n\n' "$(_bold '▶')" "$(_bold "$*")"; }
415
+ ```
416
+
417
+ ## Practices to Avoid
418
+
419
+ ### Avoid Emojis in Output
420
+
421
+ Do not use emojis in script output. Use the symbol helpers from `universal-style-helpers` (`_tick`, `_warn`, `_fail`, `_info`) which provide consistent Unicode characters (`✓`, `⚠`, `✗`).
422
+
423
+ Let’s keep it classy.
424
+
425
+ ```bash
426
+ # Good
427
+ _tick "Gem built: $gem_file"
428
+ _fail "Docker not found."
429
+
430
+ # Avoid
431
+ echo -e "\U2705 Gem built: $gem_file"
432
+ echo "❌ Docker not found."
433
+ ```
434
+
435
+ ### Avoid `eval`
436
+
437
+ The `eval` command can execute arbitrary code and poses a significant security risk if used with external or user-provided data.
438
+
439
+ It also makes code difficult to debug.
440
+
441
+ Avoid it whenever possible. Modern Bash versions provide safer alternatives like namerefs (`declare -n`) for indirect variable/array manipulation.
442
+
443
+ ### Avoid Backticks
444
+
445
+ Use `$(...)` for command substitution instead of backticks (``...``). It is easier to read and can be nested.
446
+
447
+ ```bash
448
+ # Good
449
+ current_dir="$(pwd)"
450
+
451
+ # Avoid
452
+ current_dir=`pwd`
453
+ ```
454
+
455
+ ### Avoid `which`
456
+
457
+ Use `command -v` to test whether a command is available on the `PATH`.`which` is an external binary whose behavior varies across systems and is not available everywhere.`command -v` is a Bash builtin and the POSIX-portable alternative.
458
+
459
+ ```bash
460
+ # Good
461
+ if command -v docker &>/dev/null; then
462
+ # ...
463
+ fi
464
+
465
+ # Avoid
466
+ if which docker > /dev/null 2>&1; then
467
+ # ...
468
+ fi
469
+ ```
470
+