chore(.gitignore): add loop log file to ignore list

This commit is contained in:
Dorian
2026-03-03 17:45:21 +00:00
parent 721e915a48
commit d63aba788e
15 changed files with 430 additions and 0 deletions
+56
View File
@@ -0,0 +1,56 @@
#!/usr/bin/env bash
# PreToolUse Bash guard: block dangerous shell commands.
# Denies: rm -rf, git reset --hard, git push -f, git clean -fd, chmod -R 777,
# fork bombs, block device overwrites, mkfs, paths escaping project root.
set -euo pipefail
INPUT=$(cat)
CMD=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
BASE="${CLAUDE_PROJECT_DIR:-}"
[[ -z "$BASE" ]] && BASE=$(echo "$INPUT" | jq -r '.cwd // empty')
[[ -z "$BASE" ]] && BASE="$(pwd)"
# Normalize: collapse whitespace, strip leading/trailing
CMD_NORM=$(echo "$CMD" | tr -s '[:space:]' ' ' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
deny() {
local reason="$1"
jq -n --arg r "$reason" '{
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "deny",
permissionDecisionReason: $r
}
}'
exit 0
}
# Dangerous patterns (case-insensitive where sensible)
case "$CMD_NORM" in
*"rm -rf"*|*"rm -fr"*|*"rm -f -r"*|*"rm -r -f"*) deny "Destructive rm -rf blocked by security hook" ;;
*"git reset --hard"*) deny "git reset --hard would lose uncommitted work" ;;
*"git push --force"*|*"git push -f"*|*"git push -f "*) deny "git push --force would rewrite history" ;;
*"git clean -fd"*|*"git clean -f -d"*) deny "git clean -fd deletes untracked files" ;;
*"chmod -R 777"*|*"chmod -R 0777"*) deny "chmod -R 777 is a security risk" ;;
*":(){ :"*"};:"*) deny "Fork bomb pattern blocked" ;;
*"> /dev/sd"*|*">/dev/sd"*) deny "Block device overwrite blocked" ;;
*"mkfs "*|*"mkfs."*) deny "Disk format command blocked" ;;
esac
# Check for path traversal escaping project root (../ outside project)
# Only if we have a sensible base
if [[ -n "$BASE" ]] && [[ -d "$BASE" ]]; then
ABS_BASE=$(cd "$BASE" && pwd)
# Simple heuristic: command contains .. and would resolve outside project
if echo "$CMD_NORM" | grep -qE '\.\./|/\.\.'; then
# Extract plausible paths and check - allow ../ within project
if echo "$CMD_NORM" | grep -qE '(rm|mv|cp|cat|chmod|chown)\s+.*\.\.'; then
# Could be risky; be conservative for rm/mv/cp
if echo "$CMD_NORM" | grep -qE '\brm\b.*\.\.'; then
deny "Path traversal with rm blocked"
fi
fi
fi
fi
exit 0
+80
View File
@@ -0,0 +1,80 @@
#!/usr/bin/env bash
# PreToolUse Edit|Write guard: block edits outside project and to protected paths.
# Denies: paths outside project, .git/, .env*, lockfiles, node_modules/
set -euo pipefail
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
BASE="${CLAUDE_PROJECT_DIR:-}"
[[ -z "$BASE" ]] && BASE=$(echo "$INPUT" | jq -r '.cwd // empty')
[[ -z "$BASE" ]] && BASE="$(pwd)"
# Resolve to absolute path
if [[ -z "$FILE_PATH" ]]; then
exit 0
fi
ABS_BASE=$(cd "$BASE" 2>/dev/null && pwd) || true
[[ -z "$ABS_BASE" ]] && ABS_BASE=$(python3 -c "import os,sys; print(os.path.abspath(os.path.normpath(sys.argv[1])))" "$BASE" 2>/dev/null) || true
[[ -z "$ABS_BASE" ]] && ABS_BASE="$BASE"
# Ensure base has trailing slash for prefix check
[[ "$ABS_BASE" != */ ]] && ABS_BASE="${ABS_BASE}/"
if [[ "$FILE_PATH" != /* ]]; then
ABS_PATH="$ABS_BASE${FILE_PATH#./}"
else
ABS_PATH="$FILE_PATH"
fi
# Normalize path (collapse .. and ., no symlink resolution needed)
ABS_PATH=$(python3 -c "import os,sys; print(os.path.abspath(os.path.normpath(sys.argv[1])))" "$ABS_PATH" 2>/dev/null) || true
[[ -z "$ABS_PATH" ]] && ABS_PATH="$ABS_BASE${FILE_PATH#./}"
# Protected patterns (path contains or equals)
PROTECTED_PATTERNS=(
".git/"
".env"
".env.local"
"node_modules/"
"package-lock.json"
"pnpm-lock.yaml"
)
for pattern in "${PROTECTED_PATTERNS[@]}"; do
if [[ "$ABS_PATH" == *"$pattern"* ]] || [[ "$ABS_PATH" == *"/$pattern" ]]; then
echo "Blocked: $ABS_PATH matches protected pattern '$pattern'" >&2
jq -n --arg r "Edit blocked: path matches protected pattern ($pattern)" '{
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "deny",
permissionDecisionReason: $r
}
}'
exit 0
fi
done
# .env.*.local
if [[ "$ABS_PATH" =~ \.env\..*\.local$ ]]; then
echo "Blocked: $ABS_PATH is an env secrets file" >&2
jq -n '{
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "deny",
permissionDecisionReason: "Edit blocked: .env.*.local files contain secrets"
}
}'
exit 0
fi
# Ensure path is under project root (ABS_BASE has trailing /)
if [[ "$ABS_PATH" != "$ABS_BASE"* ]] && [[ "$ABS_PATH" != "$BASE"* ]]; then
echo "Blocked: $ABS_PATH is outside project ($ABS_BASE)" >&2
jq -n --arg r "Edit blocked: path is outside project directory" '{
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "deny",
permissionDecisionReason: $r
}
}'
exit 0
fi
exit 0
+24
View File
@@ -0,0 +1,24 @@
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/block-risky-bash.sh"
}
]
},
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/protect-files.sh"
}
]
}
]
}
}
Submodule .claude/worktrees/agitated-hofstadter added at 10e12a329f
Submodule .claude/worktrees/funny-hofstadter added at 721e915a48
Submodule .claude/worktrees/happy-colden added at 721e915a48
Submodule .claude/worktrees/hardcore-beaver added at 721e915a48
Submodule .claude/worktrees/heuristic-raman added at 721e915a48
Submodule .claude/worktrees/priceless-colden added at 721e915a48
+3
View File
@@ -36,6 +36,9 @@ pnpm-debug.log*
# Test coverage
coverage/
# Overnight loop logs
loop/loop.log
# Playwright
test-results/
playwright-report/
+138
View File
@@ -0,0 +1,138 @@
# Overnight Claude Automation
Run Claude Code autonomously while you're away. Combines sleep prevention, task-based execution, the Ralph Wiggum Technique (Stop hook blocks until plan is complete), and security hooks that restrict AI to project files and block destructive commands.
## Prerequisites
- **Claude Code CLI** ([claude.ai/code](https://claude.ai/code)) — installed at `~/.local/bin/claude` or in PATH
- **Hooks** — user-level hooks in `~/.claude/` (sleep, Ralph Wiggum)
- **jq** — for security hook scripts (`brew install jq`)
## Flow
### Pre-run (before 56pm)
1. **Commit and push** — Snap current work and back up to remote.
2. **Run prepare script** — Creates date-stamped branch and verifies clean state:
```bash
./loop/prepare.sh
```
3. **Edit plan** — Update `loop/plan.md` with evening scope and tasks (see template below).
4. **Commit plan** — Version the plan so you can revert if needed:
```bash
git add loop/plan.md && git commit -m "chore: overnight plan $(date +%Y-%m-%d)"
```
5. **Push** (optional but recommended): `git push -u origin overnight/YYYY-MM-DD`
### Overnight
```bash
tmux new -s overnight
caffeinate -i ./loop/loop.sh
# Detach: Ctrl+B, then D
```
### Post-run (next morning)
1. `git status` and `git diff` to review changes.
2. Run `pnpm test && pnpm lint && pnpm typecheck`.
3. Merge branch or revert if needed.
## Quick Start
1. **Edit your plan** — Add tasks to `loop/plan.md` using the evening run format:
```markdown
# Evening Run — YYYY-MM-DD
## Scope
Add tests to chat components.
## Tasks
- [ ] Add unit tests for useAI composable
- [ ] Fix linter errors in packages/app
```
2. **Run overnight** — From project root:
```bash
caffeinate -i ./loop/loop.sh
```
## How It Works
| Component | Purpose |
|-----------|---------|
| **UserPromptSubmit hook** | Starts `caffeinate` to prevent Mac sleep when you submit a prompt |
| **Stop hook** | Checks `plan.md` for unchecked tasks; blocks Claude from stopping until all are done (Ralph Wiggum) |
| **SessionEnd hook** | Kills `caffeinate` so Mac can sleep again |
| **PreToolUse (Bash)** | Blocks dangerous commands (rm -rf, git reset --hard, etc.) |
| **PreToolUse (Edit\|Write)** | Blocks edits outside project and to protected paths |
| **loop.sh** | Runs Claude with `--dangerously-skip-permissions` and feeds the prompt from `loop/prompt.md` |
## Security Model
Project-scoped hooks in `.claude/hooks/` restrict the AI during overnight runs:
### Bash guard (`block-risky-bash.sh`)
Blocks: `rm -rf`, `git reset --hard`, `git push --force`, `git clean -fd`, `chmod -R 777`, fork bombs, block device overwrites, `mkfs`, and path traversal with destructive commands.
### File edit guard (`protect-files.sh`)
Blocks Edit/Write when:
- Path is **outside project directory**
- Path contains **`.git/`**
- Path is **`.env`**, **`.env.local`**, **`.env.*.local`**
- Path is **`package-lock.json`** or **`pnpm-lock.yaml`**
- Path contains **`node_modules/`**
Read, Glob, and Grep remain unrestricted.
## Environment Variables
| Variable | Default | Description |
|----------|---------|-------------|
| `CLAUDE_AUTONOMOUS` | `1` | Set to `1` to enable Ralph Wiggum (Stop hook checks plan). `0` disables. |
| `CLAUDE_PLAN_FILE` | `plan.md` | Plan file path (relative to project). |
| `ITERATION_COUNT` | `1` | Number of loop iterations (use >1 for multi-run without Ralph Wiggum). |
| `ITERATION_DELAY` | `600` | Seconds between iterations when `ITERATION_COUNT` > 1. |
| `PROMPT_FILE` | `loop/prompt.md` | Prompt content for Claude. |
| `LOG_FILE` | `loop/loop.log` | Log output (gitignored). |
## Scheduling (Optional)
Install [claude-code-schedule](https://github.com/macalinao/claude-code-schedule) for time-based runs:
```bash
cargo install claude-code-schedule
ccschedule --time 05:30 --message "Review plan.md and complete next task"
```
## continuous-claude (Optional)
For full PR-based workflow (branches, PRs, CI):
```bash
# Install from https://github.com/AnandChowdhary/continuous-claude
continuous-claude -p "Work through loop/plan.md" -m 10 --max-duration 8h
```
## Remote Monitoring
- **Tmux + SSH**: Attach from another machine: `ssh host 'tmux attach -t overnight'`
- **Tailscale**: Use Tailscale for easy remote SSH when away from home network
- **Log tail**: `tail -f loop/loop.log` to watch progress
## Safety
- **Start small** — Test with 12 tasks before overnight runs
- **prepare.sh** — Run before starting; fails if working tree is dirty or branch exists
- **Git** — Loop does not auto-commit; you review and merge in the morning
- **`--dangerously-skip-permissions`** — Security hooks still run and block dangerous actions
- **Project-scoped hooks** — Only apply when Claude runs in AIUI; other projects unaffected
Executable
+43
View File
@@ -0,0 +1,43 @@
#!/usr/bin/env sh
# Headless loop script for overnight Claude Code automation.
# Set CLAUDE_AUTONOMOUS=1 for Ralph Wiggum (Stop hook blocks until plan is complete).
set -eu
PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$(cd "$(dirname "$0")/.." && pwd)}"
PROMPT_FILE="${PROMPT_FILE:-$PROJECT_DIR/loop/prompt.md}"
LOG_FILE="${LOG_FILE:-$PROJECT_DIR/loop/loop.log}"
ITERATION_COUNT="${ITERATION_COUNT:-1}"
ITERATION_DELAY="${ITERATION_DELAY:-600}"
CLAUDE_BIN="${CLAUDE_BIN:-claude}"
cd "$PROJECT_DIR"
echo "=== Overnight loop started $(date '+%Y-%m-%dT%H:%M:%S' 2>/dev/null || date) ===" | tee -a "$LOG_FILE"
echo " PROMPT_FILE=$PROMPT_FILE" | tee -a "$LOG_FILE"
echo " CLAUDE_AUTONOMOUS=${CLAUDE_AUTONOMOUS:-0}" | tee -a "$LOG_FILE"
echo " ITERATION_COUNT=$ITERATION_COUNT" | tee -a "$LOG_FILE"
echo "" | tee -a "$LOG_FILE"
i=1
while [ "$i" -le "$ITERATION_COUNT" ]; do
echo "--- Iteration $i/$ITERATION_COUNT @ $(date '+%Y-%m-%dT%H:%M:%S' 2>/dev/null || date) ---" | tee -a "$LOG_FILE"
export CLAUDE_PROJECT_DIR="$PROJECT_DIR"
export CLAUDE_AUTONOMOUS="${CLAUDE_AUTONOMOUS:-1}"
# Run Claude with autonomous permissions; prompt from file
if [ -f "$PROMPT_FILE" ]; then
"$CLAUDE_BIN" -p --dangerously-skip-permissions --output-format=stream-json \
< "$PROMPT_FILE" 2>&1 | tee -a "$LOG_FILE" || true
else
echo "Error: $PROMPT_FILE not found" | tee -a "$LOG_FILE"
exit 1
fi
i=$((i + 1))
if [ "$i" -le "$ITERATION_COUNT" ] && [ "$ITERATION_DELAY" -gt 0 ]; then
echo "Waiting ${ITERATION_DELAY}s before next iteration..." | tee -a "$LOG_FILE"
sleep "$ITERATION_DELAY"
fi
done
echo "=== Loop complete $(date '+%Y-%m-%dT%H:%M:%S' 2>/dev/null || date) ===" | tee -a "$LOG_FILE"
+38
View File
@@ -0,0 +1,38 @@
# Evening Run — YYYY-MM-DD
> **Format**: Use `- [ ]` for incomplete, `- [x]` for complete. Enable autonomous mode with `CLAUDE_AUTONOMOUS=1` when running.
## Scope
Get the AIUI project to a green CI baseline: ESLint config, initial test coverage, and passing `pnpm test`, `pnpm lint`, `pnpm typecheck`. Work incrementally; complete as many tasks as possible.
## Pre-run checklist (you complete before 56pm)
- [ ] Committed all work; `git status` clean
- [ ] Pushed to remote (or at least backed up)
- [ ] Created branch for overnight: `git checkout -b overnight/YYYY-MM-DD`
## Instructions for Claude
Work through the checklist below. Complete one item per iteration, then update this file to mark it done. The Stop hook will keep you running until all tasks are checked. If a task is blocked or you hit a natural stopping point, add context to the Notes section and continue with the next task.
## Tasks
- [ ] Add `eslint.config.js` (flat config) to `packages/app` for ESLint 10 — extend recommended Vue/TypeScript rules
- [ ] Add `eslint.config.js` (flat config) to `packages/core` — same pattern as app
- [ ] Add a minimal smoke test in `packages/core` (e.g. `src/index.test.ts`) so `pnpm test` no longer fails with "No test files found"
- [ ] Add unit tests for `useTheme` composable in `packages/app` — test theme detection and toggle behavior
- [ ] Run `pnpm lint` and fix any reported issues (or document known unfixable ones in Notes)
- [ ] Add unit tests for `useAI` or `contentFiltering` composable — at least one meaningful test
- [ ] Verify `pnpm test`, `pnpm lint`, `pnpm typecheck` all pass; fix failures
- [ ] Update this plan's Success criteria section — mark items that pass
## Success criteria
- [ ] pnpm test passes
- [ ] pnpm lint passes
- [ ] No new linter/type errors
## Notes
(Claude will add context here for the next iteration.)
+41
View File
@@ -0,0 +1,41 @@
#!/usr/bin/env sh
# Pre-run script: verify repo state and create overnight branch.
# Run before 5-6pm to ensure you can safely start the overnight loop.
set -eu
PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$(cd "$(dirname "$0")/.." && pwd)}"
cd "$PROJECT_DIR"
DATE=$(date '+%Y-%m-%d')
BRANCH="overnight/${DATE}"
echo "=== Overnight pre-run check @ $(date '+%Y-%m-%dT%H:%M:%S') ==="
# 1. Check git status is clean
if ! git diff --quiet || ! git diff --cached --quiet; then
echo "Error: Working tree not clean. Commit or stash changes first." >&2
echo " git status" >&2
git status --short >&2
exit 1
fi
# 2. Check we're not already on an overnight branch
current=$(git branch --show-current 2>/dev/null || true)
if [ -n "$current" ] && [ "$current" = "$BRANCH" ]; then
echo "Already on $BRANCH. Ready to run." >&2
exit 0
fi
# 3. Create date-stamped branch
if git rev-parse --verify "$BRANCH" >/dev/null 2>&1; then
echo "Branch $BRANCH already exists. Checkout or use a different date." >&2
exit 1
fi
git checkout -b "$BRANCH"
echo "Created branch $BRANCH"
# 4. Remind to push
echo ""
echo "Reminder: Push before starting overnight run: git push -u origin $BRANCH"
echo "Then run: caffeinate -i ./loop/loop.sh"
echo "=== Ready ==="
+1
View File
@@ -0,0 +1 @@
Read `loop/plan.md` and work on the next incomplete task. Update the plan to mark completed items with `- [x]`. Add any relevant context to the Notes section for the next iteration. Do not stop until all tasks in the plan are complete.