Files
oleg-lukasonokandClaude Opus 5 31a5408a6c Add development-scripts agent skill
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>
2026-07-30 22:18:55 +03:00

204 lines
6.7 KiB
Markdown

# Module Anatomy
The four file types inside `lib/`, the `api/` wrapper, and the patterns each follows.
Read this when writing or changing a reader, a validator, an implementation function, or a
public wrapper.
---
## 1. `--env-vars-reader.sh` — defaults and CLI parsing
Responsibilities, in order:
1. export defaults, preserving any value already exported by the caller;
2. parse CLI flags into environment variables;
3. restore the positional arguments so later consumers still see them.
```bash
#!/bin/bash
export MODULE_EXAMPLE_V1_NAME="${MODULE_EXAMPLE_V1_NAME:-default-name}"
export MODULE_EXAMPLE_V1_OUTPUT_DIR="${MODULE_EXAMPLE_V1_OUTPUT_DIR:-.tmp/module-example-v1}"
export MODULE_EXAMPLE_V1_DRY_RUN="${MODULE_EXAMPLE_V1_DRY_RUN:-false}"
ALL_ARGS=("$@")
while [[ "$#" -gt 0 ]]; do
case $1 in
--name)
export MODULE_EXAMPLE_V1_NAME="$2"
shift
;;
--output-dir)
export MODULE_EXAMPLE_V1_OUTPUT_DIR="$2"
shift
;;
--dry-run)
export MODULE_EXAMPLE_V1_DRY_RUN="true"
;;
*) ;;
esac
shift
done
set -- "${ALL_ARGS[@]}"
```
Rules:
- Use a stable uppercase environment prefix per module.
- **Always use `${VAR:-default}`**, never a bare `export VAR=value`. A bare assignment
silently discards a caller's override, and the caller has no way to detect it. This is what
makes a reader safe to source more than once.
- Prefer explicit CLI flags over positional arguments.
- Defaults must be safe to run locally — a default that writes outside `.tmp/` or touches a
shared system is not a safe default.
- Never print secrets here.
The `ALL_ARGS` save/restore matters because `shift` consumes the arguments. Without the
`set --` at the end, anything sourced after the reader sees an empty argument list.
---
## 2. `--env-vars-validator.sh` — required inputs
```bash
#!/bin/bash
. ./.scripts/loggers/lib/--index-api.sh
if [ -z "${MODULE_EXAMPLE_V1_NAME}" ]; then
_loggers_error "module-example-v1" "Missing required --name / MODULE_EXAMPLE_V1_NAME"
exit 1
fi
```
Rules:
- Runs from `--index.sh`, after the reader and before the API index, so failures happen before
any implementation function is even defined.
- Use `_loggers_error` for the failure and `_loggers_info` for a usage hint.
- `exit 1` is correct **here** — the validator runs during bootstrap of a top-level command,
and stopping is the intent.
- Validate only what every command in the module needs. A variable that only one command
requires is validated inside that command's function, not globally — otherwise unrelated
commands fail on input they never use.
---
## 3. `--index-api.sh` — dependency and implementation wiring
```bash
#!/bin/bash
. ./.scripts/loggers/lib/--index-api.sh
. ./.scripts/base/lib/--index-api.sh
. ./.scripts/module-example-v1/lib/-initialize-one.sh
```
Rules:
- Upstream dependencies first, then this module's own implementation files.
- Source other modules' `--index-api.sh`, **not** their `--index.sh` — see
`references/shared-modules.md` §2.
- **Every implementation file must appear here.** A `lib/-*.sh` that is never sourced defines
nothing, and the failure surfaces as `command not found` for a function you can see on disk.
- Safe to source repeatedly. Sourcing defines functions; it should have no other side effect.
---
## 4. `--index.sh` — runtime bootstrap
```bash
#!/bin/bash
# --> passed parameters are read & exported environment variables
. ./.scripts/module-example-v1/lib/--env-vars-reader.sh
# --> required environment variables are validated for existence
. ./.scripts/module-example-v1/lib/--env-vars-validator.sh
# --> available functions are imported/exported
. ./.scripts/module-example-v1/lib/--index-api.sh
```
Rules:
- Exactly these three sources, in exactly this order.
- This is the module's **own** entrypoint, sourced by its `api/` wrappers. Other modules never
source it — they take `--index-api.sh`.
- A module with no required inputs may omit the validator, but keeping an empty one makes the
next required variable a one-line change.
---
## 5. Implementation functions — `lib/-<command>.sh`
```bash
#!/bin/bash
. ./.scripts/loggers/lib/--index-api.sh
_moduleExampleV1_initializeOne() {
local FUNCTION_NAME="_moduleExampleV1_initializeOne"
_loggers_info "${FUNCTION_NAME}" "Initializing module-example-v1"
_loggers_debug "${FUNCTION_NAME}" "Output directory: ${MODULE_EXAMPLE_V1_OUTPUT_DIR}"
if [ "${MODULE_EXAMPLE_V1_DRY_RUN}" = "true" ]; then
_loggers_info "${FUNCTION_NAME}" "Dry run enabled; no files will be changed."
return 0
fi
mkdir -p "${MODULE_EXAMPLE_V1_OUTPUT_DIR}"
_loggers_info "${FUNCTION_NAME}" "Initialization complete."
}
```
Rules:
- Open with `local FUNCTION_NAME="_<module>_<function>"` — every logger call takes it as the
first argument, which is what makes log output traceable to a specific function.
- One file, one primary function. Small private helpers may live alongside it.
- Use `_loggers_*` helpers, never raw `echo`, for status and errors.
- **`return` non-zero on failure; never `exit`.** The file is sourced, so `exit` terminates
the caller's shell — including an interactive one.
- Read configuration from the module's environment variables. Never re-parse `$@` here.
- Quote every expansion unless word splitting is deliberate.
- Never expose secret values in a log line.
---
## 6. `api/` wrappers
```bash
#!/bin/bash
. ./.scripts/module-example-v1/lib/--index.sh
_moduleExampleV1_initializeOne
```
That is the whole file. Rules:
- Source exactly `lib/--index.sh`, then call exactly one function.
- No implementation, no flag parsing, no validation, no argument forwarding.
- Arguments reach the reader automatically: sourcing happens while the wrapper's own `$@` is
still in scope, so `--env-vars-reader.sh` sees them without being passed anything.
- Create a wrapper only for a genuinely user-facing command. An internal helper stays in
`lib/` — every file in `api/` is a public interface someone may come to depend on.
---
## 7. Diagnosing a broken module
| Symptom | Cause |
|---|---|
| `command not found` for a function that exists on disk | Its file is missing from `lib/--index-api.sh` |
| No log output at all, exit code 0 | Logger level flags never exported — see `references/shared-modules.md` §3 |
| A flag is ignored | Not handled in `--env-vars-reader.sh`, or handled after the `set --` restore |
| Later sources see no arguments | Reader is missing the `ALL_ARGS` save/`set --` restore |
| `No such file or directory` for a `lib/` path | Run from inside the module instead of the repository root |
| `.env` loaded unexpectedly | Something sourced `base/lib/--index.sh` instead of `base/lib/--index-api.sh` |
| The shell exits on a handled error | `exit` used inside a sourced implementation function |