Last active
March 29, 2026 15:18
-
-
Save LeoVS09/8155a47b239e4b5c5ee7eef431e5180b to your computer and use it in GitHub Desktop.
Claude workflow and agent devcontainer managment script
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/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" | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # ── 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"}] |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Long-running Claude Harness
This script allows running Claude for days, with automatic pauses and retries during session limits
Quick start
.devcontainerfolder and set up the devcontainer cli (the gist has instructions for it).npm install -g rust-justjust helpto see the list of available commands.Launch Claude in the sandbox
Start devcontainer
Once the command is complete, launch 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 helpAdvanced 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"drafttotodo, then it will start implementation and move it toin-progress, and after it is completed, it will move it todone..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.just claude-plan-and-implement "add-basic-auth.feature.md" continue-planorcontinue-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 likeReview and Fix
This command runs the
/code-review:review-local-changescommand, and if it finds any critical or high issues, it will fix them. You can run it likeVibe Code
Vibe version uses a plan-and-implement workflow under the hood, but on top of this, it adds
claude-fix-lint-and-testandclaude-review-and-fixas an additional checks after implementation. You can run it simply byjust claude-vibe "add-basic-auth.feature.md"plan-and-implementclaude-fix-lint-and-testandclaude-review-and-fixpass 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 thisclaude-vibeand mark it in the roadmap as done before committing