Ports generic-scripts-module-v1 into a routed skill: module anatomy, shared loggers/base modules, Taskfile integration, workflows, and pattern decisions. Fixes a latent defect in the loggers example that silenced all logging in the module scaffold: loggers/lib/--index-api.sh now sources --env-vars-reader.sh, and the reader preserves already-exported values so caller overrides still win. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
4.9 KiB
Workflows
Creating a module, changing an existing one, and the checks to run before delivering either.
1. Creating a new module
-
Confirm the module name and namespace — the kebab-case directory name, the
_camelCase_function prefix, and theUPPER_SNAKE_environment prefix all derive from it and must agree. -
Inspect nearby modules for a comparable pattern before designing a new one. Matching an existing module beats inventing a variant.
-
Copy the scaffold:
cp -R assets/module-example-v1 .scripts/<new-module> -
Rename throughout — every
module-example-v1path,MODULE_EXAMPLE_V1_, and_moduleExampleV1_:cd .scripts/<new-module> grep -rl 'module-example-v1\|MODULE_EXAMPLE_V1_\|_moduleExampleV1_' . | while read -r f; do sed -i '' \ -e 's|module-example-v1|<new-module>|g' \ -e 's|MODULE_EXAMPLE_V1_|<NEW_MODULE>_|g' \ -e 's|_moduleExampleV1_|_<newModule>_|g' "$f" done grep -rn 'module-example-v1\|MODULE_EXAMPLE_V1_\|_moduleExampleV1_' . || echo "rename complete"(
sed -i ''is BSD/macOS; usesed -ion GNU.) -
Set the defaults and flags in
lib/--env-vars-reader.sh, using${VAR:-default}. -
Set the required-input checks in
lib/--env-vars-validator.sh. -
Write implementation files as
lib/-<command>.sh, one function each. -
Source every implementation file from
lib/--index-api.sh. -
Add
api/<command>.shwrappers for user-facing commands only. -
Add a module
Taskfile.yml, and a root include only if the namespace is stable —references/taskfile.md. -
Make scripts executable where they will be invoked directly:
chmod +x .scripts/<new-module>/api/*.sh. -
Validate — section 3.
-
Update the repository's script documentation if it has a memory bank or equivalent.
2. Changing an existing module
-
Read
lib/--index.shandlib/--index-api.shfirst. They show what the module loads and in what order, which determines where a change can safely go. -
Decide which layer the change belongs to:
The change is It belongs in A new user-facing command api/+lib/-<command>.sh+--index-api.shNew behaviour in an existing command that command's lib/-<command>.shA new CLI flag or default --env-vars-reader.shA new required input --env-vars-validator.shTask-runner exposure module Taskfile.yml, and the root include if public -
Preserve the existing naming and function prefixes. Consistency inside a module beats matching a newer convention used elsewhere — mixed prefixes in one module are worse than an old prefix used uniformly.
-
Add every new
lib/-*.shto--index-api.shas you create it, not afterwards. -
Add
api/wrappers only for user-facing commands; internal helpers stay inlib/. -
Validate — section 3.
-
Update the script documentation when public behaviour changes.
3. Validation checklist
- Public commands are under
api/; reusable implementation is underlib/. api/wrappers are thin — sourcelib/--index.sh, call one function.lib/--index.shsources reader, validator, API index, in that order.lib/--index-api.shsources every implementation file the wrappers need.- New CLI flags are parsed in
--env-vars-reader.sh, using${VAR:-default}. - Required variables are validated before use.
- Logs use
_loggers_*helpers, and logging actually produces output. - No secret values are printed or documented.
- Implementation functions
returnrather thanexit. - Taskfile entries invoke
./.scripts/<module>/api/<command>.sh {{.CLI_ARGS}}. - A root
Taskfile.ymlinclude was added only if intended. bash -npasses for every changed script.- The command was actually run once from the repository root.
- Script documentation is updated when public behaviour changed.
4. Validation commands
# Syntax check every script in the module
find .scripts/<module> -name '*.sh' -print0 | xargs -0 -n1 bash -n
# Every implementation file is wired into the API index.
# Note the `--` before the pattern: implementation basenames start with a dash,
# which grep would otherwise parse as an option flag and report every file.
cd .scripts/<module>
for f in lib/-*.sh; do
case "$f" in lib/--*) continue;; esac
grep -qF -- "$(basename "$f")" lib/--index-api.sh || echo "NOT INDEXED: $f"
done
# Task exposure resolves
task --list-all
# Run it — the only check that proves the wiring works
./.scripts/<module>/api/<command>.sh --dry-run
bash -n proves a file parses. It does not prove a function is reachable, that a flag is
handled, or that logging is bootstrapped — all three fail silently at runtime while every
static check passes. The final run is the one that matters; a --dry-run flag exists to make
it cheap and safe.