Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions .claude/hooks/deny-dangerous.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
#!/usr/bin/env bash
# PreToolUse hook: deny dangerous commands
# Exit 2 = block with message. Exit 0 = allow.
# Omits -e so every check runs and the final exit 0 is always reached.

set -uo pipefail

INPUT=$(cat)

# Extract command — try jq first, fall back to grep+sed if jq is missing
if command -v jq &>/dev/null; then
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
else
COMMAND=$(echo "$INPUT" | grep -o '"command"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/^"command"[[:space:]]*:[[:space:]]*"//;s/"$//' 2>/dev/null || true)
fi

[[ -z "$COMMAND" ]] && exit 0

# rm -rf without explicit path scoping
if echo "$COMMAND" | grep -qE 'rm\s+-[a-zA-Z]*r[a-zA-Z]*f|rm\s+-[a-zA-Z]*f[a-zA-Z]*r' ; then
if echo "$COMMAND" | grep -qE 'rm\s+-rf\s+/\s|rm\s+-rf\s+/$|rm\s+-rf\s+~|rm\s+-rf\s+\.\s|rm\s+-rf\s+\*'; then
echo "BLOCKED: rm -rf with dangerous target. Use a specific path instead." >&2
exit 2
fi
fi

# Force push (allow --force-with-lease)
if echo "$COMMAND" | grep -qE 'git\s+push\s+.*--force' && ! echo "$COMMAND" | grep -qF 'force-with-lease'; then
echo "BLOCKED: git push --force. Use --force-with-lease instead." >&2
exit 2
fi

# Push to main/master/production
if echo "$COMMAND" | grep -qE 'git\s+push\s+(origin\s+)?(main|master|production)\b'; then
echo "BLOCKED: direct push to main/master/production. Use a feature branch and PR." >&2
exit 2
fi

# chmod 777
if echo "$COMMAND" | grep -qE 'chmod\s+777'; then
echo "BLOCKED: chmod 777 is overly permissive. Use specific permissions (755, 644, etc.)." >&2
exit 2
fi

# Pipe to shell
if echo "$COMMAND" | grep -qE '(curl|wget)\s.*\|\s*(bash|sh|zsh)'; then
echo "BLOCKED: pipe-to-shell pattern. Download first, inspect, then execute." >&2
exit 2
fi

# .env modifications
if echo "$COMMAND" | grep -qE '(>|>>|tee|sed\s+-i|vim|nano|cat\s+>)\s*\.env'; then
echo "BLOCKED: .env file modification. Edit .env files manually." >&2
exit 2
fi

# Skip commit hooks
if echo "$COMMAND" | grep -qE 'git\s+commit\s+.*--no-verify|git\s+commit\s+.*-n\b'; then
echo "BLOCKED: --no-verify skips safety hooks. Fix the hook failure instead." >&2
exit 2
fi

# Direct edits to CONFIGURATION block values (template placeholders)
if echo "$COMMAND" | grep -qE 'sed\s.*CONFIGURATION|awk\s.*CONFIGURATION'; then
echo "BLOCKED: CONFIGURATION blocks are template placeholders. Do not modify values directly — users override via environment variables." >&2
exit 2
fi

exit 0
36 changes: 36 additions & 0 deletions .claude/hooks/stop-lint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#!/usr/bin/env bash
# Stop hook: stack-adaptive lint check after every Claude turn
# MUST exit 0 even when errors found (non-zero = infinite fix loops)

# Infinite loop guard
if [[ "${STOP_HOOK_ACTIVE:-}" == "1" ]]; then exit 0; fi
export STOP_HOOK_ACTIVE=1

repo_root=$(git rev-parse --show-toplevel 2>/dev/null) || exit 0
cd "$repo_root" || exit 0

# Check for modified .sh files
changed_sh=$(git diff --name-only 2>/dev/null | grep '\.sh$' || true)
[[ -z "$changed_sh" ]] && exit 0

# Syntax check
while IFS= read -r f; do
[[ -f "$repo_root/$f" ]] || continue
output=$(bash -n "$repo_root/$f" 2>&1) || {
echo "Syntax error in $f:" >&2
echo "$output" >&2
}
done <<< "$changed_sh"

