Skip to content

Commit 0abda7e

Browse files
authored
admin: Add AI coding assistant configuration (#2122)
Adds agent configuration supporting Claude Code, Cursor, GitHub Copilot, OpenAI Codex, and Opencode. A single AGENTS.md and contents of .agents subdirectory serve as the canonical source for instructions, skills, and other things that can be used across multiple tools. A script can be invoked by users to do the local setup of links or references to allow them to use a particular tool: `.agents/setup-agent TOOL` Currently supported tools by this script include Claude Code, Codex, Cursor, GitHub Copilot, and OpenCode. This initial commit includes two skills that I wrote and have used for the last couple monthly releases: release-notes-update and prepare-patch-release. I assume that we will add more over time as we discover tasks that we want to make standard skill automation for. Assisted-by: Claude Code / claude-sonnet-4-6 The AGENTS.md, Claude skills, and general scheme are entirely from me. I used Claude to help me establish the setup script for the other tools I don't happen to use. This basic structure was first prototyped on OIIO and then copied to OSL. Signed-off-by: Larry Gritz <lg@larrygritz.com>
1 parent 1f8d394 commit 0abda7e

9 files changed

Lines changed: 946 additions & 0 deletions

File tree

.agents/.gitignore

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Copyright Contributors to the Open Shading Language project.
2+
# SPDX-License-Identifier: BSD-3-Clause
3+
# https://github.com/AcademySoftwareFoundation/OpenShadingLanguage
4+
5+
# Ignore skills that might be installed locally by a developer, to prevent
6+
# cluttering the output of `git status` and prevent people from accidentally
7+
# checking in locally installed skills.
8+
skills/*
9+
10+
# But don't ignore the ones that belong to this project and are checked in. As
11+
# we add skills that we intend to commit and make available to all project
12+
# developers, they must be individually added here if they don't start with
13+
# "osl-".
14+
!skills/osl-*/
15+
!skills/prepare-patch-release/
16+
!skills/release-notes-update/

.agents/setup-agent

Lines changed: 313 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,313 @@
1+
#!/usr/bin/env bash
2+
# .agents/setup-agent — Set up AI coding tool integration for this repository.
3+
#
4+
# Creates the tool-specific symlinks and config files needed to use the shared
5+
# resources in AGENTS.md and .agents/ with your preferred AI coding assistant.
6+
# Run "setup-agent <tool>" once after cloning; re-running is always safe.
7+
#
8+
# The canonical project instructions live in AGENTS.md (root) and skills live
9+
# in .agents/skills/<name>/SKILL.md. Everything tool-specific is either a
10+
# symlink into those locations, or a thin wrapper that references them.
11+
# Tool-specific directories (.claude/, .cursor/, etc.) are git-ignored so that
12+
# each developer runs this script for the tool they actually use.
13+
#
14+
# ─────────────────────────────────────────────────────────────────────────────
15+
# TOOL STRATEGIES (update this section when tool conventions change)
16+
# ─────────────────────────────────────────────────────────────────────────────
17+
#
18+
# CLAUDE CODE
19+
# Reads: .claude/CLAUDE.md (project) or CLAUDE.md (root) — NOT AGENTS.md natively.
20+
# If Anthropic eventually adds native AGENTS.md support, the
21+
# .claude/CLAUDE.md wrapper can be retired.
22+
# Skills: .claude/skills/ (each subdirectory is a skill)
23+
# Setup: Write .claude/CLAUDE.md containing "@AGENTS.md" (Claude's include
24+
# syntax), symlink .claude/skills -> ../.agents/skills.
25+
# Docs: https://docs.anthropic.com/en/docs/claude-code/memory
26+
# https://docs.anthropic.com/en/docs/claude-code/skills
27+
#
28+
# OPENAI CODEX
29+
# Reads: AGENTS.md natively — no wrapper needed.
30+
# Skills: .agents/skills/ natively, and also .codex/skills/ as a fallback.
31+
# Setup: Symlink .codex/skills -> ../.agents/skills so either path works.
32+
# Docs: https://developers.openai.com/codex/guides/agents-md
33+
# https://developers.openai.com/codex/skills
34+
#
35+
# CURSOR
36+
# Reads: .cursor/rules/*.mdc files — does NOT read AGENTS.md natively.
37+
# If Cursor adds native AGENTS.md support, the rules symlink can be
38+
# retired.
39+
# Skills: Cursor uses a different slash-command mechanism; .agents/skills/
40+
# are not directly usable today, but the convention may change.
41+
# Setup: Symlink .cursor/rules/project.mdc -> ../../AGENTS.md so Cursor
42+
# picks up our instructions as a project rule.
43+
# Docs: https://docs.cursor.com/context/rules-for-ai
44+
# https://docs.cursor.com/cmdk/overview
45+
#
46+
# OPENCODE
47+
# Reads: AGENTS.md natively — no wrapper needed.
48+
# Skills: Documented to read .agents/skills/ natively, but in practice also
49+
# requires .opencode/commands/<name>.md symlinks (as of 2025-05).
50+
# Remove the per-command symlinks once opencode reliably finds
51+
# .agents/skills/ on its own.
52+
# Setup: Symlink .opencode/skills -> ../.agents/skills (future-proof),
53+
# plus individual .opencode/commands/<name>.md -> skill SKILL.md
54+
# files (current workaround).
55+
# Docs: https://opencode.ai/docs/configuration
56+
# https://opencode.ai/docs/skills
57+
#
58+
# GITHUB COPILOT
59+
# Reads: AGENTS.md natively (VS Code Copilot ≥ 1.99 / GitHub Copilot Chat).
60+
# Also reads .github/copilot-instructions.md as a fallback for older
61+
# clients or non-VS Code surfaces.
62+
# Skills: .agents/skills/ natively supported.
63+
# Setup: Symlink .github/copilot-instructions.md -> ../AGENTS.md so older
64+
# clients also receive our instructions. Remove once all Copilot
65+
# surfaces reliably pick up AGENTS.md directly.
66+
# Docs: https://code.visualstudio.com/docs/copilot/customization/custom-instructions
67+
# https://docs.github.com/en/copilot/concepts/about-prompting-copilot
68+
# https://docs.github.com/en/copilot/concepts/agents/about-agent-skills
69+
# ─────────────────────────────────────────────────────────────────────────────
70+
71+
set -euo pipefail
72+
73+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
74+
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
75+
SKILLS_DIR=".agents/skills"
76+
SUPPORTED_TOOLS="claude codex cursor opencode copilot"
77+
78+
usage() {
79+
cat <<EOF
80+
Usage: .agents/setup-agent <tool|all|clear|list> [tool...]
81+
82+
Commands:
83+
<tool> [...] Set up one or more tools
84+
all Set up all supported tools
85+
clear Remove all tool-specific setup created by this script
86+
clear <tool> Remove setup for one specific tool
87+
list List supported tools
88+
89+
Supported tools: $SUPPORTED_TOOLS
90+
91+
Examples:
92+
.agents/setup-agent claude
93+
.agents/setup-agent claude cursor
94+
.agents/setup-agent all
95+
.agents/setup-agent clear
96+
.agents/setup-agent clear opencode
97+
EOF
98+
}
99+
100+
rel() { echo "${1#"$REPO_ROOT"/}"; }
101+
102+
# Create a symlink only if the path doesn't already exist.
103+
# Silent if the link already points to the correct target.
104+
safe_symlink() {
105+
local link="$1" target="$2"
106+
if [ -L "$link" ] && [ "$(readlink "$link")" = "$target" ]; then
107+
return 0
108+
elif [ -e "$link" ] || [ -L "$link" ]; then
109+
echo " SKIP (already exists): $(rel "$link")"
110+
return 0
111+
fi
112+
mkdir -p "$(dirname "$link")"
113+
ln -s "$target" "$link"
114+
echo " LINK: $(rel "$link") -> $target"
115+
}
116+
117+
# Write a file only if the path doesn't already exist.
118+
safe_write() {
119+
local file="$1"
120+
shift
121+
local content="$*"
122+
if [ -e "$file" ]; then
123+
echo " SKIP (already exists): $(rel "$file")"
124+
return 0
125+
fi
126+
mkdir -p "$(dirname "$file")"
127+
printf '%s\n' "$content" > "$file"
128+
echo " FILE: $(rel "$file")"
129+
}
130+
131+
# Remove a symlink only if it still points to the expected target.
132+
safe_remove_symlink() {
133+
local link="$1" expected_target="$2"
134+
[ ! -L "$link" ] && return 0
135+
if [ "$(readlink "$link")" = "$expected_target" ]; then
136+
rm "$link"
137+
echo " REMOVED: $(rel "$link")"
138+
else
139+
echo " SKIP (modified, not removing): $(rel "$link")"
140+
fi
141+
}
142+
143+
# Remove a file only if its content still matches what we wrote.
144+
safe_remove_file() {
145+
local file="$1"
146+
shift
147+
local expected="$*"
148+
[ ! -f "$file" ] || [ -L "$file" ] && return 0
149+
if [ "$(cat "$file")" = "$expected" ]; then
150+
rm "$file"
151+
echo " REMOVED: $(rel "$file")"
152+
else
153+
echo " SKIP (modified, not removing): $(rel "$file")"
154+
fi
155+
}
156+
157+
try_rmdir() { rmdir "$1" 2>/dev/null && echo " RMDIR: $(rel "$1")" || true; }
158+
159+
#------------------------------------------------------------------------------
160+
# Common (tool-agnostic project setup)
161+
#------------------------------------------------------------------------------
162+
setup_common() {
163+
echo "Setting up common project infrastructure..."
164+
# specs/ at the project root is a symlink into docs/dev/specs/ so that
165+
# speckit bash scripts find the directory where they expect it, while the
166+
# actual specs are committed under docs/dev/specs/ and are not cluttered
167+
# at the project root for developers who don't use agents.
168+
safe_symlink "$REPO_ROOT/specs" "docs/dev/specs"
169+
}
170+
clear_common() {
171+
echo "Removing common project infrastructure..."
172+
safe_remove_symlink "$REPO_ROOT/specs" "docs/dev/specs"
173+
}
174+
175+
#------------------------------------------------------------------------------
176+
# Claude Code (.claude/)
177+
#------------------------------------------------------------------------------
178+
setup_claude() {
179+
echo "Setting up Claude Code..."
180+
safe_write "$REPO_ROOT/.claude/CLAUDE.md" "@AGENTS.md"
181+
safe_symlink "$REPO_ROOT/.claude/skills" "../.agents/skills"
182+
safe_write "$REPO_ROOT/.claude/.gitignore" "settings.local.json"
183+
}
184+
clear_claude() {
185+
echo "Removing Claude Code setup..."
186+
safe_remove_file "$REPO_ROOT/.claude/CLAUDE.md" "@AGENTS.md"
187+
safe_remove_symlink "$REPO_ROOT/.claude/skills" "../.agents/skills"
188+
safe_remove_file "$REPO_ROOT/.claude/.gitignore" "settings.local.json"
189+
try_rmdir "$REPO_ROOT/.claude"
190+
}
191+
192+
#------------------------------------------------------------------------------
193+
# OpenAI Codex (.codex/)
194+
#------------------------------------------------------------------------------
195+
setup_codex() {
196+
echo "Setting up OpenAI Codex..."
197+
safe_symlink "$REPO_ROOT/.codex/skills" "../.agents/skills"
198+
safe_write "$REPO_ROOT/.codex/.gitignore" "*.log"
199+
}
200+
clear_codex() {
201+
echo "Removing OpenAI Codex setup..."
202+
safe_remove_symlink "$REPO_ROOT/.codex/skills" "../.agents/skills"
203+
safe_remove_file "$REPO_ROOT/.codex/.gitignore" "*.log"
204+
try_rmdir "$REPO_ROOT/.codex"
205+
}
206+
207+
#------------------------------------------------------------------------------
208+
# Cursor (.cursor/)
209+
#------------------------------------------------------------------------------
210+
setup_cursor() {
211+
echo "Setting up Cursor..."
212+
safe_symlink "$REPO_ROOT/.cursor/rules/project.mdc" "../../AGENTS.md"
213+
safe_write "$REPO_ROOT/.cursor/.gitignore" "$(printf 'composer/\nchat/')"
214+
}
215+
clear_cursor() {
216+
echo "Removing Cursor setup..."
217+
safe_remove_symlink "$REPO_ROOT/.cursor/rules/project.mdc" "../../AGENTS.md"
218+
safe_remove_file "$REPO_ROOT/.cursor/.gitignore" "$(printf 'composer/\nchat/')"
219+
try_rmdir "$REPO_ROOT/.cursor/rules"
220+
try_rmdir "$REPO_ROOT/.cursor"
221+
}
222+
223+
#------------------------------------------------------------------------------
224+
# Opencode (.opencode/)
225+
#------------------------------------------------------------------------------
226+
OPENCODE_GITIGNORE="$(printf 'node_modules\npackage.json\npackage-lock.json\nbun.lock')"
227+
228+
setup_opencode() {
229+
echo "Setting up Opencode..."
230+
safe_symlink "$REPO_ROOT/.opencode/skills" "../.agents/skills"
231+
safe_write "$REPO_ROOT/.opencode/.gitignore" "$OPENCODE_GITIGNORE"
232+
# Individual command links (needed in practice despite docs implying otherwise)
233+
for skill_dir in "$REPO_ROOT/$SKILLS_DIR"/*/; do
234+
[ -d "$skill_dir" ] || continue
235+
local skill_name
236+
skill_name="$(basename "$skill_dir")"
237+
safe_symlink "$REPO_ROOT/.opencode/commands/${skill_name}.md" \
238+
"../../$SKILLS_DIR/${skill_name}/SKILL.md"
239+
done
240+
}
241+
clear_opencode() {
242+
echo "Removing Opencode setup..."
243+
safe_remove_symlink "$REPO_ROOT/.opencode/skills" "../.agents/skills"
244+
safe_remove_file "$REPO_ROOT/.opencode/.gitignore" "$OPENCODE_GITIGNORE"
245+
for skill_dir in "$REPO_ROOT/$SKILLS_DIR"/*/; do
246+
[ -d "$skill_dir" ] || continue
247+
local skill_name
248+
skill_name="$(basename "$skill_dir")"
249+
safe_remove_symlink "$REPO_ROOT/.opencode/commands/${skill_name}.md" \
250+
"../../$SKILLS_DIR/${skill_name}/SKILL.md"
251+
done
252+
try_rmdir "$REPO_ROOT/.opencode/commands"
253+
try_rmdir "$REPO_ROOT/.opencode"
254+
}
255+
256+
#------------------------------------------------------------------------------
257+
# GitHub Copilot (.github/copilot-instructions.md)
258+
# Copilot natively reads AGENTS.md, but also checks copilot-instructions.md.
259+
#------------------------------------------------------------------------------
260+
setup_copilot() {
261+
echo "Setting up GitHub Copilot..."
262+
safe_symlink "$REPO_ROOT/.github/copilot-instructions.md" "../AGENTS.md"
263+
}
264+
clear_copilot() {
265+
echo "Removing GitHub Copilot setup..."
266+
safe_remove_symlink "$REPO_ROOT/.github/copilot-instructions.md" "../AGENTS.md"
267+
}
268+
269+
#------------------------------------------------------------------------------
270+
# Dispatch
271+
#------------------------------------------------------------------------------
272+
run_tool() {
273+
local action="$1" tool="$2"
274+
case "$tool" in
275+
claude) "${action}_claude" ;;
276+
codex) "${action}_codex" ;;
277+
cursor) "${action}_cursor" ;;
278+
opencode) "${action}_opencode" ;;
279+
copilot) "${action}_copilot" ;;
280+
*)
281+
echo "Unknown tool: $tool (supported: $SUPPORTED_TOOLS)" >&2
282+
exit 1
283+
;;
284+
esac
285+
}
286+
287+
[ $# -eq 0 ] && { usage; exit 0; }
288+
289+
case "$1" in
290+
all)
291+
setup_common
292+
for t in $SUPPORTED_TOOLS; do run_tool setup "$t"; done
293+
;;
294+
clear)
295+
shift
296+
if [ $# -eq 0 ]; then
297+
for t in $SUPPORTED_TOOLS; do run_tool clear "$t"; done
298+
clear_common
299+
else
300+
for t in "$@"; do run_tool clear "$t"; done
301+
fi
302+
;;
303+
list)
304+
echo "Supported tools: $SUPPORTED_TOOLS"
305+
;;
306+
-h|--help|help)
307+
usage
308+
;;
309+
*)
310+
setup_common
311+
for t in "$@"; do run_tool setup "$t"; done
312+
;;
313+
esac

0 commit comments

Comments
 (0)