--- name: development-scripts description: > Create, refactor, review, document, and maintain shell script modules under a repository's .scripts/ directory — the api/ + lib/ split, the --index.sh / --index-api.sh / --env-vars-reader.sh / --env-vars-validator.sh bootstrap chain, module and root Taskfile.yml integration, logging through the shared loggers module, and shared helpers from the base module. Use when adding a new script module or a new command to an existing one; writing an api/ wrapper or a lib/ implementation function; adding a CLI flag or a required environment variable; wiring a module into task; debugging a script that produces no output, cannot find a function, or unexpectedly loads .env; or reviewing shell module work for naming, sourcing order, and secret-safety — even when the user only says ".scripts", "Taskfile", "task runner", "shell module", or names a specific module like loggers or base. license: Proprietary metadata: author: workspace-skills-code-agent version: "1.0" spec: agentskills.io/specification compatibility: Requires bash. Taskfile (task) needed only for task-runner integration. Designed for Cline, Claude Code, GitHub Copilot, OpenAI Codex, and other compatible agent environments --- # Repository Script Modules Work rules for shell automation organised as modules under a repository's `.scripts/` directory, with a strict public/private split and a fixed sourcing order. The whole model rests on one idea: **`api/` is what people run, `lib/` is what the code calls, and a single bootstrap chain guarantees that by the time an implementation function runs, its environment has been read, validated, and its dependencies sourced.** ## Scope routing Pick the reference that matches the change; do not load all of them. | The change touches | Read | |---|---| | A reader, validator, implementation function, or `api/` wrapper | `references/module-anatomy.md` | | The `loggers` or `base` module, or logging that produces no output | `references/shared-modules.md` | | A module `Taskfile.yml` or the root `Taskfile.yml` | `references/taskfile.md` | | Creating a module, changing one, or checking work before delivery | `references/workflows.md` | | Deciding what kind of module something should be | `references/module-patterns.md` | Copy-ready modules live in `assets/`: `module-example-v1/` is the scaffold for a new module; `loggers/` and `base/` are working reference implementations. ## 1. Repository model ```text / Taskfile.yml # root task runner, includes module Taskfiles .scripts/ / api/ # public command wrappers — what users run lib/ # implementation functions — what the code calls Taskfile.yml # only when the module is exposed through task ``` Commands always run **from the repository root**, never from inside the module: ```bash ./.scripts//api/.sh [args] task : -- [args] ``` Every `.` source path in every file is written relative to the repository root (`. ./.scripts/loggers/lib/--index-api.sh`). This is why running a script from inside its own directory fails to find its dependencies — that is the convention working as designed, not a bug to patch with `dirname "$0"`. ## 2. Module anatomy ```text .scripts// api/ .sh lib/ --env-vars-reader.sh # defaults + CLI flag parsing --env-vars-validator.sh # required-input checks --index-api.sh # sources dependencies + this module's implementations --index.sh # full runtime bootstrap -.sh # one implementation function per command Taskfile.yml # optional ``` | File | Responsibility | |---|---| | `api/.sh` | Thin public entrypoint. Sources `lib/--index.sh`, calls one function. | | `lib/-.sh` | The command implementation function. | | `lib/--index-api.sh` | Sources upstream module APIs, then this module's implementation files. | | `lib/--index.sh` | Runtime bootstrap: reader, then validator, then API index. | | `lib/--env-vars-reader.sh` | Exports defaults and parses CLI arguments into environment variables. | | `lib/--env-vars-validator.sh` | Validates required variables, failing with an actionable log. | | `Taskfile.yml` | Maps task names to `api/` scripts, forwarding `{{.CLI_ARGS}}`. | Not every module has every file — a support-only library like `loggers` has no `api/` and no `Taskfile.yml`. See `references/module-patterns.md`. ## 3. Standard runtime flow ```text api/.sh └── source lib/--index.sh ├── source lib/--env-vars-reader.sh # defaults + CLI flags → env vars ├── source lib/--env-vars-validator.sh # required vars present? └── source lib/--index-api.sh ├── source dependencies (loggers, base, …) └── source lib/-.sh # defines the function └── call __ ``` **The order is load-bearing.** The reader runs before the validator so the validator has something to check; both run before the API index so implementation functions see a fully-prepared environment. Reordering produces failures that look like missing variables. Arguments reach the reader because sourcing happens with the wrapper's own arguments still in scope — this is why `api/` wrappers need no argument forwarding of their own. ## 4. Naming conventions | Kind | Convention | Example | |---|---|---| | Module directory | lowercase kebab-case | `module-example-v1`, `repositories-nodejs` | | Public command | lowercase kebab-case, action-oriented | `initialize-one.sh`, `find-many-by-query.sh` | | Implementation file | single leading dash | `lib/-initialize-one.sh` | | Index / lifecycle file | double leading dash | `lib/--index.sh`, `lib/--env-vars-reader.sh` | | Function | `__` | `_moduleExampleV1_initializeOne` | | Environment variable | uppercase module prefix | `MODULE_EXAMPLE_V1_OUTPUT_DIR` | Kebab-case module names convert to camelCase function namespaces: | Module | Function prefix | Env prefix | |---|---|---| | `module-example-v1` | `_moduleExampleV1_` | `MODULE_EXAMPLE_V1_` | | `repositories-nodejs` | `_repositoriesNodejs_` | `REPOSITORIES_NODEJS_` | | `operation-utilities` | `_operationUtilities_` | `OPERATION_UTILITIES_` | The dash count is not decoration — it is how you tell at a glance whether a file defines a command (`-name.sh`) or wires the module together (`--name.sh`). ## 5. Non-negotiable rules - **`api/` wrappers stay thin.** Source `lib/--index.sh`, call one function, nothing else. No implementation, no argument parsing, no validation. - **CLI flags are parsed only in `--env-vars-reader.sh`.** Never in an implementation file. - **Every new `lib/-*.sh` gets added to `lib/--index-api.sh`.** Forgetting this is the most common cause of "command not found" for a function that plainly exists. - **Readers preserve already-exported values** — `export X="${X:-default}"` — so a caller can override any setting before invoking the module. - **Log through `_loggers_*` helpers, never raw `echo`.** Loggers write to `stderr`, keeping `stdout` clean for real output. - **Never log secrets.** Redact tokens, API keys, passwords, and kube config. - **Quote variable expansions** unless word splitting is intended. - **`return` non-zero from implementation functions; reserve `exit 1` for validators and bootstrap.** An `exit` inside a sourced library kills the caller's shell. ## 6. Creating a module Full walkthrough in `references/workflows.md`. The short path: 1. Copy `assets/module-example-v1/` to `.scripts//`. 2. Replace every `module-example-v1` path, the `MODULE_EXAMPLE_V1_` env prefix, and the `_moduleExampleV1_` function prefix. 3. Add implementation files as `lib/-.sh` and source them from `lib/--index-api.sh`. 4. Add `api/.sh` wrappers for user-facing commands only. 5. Add a module `Taskfile.yml`, and a root include only if the namespace is stable. 6. Validate (section 7). ## 7. Validation ```bash # Syntax-check every script in the module find .scripts/ -name '*.sh' -print0 | xargs -0 -n1 bash -n # Confirm task exposure resolves task --list # Run the command itself, from the repository root ./.scripts//api/.sh --dry-run ``` `bash -n` catches syntax errors but **not** a missing entry in `--index-api.sh` — that only surfaces at call time. Always run the command once, from the repository root, before delivering. A dry-run flag is worth adding precisely so this check is cheap. ## 8. Mistakes to avoid - Putting implementation logic into `api/*.sh`. - Adding a `lib/-*.sh` file and forgetting to source it from `lib/--index-api.sh`. - Parsing CLI flags in an implementation function instead of the reader. - Sourcing `base/lib/--index.sh` from a library function — it reads `.env` and environment-specific files as a side effect. Use `base/lib/--index-api.sh` when you only need shared functions. - Writing source paths relative to the script instead of the repository root. - Using `exit` inside a sourced implementation function. - Printing secrets, or documenting their values. - Adding a root `Taskfile.yml` include for an experimental module. - Assuming logging works because the code compiles — a logger whose level flags were never exported is silent and returns 0. See `references/shared-modules.md` §3.