# Shellcheck (if available)
if command -v shellcheck &>/dev/null; then
while IFS= read -r f; do
[[ -f "$repo_root/$f" ]] || continue
output=$(shellcheck -x -S warning "$repo_root/$f" 2>&1) || {
echo "Shellcheck issues in $f:" >&2
echo "$output" | head -20 >&2
}
done <<< "$changed_sh"
fi

exit 0
14 changes: 10 additions & 4 deletions .claude/settings.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
{
"permissions": {
"deny": [
"Bash(*git commit*)",
"Bash(*git push*)"
]
},
"hooks": {
"PostToolUse": [
"PreToolUse": [
{
"matcher": "Edit|Write",
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "if [[ \"$CLAUDE_FILE_PATH\" == *.sh ]]; then shellcheck \"$CLAUDE_FILE_PATH\" 2>&1 | head -20; fi"
"command": "bash \"$(git rev-parse --show-toplevel)/.claude/hooks/deny-dangerous.sh\""
}
]
}
Expand All @@ -16,7 +22,7 @@
"hooks": [
{
"type": "command",
"command": "repo_root=$(git rev-parse --show-toplevel 2>/dev/null) && for f in $(git diff --name-only 2>/dev/null); do [[ \"$f\" == *.sh ]] && bash -n \"$repo_root/$f\" 2>&1; done; echo 'syntax check complete'"
"command": "bash \"$(git rev-parse --show-toplevel)/.claude/hooks/stop-lint.sh\""
}
]
}
Expand Down
72 changes: 72 additions & 0 deletions .claude/skills/audit/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# /audit - Four-Pass Shell Audit

Use this for structured audits of scripts, directories, or workflow changes.

## Rules

- Run all four passes in order.
- Every finding must cite `script:line` evidence.
- **MUST NOT propose fixes.** Audit first; remediation only if the human asks later.

## Pass 1: Discovery

Scan the target scripts and log candidate findings with `script:line` evidence.

Audit for:
- unquoted variables in dangerous contexts
- missing error handling around external commands
- hardcoded paths
- secret exposure or credential leakage
- unsafe patterns such as `eval` or unvalidated input
- missing strict mode, unless explicitly documented as an exception
- inconsistent logging paradigm compared with sibling scripts
- helper-source pattern mismatches

## Pass 2: Verification

Re-read each finding in context and confirm it is real.

- read surrounding functions, not just the flagged line
- check whether the pattern is intentional
- remove false positives
- check `docs/footguns.md` for documented exceptions

## Pass 3: Severity Ranking

Rank verified findings in this order:
- `Security`
- `Correctness`
- `Portability`
- `Style`

Use the highest applicable severity. Do not inflate lower-risk findings.

## Pass 4: Fabrication Gate

For every remaining finding, ask:
- did I fabricate this?
- did I verify it against actual code?
- did I skip a conflicting file or exception?

Remove anything that fails this check.

## Output Format

```md
## Audit: [target]

### Security
- `script:line` finding and evidence

### Correctness
- `script:line` finding and evidence

### Portability
- `script:line` finding and evidence

### Style
- `script:line` finding and evidence

### Removed During Verification
- finding removed and why
```
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# /review - Shell Script Code Review
# /code-review - Shell Script Code Review

Review shell scripts for correctness, convention compliance, and potential issues.

Expand Down
68 changes: 68 additions & 0 deletions .claude/skills/debug-investigate/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# /debug-investigate - Diagnosis-First Shell Debugging

Use this when a shell script is failing, behaviour is inconsistent, or the root cause is unknown.

## Hard Gate

**If you want to "just try something" before tracing the execution path, STOP.**

Do not propose or apply fixes until the diagnosis is written and the human reviews it.

## Workflow

1. Read the entry script end-to-end. Identify the failing path before touching code.
2. Trace the execution path across source chains:
- entry script -> sourced helper -> caller-specific function
- `_common.sh` or `_aws-common.sh` exports, defaults, and helper calls
- pipes, command substitutions, subshells, and conditional branches
3. Track variable propagation:
- where variables are set
- where they are exported
- where they are consumed after sourcing another file
4. Check exit-code handling carefully:
- `set -e` interactions with pipes and subshells
- command substitutions masking failures
- `||` fallback paths and intentional non-zero returns
5. Check shell-specific hazards:
- quoting and word splitting
- glob expansion
- array vs string assumptions
- platform differences: WSL vs native bash vs Git Bash
6. Verify helper-source patterns:
- `lib/ai-cli/` uses same-directory `_common.sh`
- `lib/stacks/` uses parent traversal `../_common.sh`
- these are NOT interchangeable
7. Check `docs/footguns.md` for matching traps before concluding.

## Diagnosis Output Template

```md
## Diagnosis

**Symptom:** what the user observed
**Entry script:** `script:line`
**Execution path:** `script:line` -> `script:line` -> `script:line`
**Variable flow:** where key variables are set, exported, and consumed
**Exit-code path:** where failure is triggered, masked, or propagated
**Evidence:** `script:line` references that prove the diagnosis
**Platform notes:** WSL / native bash / Git Bash differences, if relevant
**Related footguns:** matching entries from docs/footguns.md, if any
**Blast radius:** what else could be affected
```

## Special Attention

- `set -e` behaviour around pipes, subshells, and command substitutions
- variable scope across `source` boundaries
- quoting problems that only fail with spaces or globs
- platform-specific command resolution
- shared helper changes that affect multiple domains

## After Review

Once the human approves the diagnosis, propose the minimal fix and verify it with:
- `bash -n`
- `shellcheck`
- `bats tests/ --recursive`

If two fix attempts fail, stop and report what was tried and why it failed.
45 changes: 31 additions & 14 deletions .claude/skills/preflight/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,32 +4,49 @@ Run validation checks on all modified shell scripts before declaring work comple

## Instructions

### MUST (cannot skip)

1. **Find all modified `.sh` files** in the current working tree:
```bash
git diff --name-only HEAD 2>/dev/null
git diff --name-only --cached 2>/dev/null
git ls-files --others --exclude-standard '*.sh' 2>/dev/null
```
Combine and deduplicate the results. Only process `.sh` files.
Combine and deduplicate. Only process `.sh` files.

2. **Run `bash -n` on each modified script** to catch syntax errors. Report any failures.
2. **Run `bash -n`** on each modified script. Report any failures.

3. **Run `shellcheck` on each modified script.** Report warnings and errors. Fix all errors before declaring complete.
3. **Run `shellcheck -x`** on each modified script. Fix all errors before declaring complete.

4. **Verify each script has the correct shebang and strict mode:**
4. **Verify shebang and strict mode:**
- `#!/usr/bin/env bash` on line 1
- `set -euo pipefail` near the top
- Exception: scripts that intentionally omit `-e` (e.g., `verify.sh`, `gpu-check.sh`) — note these as acceptable
- Exception: scripts listed in `docs/footguns.md` strict mode exceptions — note as acceptable

5. **Verify each user-facing script has `-h`/`--help` support** via a `show_help()` function.
5. **Verify `-h`/`--help`** via `show_help()` on user-facing scripts.

6. **Report results** in this format:
```
## Preflight Results
### SHOULD (skip only with reason)

| Script | bash -n | shellcheck | shebang | strict mode | help flag |
|--------|---------|------------|---------|-------------|-----------|
| path | ✅/❌ | ✅/❌ (N) | ✅/❌ | ✅/❌ | ✅/❌/N/A |
```
6. **Run `bats tests/ --recursive`** — full test suite.

7. **Check executable bit** — all `.sh` files should be `chmod +x`.

8. **Check logging paradigm** matches sibling scripts in the same directory.

### MAY (skip during debugging)

9. **Dependency audit** — check for outdated or insecure dependencies in scripts that install tools.

## Output Format

```
## Preflight Results

| Script | bash -n | shellcheck | shebang | strict mode | help flag |
|--------|---------|------------|---------|-------------|-----------|
| path | ✅/❌ | ✅/❌ (N) | ✅/❌ | ✅/❌ | ✅/❌/N/A |

Bats: ✅/❌ (N tests)
```

7. **If any checks fail**, fix the issues and re-run the failing checks. Do not declare complete until all checks pass.
If any MUST checks fail, fix the issues and re-run. Do not declare complete until all MUST items pass.
Loading
Loading