Instruction file imported from dfadler1984/cursor-rules (
.cursor/rules/shell-unix-philosophy.mdc). Copyright stays with the author.
Shell Scripts — Unix Philosophy Enforcement
Purpose
Enforce Unix Philosophy principles for all shell scripts to prevent complexity creep and maintain composability.
Core Principles (must)
1. Do One Thing Well
Rule: Each script has a single, clearly-defined responsibility.
Enforcement:
- Script name describes one action (verb-noun pattern preferred)
--helpdocumentation states one primary purpose- If you find yourself writing "and" in the purpose statement, consider splitting
Red flags:
- Script has 10+ flags
- Multiple unrelated
casebranches handling different domains - Script length > 200 lines
- Functions handling multiple concerns
Examples:
- ✅ Good:
pr-create(one job: create PRs) - ❌ Bad:
pr-createthat also manages labels, handles templates, formats output
2. Small & Focused
Size targets:
- Target: < 150 lines for most scripts
- Warning threshold: 200 lines — justify or split
- Hard limit: 300 lines — must split
Enforcement:
- Run
wc -l <script>before submitting - If over target, extract functions to separate utilities
- Large case statements → separate scripts per case
Exceptions:
- Generated code (must be marked as such)
- Scripts with extensive help documentation (but consider
--helpflag calling separate doc)
3. Composition via Text Streams
Input/Output separation (must):
- Results →
stdout(parseable, pipe-friendly) - Logs/progress →
stderr(vialog_*helpers from.lib.sh) - Errors →
stderr(viadiehelper)
Pattern:
# Good: composable output
find . -name "*.sh" | grep -v test | xargs shellcheck
# Bad: mixed logs and results to stdout
echo "Checking 10 files..." # This should go to stderr
find . -name "*.sh"
Enforcement:
- Use
.lib.shhelpers:log_info,log_warn,log_error→ all go to stderr - Never
echologs to stdout - Keep stdout clean for piping
4. Minimal, Orthogonal Flags
Flag guidelines:
- Limit: 6-8 flags per script; 10+ is a smell
- Orthogonal: Flags should be independent; avoid complex interactions
- Required vs optional: Minimize required flags; prefer smart defaults
Red flags:
- Flag behavior changes based on other flags
- Example:
--bodybehaves differently if--no-templateor--replace-bodyis set - Multiple flags for the same concept (consolidate)
Patterns:
# Good: orthogonal flags
script.sh --input file.txt --format json --verbose
# Bad: interdependent flags
script.sh --body "text" --no-template --replace-body
# (--body behavior changes based on other flags)
5. Clear Separation of Policy and Mechanism
Policy (configuration) vs Mechanism (execution):
- Defaults and config at top as constants
- Env vars resolved once at boundaries
- Execution logic doesn't embed policy decisions
Pattern:
#!/usr/bin/env bash
# Policy at top
DEFAULT_FORMAT="text"
DEFAULT_TIMEOUT=30
# Derive from env or use defaults
FORMAT="${FORMAT:-$DEFAULT_FORMAT}"
TIMEOUT="${TIMEOUT:-$DEFAULT_TIMEOUT}"
# Mechanism below (doesn't reference DEFAULT_* directly)
do_work() {
local format="$1"
local timeout="$2"
# Pure logic here
}
do_work "$FORMAT" "$TIMEOUT"
Pre-Submission Checklist
Before marking a shell script PR ready for review:
- Script length ≤ 200 lines (or justified)
- Single responsibility stated in
--help - Flags ≤ 10 (preferably ≤ 6)
- Results go to stdout, logs to stderr (via
log_*) - No complex flag interdependencies
- Policy (defaults) separated from mechanism (logic)
- Owner tests exercise the single responsibility
Split Decision Tree
When a script grows beyond targets, use this decision tree:
-
Is it > 200 lines?
- Yes → Extract functions to separate utilities
- Multiple responsibilities → Split by responsibility
-
Does it have 10+ flags?
- Group related flags → separate scripts
- Example:
--wait,--timeout→script-waitwrapper
-
Does it output multiple formats?
- Split:
script(simple output) +script-format(formatting) - Pipe:
script | script-format --dashboard
- Split:
-
Does it do multiple jobs?
- Identify each job → create focused script per job
- Example: PR creation + labeling →
pr-create+pr-label
Refactoring Examples
Split by responsibility: Multi-job script → separate focused scripts (e.g., rules-validate.sh → rules-validate-frontmatter.sh, rules-validate-refs.sh)
Split by I/O: Compute + formatting → composable pipeline (e.g., script | script-format --dashboard)
Extract complex flags: Single script with many flags → focused tools per concern (e.g., pr-create.sh, pr-label.sh, pr-template-fill.sh)
Integration with Existing Standards
Works with D1-D6 (shell-and-script-tooling) for technical foundation and TDD-First (Shell) for owner tests coupling to single responsibility.
Assistant Behavior
When creating new shell scripts
- Check size target: Aim for < 150 lines
- State single purpose: What is the one thing this does?
- Design for composition: Clean stdout, logs to stderr
- Limit flags: Start with ≤ 6; justify each addition
- Separate policy: Defaults at top, logic below
When modifying existing scripts
- Check current size: If approaching 200 lines, propose split
- Review flags: If > 10, propose consolidation or split
- Assess responsibility: If multiple jobs, propose split
- Verify I/O separation: Results to stdout, logs to stderr
When script exceeds thresholds
Propose split with: current analysis (lines/flags/responsibilities), suggested focused scripts, composition example, migration path.
Validation
Manual: Check sizes (wc -l *.sh | awk '$1 > 200'), count flags, verify stdout/stderr separation
Automated (future): Script size validator, flag count validator, stdout pollution detector
References
- Unix Philosophy: Basics of the Unix Philosophy
Scope
- Applies to all shell scripts in
.cursor/scripts/(excluding tests) - Enforced during creation and modification
- Existing scripts: aspirational; refactor when touched or when blocking
Exceptions
Request explicit exception approval when:
- Script legitimately requires > 200 lines (state why)
- Domain naturally requires 10+ flags (rare; consider wrapper scripts)
- Backward compatibility prevents splitting (document migration path)