Skip to content

Instantly share code, notes, and snippets.

@LeoVS09
Last active March 29, 2026 15:18
Show Gist options
  • Select an option

  • Save LeoVS09/8155a47b239e4b5c5ee7eef431e5180b to your computer and use it in GitHub Desktop.

Select an option

Save LeoVS09/8155a47b239e4b5c5ee7eef431e5180b to your computer and use it in GitHub Desktop.
Claude workflow and agent devcontainer managment script
#!/usr/bin/env bash
# Shared helper functions for claude justfile recipes.
# Source this file at the top of each recipe that needs these utilities:
# source "$(dirname "${BASH_SOURCE[0]:-$0}")/../scripts/claude-helpers.sh"
# Or from justfile recipes:
# source "scripts/claude-helpers.sh"
# ── Configuration defaults ──────────────────────────────────────────
MAX_SESSION_RETRIES="${MAX_SESSION_RETRIES:-12}"
SESSION_RETRY_WAIT="${SESSION_RETRY_WAIT:-3600}" # 1 hour in seconds
COMMAND_TIMEOUT="${COMMAND_TIMEOUT:-21600}" # 6 hours in seconds
PERMISSION_MODE="${PERMISSION_MODE:---permission-mode auto}"
DRAFT_DIR="${DRAFT_DIR:-.specs/tasks/draft}"
TODO_DIR="${TODO_DIR:-.specs/tasks/todo}"
DONE_DIR="${DONE_DIR:-.specs/tasks/done}"
# ── JSON schemas for structured output validation ───────────────────
COMPLETED_SCHEMA='{"type":"object","properties":{"completed":{"type":"boolean"},"message":{"type":"string"}},"required":["completed"]}'
NEXT_TASK_SCHEMA='{"type":"object","properties":{"allDone":{"type":"boolean"},"filenames":{"type":"object","properties":{"epic":{"type":"string"},"task":{"type":"string"}}}},"required":["allDone"]}'
# ── Classify error for diagnostic logging ─────────────────────────
# Outputs a human-readable error category string to stdout.
# Used solely for diagnostic log messages, not for retry decisions.
describe_error_type() {
local stderr_text="$1"
local lower
lower=$(echo "$stderr_text" | tr '[:upper:]' '[:lower:]')
case "$lower" in
*"session limit"*|*"rate limit"*|*"too many requests"*|*"rate_limit"*|*"overloaded"*|*"429"*|*"503"*)
echo "session/rate limit" ;;
*)
echo "unknown error" ;;
esac
}
# ── Wait for a PID with a timeout ─────────────────────────────────
# Returns 0 if the process exits before timeout, 1 if it times out.
# Uses a polling loop with 1-second granularity so we can detect
# process exit without relying on non-portable `timeout` flags.
#
# Args: pid, timeout_seconds
wait_with_timeout() {
local pid="$1"
local timeout_secs="$2"
local elapsed=0
while [ "$elapsed" -lt "$timeout_secs" ]; do
if ! kill -0 "$pid" 2>/dev/null; then
# Process has exited
return 0
fi
sleep 1
elapsed=$((elapsed + 1))
done
# Still running after timeout
return 1
}
# ── Generic retry loop ────────────────────────────────────────────
# Retries a caller-provided command callback on ANY non-zero exit,
# waiting SESSION_RETRY_WAIT between attempts. The Claude CLI does not
# reliably report session/rate limit details in stderr, so we retry on
# all failures.
#
# WAIT BEHAVIOR: After each failed attempt (and before the next retry),
# this function sleeps for SESSION_RETRY_WAIT seconds (default: 3600 =
# 1 hour). This long pause is intentional -- it allows session/rate
# limits to expire before the next attempt. The flow on failure is:
#
# command fails → capture stderr → check max retries → log warning
# → sleep SESSION_RETRY_WAIT (default 1 hour) → retry
#
# The callback function is invoked as:
# <callback> <attempt_number> <stderr_file>
# It MUST redirect its command's stderr through the stderr_file, e.g.:
# my_command 2> >(tee "$stderr_file" >&2)
#
# TIMEOUT & CHILD CLEANUP: The callback is run in the background. On
# timeout, `pkill -P` kills all child processes (e.g. `claude`, `jq` in
# a pipe) of the background subshell, then the subshell itself is killed.
#
# Args: label, callback_fn_name
_retry_loop() {
local label="$1"
local callback="$2"
local attempt=0
local stderr_file
stderr_file=$(mktemp)
while true; do
attempt=$((attempt + 1))
echo "==> [$label] Attempt $attempt/$((MAX_SESSION_RETRIES + 1))" >&2
# ── Run callback with COMMAND_TIMEOUT (default 6 hours) ──
# The callback runs in the background. On timeout we use pkill -P
# to terminate all child processes (e.g. claude, jq in a pipe),
# then kill the background subshell itself.
local cmd_exit=0
"$callback" "$attempt" "$stderr_file" &
local cmd_pid=$!
if wait_with_timeout "$cmd_pid" "$COMMAND_TIMEOUT"; then
# Command completed within timeout -- capture its exit code
cmd_exit=0
wait "$cmd_pid" 2>/dev/null || cmd_exit=$?
else
# Timed out -- kill children first (piped commands like claude | jq),
# then the background subshell itself.
echo "WARN: [$label] Command timed out after ${COMMAND_TIMEOUT}s (attempt $attempt/$((MAX_SESSION_RETRIES + 1))). Killing pid $cmd_pid and children." >&2
pkill -TERM -P "$cmd_pid" 2>/dev/null || true
kill -TERM "$cmd_pid" 2>/dev/null || true
sleep 2
pkill -KILL -P "$cmd_pid" 2>/dev/null || true
kill -KILL "$cmd_pid" 2>/dev/null || true
wait "$cmd_pid" 2>/dev/null || true
cmd_exit=1
echo "command timed out after ${COMMAND_TIMEOUT}s (attempt $attempt/$((MAX_SESSION_RETRIES + 1)))" > "$stderr_file"
fi
if [ "$cmd_exit" -eq 0 ]; then
rm -f "$stderr_file"
return 0
fi
local captured_stderr
captured_stderr=$(cat "$stderr_file" 2>/dev/null || echo "")
if [ "$attempt" -gt "$MAX_SESSION_RETRIES" ]; then
echo "ERROR: [$label] Failed after $((MAX_SESSION_RETRIES + 1)) attempts. Stopping." >&2
echo " stderr: $captured_stderr" >&2
rm -f "$stderr_file"
return 1
fi
local error_type
error_type=$(describe_error_type "$captured_stderr")
if [ "$error_type" = "session/rate limit" ]; then
echo "WARN: [$label] Session/rate limit hit. Waiting ${SESSION_RETRY_WAIT}s before retry ($attempt/$((MAX_SESSION_RETRIES + 1)))..." >&2
else
echo "WARN: [$label] Command failed (non-zero exit). Waiting ${SESSION_RETRY_WAIT}s before retry ($attempt/$((MAX_SESSION_RETRIES + 1)))..." >&2
echo " stderr: $captured_stderr" >&2
fi
# ── RETRY WAIT: sleep SESSION_RETRY_WAIT seconds (default 1 hour) ──
# This is the sole retry-delay point. Every failed attempt passes
# through here before looping back to the next attempt.
local sleep_start sleep_end
sleep_start=$(date '+%Y-%m-%d %H:%M:%S')
sleep_end=$(date -d "+${SESSION_RETRY_WAIT} seconds" '+%Y-%m-%d %H:%M:%S' 2>/dev/null \
|| date -v "+${SESSION_RETRY_WAIT}S" '+%Y-%m-%d %H:%M:%S' 2>/dev/null \
|| echo "unknown")
echo "INFO: [$label] Sleeping at $sleep_start, expected resume at $sleep_end" >&2
sleep "$SESSION_RETRY_WAIT"
done
}
# ── Shared retry wrapper for any CLI error ────────────────────────
# Retries a shell function on ANY non-zero exit, waiting SESSION_RETRY_WAIT
# between attempts.
#
# Args: label, fn_name, [fn_args...]
run_with_session_retry() {
local label="$1"; shift
local _rsr_args=("$@")
# Callback invoked by _retry_loop on each attempt.
# Captures _rsr_args from the enclosing scope.
# NOTE: Bash has no block scoping -- _rsr_attempt leaks to the global
# namespace. This is acceptable because justfile recipes run in separate
# shell processes, so no cross-recipe collisions occur.
_rsr_attempt() {
local _attempt="$1"
local _stderr_file="$2"
"${_rsr_args[@]}" 2> >(tee "$_stderr_file" >&2)
}
_retry_loop "$label" _rsr_attempt
}
# ── Raw claude streaming call (no retry) ────────────────────────────
# Internal helper for run_plan_or_implement_retry. Not intended for
# direct use -- prefer `just claude` which wraps this with retry logic.
_raw_claude_stream() {
local prompt="$1"
# shellcheck disable=SC2086
claude -p "$prompt" \
--output-format stream-json --verbose --include-partial-messages \
$PERMISSION_MODE | \
jq -rj 'select(.type == "stream_event" and .event.delta.type? == "text_delta") | .event.delta.text'
}
# ── Plan/implement with retry and prompt switching ──────────────────
# First attempt uses initial_prompt; retries use continue_prompt.
#
# Args: label, initial_prompt, continue_prompt
run_plan_or_implement_retry() {
local label="$1"
local _rpir_initial="$2"
local _rpir_continue="$3"
# Callback invoked by _retry_loop on each attempt.
# Captures _rpir_initial and _rpir_continue from the enclosing scope.
# NOTE: Bash has no block scoping -- _rpir_attempt leaks to the global
# namespace. This is acceptable because justfile recipes run in separate
# shell processes, so no cross-recipe collisions occur.
_rpir_attempt() {
local _attempt="$1"
local _stderr_file="$2"
if [ "$_attempt" -eq 1 ]; then
_raw_claude_stream "$_rpir_initial" 2> >(tee "$_stderr_file" >&2)
else
_raw_claude_stream "$_rpir_continue" 2> >(tee "$_stderr_file" >&2)
fi
}
_retry_loop "$label" _rpir_attempt
}
# ── Extract structured_output field from claude JSON response ───────
# Handles both array format (claude --verbose JSON output, where the
# result element has type=="result") and plain object format.
# Falls back to parsing .result as JSON if structured_output is absent.
extract_structured_field() {
local json="$1"
local field="$2"
local fallback="${3:-}"
# Normalize: if input is an array, extract the result element
local result_obj
result_obj=$(echo "$json" | jq -r '
if type == "array" then
(.[] | select(.type == "result"))
else
.
end
' 2>/dev/null || echo "")
if [ -z "$result_obj" ]; then
echo "$fallback"
return
fi
local value
value=$(echo "$result_obj" | jq -r ".structured_output.$field // empty" 2>/dev/null || echo "")
if [ -n "$value" ] && [ "$value" != "null" ]; then
echo "$value"
return
fi
# Fallback: try parsing .result as JSON
local result_text
result_text=$(echo "$result_obj" | jq -r '.result // empty' 2>/dev/null || echo "")
if [ -n "$result_text" ]; then
value=$(echo "$result_text" | jq -r ".$field // empty" 2>/dev/null || echo "")
if [ -n "$value" ] && [ "$value" != "null" ]; then
echo "$value"
return
fi
fi
echo "$fallback"
}
# ── Configuration defaults ──────────────────────────────────────────
# All configurable environment variables for claude recipes:
#
# PERMISSION_MODE Claude CLI permission flag.
# Default: "--permission-mode auto"
# Alternative: "--dangerously-skip-permissions"
#
# MAX_SESSION_RETRIES Max retry attempts on CLI failure (in claude-helpers.sh).
# Default: 12
#
# SESSION_RETRY_WAIT Seconds to wait between retries (in claude-helpers.sh).
# Default: 3600 (1 hour)
#
# COMMAND_TIMEOUT Max seconds a single `claude -p` command may run
# before being killed and retried (in claude-helpers.sh).
# Default: 21600 (6 hours)
#
# DRAFT_DIR / TODO_DIR / DONE_DIR
# Task file directories (in claude-helpers.sh).
# Defaults: .specs/tasks/{draft,todo,done}
# ────────────────────────────────────────────────────────────────────
# `export` is required so the variable is visible inside recipe shell
# environments (each recipe runs in a separate bash process).
export PERMISSION_MODE := env("PERMISSION_MODE", "--permission-mode auto")
[doc("Show all available commands with their descriptions")]
help:
@echo "PERMISSION_MODE: $PERMISSION_MODE"
@just --list
[doc("Get the running devcontainer ID (empty if not running)")]
_sandbox-id:
@docker ps --filter "label=devcontainer.local_folder={{justfile_directory()}}" --format "{{{{.ID}}" | head -n1
[doc("""
Start devcontainer and open an interactive shell.
Description:
Starts the development container using devcontainer CLI and attaches to an
interactive zsh shell. First run may take time to build the image.
Steps:
1. Runs `devcontainer up` to start the container
2. Extracts container ID, workspace folder, and user from output
3. Attaches to the container with docker exec
Usage:
just sandbox
""")]
sandbox:
#!/usr/bin/env bash
set -euo pipefail
echo "Starting devcontainer... First run can take long time to build the image"
tmpfile=$(mktemp)
devcontainer up --workspace-folder . 2>&1 | tee "$tmpfile"
output=$(cat "$tmpfile")
rm "$tmpfile"
container_id=$(echo "$output" | grep -oP '"containerId"\s*:\s*"\K[^"]+')
workspace=$(echo "$output" | grep -oP '"remoteWorkspaceFolder"\s*:\s*"\K[^"]+')
user=$(echo "$output" | grep -oP '"remoteUser"\s*:\s*"\K[^"]+')
if [ -z "$container_id" ]; then
echo "Error: could not find devcontainer"
exit 1
fi
echo "Attaching to container $container_id as ${user:-root} at $workspace..."
docker exec -it -u "${user:-root}" -w "${workspace:-/}" "$container_id" zsh
[doc("""
Attach to a running devcontainer.
Description:
Connects to an already running devcontainer shell. Requires that
the devcontainer was started with `just sandbox` first.
Steps:
1. Gets the container ID using _sandbox-id
2. Inspects container to find workspace and user
3. Attaches with docker exec
Usage:
just attach-sandbox
""")]
attach-sandbox:
#!/usr/bin/env bash
set -euo pipefail
container_id=$(just _sandbox-id)
if [ -z "$container_id" ]; then
echo "Error: no running devcontainer found. Run 'just sandbox' first."
exit 1
fi
eval "$(docker inspect "$container_id" | python3 -c "
import json,sys
c = json.load(sys.stdin)[0]
folder = c['Config']['Labels'].get('devcontainer.local_folder','')
ws = next((m['Destination'] for m in c.get('Mounts',[]) if m['Source'] == folder), '/')
meta = json.loads(c['Config']['Labels'].get('devcontainer.metadata','[]'))
user = next((i['remoteUser'] for i in meta if 'remoteUser' in i), 'root')
print(f'workspace={ws}')
print(f'user={user}')
")"
echo "Attaching to container $container_id as $user at $workspace..."
docker exec -it -u "$user" -w "$workspace" "$container_id" zsh
[doc("""
Stop and remove the devcontainer.
Description:
Gracefully stops and removes the running development container.
Safe to run even if no container is running.
Steps:
1. Gets container ID (if any)
2. Stops the container with docker stop
3. Removes the container with docker rm
Usage:
just stop-sandbox
""")]
stop-sandbox:
#!/usr/bin/env bash
set -euo pipefail
container_id=$(just _sandbox-id)
if [ -z "$container_id" ]; then
echo "No running devcontainer found."
exit 0
fi
echo "Stopping container $container_id..."
docker stop "$container_id" && docker rm "$container_id"
echo "Done."
[doc("""
Tear down the devcontainer docker-compose resources.
Description:
Runs docker compose down for the devcontainer configuration.
Use this to completely clean up devcontainer networking and volumes.
Usage:
just down-devcontainer
""")]
down-devcontainer:
docker compose --project-name decision-engine_devcontainer -f .devcontainer/docker-compose.yaml down
[doc("""
Run claude with a prompt and stream plain text output.
Description:
Executes Claude CLI with a prompt and streams the response as plain text.
Automatically retries on session-limit errors.
Parameters:
prompt - The prompt to send to Claude
label - Optional label for tracking/logging (default: "claude")
Usage:
just claude "Explain this codebase"
just claude "Summarize the README" "my-label"
""")]
[no-exit-message]
claude prompt label="claude":
#!/usr/bin/env bash
set -euo pipefail
source claude-helpers.sh
run_with_session_retry "{{ label }}" _raw_claude_stream "{{ prompt }}"
[doc("""
Create a new draft task from a prompt.
Description:
Creates a new draft task file using the SDD add-task skill.
The task will be created in the draft directory.
Parameters:
prompt - Description of the task to create
Usage:
just claude-add-task "Add validation to the /decide endpoint"
""")]
[no-exit-message]
claude-add-task prompt:
#!/usr/bin/env bash
set -euo pipefail
just claude "/sdd:add-task {{ prompt }}"
[doc("""
Run claude in JSON output mode with optional schema validation.
Description:
Executes Claude CLI and returns parseable JSON output.
Supports optional JSON schema for structured responses.
Automatically retries on session-limit errors.
Parameters:
prompt - The prompt to send to Claude
json_schema - Optional JSON schema for structured output (default: "")
label - Optional label for tracking/logging (default: "claude-json")
Usage:
just claude-json "your prompt"
just claude-json "your prompt" '{"type":"object",...}'
just claude-json "your prompt" '{"type":"object",...}' "my-label"
just claude-json "your prompt" "" "my-label"
""")]
[no-exit-message]
claude-json prompt json_schema="" label="claude-json":
#!/usr/bin/env bash
set -euo pipefail
source claude-helpers.sh
_raw_claude_json() {
local prompt="$1"
local schema="${2:-}"
# shellcheck disable=SC2086
if [ -n "$schema" ]; then
claude -p "$prompt" \
--output-format json \
--verbose \
--json-schema "$schema" \
$PERMISSION_MODE
else
claude -p "$prompt" \
--output-format json \
--verbose \
$PERMISSION_MODE
fi
}
run_with_session_retry "{{ label }}" _raw_claude_json "{{ prompt }}" '{{ json_schema }}'
[doc("""
Plan, implement, and verify a task with automatic retries.
Description:
Executes the full task implementation pipeline (Steps 1-4):
plans the task, implements it, verifies completion, and retries if needed.
Steps:
1. Plan - Creates implementation plan from draft task
2. Implement - Executes the implementation
3. Verify - Checks that all task items are completed
4. Retry - Re-implements if verification fails
Parameters:
task-filename - The task filename (e.g., "add-feature-x.feature.md")
mode - Optional mode: "", "continue-plan", or "continue-implement"
Usage:
just claude-plan-and-implement "my-task.feature.md"
just claude-plan-and-implement "my-task.feature.md" "continue-plan"
just claude-plan-and-implement "my-task.feature.md" "continue-implement"
""")]
[no-exit-message]
claude-plan-and-implement task-filename mode="":
#!/usr/bin/env bash
set -euo pipefail
source claude-helpers.sh
TASK_FILENAME="{{ task-filename }}"
MODE="{{ mode }}"
# ── STEP 1: Plan ────────────────────────────────────────────────
draft_path="$DRAFT_DIR/$TASK_FILENAME"
todo_path="$TODO_DIR/$TASK_FILENAME"
done_path="$DONE_DIR/$TASK_FILENAME"
if [ "$MODE" = "continue-implement" ]; then
echo "==> Step 1: Skipped (mode=continue-implement)"
elif [ "$MODE" = "continue-plan" ]; then
echo "==> Step 1: Continuing planning with --continue"
run_plan_or_implement_retry "plan" \
"/sdd:plan --continue $TASK_FILENAME start from judge review of last completed stage" \
"/sdd:plan --continue $TASK_FILENAME start from judge review of last completed stage"
echo ""
if [ ! -f "$todo_path" ]; then
echo "ERROR: Planning did not produce: $todo_path"
exit 1
fi
echo "==> Plan complete. Task moved to: $todo_path"
elif [ -f "$done_path" ]; then
echo "==> Step 1: Task already done, skipping to verification"
elif [ -f "$draft_path" ]; then
echo "==> Step 1: Planning task from draft"
run_plan_or_implement_retry "plan" \
"/sdd:plan @$draft_path" \
"/sdd:plan --continue $TASK_FILENAME start from judge review of last completed stage"
echo ""
if [ ! -f "$todo_path" ]; then
echo "ERROR: Planning did not produce: $todo_path"
exit 1
fi
echo "==> Plan complete. Task moved to: $todo_path"
elif [ -f "$todo_path" ]; then
echo "==> Step 1: Task already planned, skipping to implement"
else
echo "ERROR: Task file not found in draft/ or todo/: $TASK_FILENAME"
exit 1
fi
# ── STEP 2: Implement (with retry loop) ───────────────────────────
MAX_IMPLEMENT_RETRIES=3
if [ -f "$done_path" ]; then
echo ""
echo "==> Step 2: Task already implemented, skipping to verification"
else
# Determine initial prompt based on mode
if [ "$MODE" = "continue-implement" ]; then
initial_prompt="/sdd:implement --continue $TASK_FILENAME start from judge review of last completed stage"
echo ""
echo "==> Step 2: Continuing implementation with --continue"
else
initial_prompt="/sdd:implement @$todo_path"
echo ""
echo "==> Step 2: Implementing task"
fi
continue_prompt="/sdd:implement --continue $TASK_FILENAME start from judge review of last completed stage"
# Retry loop: attempt implementation until done_path exists or max retries
implement_attempt=0
while [ ! -f "$done_path" ]; do
implement_attempt=$((implement_attempt + 1))
if [ "$implement_attempt" -gt "$MAX_IMPLEMENT_RETRIES" ]; then
echo "ERROR: Implementation did not move task to done/ after $MAX_IMPLEMENT_RETRIES attempts. Stopping."
exit 1
fi
if [ "$implement_attempt" -gt 1 ]; then
echo "==> Step 2: Retrying implementation (attempt $implement_attempt/$MAX_IMPLEMENT_RETRIES) with --continue"
run_plan_or_implement_retry "implement-retry-$implement_attempt" \
"$continue_prompt" \
"$continue_prompt"
else
run_plan_or_implement_retry "implement" \
"$initial_prompt" \
"$continue_prompt"
fi
echo ""
done
echo "==> Implementation complete. Task moved to: $done_path"
fi
# ── STEP 3: Verify completion ──────────────────────────────────
echo ""
echo "==> Step 3: Verifying task completion"
# Implementation MUST move the task file to done/
if [ ! -f "$done_path" ]; then
echo "ERROR: Implementation did not move task to done/: $done_path"
exit 1
fi
verify_path="$done_path"
verify_prompt="Read the task file at $verify_path. Check that all checkmarks/subtasks are marked as done (ignore items like 'human review' and 'create PR' which are expected to be unchecked). For any unchecked item, verify if it was actually completed in the codebase. If yes, mark it done. If all relevant items are done, output {completed: true}. If something is genuinely not done, output {completed: false, message: '<what is not done>'}."
verify_output=$(just claude-json "$verify_prompt" "$COMPLETED_SCHEMA" "verify")
completed=$(extract_structured_field "$verify_output" "completed" "false")
# ── STEP 4: Retry implementation if not completed ──────────────
if [ "$completed" != "true" ]; then
not_done_msg=$(extract_structured_field "$verify_output" "message" "Unknown incomplete items")
echo "WARN: Task not fully completed: $not_done_msg"
echo "==> Step 4: Retrying implementation with --continue"
run_plan_or_implement_retry "implement-retry" \
"/sdd:implement --continue $TASK_FILENAME start from judge review of last completed stage" \
"/sdd:implement --continue $TASK_FILENAME start from judge review of last completed stage"
echo ""
# Re-verify after retry
echo "==> Re-verifying task completion after retry..."
verify_output=$(just claude-json "$verify_prompt" "$COMPLETED_SCHEMA" "verify-retry")
completed=$(extract_structured_field "$verify_output" "completed" "false")
if [ "$completed" != "true" ]; then
echo "ERROR: Task still not completed after retry. Stopping loop."
exit 1
fi
fi
echo "==> Task verification passed."
[doc("""
Run lint and tests, auto-fix with claude if they fail.
Description:
Executes Step 5 of the task pipeline. Runs linting and tests,
and if they fail, automatically invokes Claude to fix the issues.
Limited to one automatic fix attempt.
Steps:
1. Run `bun run lint` and `bun run test`
2. If failed, launch Claude to fix issues
3. Retry lint/test once after fix attempt
Usage:
just claude-fix-lint-and-test
""")]
[no-exit-message]
claude-fix-lint-and-test:
#!/usr/bin/env bash
set -euo pipefail
echo ""
echo "==> Step 5: Running lint and tests"
lint_test_pass=false
for lt_attempt in 1 2 3; do
lint_exit=0
test_exit=0
npm run lint > /tmp/lint.log 2>&1 || lint_exit=$?
npm run test > /tmp/test.log 2>&1 || test_exit=$?
if [ "$lint_exit" -eq 0 ] && [ "$test_exit" -eq 0 ]; then
lint_test_pass=true
break
fi
# Attempt 1: initial run failed, fix and retry once more
# Attempt 2: fix attempt already made, retry once more
# Attempt 3: fix attempt already made, no more retries
if [ "$lt_attempt" -ge 3 ]; then
echo "ERROR: Lint/test still failing after 2 fix attempt. Stopping loop."
exit 1
fi
echo "WARN: Lint or test failed. Launching claude to fix (fix attempt $lt_attempt/3)..."
fix_prompt="/sadd:do-and-judge This codebase was recently integrated with new functionality. But bun run lint or bun run test fails. Fix the codebase, iterate till bun run lint and bun run test pass. Do not break old and newly added functionality! Do not remove or change it, unless it is obviously a bug!"
just claude "$fix_prompt" "lint-test-fix-$lt_attempt"
echo ""
done
if [ "$lint_test_pass" != "true" ]; then
echo "ERROR: Lint/test did not pass. Stopping loop."
exit 1
fi
echo "==> Lint and tests passed."
[doc("""
Run code review and auto-fix critical/high issues.
Description:
Executes Step 6 of the task pipeline. Runs automated code review
on local changes and uses Claude to fix any critical or high severity
issues found. Limited to two fix attempts.
Steps:
1. Run code review using /code-review:review-local-changes
2. Extract critical and high issue counts
3. If issues found, launch Claude to fix them
4. Re-review after fix (up to 2 fix attempts)
Usage:
just claude-review-and-fix
""")]
[no-exit-message]
claude-review-and-fix:
#!/usr/bin/env bash
set -euo pipefail
source claude-helpers.sh
echo ""
echo "==> Step 6: Running code review"
review_pass=false
for review_attempt in 1 2 3; do
review_output=$(just claude-json "/code-review:review-local-changes --json" "" "code-review-$review_attempt")
# Extract critical and high issue counts
critical_count=$(extract_structured_field "$review_output" "summary.critical" "0")
high_count=$(extract_structured_field "$review_output" "summary.high" "0")
echo " Review result: critical=$critical_count high=$high_count"
if [ "${critical_count:-0}" -eq 0 ] 2>/dev/null && [ "${high_count:-0}" -eq 0 ] 2>/dev/null; then
review_pass=true
break
fi
# Attempt 1: initial review found issues, fix and review once more
# Attempt 2: fix attempt already made, retry once more
# Attempt 3: fix attempt already made, no more retries
if [ "$review_attempt" -ge 3 ]; then
echo "ERROR: Critical/high issues remain after 2 fix attempts. Stopping loop."
exit 1
fi
echo "WARN: Found critical=$critical_count high=$high_count issues. Launching claude to fix (fix attempt $review_attempt/3)..."
fix_review_prompt="/sadd:do-and-judge The code review found critical or high severity issues in the local changes. Fix all critical and high issues. Do not break existing functionality!"
just claude "$fix_review_prompt" "review-fix-$review_attempt"
echo ""
done
if [ "$review_pass" != "true" ]; then
echo "ERROR: Code review did not pass. Stopping loop."
exit 1
fi
echo "==> Code review passed."
[doc("""
Run lint+test and code review loop, retrying up to 3 times.
Description:
Executes the verify-and-fix loop: runs lint+test followed by code review,
retrying up to 3 times until both pass.
Steps:
1. Run claude-fix-lint-and-test
2. Run claude-review-and-fix
3. If either fails, retry from step 1 (up to 3 attempts)
Parameters:
skip-lint-on-first - If "true", skip lint+test on the first loop attempt (default: "")
Usage:
just claude-verify-and-fix
just claude-verify-and-fix "true"
""")]
[no-exit-message]
claude-verify-and-fix skip-lint-on-first="":
#!/usr/bin/env bash
set -euo pipefail
MAX_LOOP=3
loop_passed=false
SKIP_LINT_ON_FIRST="{{ skip-lint-on-first }}"
for loop_attempt in $(seq 1 $MAX_LOOP); do
echo ""
echo "==> Verify-and-fix loop: attempt $loop_attempt/$MAX_LOOP"
skip_lint=false
if [ "$loop_attempt" -eq 1 ] && [ "$SKIP_LINT_ON_FIRST" = "true" ]; then
skip_lint=true
echo "==> Skipping lint+test on first pass"
fi
if [ "$skip_lint" = "false" ]; then
if ! just claude-fix-lint-and-test; then
echo "WARN: Lint+test failed on attempt $loop_attempt"
if [ "$loop_attempt" -ge "$MAX_LOOP" ]; then
echo "ERROR: Lint+test failed on final attempt. Stopping."
exit 1
fi
continue
fi
fi
if ! just claude-review-and-fix; then
echo "WARN: Review failed on attempt $loop_attempt"
if [ "$loop_attempt" -ge "$MAX_LOOP" ]; then
echo "ERROR: Review failed on final attempt. Stopping."
exit 1
fi
continue
fi
echo "==> Lint+test and review both passed on attempt $loop_attempt"
loop_passed=true
break
done
if [ "$loop_passed" != "true" ]; then
echo "ERROR: Lint+test and review did not pass after $MAX_LOOP attempts."
exit 1
fi
[doc("""
Create a git commit for recently completed work.
Description:
Uses Claude to generate a proper commit message and commit
all staged and unstaged changes.
Usage:
just claude-commit
""")]
[no-exit-message]
claude-commit:
#!/usr/bin/env bash
set -euo pipefail
echo ""
echo "==> Committing changes"
commit_prompt="/git:commit Please create proper commit message and commit changes."
just claude "$commit_prompt" "git-commit"
echo ""
echo "==> Changes committed."
[doc("""
Full pipeline: plan, implement, verify, lint, test, review, update roadmap, and commit.
Description:
Executes the complete task pipeline for a single task.
This is the main entry point for processing a task from start to finish.
Steps:
1-4. Plan, implement, verify, retry (via claude-plan-and-implement)
5-6. Lint+test and code review loop (via claude-verify-and-fix)
7. Update roadmap tracking (marks task as done in roadmap.md)
8. Git commit the changes (via claude-commit)
Modes:
"" - Full pipeline from scratch
"continue-plan" - Continue planning, then verify-and-fix, roadmap, commit
"continue-implement" - Continue implementation, then verify-and-fix, roadmap, commit
"continue-lint-and-test" - Skip plan-and-implement, go straight to verify-and-fix
"continue-review" - Skip plan-and-implement and lint-and-test on first pass, start at review
Parameters:
task-filename - The task filename (e.g., "add-feature-x.feature.md")
mode - Optional mode (see Modes above)
Usage:
just claude-vibe "my-task.feature.md"
just claude-vibe "my-task.feature.md" "continue-plan"
just claude-vibe "my-task.feature.md" "continue-implement"
just claude-vibe "my-task.feature.md" "continue-lint-and-test"
just claude-vibe "my-task.feature.md" "continue-review"
""")]
[no-exit-message]
claude-vibe task-filename mode="":
#!/usr/bin/env bash
set -euo pipefail
source claude-helpers.sh
TASK_FILENAME="{{ task-filename }}"
MODE="{{ mode }}"
echo ""
echo "================================================================"
echo " Processing task: $TASK_FILENAME"
echo "================================================================"
echo ""
# ── Validate mode parameter ───────────────────────────────────────
case "$MODE" in
""|"continue-plan"|"continue-implement"|"continue-lint-and-test"|"continue-review") ;;
*)
echo "ERROR: Unknown mode '$MODE'."
echo "Valid modes: '', 'continue-plan', 'continue-implement', 'continue-lint-and-test', 'continue-review'"
exit 1
;;
esac
# ── Steps 1-4: Plan and implement (skipped for continue-lint-and-test and continue-review) ──
if [ "$MODE" != "continue-lint-and-test" ] && [ "$MODE" != "continue-review" ]; then
just claude-plan-and-implement "$TASK_FILENAME" "$MODE"
fi
# ── Steps 5-6: Verify and fix ─────────────────────────────────────
if [ "$MODE" = "continue-review" ]; then
just claude-verify-and-fix "true"
else
just claude-verify-and-fix
fi
# ── STEP 7: Update roadmap tracking ───────────────────────────────
echo ""
echo "==> Step 7: Updating roadmap tracking"
if [ -f ".specs/roadmap.md" ]; then
roadmap_prompt="Find the task $TASK_FILENAME in .specs/roadmap.md. Mark this task as done in the roadmap file."
just claude-json "$roadmap_prompt" "" "roadmap-update"
echo "==> Roadmap tracking updated."
else
echo "==> Roadmap file not found (.specs/roadmap.md), skipping this stage."
fi
# ── STEP 8: Git commit ─────────────────────────────────────────
just claude-commit
[doc("""
Load open PR comments, fix them, verify, and commit.
Description:
Automates the process of addressing PR review comments:
loads open comments, converts them to tasks, fixes them in parallel,
verifies the fixes, cleans up, and commits.
Steps:
1. Load open PR comments and save as task files
2. Launch parallel agents to fix all comments
3. Run verify-and-fix loop (lint+test + review)
4. Clean up comment task files
5. Commit the changes
Usage:
just claude-fix-pr-comments
""")]
[no-exit-message]
claude-fix-pr-comments:
#!/usr/bin/env bash
set -euo pipefail
echo ""
echo "================================================================"
echo " Fixing PR comments"
echo "================================================================"
echo ""
# ── Step 1: Load PR comments ────────────────────────────────────
echo "==> Step 1: Loading PR comments"
mkdir -p .specs/comments
load_prompt="/pr-comments load ONLY open PR comments for current git branch from github and save it as separate md files to @.specs/comments/. Rewrite them as tasks, avoid summarising: write human feedback as requirements, if it not exists then claude fix suggestion as requirements, add claude issue description and link to the file and line as task context. (if they available). Do not duplicate issues!"
just claude "$load_prompt" "load-pr-comments"
echo ""
# ── Step 2: Fix comments ────────────────────────────────────────
echo "==> Step 2: Fixing PR comments"
fix_prompt="/sadd:do-in-parallel launch agents to fix all comments in @.specs/comments/"
just claude "$fix_prompt" "fix-pr-comments"
echo ""
# ── Step 3: Verify and fix ──────────────────────────────────────
echo "==> Step 3: Verifying fixes"
just claude-verify-and-fix
# ── Step 4: Clean up ────────────────────────────────────────────
echo "==> Step 4: Cleaning up comment files"
rm -f .specs/comments/*.md
echo "==> Comment files cleaned up."
# ── Step 5: Commit ──────────────────────────────────────────────
echo "==> Step 5: Committing changes"
just claude-commit
[doc("""
Run the full loop: process all tasks from roadmap until complete.
Description:
Continuously processes tasks from the roadmap file until all tasks
are marked as done. For each task, runs the full claude-vibe pipeline.
Requires a .specs/roadmap.md file to exist.
Steps:
1. Extract next incomplete task from roadmap
2. Run full claude-vibe pipeline for that task
3. Repeat until all tasks are done
Usage:
just claude-loop
""")]
[no-exit-message]
claude-loop:
#!/usr/bin/env bash
set -euo pipefail
source claude-helpers.sh
# Check if roadmap file exists
if [ ! -f ".specs/roadmap.md" ]; then
echo "ERROR: Roadmap file not found (.specs/roadmap.md). Cannot run claude-loop without a roadmap."
exit 1
fi
# ══════════════════════════════════════════════════════════════════
# MAIN LOOP
# ══════════════════════════════════════════════════════════════════
while true; do
# ── Extract next task from roadmap ──────────────────────────────
echo ""
echo "==> Extracting next task from roadmap"
next_task_prompt="Read @.specs/roadmap.md. Find the next task that is not marked as done. Return in format {allDone: false, filenames: {task: '<task-filename>'}}. Where task filename is not include directory path, only filename, example 'add-agent-trigger-matcher.feature.md'. If all tasks are done, return {allDone: true}."
next_output=$(just claude-json "$next_task_prompt" "$NEXT_TASK_SCHEMA" "next-task")
all_done=$(extract_structured_field "$next_output" "allDone" "false")
if [ "$all_done" = "true" ]; then
echo ""
echo "================================================================"
echo " All tasks complete! Roadmap fully implemented."
echo "================================================================"
exit 0
fi
# Parse next task filename
TASK_FILENAME=$(extract_structured_field "$next_output" "filenames.task" "")
next_epic=$(extract_structured_field "$next_output" "filenames.epic" "")
if [ -z "$TASK_FILENAME" ]; then
echo "ERROR: Could not extract next task filename from response."
echo "Response: $next_output"
exit 1
fi
echo "==> Next task: $TASK_FILENAME (epic: $next_epic)"
# Steps 1-8: Full pipeline for the current task
just claude-vibe "$TASK_FILENAME"
done # end main loop
# JSON claude execution examples:
# Without schema:
# just claude-json "What is 2+2?"
# ==> [claude-json] Attempt 1/6
# [{"type":"system","subtype":"init","cwd":"/workspaces/agent-hooks","session_id":"b15a7626-bb05-44d5-9fae-2ee84ff9972f","tools":["Task","TaskOutput","Bash","Glob","Grep","ExitPlanMode","Read","Edit","Write","NotebookEdit","WebFetch","TodoWrite","WebSearch","TaskStop","AskUserQuestion","Skill","EnterPlanMode","EnterWorktree","ExitWorktree","CronCreate","CronDelete","CronList","ToolSearch","mcp__context7__resolve-library-id","mcp__context7__query-docs","LSP"],"mcp_servers":[{"name":"context7","status":"connected"},{"name":"claude.ai Google Calendar","status":"needs-auth"},{"name":"claude.ai Gmail","status":"needs-auth"},{"name":"claude.ai Slack","status":"needs-auth"},{"name":"claude.ai Canva","status":"needs-auth"},{"name":"claude.ai Hugging Face","status":"needs-auth"},{"name":"claude.ai Atlassian","status":"needs-auth"},{"name":"claude.ai Microsoft 365","status":"needs-auth"},{"name":"claude.ai Notion","status":"needs-auth"},{"name":"claude.ai Figma","status":"failed"}],"model":"claude-opus-4-6","permissionMode":"bypassPermissions","slash_commands":["debug","simplify","batch","loop","claude-api","hook-setup-command","claude-code-hooks","class-validator-and-transformer","commander-js","hook-configuration-patterns","claude-code-cli","nest-commander","yaml-schema","sdd:add-task","sdd:brainstorm","sdd:plan","sdd:create-ideas","sdd:implement","sadd:multi-agent-patterns","sadd:judge","sadd:do-in-steps","sadd:do-in-parallel","sadd:launch-sub-agent","sadd:do-and-judge","sadd:do-competitively","sadd:judge-with-debate","sadd:tree-of-thoughts","sadd:subagent-driven-development","git:create-pr","git:load-issues","git:commit","git:compare-worktrees","git:merge-worktree","git:attach-review-to-pr","git:create-worktree","git:notes","git:worktrees","git:analyze-issue","ddd:setup-code-formating","code-review:review-local-changes","code-review:review-pr","compact","context","cost","heapdump","init","pr-comments","release-notes","review","security-review","extra-usage","insights"],"apiKeySource":"none","claude_code_version":"2.1.74","output_style":"default","agents":["general-purpose","statusline-setup","Explore","Plan","sdd:software-architect","sdd:researcher","sdd:tech-lead","sdd:developer","sdd:qa-engineer","sdd:team-lead","sdd:business-analyst","sdd:code-explorer","sdd:tech-writer","sadd:meta-judge","sadd:judge","code-review:code-reviewer","code-review:bug-hunter","code-review:test-coverage-reviewer","code-review:contracts-reviewer","code-review:historical-context-reviewer","code-review:security-auditor"],"skills":["debug","simplify","batch","loop","claude-api","hook-setup-command","claude-code-hooks","class-validator-and-transformer","commander-js","hook-configuration-patterns","claude-code-cli","nest-commander","yaml-schema","sdd:add-task","sdd:brainstorm","sdd:plan","sdd:create-ideas","sdd:implement","sadd:multi-agent-patterns","sadd:judge","sadd:do-in-steps","sadd:do-in-parallel","sadd:launch-sub-agent","sadd:do-and-judge","sadd:do-competitively","sadd:judge-with-debate","sadd:tree-of-thoughts","sadd:subagent-driven-development","git:create-pr","git:load-issues","git:commit","git:compare-worktrees","git:merge-worktree","git:attach-review-to-pr","git:create-worktree","git:notes","git:worktrees","git:analyze-issue","ddd:setup-code-formating","code-review:review-local-changes","code-review:review-pr"],"plugins":[{"name":"typescript-lsp","path":"/home/node/.claude/plugins/cache/claude-plugins-official/typescript-lsp/1.0.0"},{"name":"sdd","path":"/home/node/.claude/plugins/cache/context-engineering-kit/sdd/2.1.1"},{"name":"sadd","path":"/home/node/.claude/plugins/cache/context-engineering-kit/sadd/1.3.0"},{"name":"git","path":"/home/node/.claude/plugins/cache/context-engineering-kit/git/1.2.0"},{"name":"ddd","path":"/home/node/.claude/plugins/cache/context-engineering-kit/ddd/2.0.0"},{"name":"code-review","path":"/home/node/.claude/plugins/cache/context-engineering-kit/code-review/1.1.0"}],"uuid":"98ad5e92-272c-4047-ade2-f29808786c34","fast_mode_state":"off"},{"type":"assistant","message":{"model":"claude-opus-4-6","id":"msg_01LpnfTf6R7uqC7SrPdHPMM2","type":"message","role":"assistant","content":[{"type":"text","text":"\n\n4"}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":3,"cache_creation_input_tokens":35339,"cache_read_input_tokens":0,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":35339},"output_tokens":2,"service_tier":"standard","inference_geo":"not_available"},"context_management":null},"parent_tool_use_id":null,"session_id":"b15a7626-bb05-44d5-9fae-2ee84ff9972f","uuid":"5c08417d-2b68-46b0-a439-c77987bba742"},{"type":"rate_limit_event","rate_limit_info":{"status":"allowed","resetsAt":1774382400,"rateLimitType":"five_hour","overageStatus":"rejected","overageDisabledReason":"out_of_credits","isUsingOverage":false},"uuid":"3873d31b-6fca-4b2d-863e-e3bf5e7c0bc5","session_id":"b15a7626-bb05-44d5-9fae-2ee84ff9972f"},{"type":"result","subtype":"success","is_error":false,"duration_ms":3214,"duration_api_ms":3092,"num_turns":1,"result":"\n\n4","stop_reason":"end_turn","session_id":"b15a7626-bb05-44d5-9fae-2ee84ff9972f","total_cost_usd":0.22100875,"usage":{"input_tokens":3,"cache_creation_input_tokens":35339,"cache_read_input_tokens":0,"output_tokens":5,"server_tool_use":{"web_search_requests":0,"web_fetch_requests":0},"service_tier":"standard","cache_creation":{"ephemeral_1h_input_tokens":35339,"ephemeral_5m_input_tokens":0},"inference_geo":"","iterations":[],"speed":"standard"},"modelUsage":{"claude-opus-4-6":{"inputTokens":3,"outputTokens":5,"cacheReadInputTokens":0,"cacheCreationInputTokens":35339,"webSearchRequests":0,"costUSD":0.22100875,"contextWindow":200000,"maxOutputTokens":32000}},"permission_denials":[],"fast_mode_state":"off","uuid":"a9c6659c-ae8b-4aec-95dc-7e770b1a1454"}]
# With schema:
# claude -p "What is 2+2?" --output-format json --verbose --json-schema '{"type":"object","properties":{"answer":{"type":"number"}},"required":["answer"]}'
# [{"type":"system","subtype":"init","cwd":"/workspaces/agent-hooks","session_id":"d0e8f92a-b33a-477d-9d8f-e239d3ffe9c1","tools":["Task","TaskOutput","Bash","Glob","Grep","ExitPlanMode","Read","Edit","Write","NotebookEdit","WebFetch","TodoWrite","WebSearch","TaskStop","AskUserQuestion","Skill","EnterPlanMode","EnterWorktree","ExitWorktree","CronCreate","CronDelete","CronList","RemoteTrigger","ToolSearch","StructuredOutput","mcp__context7__resolve-library-id","mcp__context7__query-docs","LSP"],"mcp_servers":[{"name":"context7","status":"connected"},{"name":"claude.ai Google Calendar","status":"needs-auth"},{"name":"claude.ai Gmail","status":"needs-auth"},{"name":"claude.ai Slack","status":"needs-auth"},{"name":"claude.ai Canva","status":"needs-auth"},{"name":"claude.ai Hugging Face","status":"needs-auth"},{"name":"claude.ai Figma","status":"pending"},{"name":"claude.ai Atlassian","status":"needs-auth"},{"name":"claude.ai Microsoft 365","status":"needs-auth"},{"name":"claude.ai Notion","status":"needs-auth"}],"model":"claude-opus-4-6[1m]","permissionMode":"bypassPermissions","slash_commands":["update-config","debug","simplify","batch","loop","schedule","claude-api","hook-setup-command","claude-code-hooks","class-validator-and-transformer","commander-js","hook-configuration-patterns","claude-code-cli","nest-commander","yaml-schema","sdd:add-task","sdd:brainstorm","sdd:plan","sdd:create-ideas","sdd:implement","sadd:judge","sadd:do-in-steps","sadd:do-in-parallel","sadd:judge-with-debate","sadd:subagent-driven-development","sadd:do-competitively","sadd:launch-sub-agent","sadd:do-and-judge","sadd:tree-of-thoughts","sadd:multi-agent-patterns","git:load-issues","git:commit","git:compare-worktrees","git:create-pr","git:analyze-issue","git:worktrees","git:merge-worktree","git:attach-review-to-pr","git:create-worktree","git:notes","ddd:setup-code-formating","code-review:review-local-changes","code-review:review-pr","compact","context","cost","heapdump","init","pr-comments","release-notes","review","security-review","extra-usage","insights"],"apiKeySource":"none","claude_code_version":"2.1.81","output_style":"default","agents":["general-purpose","statusline-setup","Explore","Plan","sdd:researcher","sdd:tech-lead","sdd:developer","sdd:qa-engineer","sdd:team-lead","sdd:business-analyst","sdd:code-explorer","sdd:tech-writer","sdd:software-architect","sadd:meta-judge","sadd:judge","code-review:code-reviewer","code-review:bug-hunter","code-review:test-coverage-reviewer","code-review:contracts-reviewer","code-review:historical-context-reviewer","code-review:security-auditor"],"skills":["update-config","debug","simplify","batch","loop","schedule","claude-api","hook-setup-command","claude-code-hooks","class-validator-and-transformer","commander-js","hook-configuration-patterns","claude-code-cli","nest-commander","yaml-schema","sdd:add-task","sdd:brainstorm","sdd:plan","sdd:create-ideas","sdd:implement","sadd:judge","sadd:do-in-steps","sadd:do-in-parallel","sadd:judge-with-debate","sadd:subagent-driven-development","sadd:do-competitively","sadd:launch-sub-agent","sadd:do-and-judge","sadd:tree-of-thoughts","sadd:multi-agent-patterns","git:load-issues","git:commit","git:compare-worktrees","git:create-pr","git:analyze-issue","git:worktrees","git:merge-worktree","git:attach-review-to-pr","git:create-worktree","git:notes","ddd:setup-code-formating","code-review:review-local-changes","code-review:review-pr"],"plugins":[{"name":"typescript-lsp","path":"/home/node/.claude/plugins/cache/claude-plugins-official/typescript-lsp/1.0.0"},{"name":"sdd","path":"/home/node/.claude/plugins/cache/context-engineering-kit/sdd/2.1.1"},{"name":"sadd","path":"/home/node/.claude/plugins/cache/context-engineering-kit/sadd/1.3.0"},{"name":"git","path":"/home/node/.claude/plugins/cache/context-engineering-kit/git/1.2.0"},{"name":"ddd","path":"/home/node/.claude/plugins/cache/context-engineering-kit/ddd/2.0.0"},{"name":"code-review","path":"/home/node/.claude/plugins/cache/context-engineering-kit/code-review/1.1.0"}],"uuid":"c4a8e996-76c1-4f78-92fa-5f3581756ab5","fast_mode_state":"off"},{"type":"assistant","message":{"model":"claude-opus-4-6","id":"msg_01T7Yfq3LC7pAJuPBomvPV5M","type":"message","role":"assistant","content":[{"type":"thinking","thinking":"The user is asking a simple math question.","signature":"EtIBCkYIDBgCKkBN5NJqJIeLCiAqkfpaWfsOkN1BR8OlbxUPrnJrvLg2wpB10LFEOdpBuDkqar3RoqRqnYDw3tWf0d8v3vqOZ741Egz61JohS03vXIZb2roaDLrE/IEndkBSLmYbYyIwg+Z8H4oVR/Ehh5Jq0qnIeDG+2m34Ij+Fze89xX0xQPfm4Pz3YRCYbj3l8UL2rdxMKjpbpWKIedjDm7p1e/qEvmGZrWRVYvvFrOSwhmlTw+7VAN7TLarXP4UHjjlsTcYuzBEEWABl1Cdu368dGAE="}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":3,"cache_creation_input_tokens":36041,"cache_read_input_tokens":0,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":36041},"output_tokens":45,"service_tier":"standard","inference_geo":"not_available"},"context_management":null},"parent_tool_use_id":null,"session_id":"d0e8f92a-b33a-477d-9d8f-e239d3ffe9c1","uuid":"c6441f72-7cc7-41ef-a363-283c1101aee6"},{"type":"assistant","message":{"model":"claude-opus-4-6","id":"msg_01T7Yfq3LC7pAJuPBomvPV5M","type":"message","role":"assistant","content":[{"type":"tool_use","id":"toolu_018J4oFeohmtqwPgX1d5eptw","name":"StructuredOutput","input":{"answer":4},"caller":{"type":"direct"}}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":3,"cache_creation_input_tokens":36041,"cache_read_input_tokens":0,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":36041},"output_tokens":45,"service_tier":"standard","inference_geo":"not_available"},"context_management":null},"parent_tool_use_id":null,"session_id":"d0e8f92a-b33a-477d-9d8f-e239d3ffe9c1","uuid":"f8e2c765-b0fb-4809-adb6-eaebdb251553"},{"type":"user","message":{"role":"user","content":[{"tool_use_id":"toolu_018J4oFeohmtqwPgX1d5eptw","type":"tool_result","content":"Structured output provided successfully"}]},"parent_tool_use_id":null,"session_id":"d0e8f92a-b33a-477d-9d8f-e239d3ffe9c1","uuid":"a06fc570-637c-482e-bb27-d6ae990c9fe4","timestamp":"2026-03-24T15:45:30.695Z","tool_use_result":"Structured output provided successfully"},{"type":"rate_limit_event","rate_limit_info":{"status":"allowed","resetsAt":1774382400,"rateLimitType":"five_hour","overageStatus":"rejected","overageDisabledReason":"out_of_credits","isUsingOverage":false},"uuid":"3f285401-f025-4aa0-acf7-f9e77a33bc1b","session_id":"d0e8f92a-b33a-477d-9d8f-e239d3ffe9c1"},{"type":"assistant","message":{"model":"claude-opus-4-6","id":"msg_0145HbJwcFTRtb3qL2iL9gs3","type":"message","role":"assistant","content":[{"type":"text","text":"4."}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":1,"cache_creation_input_tokens":97,"cache_read_input_tokens":36041,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":97},"output_tokens":1,"service_tier":"standard","inference_geo":"not_available"},"context_management":null},"parent_tool_use_id":null,"session_id":"d0e8f92a-b33a-477d-9d8f-e239d3ffe9c1","uuid":"688222b1-7429-4200-8dd5-18c039c1927c"},{"type":"result","subtype":"success","is_error":false,"duration_ms":8869,"duration_api_ms":8342,"num_turns":2,"result":"4.","stop_reason":"end_turn","session_id":"d0e8f92a-b33a-477d-9d8f-e239d3ffe9c1","total_cost_usd":0.24600299999999997,"usage":{"input_tokens":4,"cache_creation_input_tokens":36138,"cache_read_input_tokens":36041,"output_tokens":84,"server_tool_use":{"web_search_requests":0,"web_fetch_requests":0},"service_tier":"standard","cache_creation":{"ephemeral_1h_input_tokens":36138,"ephemeral_5m_input_tokens":0},"inference_geo":"","iterations":[],"speed":"standard"},"modelUsage":{"claude-opus-4-6[1m]":{"inputTokens":4,"outputTokens":84,"cacheReadInputTokens":36041,"cacheCreationInputTokens":36138,"webSearchRequests":0,"costUSD":0.24600299999999997,"contextWindow":1000000,"maxOutputTokens":64000}},"permission_denials":[],"structured_output":{"answer":4},"fast_mode_state":"off","uuid":"f0c40030-8e71-4e34-9624-f5b4d9895585"}]
@LeoVS09
Copy link
Copy Markdown
Author

LeoVS09 commented Mar 27, 2026

Long-running Claude Harness

This script allows running Claude for days, with automatic pauses and retries during session limits

This script supports devcontainer as an agent sandbox, and as a result is intended to be used with this gist. But it is not required. You can use Claude scripts directly; you just need to install SADD, SDD, DDD and code-review plugins from context-engineering-kit. By default, scripts start Claude in auto mode, which is only secure when used inside a container.

Quick start

  1. Copy these files to your repo. Optionally copy these gist files to the .devcontainer folder and set up the devcontainer cli (the gist has instructions for it).
  2. Install just CLI by running npm install -g rust-just
  3. Run just help to see the list of available commands.

Launch Claude in the sandbox

Start devcontainer

just sandbox

Once the command is complete, launch Claude

claude

Go through the login and initial questions. Once it's done, close the cli, and you can run any Claude prompt with retries through this command

just claude "Some prompt"

Claude will perform the given prompt if it fails due to session limits. It will wait for an hour and try again. If the limit is still not received, it will continue trying with hour pauses up to 6 times.

One-Shot Implementation

Create a task that you want to be completed

just claude-add-task "add basic authentication support"

It will generate a task file, which you can update to add additional context

Launch implementation by providing the task filename

just claude-vibe "add-basic-auth.feature.md"

Docs

For more usage examples, run

just help

Advanced Usage

Plan and Implement

In order to implement the task, you first need to have a task file, you can create it by running

just claude-add-task "add basic authentication support"

Then launch the planning, implementation and verification stages

just claude-plan-and-implement "add-basic-auth.feature.md"
  • Firstly, it will plan the task and move it from draft to todo, then it will start implementation and move it to in-progress, and after it is completed, it will move it to done.
  • After the task is implemented, it will be verified that it correctly aligns with the specification and retry the implementation till it is done.
  • If any of the stages fail due to the session limit, it will retry them after the limit is reset.
  • It will detect whether the task is in .specs/tasks/draft/, .specs/tasks/todo/, or .specs/tasks/done/ state and automatically choose where to launch it from the planning, implementation, or verification stage.
  • If there are any additional issues and you stopped it during some stage, you can simply run it like just claude-plan-and-implement "add-basic-auth.feature.md" continue-plan or continue-implment, and it will continue from this stage.

Fix Lint And Test

This command acts as a quality gate. It runs npm run lint && npm run test, and if either fails, it asks Claude to fix it. You can run it like

just claude-fix-lint-and-test
  • If the first fix fails, it will retry one more time

Review and Fix

This command runs the /code-review:review-local-changes command, and if it finds any critical or high issues, it will fix them. You can run it like

claude-review-and-fix
  • If, after the first fix, there are still critical or high issues, it will attempt one more time to fix them.
  • If, after 2 attempts, there are still issues, it will fail.

Vibe Code

Vibe version uses a plan-and-implement workflow under the hood, but on top of this, it adds claude-fix-lint-and-test and claude-review-and-fix as an additional checks after implementation. You can run it simply by

just claude-vibe "add-basic-auth.feature.md"
  • It supports the same params as plan-and-implement
  • after task implementation verified, and claude-fix-lint-and-test and claude-review-and-fix pass it will commit the changes.

Claude Loop

If you have multiple changes, you can split them into separate tasks and create .specs/roadmap.md. In this file, you need to put tasks in order that you want them to implement. After that, you can launch a command like this

just claude-loop
  • it will extract the next pending task in the roadmap
  • run for it claude-vibe and mark it in the roadmap as done before committing
  • after the task is completed and committed, it will take the next task from the roadmap and continue implementation

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment