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>
This commit is contained in:
@@ -0,0 +1,203 @@
|
||||
# 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 |
|
||||
Reference in New Issue
Block a user