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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 71 additions & 0 deletions .carranca.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# .carranca.yml — carranca project configuration
# See: https://github.com/pboueke/carranca

agents:
- name: claude
adapter: claude
command: claude

- name: codex
adapter: codex
command: codex

- name: opencode
adapter: opencode
command: opencode

- name: reviewer
adapter: stdin
command: bash /workspace/_review.sh

# Container runtime settings
runtime:
engine: auto
network: true
# Fine-grained network policy (replaces boolean; requires yq):
# network:
# default: deny
# allow:
# - "*.anthropic.com:443"
# - "registry.npmjs.org:443"
# Extra container runtime flags for the agent container (e.g. --gpus all)
# extra_flags: --gpus all
# Extra container runtime flags for the logger container
# logger_extra_flags:
# seccomp_profile: default # "default" (carranca built-in), "unconfined", or absolute path
# apparmor_profile: # AppArmor profile name (must be loaded); "unconfined" to disable
# cap_drop_all: true # Drop all Linux capabilities (--cap-drop ALL)
# read_only: true # Read-only root filesystem (--read-only + tmpfs)
# Linux capabilities for the agent container (allowlist after cap_drop_all)
# cap_add:
# - SYS_PTRACE

# Persistent volumes for the agent container
volumes:
cache: true # Cache agent memory, config, session across runs
# extra: # Custom volume mounts (host:container[:mode])
# - ~/docs:/reference:ro

policy:
docs_before_code: warn # warn, enforce, or off — git pre-commit hook
tests_before_impl: warn # warn, enforce, or off — git pre-commit hook
# max_duration: 3600 # Kill agent after N seconds (0 = no limit)
# resource_limits: # Requires yq
# memory: "2g"
# cpus: "2.0"
# pids: 256
# filesystem: # Requires yq
# enforce_watched_paths: false # Make watched_paths read-only

# observability:
# independent_observer: false # Run execve/network monitoring in independent sidecar

# Environment variables forwarded into agent containers
environment:
passthrough:
- OPENAI_API_KEY

watched_paths:
- .env
- secrets/
- "*.key"
50 changes: 50 additions & 0 deletions .carranca/Containerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Carranca agent container
# Customize this file to install your agent CLI and project dependencies.
# The shell wrapper is injected automatically — do not remove the last lines.
#
# Examples:
# - Install Python: RUN apk add python3 py3-pip
# - Install Node.js: RUN apk add nodejs npm
# - Install Go: RUN apk add go

FROM alpine:3.21

# Install base tools
RUN apk add --no-cache \
bash \
coreutils \
curl \
git \
ca-certificates \
iptables

RUN mkdir -p /home/carranca && chmod 0777 /home/carranca

# ──────────────────────────────────────────────
# Add your agent and project dependencies below:
# Node.js + npm (for project and agents)
RUN apk add --no-cache bubblewrap nodejs npm python3

# OpenAI Codex CLI
RUN npm install -g @openai/codex

# Claude Code CLI
RUN npm install -g @anthropic-ai/claude-code

# OpenCode CLI
RUN curl -fsSL https://opencode.ai/install | bash -s -- --no-modify-path && \
mv /root/.opencode/bin/opencode /usr/local/bin/opencode && \
chmod 0755 /usr/local/bin/opencode && \
rm -rf /root/.opencode
# ──────────────────────────────────────────────



# ──────────────────────────────────────────────
# Carranca shell wrapper (do not remove)
# ──────────────────────────────────────────────
COPY lib/json.sh /usr/local/bin/lib/json.sh
COPY shell-wrapper.sh /usr/local/bin/shell-wrapper.sh
RUN chmod +x /usr/local/bin/shell-wrapper.sh
WORKDIR /workspace
ENTRYPOINT ["/usr/local/bin/shell-wrapper.sh"]
57 changes: 57 additions & 0 deletions .carranca/lib/json.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#!/usr/bin/env bash
# carranca/runtime/lib/json.sh — shared JSON utility functions
# Sourced by shell-wrapper.sh and any other runtime script that needs
# safe JSON string encoding.
#
# Provides:
# json_escape "$string" — RFC 8259 compliant string escaping
# json_validate_line "$line" — basic structural check (starts with {, ends with })
#
# Implementation uses pure bash/sed for portability (no jq dependency).

