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
15 changes: 15 additions & 0 deletions .agents/hooks/scripts/check-agent-doc-clean.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#!/usr/bin/env bash
set -euo pipefail

if [[ "$#" -eq 0 ]]; then
set -- AGENTS.md frontend/AGENTS.md backend/AGENTS.md docs/agents
fi

pattern='<skills_system|<available_skills|SKILLS_TABLE_START'

if grep -R -n -E "$pattern" "$@" 2>/dev/null; then
echo "BLOCK: generated agent catalog markers found" >&2
exit 1
fi

echo "CONTINUE: no generated agent catalog markers found"
30 changes: 30 additions & 0 deletions .agents/hooks/scripts/enforce-agent-doc-branch.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#!/usr/bin/env bash
set -euo pipefail

branch="$(git branch --show-current 2>/dev/null || true)"

if [[ "$branch" == "docs/concise-agents-md" ]]; then
exit 0
fi

if [[ "$branch" == agents/* ]]; then
exit 0
fi

protected_regex='(^|/)(AGENTS\.md)$|^docs/agents/|^\.agents/hooks/'

changed="$(git diff --name-only --cached 2>/dev/null; git diff --name-only 2>/dev/null)"

if printf '%s\n' "$changed" | grep -E "$protected_regex" >/dev/null; then
cat >&2 <<EOF
Agent instruction files changed outside an agents/<short-topic> branch.

Current branch: ${branch:-unknown}

Protected files require a separate self-learning branch and PR:
- AGENTS.md files
- docs/agents/**
- .agents/hooks/**
EOF
exit 1
fi
57 changes: 57 additions & 0 deletions .agents/hooks/scripts/read-agents.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#!/usr/bin/env bash
# read-agents.sh — Session-start hook that guarantees the agent sees root
# AGENTS.md followed by machine-local agent preferences.
#
# Default output is JSON with hookSpecificOutput.additionalContext for
# cross-platform compatibility (Claude Code, VS Code, Cursor, Codex, Pi).
# Use --plain for raw text output (direct execution, OpenCode plugin).
set -euo pipefail

script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
agents_dir="$(cd "$script_dir/../.." && pwd)"
project_root="$(cd "$agents_dir/.." && pwd)"

sections=()

# 1. Root AGENTS.md — sibling of .agents/, guarantee the agent reads it
root_agents="$project_root/AGENTS.md"
if [[ -f "$root_agents" ]]; then
sections+=("$(cat "$root_agents")")
fi

# 2. Local agent preferences — inside .agents/local/
local_agents="$agents_dir/local/AGENTS.local.md"
if [[ -f "$local_agents" ]]; then
sections+=("--- BEGIN .agents/local/AGENTS.local.md ---
$(cat "$local_agents")
--- END .agents/local/AGENTS.local.md ---")
fi

if [[ ${#sections[@]} -eq 0 ]]; then
exit 0
fi

output=""
for i in "${!sections[@]}"; do
if [[ $i -gt 0 ]]; then
output+=$'\n\n'
fi
output+="${sections[$i]}"
done

# Plain text mode
if [[ "${1:-}" == "--plain" ]]; then
printf '%s\n' "$output"
exit 0
fi

# JSON output for hook platforms
if command -v jq &>/dev/null; then
escaped=$(printf '%s' "$output" | jq -Rs .)
elif command -v python3 &>/dev/null; then
escaped=$(python3 -c 'import json,sys; print(json.dumps(sys.stdin.read()))' <<< "$output")
else
escaped="\"$(printf '%s' "$output" | sed 's/\\/\\\\/g; s/"/\\"/g; s/$/\\n/' | tr -d '\n')\""
fi

printf '{"hookSpecificOutput":{"hookEventName":"SessionStart","additionalContext":%s}}\n' "$escaped"
15 changes: 15 additions & 0 deletions .claude/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"hooks": {
"SessionStart": [
{
"matcher": "startup",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.agents/hooks/scripts/read-agents.sh"
}
]
}
]
}
}
15 changes: 15 additions & 0 deletions .codex/hooks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"hooks": {
"SessionStart": [
{
"matcher": "startup|resume",
"hooks": [
{
"type": "command",
"command": ".agents/hooks/scripts/read-agents.sh"
}
]
}
]
}
}
10 changes: 10 additions & 0 deletions .cursor/hooks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"version": 1,
"hooks": {
"sessionStart": [
{
"command": ".agents/hooks/scripts/read-agents.sh"
}
]
}
}
11 changes: 9 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ pids

# npm cache
.npm
.pi/npm/

# eslint cache
.eslintcache
Expand All @@ -84,6 +85,9 @@ plans/
# .agent-workspace/ files are tracked so samples/artifacts survive across sessions
.agent/

# Machine-local agent preferences
.agents/local/




Expand All @@ -93,7 +97,7 @@ plans/
.secrets.baseline

# Cursor
.cursor/
.cursor/*

# Environment files (catch-all)
.env
Expand Down Expand Up @@ -150,13 +154,16 @@ supabase/.env
!backend/pyrightconfig.json
!backend/tests/**/assets/*.json
!frontend/.fallowrc.json
!.claude/settings.json
!.codex/hooks.json

# Upload directories
uploads/
temp-uploads/

# Cursor
.cursor/
.cursor/*
!.cursor/hooks.json

# Votecatcher data files (contain sensitive batch/API data)
Votecatcher/
Expand Down
24 changes: 24 additions & 0 deletions .opencode/plugins/local-agents.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
export const LocalAgentsPlugin = async ({ $, client }) => {
return {
event: async ({ event }) => {
if (event.type !== "session.created") return

try {
const result = await $`.agents/hooks/scripts/read-agents.sh --plain`
const output = (await result.text()).trim()
if (!output) return

await client.app.log({
body: {
service: "read-agents",
level: "info",
message: "AGENTS.md and local preferences loaded. See hook output for details.",
extra: { content: output },
},
})
} catch {
// Script not found — silent exit
}
},
}
}
4 changes: 4 additions & 0 deletions .pi/hook/hooks.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
hooks:
- event: session.created
actions:
- bash: .agents/hooks/scripts/read-agents.sh
Loading
Loading