admin: Add AI coding assistant configuration (#5206)

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.

---------

Signed-off-by: Larry Gritz <lg@larrygritz.com>
This commit is contained in:
Larry Gritz
2026-05-28 13:35:31 -07:00
committed by GitHub
parent 6b7fe61418
commit b2d32a1ab0
9 changed files with 916 additions and 0 deletions
+16
View File
@@ -0,0 +1,16 @@
# Copyright Contributors to the OpenImageIO project.
# SPDX-License-Identifier: Apache-2.0
# https://github.com/AcademySoftwareFoundation/OpenImageIO
# Ignore skills that might be installed locally by a developer, to prevent
# cluttering the output of `git status` and prevent people from accidentally
# checking in locally installed skills.
skills/*
# But don't ignore the ones that belong to this project and are checked in. As
# we add skills that we intend to commit and make available to all project
# developers, they must be individually added here if they don't start with
# "oiio-".
!skills/oiio-*/
!skills/prepare-patch-release/
!skills/release-notes-update/
+294
View File
@@ -0,0 +1,294 @@
#!/usr/bin/env bash
# .agents/setup-agent — Set up AI coding tool integration for this repository.
#
# Creates the tool-specific symlinks and config files needed to use the shared
# resources in AGENTS.md and .agents/ with your preferred AI coding assistant.
# Run "setup-agent <tool>" once after cloning; re-running is always safe.
#
# The canonical project instructions live in AGENTS.md (root) and skills live
# in .agents/skills/<name>/SKILL.md. Everything tool-specific is either a
# symlink into those locations, or a thin wrapper that references them.
# Tool-specific directories (.claude/, .cursor/, etc.) are git-ignored so that
# each developer runs this script for the tool they actually use.
#
# ─────────────────────────────────────────────────────────────────────────────
# TOOL STRATEGIES (update this section when tool conventions change)
# ─────────────────────────────────────────────────────────────────────────────
#
# CLAUDE CODE
# Reads: .claude/CLAUDE.md (project) or CLAUDE.md (root) — NOT AGENTS.md natively.
# If Anthropic eventually adds native AGENTS.md support, the
# .claude/CLAUDE.md wrapper can be retired.
# Skills: .claude/skills/ (each subdirectory is a skill)
# Setup: Write .claude/CLAUDE.md containing "@AGENTS.md" (Claude's include
# syntax), symlink .claude/skills -> ../.agents/skills.
# Docs: https://docs.anthropic.com/en/docs/claude-code/memory
# https://docs.anthropic.com/en/docs/claude-code/skills
#
# OPENAI CODEX
# Reads: AGENTS.md natively — no wrapper needed.
# Skills: .agents/skills/ natively, and also .codex/skills/ as a fallback.
# Setup: Symlink .codex/skills -> ../.agents/skills so either path works.
# Docs: https://developers.openai.com/codex/guides/agents-md
# https://developers.openai.com/codex/skills
#
# CURSOR
# Reads: .cursor/rules/*.mdc files — does NOT read AGENTS.md natively.
# If Cursor adds native AGENTS.md support, the rules symlink can be
# retired.
# Skills: Cursor uses a different slash-command mechanism; .agents/skills/
# are not directly usable today, but the convention may change.
# Setup: Symlink .cursor/rules/project.mdc -> ../../AGENTS.md so Cursor
# picks up our instructions as a project rule.
# Docs: https://docs.cursor.com/context/rules-for-ai
# https://docs.cursor.com/cmdk/overview
#
# OPENCODE
# Reads: AGENTS.md natively — no wrapper needed.
# Skills: Documented to read .agents/skills/ natively, but in practice also
# requires .opencode/commands/<name>.md symlinks (as of 2025-05).
# Remove the per-command symlinks once opencode reliably finds
# .agents/skills/ on its own.
# Setup: Symlink .opencode/skills -> ../.agents/skills (future-proof),
# plus individual .opencode/commands/<name>.md -> skill SKILL.md
# files (current workaround).
# Docs: https://opencode.ai/docs/configuration
# https://opencode.ai/docs/skills
#
# GITHUB COPILOT
# Reads: AGENTS.md natively (VS Code Copilot ≥ 1.99 / GitHub Copilot Chat).
# Also reads .github/copilot-instructions.md as a fallback for older
# clients or non-VS Code surfaces.
# Skills: .agents/skills/ natively supported.
# Setup: Symlink .github/copilot-instructions.md -> ../AGENTS.md so older
# clients also receive our instructions. Remove once all Copilot
# surfaces reliably pick up AGENTS.md directly.
# Docs: https://code.visualstudio.com/docs/copilot/customization/custom-instructions
# https://docs.github.com/en/copilot/concepts/about-prompting-copilot
# https://docs.github.com/en/copilot/concepts/agents/about-agent-skills
# ─────────────────────────────────────────────────────────────────────────────
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
SKILLS_DIR=".agents/skills"
SUPPORTED_TOOLS="claude codex cursor opencode copilot"
usage() {
cat <<EOF
Usage: .agents/setup-agent <tool|all|clear|list> [tool...]
Commands:
<tool> [...] Set up one or more tools
all Set up all supported tools
clear Remove all tool-specific setup created by this script
clear <tool> Remove setup for one specific tool
list List supported tools
Supported tools: $SUPPORTED_TOOLS
Examples:
.agents/setup-agent claude
.agents/setup-agent claude cursor
.agents/setup-agent all
.agents/setup-agent clear
.agents/setup-agent clear opencode
EOF
}
rel() { echo "${1#"$REPO_ROOT"/}"; }
# Create a symlink only if the path doesn't already exist.
# Silent if the link already points to the correct target.
safe_symlink() {
local link="$1" target="$2"
if [ -L "$link" ] && [ "$(readlink "$link")" = "$target" ]; then
return 0
elif [ -e "$link" ] || [ -L "$link" ]; then
echo " SKIP (already exists): $(rel "$link")"
return 0
fi
mkdir -p "$(dirname "$link")"
ln -s "$target" "$link"
echo " LINK: $(rel "$link") -> $target"
}
# Write a file only if the path doesn't already exist.
safe_write() {
local file="$1"
shift
local content="$*"
if [ -e "$file" ]; then
echo " SKIP (already exists): $(rel "$file")"
return 0
fi
mkdir -p "$(dirname "$file")"
printf '%s\n' "$content" > "$file"
echo " FILE: $(rel "$file")"
}
# Remove a symlink only if it still points to the expected target.
safe_remove_symlink() {
local link="$1" expected_target="$2"
[ ! -L "$link" ] && return 0
if [ "$(readlink "$link")" = "$expected_target" ]; then
rm "$link"
echo " REMOVED: $(rel "$link")"
else
echo " SKIP (modified, not removing): $(rel "$link")"
fi
}
# Remove a file only if its content still matches what we wrote.
safe_remove_file() {
local file="$1"
shift
local expected="$*"
[ ! -f "$file" ] || [ -L "$file" ] && return 0
if [ "$(cat "$file")" = "$expected" ]; then
rm "$file"
echo " REMOVED: $(rel "$file")"
else
echo " SKIP (modified, not removing): $(rel "$file")"
fi
}
try_rmdir() { rmdir "$1" 2>/dev/null && echo " RMDIR: $(rel "$1")" || true; }
#------------------------------------------------------------------------------
# Claude Code (.claude/)
#------------------------------------------------------------------------------
setup_claude() {
echo "Setting up Claude Code..."
safe_write "$REPO_ROOT/.claude/CLAUDE.md" "@AGENTS.md"
safe_symlink "$REPO_ROOT/.claude/skills" "../.agents/skills"
safe_write "$REPO_ROOT/.claude/.gitignore" "settings.local.json"
}
clear_claude() {
echo "Removing Claude Code setup..."
safe_remove_file "$REPO_ROOT/.claude/CLAUDE.md" "@AGENTS.md"
safe_remove_symlink "$REPO_ROOT/.claude/skills" "../.agents/skills"
safe_remove_file "$REPO_ROOT/.claude/.gitignore" "settings.local.json"
try_rmdir "$REPO_ROOT/.claude"
}
#------------------------------------------------------------------------------
# OpenAI Codex (.codex/)
#------------------------------------------------------------------------------
setup_codex() {
echo "Setting up OpenAI Codex..."
safe_symlink "$REPO_ROOT/.codex/skills" "../.agents/skills"
safe_write "$REPO_ROOT/.codex/.gitignore" "*.log"
}
clear_codex() {
echo "Removing OpenAI Codex setup..."
safe_remove_symlink "$REPO_ROOT/.codex/skills" "../.agents/skills"
safe_remove_file "$REPO_ROOT/.codex/.gitignore" "*.log"
try_rmdir "$REPO_ROOT/.codex"
}
#------------------------------------------------------------------------------
# Cursor (.cursor/)
#------------------------------------------------------------------------------
setup_cursor() {
echo "Setting up Cursor..."
safe_symlink "$REPO_ROOT/.cursor/rules/project.mdc" "../../AGENTS.md"
safe_write "$REPO_ROOT/.cursor/.gitignore" "$(printf 'composer/\nchat/')"
}
clear_cursor() {
echo "Removing Cursor setup..."
safe_remove_symlink "$REPO_ROOT/.cursor/rules/project.mdc" "../../AGENTS.md"
safe_remove_file "$REPO_ROOT/.cursor/.gitignore" "$(printf 'composer/\nchat/')"
try_rmdir "$REPO_ROOT/.cursor/rules"
try_rmdir "$REPO_ROOT/.cursor"
}
#------------------------------------------------------------------------------
# Opencode (.opencode/)
#------------------------------------------------------------------------------
OPENCODE_GITIGNORE="$(printf 'node_modules\npackage.json\npackage-lock.json\nbun.lock')"
setup_opencode() {
echo "Setting up Opencode..."
safe_symlink "$REPO_ROOT/.opencode/skills" "../.agents/skills"
safe_write "$REPO_ROOT/.opencode/.gitignore" "$OPENCODE_GITIGNORE"
# Individual command links (needed in practice despite docs implying otherwise)
for skill_dir in "$REPO_ROOT/$SKILLS_DIR"/*/; do
[ -d "$skill_dir" ] || continue
local skill_name
skill_name="$(basename "$skill_dir")"
safe_symlink "$REPO_ROOT/.opencode/commands/${skill_name}.md" \
"../../$SKILLS_DIR/${skill_name}/SKILL.md"
done
}
clear_opencode() {
echo "Removing Opencode setup..."
safe_remove_symlink "$REPO_ROOT/.opencode/skills" "../.agents/skills"
safe_remove_file "$REPO_ROOT/.opencode/.gitignore" "$OPENCODE_GITIGNORE"
for skill_dir in "$REPO_ROOT/$SKILLS_DIR"/*/; do
[ -d "$skill_dir" ] || continue
local skill_name
skill_name="$(basename "$skill_dir")"
safe_remove_symlink "$REPO_ROOT/.opencode/commands/${skill_name}.md" \
"../../$SKILLS_DIR/${skill_name}/SKILL.md"
done
try_rmdir "$REPO_ROOT/.opencode/commands"
try_rmdir "$REPO_ROOT/.opencode"
}
#------------------------------------------------------------------------------
# GitHub Copilot (.github/copilot-instructions.md)
# Copilot natively reads AGENTS.md, but also checks copilot-instructions.md.
#------------------------------------------------------------------------------
setup_copilot() {
echo "Setting up GitHub Copilot..."
safe_symlink "$REPO_ROOT/.github/copilot-instructions.md" "../AGENTS.md"
}
clear_copilot() {
echo "Removing GitHub Copilot setup..."
safe_remove_symlink "$REPO_ROOT/.github/copilot-instructions.md" "../AGENTS.md"
}
#------------------------------------------------------------------------------
# Dispatch
#------------------------------------------------------------------------------
run_tool() {
local action="$1" tool="$2"
case "$tool" in
claude) "${action}_claude" ;;
codex) "${action}_codex" ;;
cursor) "${action}_cursor" ;;
opencode) "${action}_opencode" ;;
copilot) "${action}_copilot" ;;
*)
echo "Unknown tool: $tool (supported: $SUPPORTED_TOOLS)" >&2
exit 1
;;
esac
}
[ $# -eq 0 ] && { usage; exit 0; }
case "$1" in
all)
for t in $SUPPORTED_TOOLS; do run_tool setup "$t"; done
;;
clear)
shift
if [ $# -eq 0 ]; then
for t in $SUPPORTED_TOOLS; do run_tool clear "$t"; done
else
for t in "$@"; do run_tool clear "$t"; done
fi
;;
list)
echo "Supported tools: $SUPPORTED_TOOLS"
;;
-h|--help|help)
usage
;;
*)
for t in "$@"; do run_tool setup "$t"; done
;;
esac
@@ -0,0 +1,104 @@
---
name: prepare-patch-release
description: Do all the tasks needed for a patch release of the project.
argument-hint: [new-version]
---
Do all the tasks needed for a patch release of the project. These patch
releases generally happen on the first day of every month.
Arguments: `$ARGUMENTS`
- First argument (optional): the version of the upcoming release we are
preparing. If omitted, find the most recent tag on this branch, and the
new version will update the third portion of the version. It might be
either a version number (like `3.1.4.0`) or a tag (like `v3.1.4.0`).
Hint: The numeric "version" is a four-part numeric deignation with the pattern
`MAJOR.MINOR.PATCH.TWEAK`. The "version tag" is usually the numeric version
with a "v" prepended, for example, `v3.1.4.0`.
## Steps and Checklist
- [ ] Determine the version of the new release.
- [ ] Update the main @CMakeLists.txt.
- [ ] Use the "release-notes-update" skill to update @CHANGES.md.
- [ ] Review the release notes to ensure that the changes do not deprecate any
API calls or break API or ABI backward-compatibility, or remove support
for any dependency or toolchain versions. If you think these rules are
being violated, ask for confirmation.
- [ ] Review @README.md for changes.
- [ ] Review @INSTALL.md for changes.
- [ ] Review @CREDITS.md for changes.
- [ ] Review @SECURITY.md for changes.
## Steps to determine the versions of the last and new releases.
1. **Determine the previous tag** if not provided by looking at the
most recent git tag in the current branch, and deducing the 4-part
version number from that.
2. The assumed new version for a patch release has the same major and minor
numbers, the patch number is incremented by one since the last tag, and the
tweak number is "0".
3. If no optional version was present in the arguments, use the assumed new
version.
4. If a version was supplied in the arguments, double check that it differs
from the last tag as explained above. If it does not, ask for verification
that the version requested is correct before proceeding.
## Steps to update CMakeLists.txt
1. In the main @CMakeLists.txt, alter the version to that of the new release,
if it isn't already the same.
2. For a patch release, ensure that `${PROJECT_NAME}_SUPPORTED_RELEASE` should
be set to ON. In main (not yet a supported release) it shold be OFF. If you
find these to not be as expected, ask for confirmation of whether to fix.
3. For a patch release, `PROJECT_VERSEION_RELEASE_TYPE` should be set to empty
(""). In main, it will typically be "dev", "beta", or other designations.
If you find these to not be as expected, ask for confirmation of whether to
fix.
## Reviewing README.md
If this release added support for any new image file formats, be sure that
README.md mentions them in its list of image formats supported.
## Reviewing INSTALL.md
If any of the new commits (as described in the release notes for this version)
appear to add dependencies, or add support for new versions of dependencies,
be sure that INSTALL.md is updated to include that dependency (if not already
listed among the required and optional dependencies), and reflects the latest
version that we claim to support.
Double check @externalpackages.cmake and ensure that any minimum required
versions of dependencies that cmake will enforce match the oldest versions
of those dependencies as documentd in INSTALL.md.
## Reviewing CREDITS.md
The @CREDITS.md file lists all known contributors to the project, sorted alpha
by first name. In cases where an author's actual name is unknown, we use the
GitHub userid.
Ensure that any authors referenced in the new release notes for the version we
are releasing are inserted in the credit list, if they are not already
present. You don't need to check older versions, we presume those have already
been included.
## Reviewing SECURITY.md
The @SECURITY.md file lists which versions are currently supported at what
levels, and lists all previously-fixed SVE's or security advisories. Be sure
to check this file in older branches (the last two releases, say), and if you
are preparing a patch release, also check in main and more recent (higher
numbered) releases for more recently modified SECURITY.md, and be sure that
this branch gets amended with any information that seems to have been updated
more recently in those other branches. Check whichever is newer of the local
and remote copy of that branch, since the local one may have had release notes
or its version in CMakeLists.txt updated, but not yet pushed to GitHub.
## Reference
- Full release procedures: `docs/dev/RELEASING.md`
- Steps for updating release notes: `release-notes-update/SKILL.md`
@@ -0,0 +1,232 @@
---
name: release-notes-update
description: Generate or update release notes for a patch, minor, or major release, or just to update main. Run git-cliff, organize and edit the output per project conventions, and insert into CHANGES.md.
argument-hint: [patch|minor|major|main] [prev-tag]
---
Generate release notes for an OpenImageIO release.
Arguments: `$ARGUMENTS`
- First argument (optional): release type — `patch` (default), `minor`, `major`, or `main`.
- Second argument (optional): previous release tag (e.g. `v3.1.2.0`). If omitted, find the most recent tag automatically. If the release type is `main`, instead of a tag, look for the last commit at which the CHANGES.md file was updated.
## Steps
1. **Determine the previous tag** if not provided:
```
git describe --tags --abbrev=0
```
or look at recent tags: `git tag --sort=-version:refname | head -10`.
However, if the "release type" is `main`, instead of a tag, just find
the commit at which CHANGES.md was last updated.
2. **Run git-cliff** to get raw commit data:
```
git cliff -c src/doc/cliff.toml <prev-tag>..HEAD > /tmp/cliff-out.md
```
Read `/tmp/cliff-out.md` to see the raw output.
3. **Read CHANGES.md** to see the current top of the file and understand where to insert.
4. **Format the release notes** according to the release type:
### For patch releases:
Follow the skeleton in `docs/dev/Changes-skeleton-patch.md`:
```
Release X.Y.Z.W (Month DD, YYYY) -- compared to X.Y.Z.W-1
---------------------------------------------------------
- *category*: Description. [#NNNN](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/NNNN) (by author)
```
Rules:
- **Remove** the section headings git-cliff generates; patch notes are a flat
list.
- **Add conventional commit prefixes** to any "uncategorized" entries (those
lacking a `feat:`, `fix:`, etc. prefix).
- **Reorder** entries logically: feature enhancements first, then bug fixes,
then build/CI fixes, then internal changes, then test improvements, then
docs/admin.
- **Omit** entries that are purely internal and too minor to matter to users.
Ask for confirmation about entries you propose to omit.
- Prefer to use author's actual name if known. If the name cannot be found,
the GitHub userid can be used instead.
- Omit the author if it is the project leader, Larry Gritz, unless he is not
the dominant author (at least 75% of commits) in this release.
- Keep entries to one line each. Be terse but informative.
- Use the format `*subsystem*:` for the category prefix (e.g., image file
format like `*exr*:`, utility like `*oiiotool*:`, class or API category like
`*IBA*:`, or topic category such as `*build*:`, `*ci*:`, `*docs*:`).
- We aim to make patch releases on the first day of each month. If we are
within a few days of a month end, list the date as the beginning of the
upcoming month. Ask for confirmation that this is the planned release date.
### For minor or major releases:
Follow the skeleton in `docs/dev/Changes-skeleton-major.md`. Sections:
```
Release X.Y.0.0 (Month, YYYY) -- compared to X.Y-1
--------------------------------------------------
### New minimum dependencies and compatibility changes:
### ⛰️ Major new features and public API changes:
* *New image file format support:*
* *oiiotool new features and major improvements*:
* *Command line utilities*:
* *API changes*
* Other notable new feature:
### 🚀 Performance improvements:
### 🐛 Fixes and feature enhancements:
### 🔧 Internals and developer goodies
### 🏗 Build/test/CI and platform ports:
* CMake build system and scripts:
* Dependency and platform support:
* Testing and Continuous integration (CI) systems:
### 📚 Notable documentation changes:
### 🏢 Project Administration
### 🤝 Contributors
```
Note that the section outline may already be present, in which case you
only need to fit items into the existing category outline.
Rules:
- Group commits into sections; within each section, cluster related items together.
- When needed, expand terse one-liners into enough prose that users understand what changed and why it matters.
- For `feat:` commits, make sure the feature is explained sufficiently — don't just copy the commit subject.
- For `api:` or `api!:` commits, clearly call out what changed in the public API.
- Include PR links and author attribution for every entry.
- The notes should "tell the story" of the release, not just be a dump of commit subjects.
- We aim to make major/minor releases approximately in October 1 of each year. If the anticipted release date is already in the file, don't change it. If it is not present, ask for confirmation of the planned release date.
### For updating release notes in main:
Rules:
- Generally, follow the rules for "major/minor releases", except as noted in other items below.
- Don't apply a new skeleton of category listings unless they are not present for the upcoming release.
5. **Insert the formatted notes** into `CHANGES.md` in the appropriate place (as detailed below). Leave the existing content intact.
- When updating `main` or preparing a `major` or `minor` release, insert the updates in the top section for the upcoming major/minor release. Insert a new set of category sections only if it's not already present.
- When doing a `patch` release, insert the changes immediately above the last patch release of that branch, so that CHANGES.md lists releases in descending numerical (version) order.
- When porting a set of release notes from a release branch into main, or from an older (obsolete) release branch to the current release branch, insert it into the right place to maintain overall descending order.
6. **Double check that the notes are adequately descriptive.** (See more
detailed description of this step below.)
7. **Forward-port release notes from release branches if needed**
8. **Show a summary** of what was inserted and ask the user to review before finalizing.
## Forward-porting release notes
Release notes may have been generated independently in main, and release
branches. When updating one branch, we ensure that any changes from older
branches have been incorporated.
- When preparing `patch` release notes, check the CHANGES.md file in the
"dev-X.(Y-1)" branch for the previous minor release family to identify any
X.(Y-1).Z patch release notes that are not reflected in the current release
notes that we are updating.
- When updating `main` or doing a `major` or `minor` release, check the
CHANGES.md file in both the "dev-X.(Y-1)" and "dev-X.(Y-2)" branches.
Check whichever is newer of the local and remote copy of that branch, since
the local one may have had release notes or its version in CMakeLists.txt
updated, but not yet pushed to GitHub.
- If any patch releases are present in the older dev branches checked, insert
the release notes for those patch releases into the right positions in the
current release notes that we are updating.
- If a change is forward-ported in this manner and the same PR is an update in
the current set of changes we are updating as our main task, document that
in the current set of notes using the following convention: in the line of
the notes, the explanation of the version where it appears should reflect
the first version of all branches where it appeared, for example, `(3.2.0.0,
3.1.3.0, 3.0.8.0)` to indicate that the patch was added to each of those
versions. The versions should be listed in descending order.
## Useful abbreviations for category labels
| Abbrev | Meaning |
|--------|---------|
| IB | ImageBuf |
| IBA | ImageBufAlgo |
| IC | ImageCache |
| TS | TextureSystem |
| oiiotool | the oiiotool command |
| build | CMake/build system |
| deps | Changes to accommodate dependency or toolchain changes |
| ci | CI/GitHub Actions |
| docs | documentation |
| int | internal/refactor |
| test | testsuite or unit tests |
| HEADER.h | Developer utilities in a public header file |
## Combining PRs into single entries
To be more concise and easier to read, within a release's notes, related
PRs/commits can be combined into a single bullet-point line, which would look
like
```
- *category*: Combined Description. [#NNNN1](URL) (by author1) [#NNNN2](URL2) (by author2) [#NNNN2](URL2) (by author2)
```
if fully combined, or if explained one by one,
```
- *category*: Description 1 [#NNNN1](URL) (by author1), amendment 2, [#NNNN2](URL2) (by author2), amendment 3, [#NNNN3](URL3) (by author3)
```
Choose the fully combined or explained one by one based on which is more clear
to the reader.
If the authors are all the same, only have the author designation at the end.
Here are the cases where it's ok to combine commits in this way:
- If it is clear that multiple PRs are part of the same feature or fix,
consisting of an initial commit, and subsequent smaller changes or
continuations of the same topic.
- An initial commit, and a subsequent commit that is obviously a fix to a bug
in the initial commit.
- "CI" changes that all only add new cases to the test matrix.
- "CI" changes that all only fix spontaneous breakages in the GitHub runners.
- "Build" changes that are all just minor updates to versions of dependencies
that we support or test against.
Some more examples of combined commit messages:
```
- feat: Add GPS metadata functionality for TIFF [PR1](PR URL 1) [PR2](PR URL 2) [PR3](PR URL 3) (by author).
- ci: New CI variants for MSVS 2026 [PR1](PR URL 1) (by author1), VFX Platform 2027 [PR2](PR URL 2) (by author2).
- ci: Various fixes for unexpected changes to GitHub Actions runners [PR1](PR URL 1), [PR2](PR URL 2) (by author)
- build: Added support for gcc 15 [PR1](PR URL 1) (by author1), OpenEXR 3.5.1 [PR2](PR URL 2) (by author2), libtiff 4.8 [PR3](PR URL 3) (by author3).
```
Always ask for confirmation before combining commits in this manner. Confirm
separately for each proposed combination of a group of multiple original
commits into a single commit. Give the user the opportunity to revise the
combined description or request other changes for the grouping.
## Double check that the notes are adequately descriptive
For newly added items for this release, read the short descriptions provided
by git-cliff, double check them against the full commit messages to be sure
the one-line summary is adequate. If the summary is misleading, too brief and
leaving out the fact that an important thing was changed, or not adequately
capturing the scope of changes, feel free to propose an alternate wording that
will make it more clear to readers what changed as a result of the PR. Ask for
confirmation on these and explain why you felt the one-line description wasn't
enough.
One example of a case where this is needed is if the one line description
merely mentions a new image processing capability by name, but the full commit
message makes it clear that a new ImageBufAlgo API call was added, and also a
new oiiotool command line argument was added, and that couldn't all be
described in the one-line brief message of the commit.
## Reference
Full release procedures: `docs/dev/RELEASING.md`
Patch skeleton: `docs/dev/Changes-skeleton-patch.md`
Major skeleton: `docs/dev/Changes-skeleton-major.md`
Example good patch notes: https://github.com/AcademySoftwareFoundation/OpenImageIO/releases/tag/v3.0.0.0
Example good major/minor notes: https://github.com/AcademySoftwareFoundation/OpenImageIO/releases/tag/v3.1.6.1
+7
View File
@@ -29,4 +29,11 @@ gastest.o
/*.tx
/*.log
# AI coding tool directories — generated locally by .agents/setup-agent
.claude/
.codex/
.cursor/
.opencode/
.github/copilot-instructions.md
+182
View File
@@ -0,0 +1,182 @@
# OpenImageIO Agent Guide
This file provides guidance to coding assistants when working with code in
this repository.
## Project Overview
OpenImageIO (OIIO) is a C++17 library and toolset for reading, writing, and
manipulating image files across many formats, designed for VFX/animation/film
production. Part of the Academy Software Foundation (ASWF). Canonical repo:
http://github.com/AcademySoftwareFoundation/OpenImageIO
**Core libraries:**
- `libOpenImageIO` — Main library: ImageInput/ImageOutput (file I/O), ImageBuf/ImageBufAlgo (image processing), ImageCache/TextureSystem (texture mapping)
- `libOpenImageIO_Util` — Utility library (string_view, ustring, span, threading, filesystem, etc.)
**Command line tools**
- `oiiotool`: general image processing CLI
- `iinfo`: print info & metadata for an image file
- `iconvert`: simple format conversion
- `maketx`: convert images to tiled and MIP-mapped textures.
- `iv`: image viewer
## Repo map
- `src/include/OpenImageIO/` : Public API headers
- `src/libOpenImageIO/` : Core library (ImageBuf, ImageBufAlgo,
ImageInput/ImageOutput base classes)
- `src/libtexture/` : ImageCache, TextureSystem
- `src/libutil/` : Utility class implementations
- `src/<FORMAT>.imageio/` : Per-format ImageInput/ImageOutput plugins.
- `src/python/` : Python bindings using pybind11
- `src/<TOOL>` : CLI tools (oiiotool, iinfo, iconvert, maketx, iv)
- `testsuite/` : End-to-end/regression tests + reference outputs
- `src/cmake/`, `CMakeLists.txt` : Build system
- `.github/workflows/ci.yml` : GitHub Actions CI
- `src/build-scripts` Helper scripts used for build & CI
- `src/doc/` : User manual source (+ Doxygen comments in the public headers)
- `docs/dev/` : Developer documentation
- `docs/dev/Architecture.md` : Major subsystems overview
## Build and verification
Common build commands via the Makefile convenience wrapper:
```bash
make # configure, build, install (Release)
make debug # debug build
make clean # wipe build dir (needed when switching branches/modes)
make clang-format # format all source files
make test # full testsuite
make test TEST=<pattern> # subset matching regex
```
Or directly with cmake:
```bash
cmake -B build -S .
cmake --build build --target install
ctest --test-dir build -R <pattern> --output-on-failure
```
By default, builds into `./build` and installs into `./dist`.
## Testsuite notes
- Test output lands in `build/testsuite/<testname>/`; references in
`testsuite/<testname>/ref/`
- Read `testsuite/TESTSUITE-README.md` before updating references or
diagnosing failures
- For platform-specific diffs, add a variant ref (e.g. `out-win.txt`) rather
than overwriting
- Be conservative loosening image diff thresholds — use the minimum needed
- Check uploaded CI artifacts before changing references when local
reproduction is unclear
## Code formatting and file conventions
- `clang-format` enforced (`.clang-format`); CI rejects non-conforming code —
run `make clang-format` before committing
- Lines ~80 cols; ASCII only in code and comments; `#pragma once` for headers
- New files: standard copyright + SPDX notice
- `CamelCase` classes, `snake_case` locals, `ALL_CAPS` macros, `m_foo` private
members
- 3 blank lines between free functions/classes; 1 between class methods; max 1
blank line inside a function body
- `//` for regular comments; `///` Doxygen for public API
- If a file has a strong local style, imitate that.
## C++ guidelines
- Error handling: prefer `bool` returns, explicit status propagation, and
`errorfmt()`-style reporting — not exceptions — consistent with the
surrounding subsystem
- Preserve API, ABI, and behavior compatibility unless the task explicitly
requires a break
- For hot paths: no hidden allocation in inner loops or parallel regions;
precompute outside hot paths
- For kernel-style image work, use existing `ImageBufAlgo` and
`parallel_image` patterns before inventing a new execution model
## OIIO utility types (prefer over raw C++ equivalents)
Prefer C++17 `std` and Imath types except as noted below. Avoid introducing
new third-party dependencies without a strong reason.
- `OIIO::string_view` — non-owning string/`char*` (like `std::string_view`)
- `OIIO::span` / `OIIO::cspan` — non-owning contiguous data (like
`std::span`); use instead of pointer+length pairs
- `OIIO::image_span` — describes pixel buffer memory layout (pointer, sizes,
strides)
- `OIIO::Filesystem::*` — file/directory utilities (`filesystem.h`)
- `strutil.h` : string processing utilities
- `Strutil::format()` / `Strutil::print()` (= `OIIO::print()`) — string
formatting/output; **never** `printf` or `<<` streams
- `fmath.h` — fast/safe math, avoids NaN/Inf
- `simd.h` — SIMD helpers
- `unittest.h` — unit test macros
## Safe programming in C++
- Try to avoid passing raw pointers as function arguments.
- Use `std::unique_ptr` and `std::shared_ptr` rather than raw ownership when
new ownership must be expressed, but do not churn existing code just to
"modernize" it.
- Prefer `OIIO::string_view` when passing non-mutable strings or C-style
`char*` strings.
- Prefer `OIIO::span` rather than passing raw pointers + a separate length, or
passing a raw pointer with an implied (but not explicitly passed) length.
`OIIO::cspan` is a synonmym when the underlying data is const/non-mutable.
`OIIO::span<std::byte>` or `OIIO::cspan<std::byte>` can be used to represent
contiguous untyped data. These are our equivalent of C++ `std::span`.
- Prefer `OIIO::image_span` to describe the memory layout of multi-dimensional
pixel buffers (such as are used to describe where to read or write image
pixels), rather than passing raw pointers, sizes, and strides.
## Performance and determinism
- For hot image-processing code, avoid hidden allocation in inner loops or
parallel regions.
- Precompute setup work outside hot paths when practical.
- Keep data flow and ownership explicit.
- Preserve deterministic results where practical.
- Avoid unnecessary global state or synchronization.
- For kernel-style work, look for existing `ImageBufAlgo` and
`parallel_image` patterns before inventing a new execution model.
- Prefer incremental performance fixes backed by measurement or concrete
reasoning over speculative rewrites.
## Change impact checklist
- Bug fixes and behavior changes → add/update tests
- Public API changes → update Python bindings, docs, `CHANGES.md`
- `ImageBufAlgo` changes → consider oiiotool exposure, docs, Python, tests
- Format plugin changes → regression coverage for metadata, edge cases,
read/write
- CLI changes → update tests and help text
- Build/dependency changes → check CMake logic, CI, optional-feature coverage
## Commits and PRs
- Keep PRs narrow and easy to review.
- Prefix format: `type(subsystem): message` — subsystem is optional but helpful.
- Valid types: `fix:`, `feat:`, `perf:`, `api:`, `int:`, `build:`, `test:`,
`ci:`, `docs:`, `refactor:`, `style:`, `admin:`, `revert:`.
- Add a subsystem tag when it helps, e.g. `fix(exr):` or `perf(IBA):`.
- Write commit messages and PR descriptions that explain why the change is
needed, what behavior changes, and any non-obvious implementation choices.
## AI policy
Refer to `docs/dev/AI_Policy.md`.
See `docs/dev/AI_Policy.md`. Key rule: if AI assistance contributed materially
to a patch, the commit must include `Assisted-by: <TOOL> / <MODEL>`. The human
author is responsible for understanding, testing, and defending all changes.
## References
- `CONTRIBUTING.md` : general contribution guidelines and recap of coding
conventions.
+10
View File
@@ -93,6 +93,16 @@ High-level summary:
Please do read the whole [Policy on AI Coding Assistants](docs/dev/AI_Policy.md)
for all the details.
The repository supports several AI coding assistants (Claude Code, Cursor,
GitHub Copilot, OpenAI Codex, Opencode). After cloning, run the setup script
for whichever tool(s) you use:
```
.agents/setup-agent claude # or: cursor, codex, opencode, copilot, all
```
See [docs/dev/Agentic_Coding.md](docs/dev/Agentic_Coding.md) for details.
Contributor License Agreement (CLA) and Intellectual Property
-------------------------------------------------------------
+14
View File
@@ -0,0 +1,14 @@
# Copyright Contributors to the OpenImageIO project.
# SPDX-License-Identifier: Apache-2.0
# https://github.com/AcademySoftwareFoundation/OpenImageIO
# Ignore temporary specs that may be ephemeral or local to an individual
# developer, to prevent cluttering the output of `git status` and prevent
# people from accidentally checking in specs that are only meant for them.
specs/*
# But don't ignore the ones that belong to this project and are checked in. As
# we add specs that we intend to commit and make available to all project
# developers, they must be individually added here.
# !specs/example
+57
View File
@@ -0,0 +1,57 @@
<!-- SPDX-License-Identifier: CC-BY-4.0 -->
<!-- Copyright Contributors to the OpenImageIO Project. -->
# Agentic coding / coding assistants
Please start by reading [our policy on using "AI" coding
assistsants](AI_Policy.md).
## Multi-tool setup
We aim to allow people to use whatever agentic or coding assistant tools they
want. The repository ships with a setup script that configures tool-specific
files locally for whichever assistant(s) you use.
### Generic: `AGENTS.md` and `.agents/`
As much as possible, we use identical instructions and files across all coding
assistants.
`AGENTS.md` is the main file providing overall repo-specific instructions and
context to agents. Many coding agents already read this file directly; for
those that don't, the setup script creates the necessary links or wrappers.
`.agents/skills/` is where shared skill files live. Many tools find skills
here automatically; for those that need them elsewhere, the setup script
creates the appropriate links.
### Running the setup script
After cloning, run `.agents/setup-agent <tool>` for the tool(s) you use:
```
.agents/setup-agent claude # Claude Code
.agents/setup-agent cursor # Cursor
.agents/setup-agent codex # OpenAI Codex
.agents/setup-agent opencode # Opencode
.agents/setup-agent copilot # GitHub Copilot
.agents/setup-agent all # all of the above
```
The script is idempotent — running it multiple times is safe. To undo:
```
.agents/setup-agent clear # remove all tool setup
.agents/setup-agent clear claude # remove setup for one tool
```
Tool-specific directories (`.claude/`, `.cursor/`, `.codex/`, `.opencode/`,
`.github/copilot-instructions.md`) are created locally only and are listed in
`.gitignore`. Each developer runs the script for the tool they use; nothing
tool-specific is committed to the repository.
The script header contains per-tool documentation (what each tool reads
natively, what requires setup, and links to relevant docs) — consult it when
adding support for a new tool or updating the strategy for an existing one.