#!/usr/bin/env bash # Generates harness-specific context files from .context/PROJECT.md # Project-level script — run from a project directory. # # For Claude Code: generates project-only CLAUDE.md (it inherits root via tree walk) # For everything else: concatenates root AGENT.md + project PROJECT.md # # Usage: ./scripts/context-sync.sh [adapter...] # Task: task context:sync # # Override root context: ROOT_CONTEXT=~/dev/.context/AGENT.md ./scripts/context-sync.sh set -euo pipefail PROJECT_FILE=".context/PROJECT.md" SKILLS_DIR=".skills" HANDROLLED_MARKER="HANDROLLED: do not regenerate" # Skip regenerating any target file that opts out via the HANDROLLED sentinel. # Add `` near the top of a file to keep # it hand-maintained even when context:sync runs. Useful for projects whose # CLAUDE.md (or other adapter) is the single source of truth — e.g. ops repos # where incident-derived safety rules must not be diluted by templated content. skip_if_handrolled() { local file="$1" if [ -f "$file" ] && head -n 20 "$file" | grep -q "$HANDROLLED_MARKER"; then echo " → $file (handrolled, skipped)" return 0 fi return 1 } # Walk up to find root .context/AGENT.md find_root_context() { local dir dir="$(pwd)" while [ "$dir" != "/" ]; do dir="$(dirname "$dir")" if [ -f "$dir/.context/AGENT.md" ]; then echo "$dir/.context/AGENT.md" return fi done echo "" } ROOT_CONTEXT="${ROOT_CONTEXT:-$(find_root_context)}" if [ ! -f "$PROJECT_FILE" ]; then echo "Error: $PROJECT_FILE not found. Are you in a project root?" exit 1 fi if [ -n "$ROOT_CONTEXT" ] && [ -f "$ROOT_CONTEXT" ]; then echo " Root context: $ROOT_CONTEXT" else echo " No root AGENT.md found (project context only)" fi # Emit root context + separator root_block() { if [ -n "$ROOT_CONTEXT" ] && [ -f "$ROOT_CONTEXT" ]; then cat "$ROOT_CONTEXT" echo "" echo "---" echo "" fi } # ── Claude Code ────────────────────────────────────────────── # Claude Code walks up the tree — it finds ~/dev/CLAUDE.md automatically. # Project-level CLAUDE.md only needs project-specific context. generate_claude() { skip_if_handrolled CLAUDE.md && return cat "$PROJECT_FILE" > CLAUDE.md echo " → CLAUDE.md (project-only; Claude Code inherits root)" } # ── AGENTS.md (Crush, Pi, Antigravity) ────────────────────── # These tools read AGENTS.md from cwd but don't walk up. # Concatenate root + project. generate_agents() { skip_if_handrolled AGENTS.md && return { root_block; cat "$PROJECT_FILE"; } > AGENTS.md echo " → AGENTS.md (root + project; Crush, Pi, Antigravity)" } # ── Cursor ─────────────────────────────────────────────────── generate_cursor() { skip_if_handrolled .cursorrules && return { echo "# Cursor rules — auto-generated" echo "# Do not edit. Run: task context:sync" echo "" root_block cat "$PROJECT_FILE" } > .cursorrules echo " → .cursorrules (root + project)" } # ── Aider ──────────────────────────────────────────────────── generate_aider() { skip_if_handrolled .aider.conventions.md && return { root_block; cat "$PROJECT_FILE"; } > .aider.conventions.md if [ ! -f .aider.conf.yml ]; then cat > .aider.conf.yml << 'YAML' read: .aider.conventions.md auto-commits: false YAML fi echo " → .aider.conventions.md (root + project)" } # ── Generic system prompt (Open WebUI, Mods, etc.) ────────── generate_system_prompt() { skip_if_handrolled .context/system-prompt.txt && return { echo "You are a coding assistant working on a specific project." echo "Follow all conventions from both the root agent context and project context." echo "" echo "---" echo "" root_block cat "$PROJECT_FILE" echo "" echo "---" } > .context/system-prompt.txt echo " → .context/system-prompt.txt (root + project)" } # ── MCP config ─────────────────────────────────────────────── generate_mcp() { if [ ! -f .context/mcp.json ]; then cat > .context/mcp.json << 'JSON' { "mcpServers": { "knowledge": { "url": "http://localhost:3100/mcp", "description": "Project knowledge base — vector + graph retrieval" } } } JSON echo " → .context/mcp.json (new)" else echo " → .context/mcp.json (exists, skipped)" fi } echo "Syncing project context from $PROJECT_FILE..." if [ $# -eq 0 ]; then generate_claude generate_agents generate_cursor generate_aider generate_system_prompt generate_mcp else for adapter in "$@"; do case "$adapter" in claude) generate_claude ;; agents) generate_agents ;; cursor) generate_cursor ;; aider) generate_aider ;; prompt|system|openwebui|owui|generic) generate_system_prompt ;; mcp) generate_mcp ;; *) echo "Unknown adapter: $adapter" ;; esac done fi echo "Done."