diff --git a/.agents/.gitignore b/.agents/.gitignore new file mode 100644 index 0000000000..71dac709df --- /dev/null +++ b/.agents/.gitignore @@ -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/ diff --git a/.agents/setup-agent b/.agents/setup-agent new file mode 100755 index 0000000000..538891eb5b --- /dev/null +++ b/.agents/setup-agent @@ -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 " once after cloning; re-running is always safe. +# +# The canonical project instructions live in AGENTS.md (root) and skills live +# in .agents/skills//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/.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/.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 < [tool...] + +Commands: + [...] Set up one or more tools + all Set up all supported tools + clear Remove all tool-specific setup created by this script + clear 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 diff --git a/.agents/skills/prepare-patch-release/SKILL.md b/.agents/skills/prepare-patch-release/SKILL.md new file mode 100644 index 0000000000..73e37b40e1 --- /dev/null +++ b/.agents/skills/prepare-patch-release/SKILL.md @@ -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` diff --git a/.agents/skills/release-notes-update/SKILL.md b/.agents/skills/release-notes-update/SKILL.md new file mode 100644 index 0000000000..ab8fcf2b30 --- /dev/null +++ b/.agents/skills/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 ..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 diff --git a/.gitignore b/.gitignore index a949abcaee..2058a138ce 100644 --- a/.gitignore +++ b/.gitignore @@ -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 + diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000000..159302aa56 --- /dev/null +++ b/AGENTS.md @@ -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/.imageio/` : Per-format ImageInput/ImageOutput plugins. +- `src/python/` : Python bindings using pybind11 +- `src/` : 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= # subset matching regex +``` + +Or directly with cmake: +```bash +cmake -B build -S . +cmake --build build --target install +ctest --test-dir build -R --output-on-failure +``` + +By default, builds into `./build` and installs into `./dist`. + +## Testsuite notes + +- Test output lands in `build/testsuite//`; references in + `testsuite//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` or `OIIO::cspan` 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: / `. The human +author is responsible for understanding, testing, and defending all changes. + +## References + +- `CONTRIBUTING.md` : general contribution guidelines and recap of coding + conventions. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c1287b91aa..ee2154212d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -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 ------------------------------------------------------------- diff --git a/docs/dev/.gitignore b/docs/dev/.gitignore new file mode 100644 index 0000000000..6460e0aec9 --- /dev/null +++ b/docs/dev/.gitignore @@ -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 diff --git a/docs/dev/Agentic_Coding.md b/docs/dev/Agentic_Coding.md new file mode 100644 index 0000000000..cc693b8632 --- /dev/null +++ b/docs/dev/Agentic_Coding.md @@ -0,0 +1,57 @@ + + + +# 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 ` 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. +