Phase 4 sweep updated .gitignore and Taskfile.yml but missed this script. Copy the canonical from ~/dev/project-template/ so the HANDROLLED escape hatch works in this project (a CLAUDE.md or adapter file containing '<!-- HANDROLLED: do not regenerate -->' near the top is now skipped on regeneration).
174 lines
5.3 KiB
Bash
Executable File
174 lines
5.3 KiB
Bash
Executable File
#!/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 `<!-- HANDROLLED: do not regenerate -->` 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."
|