feat: add agent scaffold (context-sync, skills, context tasks) (#2)
This commit was merged in pull request #2.
This commit is contained in:
201
scripts/context-sync.sh
Normal file
201
scripts/context-sync.sh
Normal file
@@ -0,0 +1,201 @@
|
||||
#!/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 [--force] [adapter...]
|
||||
# Task: task context:sync
|
||||
#
|
||||
# Override root context: ROOT_CONTEXT=~/dev/.context/AGENT.md ./scripts/context-sync.sh
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Parse --force flag and collect adapter names separately
|
||||
FORCE=false
|
||||
ADAPTERS=()
|
||||
for _arg in "$@"; do
|
||||
case "$_arg" in
|
||||
--force) FORCE=true ;;
|
||||
*) ADAPTERS+=("$_arg") ;;
|
||||
esac
|
||||
done
|
||||
|
||||
PROJECT_FILE=".context/PROJECT.md"
|
||||
|
||||
# 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
|
||||
|
||||
# Pre-flight: reject unfilled {{...}} placeholders unless --force
|
||||
if [ "$FORCE" = false ]; then
|
||||
_placeholders=$(grep -n '{{[^}]*}}' "$PROJECT_FILE" 2>/dev/null || true)
|
||||
if [ -n "$_placeholders" ]; then
|
||||
echo "Error: unfilled placeholders in $PROJECT_FILE:" >&2
|
||||
while IFS= read -r _match; do
|
||||
_lineno="${_match%%:*}"
|
||||
_content="${_match#*:}"
|
||||
_token=$(printf '%s' "$_content" | grep -o '{{[^}]*}}' | head -1)
|
||||
echo " $PROJECT_FILE:$_lineno: unfilled placeholder $_token" >&2
|
||||
done <<< "$_placeholders"
|
||||
echo "" >&2
|
||||
echo "Fill these placeholders, then re-run: task context:sync" >&2
|
||||
echo "To bypass validation: bash scripts/context-sync.sh --force" >&2
|
||||
exit 1
|
||||
fi
|
||||
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() {
|
||||
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() {
|
||||
{ root_block; cat "$PROJECT_FILE"; } > AGENTS.md
|
||||
echo " → AGENTS.md (root + project; Crush, Pi, Antigravity)"
|
||||
}
|
||||
|
||||
# ── Cursor ───────────────────────────────────────────────────
|
||||
generate_cursor() {
|
||||
{
|
||||
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() {
|
||||
{ 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() {
|
||||
{
|
||||
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() {
|
||||
# Ensure baseline file exists with project-specific knowledge server
|
||||
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
|
||||
fi
|
||||
|
||||
# Merge root mcp-servers.json if found alongside root AGENT.md
|
||||
local root_mcp=""
|
||||
if [ -n "$ROOT_CONTEXT" ] && [ -f "$ROOT_CONTEXT" ]; then
|
||||
local candidate
|
||||
candidate="$(dirname "$ROOT_CONTEXT")/mcp-servers.json"
|
||||
[ -f "$candidate" ] && root_mcp="$candidate"
|
||||
fi
|
||||
|
||||
if [ -z "$root_mcp" ]; then
|
||||
echo " → .context/mcp.json (exists, no root mcp-servers.json found)"
|
||||
return
|
||||
fi
|
||||
|
||||
# Root servers take precedence over project entries on key conflict
|
||||
local root_servers count updated
|
||||
root_servers=$(jq '.servers' "$root_mcp")
|
||||
count=$(printf '%s' "$root_servers" | jq 'keys | length')
|
||||
updated=$(jq --argjson root "$root_servers" \
|
||||
'.mcpServers = (.mcpServers + $root)' \
|
||||
.context/mcp.json)
|
||||
printf '%s\n' "$updated" > .context/mcp.json
|
||||
echo " → .context/mcp.json (merged $count root servers)"
|
||||
}
|
||||
|
||||
echo "Syncing project context from $PROJECT_FILE..."
|
||||
|
||||
if [ ${#ADAPTERS[@]} -eq 0 ]; then
|
||||
generate_claude
|
||||
generate_agents
|
||||
generate_cursor
|
||||
generate_aider
|
||||
generate_system_prompt
|
||||
generate_mcp
|
||||
else
|
||||
for adapter in "${ADAPTERS[@]}"; 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" >&2; exit 1 ;;
|
||||
esac
|
||||
done
|
||||
fi
|
||||
|
||||
echo "Done."
|
||||
Reference in New Issue
Block a user