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,198 @@
|
||||
---
|
||||
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
|
||||
<target-repository>/
|
||||
Taskfile.yml # root task runner, includes module Taskfiles
|
||||
.scripts/
|
||||
<module>/
|
||||
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/<module>/api/<command>.sh [args]
|
||||
task <module-alias>:<task-name> -- [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/<module>/
|
||||
api/
|
||||
<command-name>.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
|
||||
-<command-name>.sh # one implementation function per command
|
||||
Taskfile.yml # optional
|
||||
```
|
||||
|
||||
| File | Responsibility |
|
||||
|---|---|
|
||||
| `api/<command>.sh` | Thin public entrypoint. Sources `lib/--index.sh`, calls one function. |
|
||||
| `lib/-<command>.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/<command>.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/-<command>.sh # defines the function
|
||||
└── call _<moduleNamespace>_<commandFunction>
|
||||
```
|
||||
|
||||
**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 | `_<moduleNamespace>_<actionName>` | `_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/<new-module>/`.
|
||||
2. Replace every `module-example-v1` path, the `MODULE_EXAMPLE_V1_` env prefix, and the
|
||||
`_moduleExampleV1_` function prefix.
|
||||
3. Add implementation files as `lib/-<command>.sh` and source them from `lib/--index-api.sh`.
|
||||
4. Add `api/<command>.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/<module> -name '*.sh' -print0 | xargs -0 -n1 bash -n
|
||||
|
||||
# Confirm task exposure resolves
|
||||
task --list
|
||||
|
||||
# Run the command itself, from the repository root
|
||||
./.scripts/<module>/api/<command>.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.
|
||||
Reference in New Issue
Block a user