# Escape a string for safe embedding in a JSON value per RFC 8259.
# Handles: \ " newline carriage-return tab backspace form-feed
# and all remaining control characters U+0000–U+001F as \uXXXX.
json_escape() {
local input="$1"
# Phase 1: escape backslash first (must come before other escapes that
# introduce backslashes), then double-quote, then named controls.
local result
result="$(printf '%s' "$input" | sed \
-e 's/\\/\\\\/g' \
-e 's/"/\\"/g' \
-e 's/\x08/\\b/g' \
-e 's/\x0c/\\f/g' \
-e 's/\t/\\t/g')"

# Phase 2: handle newlines and carriage returns.
# sed operates line-by-line so we use bash parameter expansion instead.
result="${result//$'\n'/\\n}"
result="${result//$'\r'/\\r}"

# Phase 3: escape remaining control characters U+0000–U+001F as \uXXXX.
# After the above, the only remaining controls are 0x00-0x07, 0x0e-0x1f
# (0x08=\b, 0x09=\t, 0x0a=\n, 0x0c=\f, 0x0d=\r already handled).
local i char_val hex out=""
for (( i=0; i<${#result}; i++ )); do
char_val="$(printf '%d' "'${result:i:1}" 2>/dev/null || echo 0)"
if (( char_val >= 0 && char_val <= 31 )); then
hex="$(printf '%04x' "$char_val")"
out+="\\u${hex}"
else
out+="${result:i:1}"
fi
done

printf '%s' "$out"
}

# Basic structural validation: a JSON line must start with { and end with }.
# Returns 0 (true) if valid, 1 (false) otherwise.
json_validate_line() {
local line="$1"
# Strip leading/trailing whitespace
local trimmed
trimmed="$(printf '%s' "$line" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')"
[[ "$trimmed" == "{"* && "$trimmed" == *"}" ]]
}
125 changes: 125 additions & 0 deletions .carranca/shell-wrapper.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
#!/usr/bin/env bash
# carranca shell-wrapper — wraps agent command execution and writes events to FIFO
#
# This script is the ENTRYPOINT of the agent container. It:
# 1. Waits for the FIFO to be ready (created by logger)
# 2. Starts a heartbeat background process (30s interval)
# 3. Writes agent_start event
# 4. Executes the agent command, capturing exit code
# 5. Writes agent_stop event
# 6. Exits immediately if the FIFO breaks (fail closed)
set -uo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

FIFO_PATH="/fifo/events"
SESSION_ID="${SESSION_ID:-unknown}"
AGENT_COMMAND="${AGENT_COMMAND:-bash}"

# --- Helpers ---

timestamp() {
date -u +%Y-%m-%dT%H:%M:%S.%3NZ
}

ms_now() {
date +%s%3N 2>/dev/null || date +%s
}

fail_closed() {
local message="$1"
echo "[carranca] $message — exiting (fail closed)" >&2
kill 0 2>/dev/null
exit 1
}

fifo_is_healthy() {
[ -p "$FIFO_PATH" ] && [ -w "$FIFO_PATH" ]
}

write_event() {
if ! fifo_is_healthy; then
fail_closed "FIFO is unavailable"
fi

printf '%s\n' "$1" > "$FIFO_PATH" 2>/dev/null
local rc=$?
if [ "$rc" -ne 0 ]; then
fail_closed "FIFO write failed"
fi
}

# shellcheck source=lib/json.sh
source "$SCRIPT_DIR/lib/json.sh"

# --- Wait for FIFO ---

WAIT_LIMIT=20
WAIT_COUNT=0
while [ ! -p "$FIFO_PATH" ]; do
WAIT_COUNT=$((WAIT_COUNT + 1))
if [ "$WAIT_COUNT" -ge "$WAIT_LIMIT" ]; then
fail_closed "FIFO not found after ${WAIT_LIMIT}s"
fi
sleep 0.5
done

# --- Heartbeat ---

_heartbeat_loop() {
while true; do
sleep 30
printf '{"type":"heartbeat","source":"shell-wrapper","ts":"%s","session_id":"%s"}\n' "$(timestamp)" "$SESSION_ID" > "$FIFO_PATH" 2>/dev/null || exit 1
done
}

_heartbeat_loop &
HEARTBEAT_PID=$!

_fifo_watchdog_loop() {
while true; do
sleep 1
fifo_is_healthy || fail_closed "FIFO disappeared"
done
}

_fifo_watchdog_loop &
WATCHDOG_PID=$!

# --- Session start event ---

write_event "{\"type\":\"session_event\",\"source\":\"shell-wrapper\",\"event\":\"agent_start\",\"ts\":\"$(timestamp)\",\"session_id\":\"$SESSION_ID\"}"

# --- Policy hooks setup (4.3) ---

if [ "${POLICY_HOOKS:-}" = "true" ] && [ -d "/carranca-hooks" ]; then
git config --global core.hooksPath /carranca-hooks 2>/dev/null || true
fi

# --- Execute agent command ---
# We log the overall agent command as a shell_command event.
# The agent may run sub-commands internally — those are captured by
# inotifywait (file mutations) but not individually logged as shell_command
# events in MVP (that requires execve tracing, Phase 3).

START_MS="$(ms_now)"
# AGENT_COMMAND is operator-authored (from .carranca.yml), not agent-controlled.
# eval is required to support shell syntax (pipes, &&, env vars, subshells).
# .carranca.yml is trusted operator input, hidden from the agent at runtime.
eval "$AGENT_COMMAND"
AGENT_EXIT=$?
END_MS="$(ms_now)"
DURATION=$((END_MS - START_MS))

ESCAPED_CMD="$(json_escape "$AGENT_COMMAND")"
ESCAPED_CWD="$(json_escape "$(pwd)")"
write_event "{\"type\":\"shell_command\",\"source\":\"shell-wrapper\",\"ts\":\"$(timestamp)\",\"session_id\":\"$SESSION_ID\",\"command\":\"$ESCAPED_CMD\",\"exit_code\":$AGENT_EXIT,\"duration_ms\":$DURATION,\"cwd\":\"$ESCAPED_CWD\"}"

# --- Session stop event ---

write_event "{\"type\":\"session_event\",\"source\":\"shell-wrapper\",\"event\":\"agent_stop\",\"ts\":\"$(timestamp)\",\"session_id\":\"$SESSION_ID\",\"exit_code\":$AGENT_EXIT}"

# Cleanup
kill $HEARTBEAT_PID 2>/dev/null || true
kill $WATCHDOG_PID 2>/dev/null || true
exit $AGENT_EXIT
18 changes: 18 additions & 0 deletions .carranca/skills/carranca/confiskill/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
name: carranca-confiskill
description: Guidance for proposing carranca runtime configuration updates for the current workspace
---

# Carranca Config Skill

When asked to configure carranca for a repo:

1. Review the workspace to identify the project stack, package managers, and development tooling needs.
2. Read both Carranca-managed skills and any user-provided skills before proposing changes.
3. Propose changes only to `.carranca.yml` and `.carranca/Containerfile`.
4. Preserve the shell-wrapper lines in the Containerfile.
5. Keep `.carranca.yml` on the `agents:` format and preserve valid existing agent entries unless the operator request says otherwise.
6. Use any explicit operator request in the prompt as a hard input when deciding what to change.
7. Prefer adding the minimum container dependencies needed for development inside the container.
8. Explain the rationale for each change clearly and briefly.
9. If no changes are needed, say so explicitly and output unchanged proposed files.
Loading
Loading