Files
root-at-skicandClaude Opus 5 6c8446cf90 Add layered environment model and taskfile composition rules
Fold two overlapping local skills (scripts-shell, shell-scripts-module)
into this one, keeping only what it did not already cover.

- Add references/environment-model.md: the three-layer .env selector model,
  why set -a dot-sourcing beats export $(grep|xargs), tracked samples, the
  no-re-sourcing rule for module readers, and the deployment boundary
  (repo-presence is not git-tracked is not deployed).
- Extend references/taskfile.md with the one-env-driven-task rule (no
  :default pairs) and composing commands through the task surface.
- Add the lifecycle-hook module type to references/module-patterns.md.
- Keep this skill's _moduleExampleV1_ naming as canonical; the retired
  skills' HL_V1_ and double-underscore dialects were not carried over.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-12 22:45:32 +03:00

3.3 KiB

Module Patterns

A decision guide for what kind of module you are building and how it should connect to the others.


1. Module type

Module type Has api/ Has Taskfile.yml Example
Support-only library No No loggers
Shared foundation with public commands Yes Optional / no base
Feature module Yes Usually yes kube, github, data-migration
Wrapper around an external CLI Yes Usually yes helm, openshift, github
Lifecycle hook Yes No devbox (an on-init-hook)

A lifecycle hook module is invoked by external tooling at a fixed moment — a devbox init hook, a container entrypoint, a git hook — rather than by an operator. It keeps api/ because the tool calls that path directly, and skips Taskfile.yml because nobody runs it by hand. Its implementation commonly branches per platform (-install-dependencies-osx.sh, -install-dependencies-linux-ubuntu.sh), one file per branch, sourced from --index-api.sh like any other implementation file.

Pick the type before creating any file — it decides whether api/ and Taskfile.yml exist at all, and adding them later means reworking every consumer that has already started sourcing the module.

A module with no user-facing command is a support library. Do not give it an api/ directory speculatively; every file in api/ is a public interface someone may come to depend on.


2. Dependency sourcing

You need Source
Logging ./.scripts/loggers/lib/--index-api.sh
Shared base functions ./.scripts/base/lib/--index-api.sh
Another module's exported functions that module's lib/--index-api.sh
Your own module's full runtime, from its api/ wrapper your lib/--index.sh

The rule underneath: --index-api.sh is the public edge between modules; --index.sh is a module's own private entrypoint. Sourcing another module's --index.sh runs its reader and validator inside your command — importing its defaults, its required-input checks, and in base's case its .env loading. Symptoms show up as variables nobody set, or a validation failure naming a module the user never invoked.


3. Command exposure

Three independent decisions, in increasing order of commitment:

  1. lib/ only — internal. Reachable from other modules that source your --index-api.sh.
  2. api/<command>.sh — user-facing. Someone can run it directly from the repository root.
  3. Module Taskfile.yml — discoverable through task --list-all.
  4. Root Taskfile.yml include — a stable public namespace, effectively permanent.

Escalate only as far as the command genuinely needs. Each step is easy to add later and disruptive to withdraw, because each one is a promise someone may already be relying on.


4. Common mistakes

  • Putting full implementation logic into api/*.sh.
  • Adding a lib/-*.sh file and forgetting to source it from lib/--index-api.sh.
  • Parsing CLI flags in implementation files instead of --env-vars-reader.sh.
  • Printing secrets in logs.
  • Sourcing base/lib/--index.sh from a library function and unexpectedly loading .env.
  • Adding root Taskfile includes for experimental modules.
  • Giving a support-only library an api/ directory it does not need.
  • Mixing function prefixes inside one module after a rename.