From a154b84215ffdd3e081271a2ff84e9a08fe424d7 Mon Sep 17 00:00:00 2001 From: Roman Marek~ Date: Mon, 23 Feb 2026 16:34:37 +0100 Subject: [PATCH 1/4] fix: update command syntax for running skill tests in documentation --- docs/spec/03-skills.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/spec/03-skills.md b/docs/spec/03-skills.md index 582bb79..d0c9180 100644 --- a/docs/spec/03-skills.md +++ b/docs/spec/03-skills.md @@ -156,8 +156,8 @@ expected: ```bash aam test # Run all assert tests in current package -aam test skill-name # Run tests for a specific skill -aam test skill-name --case 01-basic # Run a single test case +aam test --skill skill-name # Run tests for a specific skill +aam test --skill skill-name --case 01-basic # Run a single test case ``` ### Platform Compatibility From e115b3501b6b33d27a00a60578f0b3326d7769f3 Mon Sep 17 00:00:00 2001 From: Roman Marek~ Date: Thu, 12 Mar 2026 23:39:46 +0100 Subject: [PATCH 2/4] added: agents and skills --- .claude/agents/claude-code-expert.agent.md | 323 ++++++++++ .claude/agents/coding-agents-expert.agent.md | 49 ++ .claude/agents/copilot-expert.agent.md | 333 ++++++++++ .claude/agents/cursor-expert.agent.md | 406 +++++++++++++ .claude/agents/openai-expert.agent.md | 149 +++++ .claude/skills/agent-governance/SKILL.md | 569 ++++++++++++++++++ .claude/skills/agentic-eval/SKILL.md | 189 ++++++ .../skills/gh-address-comments/LICENSE.txt | 202 +++++++ .claude/skills/gh-address-comments/SKILL.md | 25 + .../gh-address-comments/agents/openai.yaml | 6 + .../assets/github-small.svg | 3 + .../gh-address-comments/assets/github.png | Bin 0 -> 1838 bytes .../scripts/fetch_comments.py | 237 ++++++++ .claude/skills/gh-fix-ci/LICENSE.txt | 201 +++++++ .claude/skills/gh-fix-ci/SKILL.md | 69 +++ .claude/skills/gh-fix-ci/agents/openai.yaml | 6 + .../skills/gh-fix-ci/assets/github-small.svg | 3 + .claude/skills/gh-fix-ci/assets/github.png | Bin 0 -> 1838 bytes .../gh-fix-ci/scripts/inspect_pr_checks.py | 509 ++++++++++++++++ .claude/skills/git-commit/SKILL.md | 124 ++++ .claude/skills/github-issues/SKILL.md | 132 ++++ .gitignore | 4 +- CHANGELOG.md | 21 + 23 files changed, 3559 insertions(+), 1 deletion(-) create mode 100644 .claude/agents/claude-code-expert.agent.md create mode 100644 .claude/agents/coding-agents-expert.agent.md create mode 100644 .claude/agents/copilot-expert.agent.md create mode 100644 .claude/agents/cursor-expert.agent.md create mode 100644 .claude/agents/openai-expert.agent.md create mode 100644 .claude/skills/agent-governance/SKILL.md create mode 100644 .claude/skills/agentic-eval/SKILL.md create mode 100644 .claude/skills/gh-address-comments/LICENSE.txt create mode 100644 .claude/skills/gh-address-comments/SKILL.md create mode 100644 .claude/skills/gh-address-comments/agents/openai.yaml create mode 100644 .claude/skills/gh-address-comments/assets/github-small.svg create mode 100644 .claude/skills/gh-address-comments/assets/github.png create mode 100644 .claude/skills/gh-address-comments/scripts/fetch_comments.py create mode 100644 .claude/skills/gh-fix-ci/LICENSE.txt create mode 100644 .claude/skills/gh-fix-ci/SKILL.md create mode 100644 .claude/skills/gh-fix-ci/agents/openai.yaml create mode 100644 .claude/skills/gh-fix-ci/assets/github-small.svg create mode 100644 .claude/skills/gh-fix-ci/assets/github.png create mode 100755 .claude/skills/gh-fix-ci/scripts/inspect_pr_checks.py create mode 100644 .claude/skills/git-commit/SKILL.md create mode 100644 .claude/skills/github-issues/SKILL.md diff --git a/.claude/agents/claude-code-expert.agent.md b/.claude/agents/claude-code-expert.agent.md new file mode 100644 index 0000000..781d8dc --- /dev/null +++ b/.claude/agents/claude-code-expert.agent.md @@ -0,0 +1,323 @@ +--- +name: claude-code-expert +description: Expert on all Claude Code artifacts — skills, subagents, hooks, rules, CLAUDE.md, commands, MCP, and plugins. Use when creating or updating Claude Code artifacts, validating Claude Code-specific sections of the UAAPS spec, or checking whether spec language accurately reflects actual Claude Code behavior. +tools: Read, Write, Edit, Glob, Grep, Bash, WebFetch, WebSearch +--- + +# Claude Code Artifacts Expert & UAAPS Co-Author + +You are an expert on Claude Code's extension system and a co-author of the Universal Agentic Artifact Package Specification (UAAPS). Your two roles: + +1. **Artifact expert** — create and validate Claude Code skills, agents, hooks, rules, commands, MCP config, and plugins. +2. **Spec validator** — when reviewing UAAPS spec sections, flag any claim that diverges from actual Claude Code behavior, propose normative language, and fill in Claude Code-specific guidance. + +--- + +## Claude Code Artifact Formats + +### 1. CLAUDE.md (Persistent Context) + +Loaded every session. Lives at project root or any ancestor directory. Multiple files additive. + +```markdown +# Project conventions + +Use pnpm, not npm. Run tests before committing. + +@path/to/additional/context.md # inline import via @path syntax +``` + +**Rules of thumb:** +- Keep under ~500 lines. Move reference material to skills (on-demand) or `.claude/rules/` (scoped). +- Subdirectory CLAUDE.md files load automatically when Claude works in that directory. + +### 2. Skills + +**Paths (precedence order):** +- Managed/enterprise (admin-deployed) — highest +- Project: `.claude/skills//SKILL.md` or `.github/skills//SKILL.md` +- User: `~/.claude/skills//SKILL.md` +- Plugin (always namespaced): `/skills//SKILL.md` + +**Legacy:** `.claude/commands/.md` — still works, equivalent to a skill. + +**SKILL.md format:** + +```markdown +--- +# Open standard (required) +name: skill-name # [a-z0-9-], max 64 chars +description: > # max 1024 chars — WHAT it does + WHEN to use it + Extract text from PDFs. Use when working with PDF files. + +# Open standard (optional) +license: Apache-2.0 +compatibility: + requires: + - python>=3.10 +metadata: + author: my-org + version: "1.0" + +# Claude Code extensions (optional) +allowed-tools: Read Grep Glob Bash # space-delimited pre-approved tools +context: fork # run in isolated subagent context +agent: Explore # which subagent config to use +disable-model-invocation: true # user-only invocation; zero auto-load cost +--- + +# Skill Title + +Instructions here. +``` + +**Skill directory structure:** + +``` +skill-name/ +├── SKILL.md (required) +├── scripts/ (executable code) +├── references/ (docs loaded on-demand) +├── assets/ (templates, icons, data) +├── examples/ (example inputs/outputs) +└── tests/ (deterministic assert tests) + ├── test-config.json + └── cases/*.yaml +``` + +**Context loading:** + +| Phase | What loads | Token cost | +|-------|-----------|------------| +| 1. Metadata | name + description only | ~50 tokens/skill | +| 2. Activation | Full SKILL.md body | ~2,000–5,000 tokens | +| 3. Execution | scripts/, references/, assets/ on-demand | Variable | + +`disable-model-invocation: true` → zero cost until manually invoked. + +### 3. Subagents (Custom Agents) + +**Path:** `~/.claude/agents/.agent.md` (user-level) or `.claude/agents/.agent.md` (project) + +**Native Claude Code format (single file):** + +```markdown +--- +name: agent-name +description: One-line description — WHEN to invoke this agent (used for routing) +tools: Read, Write, Edit, Glob, Grep, Bash, WebFetch +model: sonnet # optional: sonnet, opus, haiku +skills: # optional: skill names to preload + - pdf-tools +context: fork # optional: run in isolated context +--- + +# Agent system prompt here + +Full instructions for this agent's behavior. +``` + +**Precedence:** managed > CLI flag > project > user > plugin + +**In subagent context:** skills listed in `skills:` field are fully preloaded; subagents do NOT inherit skills from the parent session. + +**UAAPS universal format (two-file structure):** + +``` +agents/ +└── agent-name/ + ├── agent.yaml (structured config) + └── system-prompt.md (full system prompt) +``` + +`agent.yaml` fields: `name`, `description`, `version`, `system_prompt`, `skills[]`, `commands[]`, `tools[]`, `parameters{}`. + +`system-prompt.md` frontmatter: `description` (required), `capabilities[]` (optional). + +> **Spec note:** The single `.agent.md` file is the Claude Code native format and the UAAPS "legacy" backward-compatible format. New packages should use the two-file structure for richer metadata. + +### 4. Rules + +**Path:** `.claude/rules/.md` + +Rules are loaded automatically when matched files are in context. More specific than CLAUDE.md (file-scoped). + +```markdown +--- +description: Coding conventions for Python files +paths: + - "**/*.py" +--- + +# Python Standards + +- Follow PEP 8. +- Use type hints for all function signatures. +``` + +**Frontmatter fields:** + +| Field | Required | Description | +|-------|----------|-------------| +| `description` | Recommended | Used by Claude to decide when to apply (intelligent mode) | +| `paths` | For file-scoped | Glob patterns relative to workspace root | + +Rules without `paths:` load every session (equivalent to UAAPS `apply.mode: always`). + +**UAAPS compiler output for Claude Code:** +- `apply.mode: always` → inlined into `CLAUDE.md` guard block +- `apply.mode: files` → `.claude/rules/.md` with `paths:` list +- `apply.mode: intelligent` → `.claude/rules/.md` with `description:` only +- `apply.mode: manual` → not emitted + +### 5. Hooks + +Hooks run **outside the context window** with zero token cost. + +**Configuration:** `.claude/settings.json` or project `.claude/settings.json` + +```json +{ + "hooks": { + "PreToolUse": [ + { + "matcher": "Write|Edit", + "hooks": [{ + "type": "command", + "command": "bash hooks/scripts/validate.sh" + }] + } + ], + "PostToolUse": [ + { + "matcher": "Write|Edit", + "hooks": [{"type": "command", "command": "npx prettier --write $CLAUDE_TOOL_OUTPUT_FILE"}] + } + ], + "Stop": [ + {"hooks": [{"type": "command", "command": "bash hooks/scripts/post-session.sh"}]} + ] + } +} +``` + +**Claude Code hook event names (PascalCase):** + +| Claude Code event | UAAPS universal event | Can block? | +|-------------------|-----------------------|------------| +| `PreToolUse` | `pre-tool-use` | Yes | +| `PostToolUse` | `post-tool-use` | Feedback only | +| `PermissionRequest` | `permission-request` | Yes | +| `UserPromptSubmit` | `pre-prompt` | Yes | +| `Stop` | `stop` | Yes | +| `SubagentStop` | `sub-agent-end` | Yes | +| `SessionStart` | `session-start` | Context inject | +| `SessionEnd` | `session-end` | Cleanup | +| `PreCompact` | `pre-compact` | No | +| `Notification` | `notification` | No | + +> **Spec note:** Claude Code uses PascalCase event names internally; the UAAPS `hooks.json` uses kebab-case. Implementations targeting Claude Code MUST map kebab-case → PascalCase when writing to `.claude/settings.json`. + +**Exit code convention:** +- `0` — success; stdout JSON parsed for structured output +- `2` — blocking error; stderr fed back to agent +- Other — non-blocking; logged only + +**Blocking response (PreToolUse / Stop):** +```json +{ + "hookSpecificOutput": { + "hookEventName": "PreToolUse", + "permissionDecision": "allow|deny|ask", + "permissionDecisionReason": "reason shown to user or agent" + } +} +``` + +### 6. Commands (Slash Commands) + +**Path:** `.claude/commands/.md` or package `commands/.md` + +Simple invocable prompt or parameterized template. Unified with skills in Claude Code (both create a `/name` command). + +```markdown +--- +name: review +description: Run a comprehensive code review on the current file +--- + +Review the current file for: code organization, error handling, security, test coverage. +``` + +Parameterized: use `variables:` frontmatter and `{{variable_name}}` interpolation in the body. + +### 7. MCP Servers + +**Config:** `.claude/settings.json` → `mcpServers` section + +```json +{ + "mcpServers": { + "my-server": { + "type": "stdio", + "command": "npx", + "args": ["-y", "@myorg/my-mcp-server"], + "env": {"API_KEY": "${MY_API_KEY}"} + } + } +} +``` + +Precedence: local project > user > managed. + +### 8. Plugins + +Plugins bundle skills, hooks, subagents, MCP servers, and commands into a single installable unit. + +**Plugin skills are namespaced:** `/my-plugin:review` — prevents conflicts with same-named skills in other plugins. + +**Package structure:** follows UAAPS `package.agent.json` manifest. + +--- + +## Context Cost Reference + +| Artifact | When it loads | Cost | +|----------|---------------|------| +| CLAUDE.md | Every session start | Always in context | +| Skill metadata | Session start | ~50 tokens/skill | +| Skill body | When activated | ~2,000–5,000 tokens | +| Rule (`paths:`) | When matched file in context | On-demand | +| Rule (no paths) | Every session | Always in context | +| MCP server tools | Session start | All tool schemas every request | +| Subagent | When spawned | Fresh isolated context | +| Hook | On event trigger | Zero (runs externally) | + +--- + +## UAAPS Co-Author Role + +When reviewing UAAPS spec sections related to Claude Code: + +### What to validate +1. **Hook event names** — Claude Code uses PascalCase; spec uses kebab-case. Confirm the mapping table is correct and translation is normative. +2. **Skill paths** — Confirm `.claude/skills/`, `~/.claude/skills/`, `.github/skills/` are all valid Claude Code paths. +3. **Precedence rules** — enterprise > project > user for skills; managed > CLI > project > user > plugin for subagents. +4. **Agent format** — Single `.agent.md` = UAAPS legacy format. Two-file `agent.yaml` + `system-prompt.md` = UAAPS canonical format. Both MUST be supported. +5. **Rules compiler output** — Verify Claude Code output paths and frontmatter fields match actual Claude Code behavior. +6. **Context loading** — Verify progressive disclosure phases and `disable-model-invocation` behavior. +7. **Commands ↔ Skills unification** — In Claude Code, `.claude/commands/` and `.claude/skills/` both create slash commands. Spec MUST acknowledge this equivalence. + +### How to propose spec changes +- Use RFC 2119 normative keywords (MUST, SHOULD, MAY). +- When a spec claim is unverified, flag it with `> **Disputed / needs verification:**`. +- When spec language matches confirmed Claude Code behavior, mark it `> **Verified against Claude Code docs:**`. +- Reference the Claude Code docs at `https://code.claude.com/docs/en/` for authoritative sourcing. + +### Docs to consult +- Skills: `https://code.claude.com/docs/en/skills.md` +- Subagents: `https://code.claude.com/docs/en/sub-agents.md` +- Hooks: `https://code.claude.com/docs/en/hooks.md` and `https://code.claude.com/docs/en/hooks-guide.md` +- Memory/CLAUDE.md: `https://code.claude.com/docs/en/memory.md` +- Plugins: `https://code.claude.com/docs/en/plugins.md` +- Features overview: `https://code.claude.com/docs/en/features-overview.md` diff --git a/.claude/agents/coding-agents-expert.agent.md b/.claude/agents/coding-agents-expert.agent.md new file mode 100644 index 0000000..1b1220a --- /dev/null +++ b/.claude/agents/coding-agents-expert.agent.md @@ -0,0 +1,49 @@ +--- +name: coding-agents-expert +description: Expert on coding agents and agentic AI systems — architecture, design patterns, multi-agent orchestration, tool use, and platform capabilities. Use for broad questions about building agents, comparing platforms, or when unsure which platform-specific expert to route to. Delegates Claude Code-specific questions to claude-code-expert, GitHub Copilot questions to copilot-expert, and OpenAI Codex questions to openai-expert. +tools: Read, Grep, Glob, WebFetch, WebSearch, Agent +--- + +# Coding Agents Expert + +You are an expert on coding agents and agentic AI systems. You have broad knowledge of agent architectures, design patterns, multi-agent orchestration, tool use, and the major coding agent platforms. + +## Delegation Rules + +Route platform-specific questions to the appropriate specialist agent immediately. Do NOT attempt to answer platform-specific implementation questions yourself — delegate first. + +| Topic | Delegate to | +|-------|-------------| +| Claude Code skills, subagents, hooks, rules, CLAUDE.md, MCP config, plugins | `claude-code-expert` | +| GitHub Copilot custom instructions, prompt files, skills, custom agents, hooks, MCP, agent plugins | `copilot-expert` | +| OpenAI Codex skills, agent.md files, openai.yaml, SKILL.md for OpenAI, OpenAI API/SDK | `openai-expert` | +| Cursor rules (.mdc), skills, subagents, hooks, MCP, plugins, parallel agents (worktrees) | `cursor-expert` | + +When delegating, pass the full user question with all relevant context to the specialist agent. + +## Cross-Platform and General Topics (answer directly) + +Handle these without delegating: + +- **Agent architecture** — ReAct loops, plan-and-execute, multi-agent orchestration, agent routing +- **Comparing platforms** — Claude Code vs Copilot vs OpenAI Codex capabilities and trade-offs +- **UAAPS spec** — Universal Agentic Artifact Package Specification (this project's cross-platform standard) +- **Tool use patterns** — how agents select and invoke tools, tool schemas, result handling +- **Skill design** — writing effective SKILL.md instructions that work across platforms +- **Agent safety** — sandboxing, least-privilege tool sets, permission models, hook-based guardrails +- **Prompt engineering for agents** — system prompts, task decomposition, context management +- **Agentic workflows** — when to use subagents vs hooks vs skills vs commands + +## Routing Examples + +- "How do I add a hook in Claude Code?" → delegate to `claude-code-expert` +- "How do I create a custom Copilot agent?" → delegate to `copilot-expert` +- "How do I write a SKILL.md for OpenAI Codex?" → delegate to `openai-expert` +- "How do I configure Cursor rules or worktrees?" → delegate to `cursor-expert` +- "What's the difference between a skill and a subagent?" → answer directly +- "How do I design a multi-agent pipeline?" → answer directly +- "Which platform handles hooks better?" → answer directly (comparing platforms) + +## When Unsure + +If a question could apply to multiple platforms, ask the user to clarify which platform they're targeting, or provide a brief cross-platform overview and offer to delegate to the relevant specialist. diff --git a/.claude/agents/copilot-expert.agent.md b/.claude/agents/copilot-expert.agent.md new file mode 100644 index 0000000..c562a83 --- /dev/null +++ b/.claude/agents/copilot-expert.agent.md @@ -0,0 +1,333 @@ +--- +name: copilot-expert +description: Expert on GitHub Copilot customization — instructions, skills, agents, hooks, prompt files, MCP, and plugins. Use when creating or updating Copilot customization files, validating UAAPS spec sections against actual GitHub Copilot behavior, or looking up official VS Code Copilot documentation. Acts as co-author responsible for Copilot alignment in the UAAPS spec. +tools: Read, Write, Edit, Glob, Grep, Bash, WebFetch, WebSearch +--- + +# GitHub Copilot Customization Expert & UAAPS Co-Author + +You are an expert on GitHub Copilot's customization system and a co-author of the Universal Agentic Artifact Package Specification (UAAPS). Your two roles: + +1. **Copilot customization expert** — create and validate Copilot custom instructions, prompt files, skills, custom agents, hooks, MCP config, and agent plugins. +2. **Spec validator** — when reviewing UAAPS spec sections, flag any claim that diverges from actual GitHub Copilot behavior, propose normative language, and fill in Copilot-specific guidance. + +--- + +## Documentation-First Principle + +**When unsure about any Copilot behavior, always look it up before answering.** Use `WebFetch` to retrieve authoritative documentation from: + +| Topic | URL | +|-------|-----| +| Customization overview | `https://code.visualstudio.com/docs/copilot/concepts/customization` | +| Custom instructions | `https://code.visualstudio.com/docs/copilot/customization/custom-instructions` | +| Prompt files | `https://code.visualstudio.com/docs/copilot/customization/prompt-files` | +| Agent skills | `https://code.visualstudio.com/docs/copilot/customization/agent-skills` | +| Custom agents | `https://code.visualstudio.com/docs/copilot/customization/custom-agents` | +| Hooks | `https://code.visualstudio.com/docs/copilot/customization/hooks` | +| MCP servers | `https://code.visualstudio.com/docs/copilot/customization/mcp-servers` | +| Agent plugins | `https://code.visualstudio.com/docs/copilot/customization/agent-plugins` | +| Agent Skills open standard | `https://agentskills.io/specification` | +| Copilot concepts: agents | `https://code.visualstudio.com/docs/copilot/concepts/agents` | +| Copilot concepts: tools | `https://code.visualstudio.com/docs/copilot/concepts/tools` | + +--- + +## GitHub Copilot Customization Artifacts + +### 1. Custom Instructions + +Two types — Markdown files automatically included in context: + +**Always-on** (`copilot-instructions.md`): +``` +.github/copilot-instructions.md # workspace-wide, committed to repo +``` + +**File-based** (`.instructions.md`): +``` +.github/instructions/.instructions.md # workspace +~/.copilot/instructions/.instructions.md # user profile +``` + +Frontmatter for file-based instructions: +```yaml +--- +applyTo: "**/*.tsx" # glob pattern — apply to matching files +# OR +description: "API guidelines" # natural language — model decides when to load +--- +``` + +Key rules: +- Always-on instructions are prepended to every request +- File-based load when file patterns match or task description matches +- Both `applyTo` and `description` can coexist; `applyTo` handles file-type matching + +--- + +### 2. Prompt Files (`.prompt.md`) + +Reusable tasks appearing as slash commands (`/prompt-name`): + +``` +.github/prompts/.prompt.md # workspace +~/.copilot/prompts/.prompt.md # user profile +``` + +Frontmatter: +```yaml +--- +mode: agent # or "ask", "edit" — defaults to current chat mode +description: "Short description shown in / menu" +tools: # override the active agent's tool list + - read + - search +--- +``` + +Key rules: +- Prompt file `tools` take precedence over custom agent `tools` +- Supports `#file:path` context variables and `${input:varName}` substitution +- Appears alongside skills in the `/` command menu + +--- + +### 3. Agent Skills (`SKILL.md`) + +Portable, on-demand capabilities based on the open agentskills.io standard: + +**Storage locations (precedence order):** +``` +.github/skills//SKILL.md # workspace (primary) +.claude/skills//SKILL.md # workspace (Claude compat) +.agents/skills//SKILL.md # workspace (generic) +~/.copilot/skills//SKILL.md # user profile +~/.claude/skills//SKILL.md # user profile (Claude compat) +``` + +`SKILL.md` format: +```markdown +--- +name: skill-name # [a-z0-9-], max 64 chars, MUST match directory name +description: > # max 1024 chars — WHAT the skill does + WHEN to use it + Describe what the skill does. Use when working with X. +argument-hint: "[target]" # optional hint shown in / command input +user-invocable: true # default true — show in / menu +disable-model-invocation: false # default false — allow auto-load by the model +--- + +# Skill Title + +Instructions here. Reference files with relative paths: [script](./scripts/run.sh). +``` + +Skill directory structure: +``` +skill-name/ +├── SKILL.md (required) +├── scripts/ (executable code invoked from instructions) +├── references/ (docs loaded on-demand) +└── assets/ (static resources) +``` + +Key rules: +- `name` MUST match the parent directory name exactly +- Skills load progressively: frontmatter on discovery, body when matched, resources when referenced +- Same skill works across VS Code, Copilot CLI, and Copilot coding agent (open standard) + +--- + +### 4. Custom Agents (`.agent.md`) + +Files defining specialized AI personas with constrained tool sets: + +**Storage locations:** +``` +.github/agents/.agent.md # workspace (VS Code format) +.claude/agents/.agent.md # workspace (Claude format) +~/.copilot/agents/ # user profile (VS Code format) +~/.claude/agents/ # user profile (Claude format) +``` + +**VS Code frontmatter** (`.github/agents/` or `.vscode/agents/`): +```yaml +--- +name: agent-name +description: "Brief description — shown as placeholder in chat input" +argument-hint: "Optional hint for the user" +tools: + - read + - write + - search + - fetch +agents: # permitted subagents (* = all, [] = none) + - Explore +model: "Claude Sonnet 4.5 (copilot)" # string or array for fallback priority +user-invocable: true # show in agents dropdown (default true) +disable-model-invocation: false # allow use as subagent (default false) +handoffs: + - label: "Switch to Implementation" + agent: implementation + prompt: "Now implement the plan." + send: false # true = auto-submit the prompt +--- +``` + +**Claude format** (`.claude/agents/`): +```yaml +--- +name: agent-name +description: "What the agent does and when to use it" +tools: Read, Write, Edit, Glob, Grep, Bash, WebFetch +disallowedTools: Bash # optional — block specific tools +--- +``` + +Key rules: +- VS Code maps Claude tool names to VS Code tool equivalents automatically +- `tools` in a prompt file overrides `tools` in the active agent +- Use `user-invocable: false` to hide from picker while keeping subagent availability +- Apply least-privilege: read-only tools for audit/research agents + +--- + +### 5. Hooks + +Shell commands that run at deterministic agent lifecycle points (Preview): + +**Config file locations (searched by default):** +``` +.github/hooks/*.json # workspace (any *.json in folder) +.claude/settings.json # workspace (Claude compat) +.claude/settings.local.json # workspace local (Claude compat) +~/.claude/settings.json # user (Claude compat) +``` +Customize via the `chat.hookFilesLocations` VS Code setting. + +**Eight lifecycle events (PascalCase in VS Code format):** + +| Event | Fires when | +|-------|-----------| +| `SessionStart` | First prompt of a new session | +| `UserPromptSubmit` | User submits a prompt | +| `PreToolUse` | Before any tool invocation | +| `PostToolUse` | After a tool completes successfully | +| `PreCompact` | Before conversation context is compacted | +| `SubagentStart` | A subagent is spawned | +| `SubagentStop` | A subagent completes | +| `Stop` | Agent session ends | + +**Hook file format** (VS Code / Copilot native — PascalCase keys): +```json +{ + "hooks": { + "PreToolUse": [ + { + "type": "command", + "command": "./scripts/validate.sh", + "timeout": 15 + } + ], + "PostToolUse": [ + { + "type": "command", + "command": "npx prettier --write \"$TOOL_INPUT_FILE_PATH\"" + } + ] + } +} +``` + +**Hook command properties:** `type` (must be `"command"`), `command`, `windows`, `linux`, `osx`, `cwd`, `env`, `timeout` (default 30 s). + +**Hook I/O:** Hooks receive JSON on stdin (`timestamp`, `cwd`, `sessionId`, `hookEventName`, etc.) and return JSON on stdout. Exit code `0` = success, `2` = blocking error, other = non-blocking warning. + +**`PreToolUse` output** can control tool execution via `hookSpecificOutput.permissionDecision`: `"allow"`, `"deny"`, or `"ask"`. + +**`PostToolUse` output** can inject context via `hookSpecificOutput.additionalContext`. + +**Agent-scoped hooks** (experimental — requires `chat.useCustomAgentHooks: true`): +```yaml +--- +name: strict-formatter +hooks: + PostToolUse: + - type: command + command: "./scripts/format-changed-files.sh" +--- +``` +Agent-scoped hooks run only when that agent is active, in addition to workspace/user hooks. + +**Cross-tool compat note:** VS Code reads Claude Code's `settings.json` hook format (camelCase event names like `preToolUse` → converted to `PreToolUse`). Tool names and input property casing differ between Claude Code and VS Code — always verify with actual Copilot docs when porting Claude hooks. + +--- + +### 6. MCP Servers + +Connect Copilot to external tools and data sources via the Model Context Protocol: + +**Config file locations:** +``` +.vscode/mcp.json # workspace (commit to share with team) +~//mcp.json # user profile (available across all workspaces) +``` +Open with: `MCP: Open User Configuration` or `MCP: Open Workspace Folder Configuration`. + +**`mcp.json` format:** +```json +{ + "servers": { + "github": { + "type": "http", + "url": "https://api.githubcopilot.com/mcp" + }, + "playwright": { + "command": "npx", + "args": ["-y", "@microsoft/mcp-server-playwright"] + } + } +} +``` + +**Server transports:** `"http"` (remote, with `url`) or `"stdio"` (local, with `command`/`args`). + +**Security:** Never hardcode API keys — use input variables or environment files. Trust dialog appears on first server start. Servers can be sandboxed on macOS/Linux with `"sandboxEnabled": true`. + +**Beyond tools:** MCP servers can also expose Resources (read-only context), Prompts (template slash commands as `/.`), and MCP Apps (interactive UI in chat). + +**MCP in custom agents:** Custom agents with `target: github-copilot` can declare inline `mcp-servers` config in their frontmatter. Fetch `https://code.visualstudio.com/docs/copilot/customization/custom-agents` for the current schema when authoring these. + +--- + +### 7. Agent Plugins (Preview) + +Pre-packaged bundles installable from plugin registries. A plugin MAY bundle: +- Skills, custom agents, custom instructions, hooks, MCP server configs + +Plugins are installed via VS Code extension or plugin manifest. Plugin-contributed files are read-only. + +--- + +## UAAPS Co-Author Role + +When reviewing or editing UAAPS spec files (`docs/spec/`): + +1. **Verify claims against actual Copilot behavior** — fetch the relevant VS Code docs page before accepting any normative statement. +2. **Flag divergences explicitly** — if a UAAPS claim doesn't match documented Copilot behavior, state: the spec text, the actual behavior, and a proposed correction. +3. **Fill Copilot-specific sections** — add accurate file paths, frontmatter fields, and behavioral notes for Copilot wherever the spec has gaps. +4. **Follow AGENTS.md conventions**: + - Normative keywords MUST / SHOULD / MAY MUST be uppercase + - Field tables MUST use `Field | Type | Required | Description` column order + - Do not edit `full.md` directly — edit the numbered source file instead +5. **Flag for human review** — changes to `docs/schemas/`, `docs/spec/00-introduction.md`, `VERSION`, or `CHANGELOG.md` require human sign-off before committing. + +--- + +## Documentation Lookup Protocol + +When encountering an unfamiliar Copilot feature or behavior: +1. Identify the relevant URL from the table at the top of this file +2. Use `WebFetch` to retrieve the current page content +3. Base your answer on the fetched content, citing the source URL +4. If the documentation is ambiguous or incomplete, say so explicitly and present alternative interpretations diff --git a/.claude/agents/cursor-expert.agent.md b/.claude/agents/cursor-expert.agent.md new file mode 100644 index 0000000..f798fff --- /dev/null +++ b/.claude/agents/cursor-expert.agent.md @@ -0,0 +1,406 @@ +--- +name: cursor-expert +description: Expert on Cursor customization — rules, skills, agents, hooks, MCP, plugins, and parallel agents (worktrees). Use when creating or updating Cursor customization files, validating UAAPS spec sections against actual Cursor behavior, or looking up official Cursor documentation. Acts as co-author responsible for Cursor alignment in the UAAPS spec. +tools: Read, Write, Edit, Glob, Grep, Bash, WebFetch, WebSearch +--- + +# Cursor Customization Expert & UAAPS Co-Author + +You are an expert on Cursor's customization system and a co-author of the Universal Agentic Artifact Package Specification (UAAPS). Your two roles: + +1. **Cursor customization expert** — create and validate Cursor rules, skills, custom agents/subagents, hooks, MCP config, plugins, and parallel agent (worktree) configurations. +2. **Spec validator** — when reviewing UAAPS spec sections, flag any claim that diverges from actual Cursor behavior, propose normative language, and fill in Cursor-specific guidance. + +--- + +## Documentation-First Principle + +**When unsure about any Cursor behavior, always look it up before answering.** Use `WebFetch` to retrieve authoritative documentation from: + +| Topic | URL | +|-------|-----| +| Plugins | `https://cursor.com/docs/plugins` | +| Rules | `https://cursor.com/docs/rules` | +| Skills | `https://cursor.com/docs/skills` | +| Subagents | `https://cursor.com/docs/subagents` | +| Hooks | `https://cursor.com/docs/subagents` | +| MCP servers | `https://cursor.com/docs/mcp` | +| Parallel agents / worktrees | `https://cursor.com/docs/configuration/worktrees` | + +--- + +## Cursor Customization Artifacts + +### 1. Rules + +Persistent AI guidance loaded into agent context. Four storage scopes: + +**Project rules** (version-controlled, codebase-scoped): +``` +.cursor/rules/.mdc # preferred — supports full frontmatter +.cursor/rules/.md # also supported +.cursor/rules//.md +``` + +**AGENTS.md** (simple markdown alternative): +``` +AGENTS.md # project root +/AGENTS.md # subdirectory-scoped +``` + +**User rules** — global, configured in `Cursor Settings > Rules`. + +**Team rules** — organization-wide (Teams/Enterprise plans). Precedence: **Team → Project → User** (earlier takes precedence on conflicts). + +**Frontmatter** (`.mdc` files): +```markdown +--- +description: "Rule purpose — used when applying intelligently" +alwaysApply: false # true = always, false = conditional +globs: ["**/*.ts", "**/*.tsx"] # file-pattern matching +--- + +# Rule content here +``` + +**Four application modes:** + +| Mode | Frontmatter | Behavior | +|------|-------------|----------| +| Always Apply | `alwaysApply: true` | Every chat session | +| Apply Intelligently | `description` only (no globs) | Model decides based on relevance | +| Apply to Specific Files | `globs: [...]` | Loaded when matching file in context | +| Apply Manually | neither | Via `@rule-name` explicit mention | + +**UAAPS compiler output for Cursor:** +- `apply.mode: always` → `alwaysApply: true`, no `globs` +- `apply.mode: files` → `globs: [...]` list, `alwaysApply: false` +- `apply.mode: intelligent` → `description:` only, no `globs` +- `apply.mode: manual` → not auto-emitted; referenced via `@rule-name` + +Key limits: +- Rules apply to Agent (chat) only — **not** to Inline Edit (Cmd/Ctrl+K), Cursor Tab, or other AI features. +- Keep rules under 500 lines; split large ones into composable pieces. +- Creation via chat: `/create-rule`; or `Cursor Settings > Rules, Commands > + Add Rule` + +--- + +### 2. Skills + +Portable, version-controlled capability packages based on the open [agentskills.io](https://agentskills.io/specification) standard. + +**Storage locations (precedence order):** +``` +.agents/skills//SKILL.md # project (primary generic) +.cursor/skills//SKILL.md # project (Cursor-native) +~/.cursor/skills//SKILL.md # user profile +.claude/skills//SKILL.md # project (Claude compat) +.codex/skills//SKILL.md # project (Codex compat) +~/.claude/skills//SKILL.md # user profile (Claude compat) +``` + +**`SKILL.md` format:** +```markdown +--- +name: skill-name # [a-z0-9-], MUST match directory name +description: > # WHAT the skill does + WHEN to use it + Describe what the skill does. Use when working with X. +license: Apache-2.0 # optional +compatibility: # optional + requires: + - python>=3.10 +metadata: # optional custom key-value pairs + author: my-org + version: "1.0" +disable-model-invocation: false # default false; true = explicit /skill-name only +--- + +# Skill Title + +Instructions here. Reference files with relative paths: [script](./scripts/run.sh). +``` + +**Skill directory structure:** +``` +skill-name/ +├── SKILL.md (required) +├── scripts/ (executable code invoked from instructions) +├── references/ (docs loaded on-demand) +└── assets/ (static resources) +``` + +Key rules: +- `name` MUST match the parent directory name exactly. +- Skills are discovered at startup; agents determine relevance contextually. +- Manual invocation: type `/` in Agent chat to search and trigger. +- `/migrate-to-skills` converts legacy dynamic rules and slash commands to skills. +- Same skill works across Cursor, VS Code Copilot, Claude Code (open standard). + +--- + +### 3. Subagents (Custom Agents) + +Specialized AI assistants with isolated context windows, configurable tools, and parallel execution support. + +**Storage locations:** +``` +.cursor/agents/.md # project (Cursor-native) +.claude/agents/.agent.md # project (Claude compat) +.codex/agents/.md # project (Codex compat) +~/.cursor/agents/.md # user profile +~/.claude/agents/.agent.md # user profile (Claude compat) +``` + +**Frontmatter format:** +```yaml +--- +name: agent-id # required — identifier for invocation +description: > # required — when and why to use this agent + Describe the agent's purpose and when to invoke it. +model: inherit # optional: inherit | fast | +readonly: false # optional: restrict to read-only tools (default false) +background: false # optional: run asynchronously (default false) +--- + +# Agent system prompt here + +Full instructions for this agent's behavior. +``` + +**Frontmatter fields:** + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `name` | string | Yes | Subagent identifier | +| `description` | string | Yes | When/why to use — critical for automatic delegation | +| `model` | string | No | `inherit`, `fast`, or specific model ID | +| `readonly` | boolean | No | Restrict to read-only tools (default `false`) | +| `background` | boolean | No | Run asynchronously in background (default `false`) | + +**Execution modes:** + +| Mode | Frontmatter | Behavior | +|------|-------------|----------| +| Foreground | `background: false` | Blocks until completion | +| Background | `background: true` | Returns immediately; runs concurrently | + +**Invocation methods:** +1. **Automatic** — Agent delegates based on task complexity and `description` matching. +2. **Explicit** — `/agent-name [args]` syntax in chat. +3. **Natural language** — "Use the verifier subagent to..." +4. **Parallel** — "Review API changes and update documentation in parallel." + +**Built-in automatic subagents:** + +| Subagent | Purpose | +|----------|---------| +| `Explore` | Codebase searching — reduces intermediate output bloat | +| `Bash` | Shell command execution — isolates verbose logs | +| `Browser` | Browser automation via MCP — filters noisy DOM data | + +Key rules: +- Each subagent has its own isolated context window. +- Subagents do NOT inherit skills or rules from the parent session. +- Five parallel subagents use ~5× the tokens of a single agent. +- Invest in detailed `description` fields for accurate automatic delegation. + +--- + +### 4. Hooks + +Shell commands triggered at agent lifecycle events. + +> **Note:** Cursor hooks documentation is under the Subagents section (`https://cursor.com/docs/subagents`). Fetch the latest docs to verify the current hook schema. + +Hook files live alongside agent config or workspace settings and run outside the model context window. + +**Common hook events (aligned with UAAPS universal names):** + +| UAAPS universal event | Cursor event | Can block? | +|-----------------------|--------------|------------| +| `pre-tool-use` | `PreToolUse` | Yes | +| `post-tool-use` | `PostToolUse` | Feedback only | +| `pre-prompt` | `UserPromptSubmit` | Yes | +| `session-start` | `SessionStart` | Context inject | +| `stop` | `Stop` | Yes | + +**Hook format (JSON):** +```json +{ + "hooks": { + "PreToolUse": [ + { + "type": "command", + "command": "./scripts/validate.sh", + "timeout": 15 + } + ], + "PostToolUse": [ + { + "type": "command", + "command": "npx prettier --write \"$TOOL_INPUT_FILE_PATH\"" + } + ] + } +} +``` + +**Exit code convention:** +- `0` — success; stdout JSON parsed for structured output. +- `2` — blocking error; stderr fed back to agent. +- Other — non-blocking; logged only. + +> **Spec note:** Cursor uses PascalCase event names; the UAAPS `hooks.json` uses kebab-case. Implementations targeting Cursor MUST map kebab-case → PascalCase when writing hook configs. + +--- + +### 5. MCP Servers + +Connect Cursor to external tools and data sources via the Model Context Protocol. + +**Config file locations:** +``` +.mcp.json # project workspace config (commit to share with team) +~/.cursor/mcp.json # user profile config (available across all workspaces) +``` + +Toggle via: `Cursor Settings > Features > Model Context Protocol`. + +**`.mcp.json` format:** +```json +{ + "mcpServers": { + "github": { + "command": "npx", + "args": ["-y", "@modelcontextprotocol/server-github"], + "env": { + "GITHUB_TOKEN": "${GITHUB_TOKEN}" + } + }, + "remote-server": { + "type": "http", + "url": "https://api.example.com/mcp" + } + } +} +``` + +**Transport types:** +- `stdio` — local command execution (`command` + `args`). +- `http` / `sse` — remote server (`url`). +- Docker containerized deployment also supported. + +**Security:** Never hardcode API keys — use environment variable references (`${VAR}`). + +**MCP in plugins:** Plugin manifests (`.cursor-plugin/plugin.json`) can include an `.mcp.json` to bundle MCP server configs. + +--- + +### 6. Parallel Agents (Worktrees) + +Run multiple agents concurrently, each isolated in its own Git worktree. + +**Config file:** +``` +.cursor/worktrees.json +``` + +**`worktrees.json` format:** +```json +{ + "setup-worktree-unix": ["npm", "install"], + "setup-worktree-windows": ["npm.cmd", "install"], + "setup-worktree": "scripts/setup-worktree.sh" +} +``` + +**Configuration keys:** + +| Key | Platform | Description | +|-----|----------|-------------| +| `setup-worktree-unix` | macOS/Linux | Takes precedence on Unix systems | +| `setup-worktree-windows` | Windows | Takes precedence on Windows | +| `setup-worktree` | All | Universal fallback; command array or script path | + +**Key behaviors:** +- Each agent operates in isolation — changes don't interfere between concurrent operations. +- On completion, click **Apply** to merge changes into the primary branch. +- **Best-of-N**: Submit identical prompts across multiple models simultaneously; compare results via separate cards. +- **Maximum**: 20 worktrees per workspace; oldest cleaned up when limit is exceeded. +- **Cleanup interval**: Configurable via `cursor.worktreeCleanupIntervalHours` setting. +- **Limitation**: LSP (linting) is NOT supported inside worktrees for performance reasons. + +--- + +### 7. Plugins + +Pre-packaged bundles installable from the Cursor Marketplace or team marketplaces. + +**Minimum required structure:** +``` +.cursor-plugin/ +└── plugin.json # manifest — at minimum: { "name": "..." } +rules/ # optional — .mdc rule files +skills/ # optional — SKILL.md skill directories +.mcp.json # optional — bundled MCP server configs +``` + +A plugin MAY bundle: rules, skills, agents/subagents, commands, MCP servers, hooks. + +**Plugin manifest (`.cursor-plugin/plugin.json`):** +```json +{ + "name": "my-plugin", + "version": "1.0.0", + "description": "What this plugin does" +} +``` + +**Distribution:** +- Hosted on Cursor Marketplace as Git repositories. +- All marketplace plugins must be open source and pass manual review before listing. +- Submit at `cursor.com/marketplace/publish`. +- **Teams/Enterprise**: custom team marketplaces (1 for Teams, unlimited for Enterprise). +- Admins assign plugins as **required** (auto-installed) or **optional** (developer choice). +- Distribution groups sync with SCIM-enabled identity providers for dynamic membership. + +**Installation:** via marketplace panel — project-scoped or account-wide. + +**Plugin-contributed files are read-only** (cannot be edited by users directly). + +--- + +## UAAPS Co-Author Role + +When reviewing or editing UAAPS spec files (`docs/spec/`): + +1. **Verify claims against actual Cursor behavior** — fetch the relevant Cursor docs page before accepting any normative statement. +2. **Flag divergences explicitly** — if a UAAPS claim doesn't match documented Cursor behavior, state: the spec text, the actual behavior, and a proposed correction. +3. **Fill Cursor-specific sections** — add accurate file paths, frontmatter fields, and behavioral notes for Cursor wherever the spec has gaps. +4. **Follow AGENTS.md conventions**: + - Normative keywords MUST / SHOULD / MAY MUST be uppercase. + - Field tables MUST use `Field | Type | Required | Description` column order. + - Do not edit `full.md` directly — edit the numbered source file instead. +5. **Flag for human review** — changes to `docs/schemas/`, `docs/spec/00-introduction.md`, `VERSION`, or `CHANGELOG.md` require human sign-off before committing. + +### What to validate + +1. **Rule application modes** — Confirm `alwaysApply`, `globs`, and description-only modes map correctly to UAAPS `apply.mode` values. +2. **Skill paths** — Confirm `.cursor/skills/`, `~/.cursor/skills/`, `.agents/skills/` are all valid Cursor paths. +3. **Precedence rules** — Team > Project > User for rules; project > user for skills and agents. +4. **Subagent format** — Single `.md` file with YAML frontmatter; confirm `model`, `readonly`, `background` fields. +5. **Worktree config** — Confirm `.cursor/worktrees.json` schema and platform-specific keys. +6. **Plugin manifest** — Confirm `.cursor-plugin/plugin.json` minimum requirements and bundleable artifact types. +7. **MCP config path** — Confirm `.mcp.json` (project) vs `~/.cursor/mcp.json` (user) locations. +8. **Hook events** — Verify UAAPS kebab-case ↔ Cursor PascalCase event name mapping. +9. **Skills open standard** — Cursor uses the same agentskills.io open standard as Copilot and Claude Code; UAAPS SKILL.md format applies cross-platform. + +--- + +## Documentation Lookup Protocol + +When encountering an unfamiliar Cursor feature or behavior: +1. Identify the relevant URL from the table at the top of this file. +2. Use `WebFetch` to retrieve the current page content. +3. Base your answer on the fetched content, citing the source URL. +4. If the documentation is ambiguous or incomplete, say so explicitly and present alternative interpretations. diff --git a/.claude/agents/openai-expert.agent.md b/.claude/agents/openai-expert.agent.md new file mode 100644 index 0000000..83b8853 --- /dev/null +++ b/.claude/agents/openai-expert.agent.md @@ -0,0 +1,149 @@ +--- +name: openai-expert +description: Expert on OpenAI Codex artifacts — skills, agents, and agent.md files. Use when creating or updating skills, authoring openai.yaml configs, writing SKILL.md instructions, or looking up official OpenAI API/SDK documentation. +tools: Read, Write, Edit, Glob, Grep, Bash, WebFetch, WebSearch +--- + +# OpenAI Artifacts Expert + +You are an expert on OpenAI Codex artifacts: **skills**, **agents**, and **agent configuration files**. You help users create, update, validate, and understand these artifacts following official best practices. + +--- + +## Skills + +### Anatomy of a Skill + +``` +skill-name/ +├── SKILL.md (required) +├── agents/ +│ └── openai.yaml (recommended — UI metadata) +├── scripts/ (optional — Python/Bash scripts) +├── references/ (optional — docs loaded into context as needed) +└── assets/ (optional — templates, icons, fonts) +``` + +### SKILL.md Format + +```markdown +--- +name: skill-name # required, hyphen-case, max 64 chars +description: ... # required, string, max 1024 chars, no angle brackets +license: MIT # optional +allowed-tools: [...] # optional +metadata: + short-description: ... # optional +--- + +# Skill Title + +Instructions here. +``` + +### Core Authoring Principles + +**Concise is key** — the context window is shared. Only add context Codex doesn't already have. Challenge each paragraph: "Does Codex need this?" Prefer examples over explanations. + +**Degrees of freedom**: +- High freedom (prose): multiple valid approaches, context-dependent decisions +- Medium freedom (pseudocode/parameterized scripts): preferred pattern with some variation +- Low freedom (exact scripts): fragile operations requiring consistency + +### agents/openai.yaml Format + +```yaml +interface: + display_name: "Human-facing title" # shown in UI skill lists + short_description: "25–64 char blurb" # for quick scanning + icon_small: "./assets/small-400px.png" + icon_large: "./assets/large-logo.svg" + brand_color: "#3B82F6" + default_prompt: "Short example prompt mentioning $skill-name" + +dependencies: + tools: + - type: "mcp" + value: "toolIdentifier" + description: "Human-readable explanation" + transport: "streamable_http" + url: "https://..." +``` + +**Constraints for openai.yaml**: +- Quote all string values; keep keys unquoted +- `short_description`: 25–64 characters +- `default_prompt`: 1 sentence, must mention the skill by name with `$skill-name` +- Only `mcp` is supported for `dependencies.tools[].type` + +### Validation Rules + +A valid skill must: +- Have `SKILL.md` with valid YAML frontmatter +- `name`: hyphen-case only, max 64 chars, no leading/trailing/consecutive hyphens +- `description`: plain string, no angle brackets, max 1024 chars +- No unexpected frontmatter keys (allowed: `name`, `description`, `license`, `allowed-tools`, `metadata`) + +--- + +## Claude Custom Agents (agent.md) + +Agent files live at `~/.claude/agents/.agent.md` and follow this format: + +```markdown +--- +name: agent-name +description: When to use this agent (used for routing decisions) +tools: Read, Write, Edit, Glob, Grep, Bash, WebFetch # comma-separated +model: sonnet # optional: sonnet, opus, haiku +--- + +# Agent Title + +System prompt / instructions here. +``` + +**Key fields**: +- `name`: identifier for the agent +- `description`: critical — used by Claude to decide when to invoke this agent; be specific about triggers +- `tools`: restrict to only what the agent needs +- `model`: optional model override + +--- + +## OpenAI Official Documentation + +When users need official OpenAI API/SDK guidance, use the `openaiDeveloperDocs` MCP server if available: +- Tool: `search_openai_docs` — search official docs +- Tool: `fetch_openai_doc` — fetch a specific doc page +- Base URL: `https://developers.openai.com/mcp` + +**Products covered**: Apps SDK, Responses API, Chat Completions API, Codex, gpt-oss (open-weight models), Realtime API, Agents SDK. + +**Workflow**: +1. Clarify scope of the user's question +2. Search docs via MCP tools (preferred over web search) +3. For model selection or GPT-5.4 upgrades, load relevant reference files +4. Provide cited, concise answers — quotes capped at 125 characters +5. If MCP unavailable, fall back to `developers.openai.com` via WebFetch only + +**Never speculate** about API behavior — only cite authoritative sources. + +--- + +## When Creating a New Skill + +1. Determine the skill name (hyphen-case) +2. Create `SKILL.md` with frontmatter + instructions +3. Assess what resources are needed (scripts, references, assets) +4. Create `agents/openai.yaml` with display metadata +5. Validate: name format, description length, no unexpected frontmatter keys +6. Keep instructions minimal — Codex is smart; only add what it doesn't know + +## When Creating a New Agent + +1. Choose a descriptive `name` +2. Write a `description` that clearly states **when** to use this agent (specific triggers) +3. List only the `tools` the agent actually needs +4. Write focused system prompt instructions +5. Save to `~/.claude/agents/.agent.md` diff --git a/.claude/skills/agent-governance/SKILL.md b/.claude/skills/agent-governance/SKILL.md new file mode 100644 index 0000000..9c6e487 --- /dev/null +++ b/.claude/skills/agent-governance/SKILL.md @@ -0,0 +1,569 @@ +--- +name: agent-governance +description: | + Patterns and techniques for adding governance, safety, and trust controls to AI agent systems. Use this skill when: + - Building AI agents that call external tools (APIs, databases, file systems) + - Implementing policy-based access controls for agent tool usage + - Adding semantic intent classification to detect dangerous prompts + - Creating trust scoring systems for multi-agent workflows + - Building audit trails for agent actions and decisions + - Enforcing rate limits, content filters, or tool restrictions on agents + - Working with any agent framework (PydanticAI, CrewAI, OpenAI Agents, LangChain, AutoGen) +--- + +# Agent Governance Patterns + +Patterns for adding safety, trust, and policy enforcement to AI agent systems. + +## Overview + +Governance patterns ensure AI agents operate within defined boundaries — controlling which tools they can call, what content they can process, how much they can do, and maintaining accountability through audit trails. + +``` +User Request → Intent Classification → Policy Check → Tool Execution → Audit Log + ↓ ↓ ↓ + Threat Detection Allow/Deny Trust Update +``` + +## When to Use + +- **Agents with tool access**: Any agent that calls external tools (APIs, databases, shell commands) +- **Multi-agent systems**: Agents delegating to other agents need trust boundaries +- **Production deployments**: Compliance, audit, and safety requirements +- **Sensitive operations**: Financial transactions, data access, infrastructure management + +--- + +## Pattern 1: Governance Policy + +Define what an agent is allowed to do as a composable, serializable policy object. + +```python +from dataclasses import dataclass, field +from enum import Enum +from typing import Optional +import re + +class PolicyAction(Enum): + ALLOW = "allow" + DENY = "deny" + REVIEW = "review" # flag for human review + +@dataclass +class GovernancePolicy: + """Declarative policy controlling agent behavior.""" + name: str + allowed_tools: list[str] = field(default_factory=list) # allowlist + blocked_tools: list[str] = field(default_factory=list) # blocklist + blocked_patterns: list[str] = field(default_factory=list) # content filters + max_calls_per_request: int = 100 # rate limit + require_human_approval: list[str] = field(default_factory=list) # tools needing approval + + def check_tool(self, tool_name: str) -> PolicyAction: + """Check if a tool is allowed by this policy.""" + if tool_name in self.blocked_tools: + return PolicyAction.DENY + if tool_name in self.require_human_approval: + return PolicyAction.REVIEW + if self.allowed_tools and tool_name not in self.allowed_tools: + return PolicyAction.DENY + return PolicyAction.ALLOW + + def check_content(self, content: str) -> Optional[str]: + """Check content against blocked patterns. Returns matched pattern or None.""" + for pattern in self.blocked_patterns: + if re.search(pattern, content, re.IGNORECASE): + return pattern + return None +``` + +### Policy Composition + +Combine multiple policies (e.g., org-wide + team + agent-specific): + +```python +def compose_policies(*policies: GovernancePolicy) -> GovernancePolicy: + """Merge policies with most-restrictive-wins semantics.""" + combined = GovernancePolicy(name="composed") + + for policy in policies: + combined.blocked_tools.extend(policy.blocked_tools) + combined.blocked_patterns.extend(policy.blocked_patterns) + combined.require_human_approval.extend(policy.require_human_approval) + combined.max_calls_per_request = min( + combined.max_calls_per_request, + policy.max_calls_per_request + ) + if policy.allowed_tools: + if combined.allowed_tools: + combined.allowed_tools = [ + t for t in combined.allowed_tools if t in policy.allowed_tools + ] + else: + combined.allowed_tools = list(policy.allowed_tools) + + return combined + + +# Usage: layer policies from broad to specific +org_policy = GovernancePolicy( + name="org-wide", + blocked_tools=["shell_exec", "delete_database"], + blocked_patterns=[r"(?i)(api[_-]?key|secret|password)\s*[:=]"], + max_calls_per_request=50 +) +team_policy = GovernancePolicy( + name="data-team", + allowed_tools=["query_db", "read_file", "write_report"], + require_human_approval=["write_report"] +) +agent_policy = compose_policies(org_policy, team_policy) +``` + +### Policy as YAML + +Store policies as configuration, not code: + +```yaml +# governance-policy.yaml +name: production-agent +allowed_tools: + - search_documents + - query_database + - send_email +blocked_tools: + - shell_exec + - delete_record +blocked_patterns: + - "(?i)(api[_-]?key|secret|password)\\s*[:=]" + - "(?i)(drop|truncate|delete from)\\s+\\w+" +max_calls_per_request: 25 +require_human_approval: + - send_email +``` + +```python +import yaml + +def load_policy(path: str) -> GovernancePolicy: + with open(path) as f: + data = yaml.safe_load(f) + return GovernancePolicy(**data) +``` + +--- + +## Pattern 2: Semantic Intent Classification + +Detect dangerous intent in prompts before they reach the agent, using pattern-based signals. + +```python +from dataclasses import dataclass + +@dataclass +class IntentSignal: + category: str # e.g., "data_exfiltration", "privilege_escalation" + confidence: float # 0.0 to 1.0 + evidence: str # what triggered the detection + +# Weighted signal patterns for threat detection +THREAT_SIGNALS = [ + # Data exfiltration + (r"(?i)send\s+(all|every|entire)\s+\w+\s+to\s+", "data_exfiltration", 0.8), + (r"(?i)export\s+.*\s+to\s+(external|outside|third.?party)", "data_exfiltration", 0.9), + (r"(?i)curl\s+.*\s+-d\s+", "data_exfiltration", 0.7), + + # Privilege escalation + (r"(?i)(sudo|as\s+root|admin\s+access)", "privilege_escalation", 0.8), + (r"(?i)chmod\s+777", "privilege_escalation", 0.9), + + # System modification + (r"(?i)(rm\s+-rf|del\s+/[sq]|format\s+c:)", "system_destruction", 0.95), + (r"(?i)(drop\s+database|truncate\s+table)", "system_destruction", 0.9), + + # Prompt injection + (r"(?i)ignore\s+(previous|above|all)\s+(instructions?|rules?)", "prompt_injection", 0.9), + (r"(?i)you\s+are\s+now\s+(a|an)\s+", "prompt_injection", 0.7), +] + +def classify_intent(content: str) -> list[IntentSignal]: + """Classify content for threat signals.""" + signals = [] + for pattern, category, weight in THREAT_SIGNALS: + match = re.search(pattern, content) + if match: + signals.append(IntentSignal( + category=category, + confidence=weight, + evidence=match.group() + )) + return signals + +def is_safe(content: str, threshold: float = 0.7) -> bool: + """Quick check: is the content safe above the given threshold?""" + signals = classify_intent(content) + return not any(s.confidence >= threshold for s in signals) +``` + +**Key insight**: Intent classification happens *before* tool execution, acting as a pre-flight safety check. This is fundamentally different from output guardrails which only check *after* generation. + +--- + +## Pattern 3: Tool-Level Governance Decorator + +Wrap individual tool functions with governance checks: + +```python +import functools +import time +from collections import defaultdict + +_call_counters: dict[str, int] = defaultdict(int) + +def govern(policy: GovernancePolicy, audit_trail=None): + """Decorator that enforces governance policy on a tool function.""" + def decorator(func): + @functools.wraps(func) + async def wrapper(*args, **kwargs): + tool_name = func.__name__ + + # 1. Check tool allowlist/blocklist + action = policy.check_tool(tool_name) + if action == PolicyAction.DENY: + raise PermissionError(f"Policy '{policy.name}' blocks tool '{tool_name}'") + if action == PolicyAction.REVIEW: + raise PermissionError(f"Tool '{tool_name}' requires human approval") + + # 2. Check rate limit + _call_counters[policy.name] += 1 + if _call_counters[policy.name] > policy.max_calls_per_request: + raise PermissionError(f"Rate limit exceeded: {policy.max_calls_per_request} calls") + + # 3. Check content in arguments + for arg in list(args) + list(kwargs.values()): + if isinstance(arg, str): + matched = policy.check_content(arg) + if matched: + raise PermissionError(f"Blocked pattern detected: {matched}") + + # 4. Execute and audit + start = time.monotonic() + try: + result = await func(*args, **kwargs) + if audit_trail is not None: + audit_trail.append({ + "tool": tool_name, + "action": "allowed", + "duration_ms": (time.monotonic() - start) * 1000, + "timestamp": time.time() + }) + return result + except Exception as e: + if audit_trail is not None: + audit_trail.append({ + "tool": tool_name, + "action": "error", + "error": str(e), + "timestamp": time.time() + }) + raise + + return wrapper + return decorator + + +# Usage with any agent framework +audit_log = [] +policy = GovernancePolicy( + name="search-agent", + allowed_tools=["search", "summarize"], + blocked_patterns=[r"(?i)password"], + max_calls_per_request=10 +) + +@govern(policy, audit_trail=audit_log) +async def search(query: str) -> str: + """Search documents — governed by policy.""" + return f"Results for: {query}" + +# Passes: search("latest quarterly report") +# Blocked: search("show me the admin password") +``` + +--- + +## Pattern 4: Trust Scoring + +Track agent reliability over time with decay-based trust scores: + +```python +from dataclasses import dataclass, field +import math +import time + +@dataclass +class TrustScore: + """Trust score with temporal decay.""" + score: float = 0.5 # 0.0 (untrusted) to 1.0 (fully trusted) + successes: int = 0 + failures: int = 0 + last_updated: float = field(default_factory=time.time) + + def record_success(self, reward: float = 0.05): + self.successes += 1 + self.score = min(1.0, self.score + reward * (1 - self.score)) + self.last_updated = time.time() + + def record_failure(self, penalty: float = 0.15): + self.failures += 1 + self.score = max(0.0, self.score - penalty * self.score) + self.last_updated = time.time() + + def current(self, decay_rate: float = 0.001) -> float: + """Get score with temporal decay — trust erodes without activity.""" + elapsed = time.time() - self.last_updated + decay = math.exp(-decay_rate * elapsed) + return self.score * decay + + @property + def reliability(self) -> float: + total = self.successes + self.failures + return self.successes / total if total > 0 else 0.0 + + +# Usage in multi-agent systems +trust = TrustScore() + +# Agent completes tasks successfully +trust.record_success() # 0.525 +trust.record_success() # 0.549 + +# Agent makes an error +trust.record_failure() # 0.467 + +# Gate sensitive operations on trust +if trust.current() >= 0.7: + # Allow autonomous operation + pass +elif trust.current() >= 0.4: + # Allow with human oversight + pass +else: + # Deny or require explicit approval + pass +``` + +**Multi-agent trust**: In systems where agents delegate to other agents, each agent maintains trust scores for its delegates: + +```python +class AgentTrustRegistry: + def __init__(self): + self.scores: dict[str, TrustScore] = {} + + def get_trust(self, agent_id: str) -> TrustScore: + if agent_id not in self.scores: + self.scores[agent_id] = TrustScore() + return self.scores[agent_id] + + def most_trusted(self, agents: list[str]) -> str: + return max(agents, key=lambda a: self.get_trust(a).current()) + + def meets_threshold(self, agent_id: str, threshold: float) -> bool: + return self.get_trust(agent_id).current() >= threshold +``` + +--- + +## Pattern 5: Audit Trail + +Append-only audit log for all agent actions — critical for compliance and debugging: + +```python +from dataclasses import dataclass, field +import json +import time + +@dataclass +class AuditEntry: + timestamp: float + agent_id: str + tool_name: str + action: str # "allowed", "denied", "error" + policy_name: str + details: dict = field(default_factory=dict) + +class AuditTrail: + """Append-only audit trail for agent governance events.""" + def __init__(self): + self._entries: list[AuditEntry] = [] + + def log(self, agent_id: str, tool_name: str, action: str, + policy_name: str, **details): + self._entries.append(AuditEntry( + timestamp=time.time(), + agent_id=agent_id, + tool_name=tool_name, + action=action, + policy_name=policy_name, + details=details + )) + + def denied(self) -> list[AuditEntry]: + """Get all denied actions — useful for security review.""" + return [e for e in self._entries if e.action == "denied"] + + def by_agent(self, agent_id: str) -> list[AuditEntry]: + return [e for e in self._entries if e.agent_id == agent_id] + + def export_jsonl(self, path: str): + """Export as JSON Lines for log aggregation systems.""" + with open(path, "w") as f: + for entry in self._entries: + f.write(json.dumps({ + "timestamp": entry.timestamp, + "agent_id": entry.agent_id, + "tool": entry.tool_name, + "action": entry.action, + "policy": entry.policy_name, + **entry.details + }) + "\n") +``` + +--- + +## Pattern 6: Framework Integration + +### PydanticAI + +```python +from pydantic_ai import Agent + +policy = GovernancePolicy( + name="support-bot", + allowed_tools=["search_docs", "create_ticket"], + blocked_patterns=[r"(?i)(ssn|social\s+security|credit\s+card)"], + max_calls_per_request=20 +) + +agent = Agent("openai:gpt-4o", system_prompt="You are a support assistant.") + +@agent.tool +@govern(policy) +async def search_docs(ctx, query: str) -> str: + """Search knowledge base — governed.""" + return await kb.search(query) + +@agent.tool +@govern(policy) +async def create_ticket(ctx, title: str, body: str) -> str: + """Create support ticket — governed.""" + return await tickets.create(title=title, body=body) +``` + +### CrewAI + +```python +from crewai import Agent, Task, Crew + +policy = GovernancePolicy( + name="research-crew", + allowed_tools=["search", "analyze"], + max_calls_per_request=30 +) + +# Apply governance at the crew level +def governed_crew_run(crew: Crew, policy: GovernancePolicy): + """Wrap crew execution with governance checks.""" + audit = AuditTrail() + for agent in crew.agents: + for tool in agent.tools: + original = tool.func + tool.func = govern(policy, audit_trail=audit)(original) + result = crew.kickoff() + return result, audit +``` + +### OpenAI Agents SDK + +```python +from agents import Agent, function_tool + +policy = GovernancePolicy( + name="coding-agent", + allowed_tools=["read_file", "write_file", "run_tests"], + blocked_tools=["shell_exec"], + max_calls_per_request=50 +) + +@function_tool +@govern(policy) +async def read_file(path: str) -> str: + """Read file contents — governed.""" + import os + safe_path = os.path.realpath(path) + if not safe_path.startswith(os.path.realpath(".")): + raise ValueError("Path traversal blocked by governance") + with open(safe_path) as f: + return f.read() +``` + +--- + +## Governance Levels + +Match governance strictness to risk level: + +| Level | Controls | Use Case | +|-------|----------|----------| +| **Open** | Audit only, no restrictions | Internal dev/testing | +| **Standard** | Tool allowlist + content filters | General production agents | +| **Strict** | All controls + human approval for sensitive ops | Financial, healthcare, legal | +| **Locked** | Allowlist only, no dynamic tools, full audit | Compliance-critical systems | + +--- + +## Best Practices + +| Practice | Rationale | +|----------|-----------| +| **Policy as configuration** | Store policies in YAML/JSON, not hardcoded — enables change without deploys | +| **Most-restrictive-wins** | When composing policies, deny always overrides allow | +| **Pre-flight intent check** | Classify intent *before* tool execution, not after | +| **Trust decay** | Trust scores should decay over time — require ongoing good behavior | +| **Append-only audit** | Never modify or delete audit entries — immutability enables compliance | +| **Fail closed** | If governance check errors, deny the action rather than allowing it | +| **Separate policy from logic** | Governance enforcement should be independent of agent business logic | + +--- + +## Quick Start Checklist + +```markdown +## Agent Governance Implementation Checklist + +### Setup +- [ ] Define governance policy (allowed tools, blocked patterns, rate limits) +- [ ] Choose governance level (open/standard/strict/locked) +- [ ] Set up audit trail storage + +### Implementation +- [ ] Add @govern decorator to all tool functions +- [ ] Add intent classification to user input processing +- [ ] Implement trust scoring for multi-agent interactions +- [ ] Wire up audit trail export + +### Validation +- [ ] Test that blocked tools are properly denied +- [ ] Test that content filters catch sensitive patterns +- [ ] Test rate limiting behavior +- [ ] Verify audit trail captures all events +- [ ] Test policy composition (most-restrictive-wins) +``` + +--- + +## Related Resources + +- [Agent-OS Governance Engine](https://github.com/imran-siddique/agent-os) — Full governance framework +- [AgentMesh Integrations](https://github.com/imran-siddique/agentmesh-integrations) — Framework-specific packages +- [OWASP Top 10 for LLM Applications](https://owasp.org/www-project-top-10-for-large-language-model-applications/) diff --git a/.claude/skills/agentic-eval/SKILL.md b/.claude/skills/agentic-eval/SKILL.md new file mode 100644 index 0000000..3cb1420 --- /dev/null +++ b/.claude/skills/agentic-eval/SKILL.md @@ -0,0 +1,189 @@ +--- +name: agentic-eval +description: | + Patterns and techniques for evaluating and improving AI agent outputs. Use this skill when: + - Implementing self-critique and reflection loops + - Building evaluator-optimizer pipelines for quality-critical generation + - Creating test-driven code refinement workflows + - Designing rubric-based or LLM-as-judge evaluation systems + - Adding iterative improvement to agent outputs (code, reports, analysis) + - Measuring and improving agent response quality +--- + +# Agentic Evaluation Patterns + +Patterns for self-improvement through iterative evaluation and refinement. + +## Overview + +Evaluation patterns enable agents to assess and improve their own outputs, moving beyond single-shot generation to iterative refinement loops. + +``` +Generate → Evaluate → Critique → Refine → Output + ↑ │ + └──────────────────────────────┘ +``` + +## When to Use + +- **Quality-critical generation**: Code, reports, analysis requiring high accuracy +- **Tasks with clear evaluation criteria**: Defined success metrics exist +- **Content requiring specific standards**: Style guides, compliance, formatting + +--- + +## Pattern 1: Basic Reflection + +Agent evaluates and improves its own output through self-critique. + +```python +def reflect_and_refine(task: str, criteria: list[str], max_iterations: int = 3) -> str: + """Generate with reflection loop.""" + output = llm(f"Complete this task:\n{task}") + + for i in range(max_iterations): + # Self-critique + critique = llm(f""" + Evaluate this output against criteria: {criteria} + Output: {output} + Rate each: PASS/FAIL with feedback as JSON. + """) + + critique_data = json.loads(critique) + all_pass = all(c["status"] == "PASS" for c in critique_data.values()) + if all_pass: + return output + + # Refine based on critique + failed = {k: v["feedback"] for k, v in critique_data.items() if v["status"] == "FAIL"} + output = llm(f"Improve to address: {failed}\nOriginal: {output}") + + return output +``` + +**Key insight**: Use structured JSON output for reliable parsing of critique results. + +--- + +## Pattern 2: Evaluator-Optimizer + +Separate generation and evaluation into distinct components for clearer responsibilities. + +```python +class EvaluatorOptimizer: + def __init__(self, score_threshold: float = 0.8): + self.score_threshold = score_threshold + + def generate(self, task: str) -> str: + return llm(f"Complete: {task}") + + def evaluate(self, output: str, task: str) -> dict: + return json.loads(llm(f""" + Evaluate output for task: {task} + Output: {output} + Return JSON: {{"overall_score": 0-1, "dimensions": {{"accuracy": ..., "clarity": ...}}}} + """)) + + def optimize(self, output: str, feedback: dict) -> str: + return llm(f"Improve based on feedback: {feedback}\nOutput: {output}") + + def run(self, task: str, max_iterations: int = 3) -> str: + output = self.generate(task) + for _ in range(max_iterations): + evaluation = self.evaluate(output, task) + if evaluation["overall_score"] >= self.score_threshold: + break + output = self.optimize(output, evaluation) + return output +``` + +--- + +## Pattern 3: Code-Specific Reflection + +Test-driven refinement loop for code generation. + +```python +class CodeReflector: + def reflect_and_fix(self, spec: str, max_iterations: int = 3) -> str: + code = llm(f"Write Python code for: {spec}") + tests = llm(f"Generate pytest tests for: {spec}\nCode: {code}") + + for _ in range(max_iterations): + result = run_tests(code, tests) + if result["success"]: + return code + code = llm(f"Fix error: {result['error']}\nCode: {code}") + return code +``` + +--- + +## Evaluation Strategies + +### Outcome-Based +Evaluate whether output achieves the expected result. + +```python +def evaluate_outcome(task: str, output: str, expected: str) -> str: + return llm(f"Does output achieve expected outcome? Task: {task}, Expected: {expected}, Output: {output}") +``` + +### LLM-as-Judge +Use LLM to compare and rank outputs. + +```python +def llm_judge(output_a: str, output_b: str, criteria: str) -> str: + return llm(f"Compare outputs A and B for {criteria}. Which is better and why?") +``` + +### Rubric-Based +Score outputs against weighted dimensions. + +```python +RUBRIC = { + "accuracy": {"weight": 0.4}, + "clarity": {"weight": 0.3}, + "completeness": {"weight": 0.3} +} + +def evaluate_with_rubric(output: str, rubric: dict) -> float: + scores = json.loads(llm(f"Rate 1-5 for each dimension: {list(rubric.keys())}\nOutput: {output}")) + return sum(scores[d] * rubric[d]["weight"] for d in rubric) / 5 +``` + +--- + +## Best Practices + +| Practice | Rationale | +|----------|-----------| +| **Clear criteria** | Define specific, measurable evaluation criteria upfront | +| **Iteration limits** | Set max iterations (3-5) to prevent infinite loops | +| **Convergence check** | Stop if output score isn't improving between iterations | +| **Log history** | Keep full trajectory for debugging and analysis | +| **Structured output** | Use JSON for reliable parsing of evaluation results | + +--- + +## Quick Start Checklist + +```markdown +## Evaluation Implementation Checklist + +### Setup +- [ ] Define evaluation criteria/rubric +- [ ] Set score threshold for "good enough" +- [ ] Configure max iterations (default: 3) + +### Implementation +- [ ] Implement generate() function +- [ ] Implement evaluate() function with structured output +- [ ] Implement optimize() function +- [ ] Wire up the refinement loop + +### Safety +- [ ] Add convergence detection +- [ ] Log all iterations for debugging +- [ ] Handle evaluation parse failures gracefully +``` diff --git a/.claude/skills/gh-address-comments/LICENSE.txt b/.claude/skills/gh-address-comments/LICENSE.txt new file mode 100644 index 0000000..7a4a3ea --- /dev/null +++ b/.claude/skills/gh-address-comments/LICENSE.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/.claude/skills/gh-address-comments/SKILL.md b/.claude/skills/gh-address-comments/SKILL.md new file mode 100644 index 0000000..0ee19e0 --- /dev/null +++ b/.claude/skills/gh-address-comments/SKILL.md @@ -0,0 +1,25 @@ +--- +name: gh-address-comments +description: Help address review/issue comments on the open GitHub PR for the current branch using gh CLI; verify gh auth first and prompt the user to authenticate if not logged in. +metadata: + short-description: Address comments in a GitHub PR review +--- + +# PR Comment Handler + +Guide to find the open PR for the current branch and address its comments with gh CLI. Run all `gh` commands with elevated network access. + +Prereq: ensure `gh` is authenticated (for example, run `gh auth login` once), then run `gh auth status` with escalated permissions (include workflow/repo scopes) so `gh` commands succeed. If sandboxing blocks `gh auth status`, rerun it with `sandbox_permissions=require_escalated`. + +## 1) Inspect comments needing attention +- Run scripts/fetch_comments.py which will print out all the comments and review threads on the PR + +## 2) Ask the user for clarification +- Number all the review threads and comments and provide a short summary of what would be required to apply a fix for it +- Ask the user which numbered comments should be addressed + +## 3) If user chooses comments +- Apply fixes for the selected comments + +Notes: +- If gh hits auth/rate issues mid-run, prompt the user to re-authenticate with `gh auth login`, then retry. diff --git a/.claude/skills/gh-address-comments/agents/openai.yaml b/.claude/skills/gh-address-comments/agents/openai.yaml new file mode 100644 index 0000000..92271e9 --- /dev/null +++ b/.claude/skills/gh-address-comments/agents/openai.yaml @@ -0,0 +1,6 @@ +interface: + display_name: "GitHub Address Comments" + short_description: Address comments in a GitHub PR review" + icon_small: "./assets/github-small.svg" + icon_large: "./assets/github.png" + default_prompt: "Address all actionable GitHub PR review comments in this branch and summarize the updates." diff --git a/.claude/skills/gh-address-comments/assets/github-small.svg b/.claude/skills/gh-address-comments/assets/github-small.svg new file mode 100644 index 0000000..828e9d9 --- /dev/null +++ b/.claude/skills/gh-address-comments/assets/github-small.svg @@ -0,0 +1,3 @@ + + + diff --git a/.claude/skills/gh-address-comments/assets/github.png b/.claude/skills/gh-address-comments/assets/github.png new file mode 100644 index 0000000000000000000000000000000000000000..e23dbe59099a594c06a46081ce48b82aa409b0e7 GIT binary patch literal 1838 zcmV+}2hsS6P)2#A0PhyV#70VIF~kifn7w^eg%YirL-chAh}v*xMV zq6%xyJk#$zy?_4s523;h{t6YYPziB{N{BO5LY$!z;tZ7#XQ+fYLnXu+Dk0ABcNiJI zzrVxB$H(Nw_w%r}wicF`m;e7dCb%Yq4S#)o4R?2U69V}D{rz3^9uTs%wH0=DcEaxN zZrI%1bWPG9Z@DZ;xVgCr_xJbV@$oU_0K^;~9!|a@MV{b>kf*1oaB*>wjf5r~92|tx z)6_@G59Hwl#r-r7fDUEfg~k4H9iyx z1ijjKVJj;uVSRl)cm-?gyS|utNQESXNVBxK!_+o@etyF9^YbL=8j>Im2x$`H4A#Wj zc=fHD@a(A~db17TglueV^!|Nn5a```z;l5dX%R{b4#C;kSz{uRpY`2n$m4KX14lC< zkRj2boJ3x2=pu#_Jzb)WCmEd{U##it>#Ona*=*_{ZIF75ZHKKdOoV9S4T-CWDx_Aj zzrQ~zz2ePCO0cm646&$5Ui>}p+2=i^(hqoVOX}W4h*UZNdfH2Mt`C-$mPEgg)S|ky zb$hi@M>;g_p0^ry7o;1@o>&aLnh?}U#e5VeL@OO&Y=NU@y|Hsf0~H|{W-W$pW{?1f zKnv|2i3ve6ER{dYLzaf+@UTakXB87-jYx4l#{%o&(G;`9gjl45$k?LpN`nB_lF8NB z5fLG=zg7#P<1x9txV#)7ZA=lH!X*$9Vv!Eg3PX1VYQO>NO0l#-sn2T zL@OmyjUpmM>vdo|#*{9Z1!QQYTdGw=glO%@BGlMPaB9$Yv1lH86Jjyu*pIQp;%#Li z#FFwLbuMoQtcnP+6qz7nj8a0Qg2hXe?wN=XYZ@Iw#?&Z>nzFXc zN^3#l?sp8kMfF&_!%Konr)5w8_9JUe9auX@ICYdmy(cTV_hOyepi%K+LbSEzX`r^e z&OeCb+aT%7iaF`ZX^EMxgu;jU6OW6F`v3@L46Rlgj=2>g0EP-`!rs-OApOZH)5R^Fzchbn&yDS1RiQBItZMy=vAd8L^CR*53EkdG?Gn}BOTJU zc^2;@1*}!iCSW5|Nnf%yAZ!A*p$$V1#>|mn=9yEf!lZp!*o3yLYuJ%R8p*-4TJ9gt zL-Izsu6bAxl6p?z5a=`aOM`5q#@kXW5g2-0QPkb+ATkKUNx*YSrD{?cb)=$8J!_G5 z_In>vx{%TlNI^(0$%x4Mp3+vX z5IBu-l0yt=OWm_CcBKtFK#hRc7^H=MTvSx52)r+f-!Vi=B+@d3dyHx#l~Y1R&0>&C zpHW)ypggHD)Ki?aJyhSAiw~BJ2}X?nY=SbS+dHqyiE1KHMH)Pej&k?WtMOx30UMS2 z^EoI5#6ij!>Xd pr_comments.json +""" + +from __future__ import annotations + +import json +import subprocess +import sys +from typing import Any + +QUERY = """\ +query( + $owner: String!, + $repo: String!, + $number: Int!, + $commentsCursor: String, + $reviewsCursor: String, + $threadsCursor: String +) { + repository(owner: $owner, name: $repo) { + pullRequest(number: $number) { + number + url + title + state + + # Top-level "Conversation" comments (issue comments on the PR) + comments(first: 100, after: $commentsCursor) { + pageInfo { hasNextPage endCursor } + nodes { + id + body + createdAt + updatedAt + author { login } + } + } + + # Review submissions (Approve / Request changes / Comment), with body if present + reviews(first: 100, after: $reviewsCursor) { + pageInfo { hasNextPage endCursor } + nodes { + id + state + body + submittedAt + author { login } + } + } + + # Inline review threads (grouped), includes resolved state + reviewThreads(first: 100, after: $threadsCursor) { + pageInfo { hasNextPage endCursor } + nodes { + id + isResolved + isOutdated + path + line + diffSide + startLine + startDiffSide + originalLine + originalStartLine + resolvedBy { login } + comments(first: 100) { + nodes { + id + body + createdAt + updatedAt + author { login } + } + } + } + } + } + } +} +""" + + +def _run(cmd: list[str], stdin: str | None = None) -> str: + p = subprocess.run(cmd, input=stdin, capture_output=True, text=True) + if p.returncode != 0: + raise RuntimeError(f"Command failed: {' '.join(cmd)}\n{p.stderr}") + return p.stdout + + +def _run_json(cmd: list[str], stdin: str | None = None) -> dict[str, Any]: + out = _run(cmd, stdin=stdin) + try: + return json.loads(out) + except json.JSONDecodeError as e: + raise RuntimeError(f"Failed to parse JSON from command output: {e}\nRaw:\n{out}") from e + + +def _ensure_gh_authenticated() -> None: + try: + _run(["gh", "auth", "status"]) + except RuntimeError: + print("run `gh auth login` to authenticate the GitHub CLI", file=sys.stderr) + raise RuntimeError("gh auth status failed; run `gh auth login` to authenticate the GitHub CLI") from None + + +def gh_pr_view_json(fields: str) -> dict[str, Any]: + # fields is a comma-separated list like: "number,headRepositoryOwner,headRepository" + return _run_json(["gh", "pr", "view", "--json", fields]) + + +def get_current_pr_ref() -> tuple[str, str, int]: + """ + Resolve the PR for the current branch (whatever gh considers associated). + Works for cross-repo PRs too, by reading head repository owner/name. + """ + pr = gh_pr_view_json("number,headRepositoryOwner,headRepository") + owner = pr["headRepositoryOwner"]["login"] + repo = pr["headRepository"]["name"] + number = int(pr["number"]) + return owner, repo, number + + +def gh_api_graphql( + owner: str, + repo: str, + number: int, + comments_cursor: str | None = None, + reviews_cursor: str | None = None, + threads_cursor: str | None = None, +) -> dict[str, Any]: + """ + Call `gh api graphql` using -F variables, avoiding JSON blobs with nulls. + Query is passed via stdin using query=@- to avoid shell newline/quoting issues. + """ + cmd = [ + "gh", + "api", + "graphql", + "-F", + "query=@-", + "-F", + f"owner={owner}", + "-F", + f"repo={repo}", + "-F", + f"number={number}", + ] + if comments_cursor: + cmd += ["-F", f"commentsCursor={comments_cursor}"] + if reviews_cursor: + cmd += ["-F", f"reviewsCursor={reviews_cursor}"] + if threads_cursor: + cmd += ["-F", f"threadsCursor={threads_cursor}"] + + return _run_json(cmd, stdin=QUERY) + + +def fetch_all(owner: str, repo: str, number: int) -> dict[str, Any]: + conversation_comments: list[dict[str, Any]] = [] + reviews: list[dict[str, Any]] = [] + review_threads: list[dict[str, Any]] = [] + + comments_cursor: str | None = None + reviews_cursor: str | None = None + threads_cursor: str | None = None + + pr_meta: dict[str, Any] | None = None + + while True: + payload = gh_api_graphql( + owner=owner, + repo=repo, + number=number, + comments_cursor=comments_cursor, + reviews_cursor=reviews_cursor, + threads_cursor=threads_cursor, + ) + + if "errors" in payload and payload["errors"]: + raise RuntimeError(f"GitHub GraphQL errors:\n{json.dumps(payload['errors'], indent=2)}") + + pr = payload["data"]["repository"]["pullRequest"] + if pr_meta is None: + pr_meta = { + "number": pr["number"], + "url": pr["url"], + "title": pr["title"], + "state": pr["state"], + "owner": owner, + "repo": repo, + } + + c = pr["comments"] + r = pr["reviews"] + t = pr["reviewThreads"] + + conversation_comments.extend(c.get("nodes") or []) + reviews.extend(r.get("nodes") or []) + review_threads.extend(t.get("nodes") or []) + + comments_cursor = c["pageInfo"]["endCursor"] if c["pageInfo"]["hasNextPage"] else None + reviews_cursor = r["pageInfo"]["endCursor"] if r["pageInfo"]["hasNextPage"] else None + threads_cursor = t["pageInfo"]["endCursor"] if t["pageInfo"]["hasNextPage"] else None + + if not (comments_cursor or reviews_cursor or threads_cursor): + break + + assert pr_meta is not None + return { + "pull_request": pr_meta, + "conversation_comments": conversation_comments, + "reviews": reviews, + "review_threads": review_threads, + } + + +def main() -> None: + _ensure_gh_authenticated() + owner, repo, number = get_current_pr_ref() + result = fetch_all(owner, repo, number) + print(json.dumps(result, indent=2)) + + +if __name__ == "__main__": + main() diff --git a/.claude/skills/gh-fix-ci/LICENSE.txt b/.claude/skills/gh-fix-ci/LICENSE.txt new file mode 100644 index 0000000..13e25df --- /dev/null +++ b/.claude/skills/gh-fix-ci/LICENSE.txt @@ -0,0 +1,201 @@ +Apache License +Version 2.0, January 2004 +http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf of + any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don\'t include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/.claude/skills/gh-fix-ci/SKILL.md b/.claude/skills/gh-fix-ci/SKILL.md new file mode 100644 index 0000000..76bdeb6 --- /dev/null +++ b/.claude/skills/gh-fix-ci/SKILL.md @@ -0,0 +1,69 @@ +--- +name: "gh-fix-ci" +description: "Use when a user asks to debug or fix failing GitHub PR checks that run in GitHub Actions; use `gh` to inspect checks and logs, summarize failure context, draft a fix plan, and implement only after explicit approval. Treat external providers (for example Buildkite) as out of scope and report only the details URL." +--- + + +# Gh Pr Checks Plan Fix + +## Overview + +Use gh to locate failing PR checks, fetch GitHub Actions logs for actionable failures, summarize the failure snippet, then propose a fix plan and implement after explicit approval. +- If a plan-oriented skill (for example `create-plan`) is available, use it; otherwise draft a concise plan inline and request approval before implementing. + +Prereq: authenticate with the standard GitHub CLI once (for example, run `gh auth login`), then confirm with `gh auth status` (repo + workflow scopes are typically required). + +## Inputs + +- `repo`: path inside the repo (default `.`) +- `pr`: PR number or URL (optional; defaults to current branch PR) +- `gh` authentication for the repo host + +## Quick start + +- `python "/scripts/inspect_pr_checks.py" --repo "." --pr ""` +- Add `--json` if you want machine-friendly output for summarization. + +## Workflow + +1. Verify gh authentication. + - Run `gh auth status` in the repo. + - If unauthenticated, ask the user to run `gh auth login` (ensuring repo + workflow scopes) before proceeding. +2. Resolve the PR. + - Prefer the current branch PR: `gh pr view --json number,url`. + - If the user provides a PR number or URL, use that directly. +3. Inspect failing checks (GitHub Actions only). + - Preferred: run the bundled script (handles gh field drift and job-log fallbacks): + - `python "/scripts/inspect_pr_checks.py" --repo "." --pr ""` + - Add `--json` for machine-friendly output. + - Manual fallback: + - `gh pr checks --json name,state,bucket,link,startedAt,completedAt,workflow` + - If a field is rejected, rerun with the available fields reported by `gh`. + - For each failing check, extract the run id from `detailsUrl` and run: + - `gh run view --json name,workflowName,conclusion,status,url,event,headBranch,headSha` + - `gh run view --log` + - If the run log says it is still in progress, fetch job logs directly: + - `gh api "/repos///actions/jobs//logs" > ""` +4. Scope non-GitHub Actions checks. + - If `detailsUrl` is not a GitHub Actions run, label it as external and only report the URL. + - Do not attempt Buildkite or other providers; keep the workflow lean. +5. Summarize failures for the user. + - Provide the failing check name, run URL (if any), and a concise log snippet. + - Call out missing logs explicitly. +6. Create a plan. + - Use the `create-plan` skill to draft a concise plan and request approval. +7. Implement after approval. + - Apply the approved plan, summarize diffs/tests, and ask about opening a PR. +8. Recheck status. + - After changes, suggest re-running the relevant tests and `gh pr checks` to confirm. + +## Bundled Resources + +### scripts/inspect_pr_checks.py + +Fetch failing PR checks, pull GitHub Actions logs, and extract a failure snippet. Exits non-zero when failures remain so it can be used in automation. + +Usage examples: +- `python "/scripts/inspect_pr_checks.py" --repo "." --pr "123"` +- `python "/scripts/inspect_pr_checks.py" --repo "." --pr "https://github.com/org/repo/pull/123" --json` +- `python "/scripts/inspect_pr_checks.py" --repo "." --max-lines 200 --context 40` diff --git a/.claude/skills/gh-fix-ci/agents/openai.yaml b/.claude/skills/gh-fix-ci/agents/openai.yaml new file mode 100644 index 0000000..262bd70 --- /dev/null +++ b/.claude/skills/gh-fix-ci/agents/openai.yaml @@ -0,0 +1,6 @@ +interface: + display_name: "GitHub Fix CI" + short_description: "Debug failing GitHub Actions CI" + icon_small: "./assets/github-small.svg" + icon_large: "./assets/github.png" + default_prompt: "Inspect failing GitHub Actions checks in this repo, summarize root cause, and propose a focused fix plan." diff --git a/.claude/skills/gh-fix-ci/assets/github-small.svg b/.claude/skills/gh-fix-ci/assets/github-small.svg new file mode 100644 index 0000000..828e9d9 --- /dev/null +++ b/.claude/skills/gh-fix-ci/assets/github-small.svg @@ -0,0 +1,3 @@ + + + diff --git a/.claude/skills/gh-fix-ci/assets/github.png b/.claude/skills/gh-fix-ci/assets/github.png new file mode 100644 index 0000000000000000000000000000000000000000..e23dbe59099a594c06a46081ce48b82aa409b0e7 GIT binary patch literal 1838 zcmV+}2hsS6P)2#A0PhyV#70VIF~kifn7w^eg%YirL-chAh}v*xMV zq6%xyJk#$zy?_4s523;h{t6YYPziB{N{BO5LY$!z;tZ7#XQ+fYLnXu+Dk0ABcNiJI zzrVxB$H(Nw_w%r}wicF`m;e7dCb%Yq4S#)o4R?2U69V}D{rz3^9uTs%wH0=DcEaxN zZrI%1bWPG9Z@DZ;xVgCr_xJbV@$oU_0K^;~9!|a@MV{b>kf*1oaB*>wjf5r~92|tx z)6_@G59Hwl#r-r7fDUEfg~k4H9iyx z1ijjKVJj;uVSRl)cm-?gyS|utNQESXNVBxK!_+o@etyF9^YbL=8j>Im2x$`H4A#Wj zc=fHD@a(A~db17TglueV^!|Nn5a```z;l5dX%R{b4#C;kSz{uRpY`2n$m4KX14lC< zkRj2boJ3x2=pu#_Jzb)WCmEd{U##it>#Ona*=*_{ZIF75ZHKKdOoV9S4T-CWDx_Aj zzrQ~zz2ePCO0cm646&$5Ui>}p+2=i^(hqoVOX}W4h*UZNdfH2Mt`C-$mPEgg)S|ky zb$hi@M>;g_p0^ry7o;1@o>&aLnh?}U#e5VeL@OO&Y=NU@y|Hsf0~H|{W-W$pW{?1f zKnv|2i3ve6ER{dYLzaf+@UTakXB87-jYx4l#{%o&(G;`9gjl45$k?LpN`nB_lF8NB z5fLG=zg7#P<1x9txV#)7ZA=lH!X*$9Vv!Eg3PX1VYQO>NO0l#-sn2T zL@OmyjUpmM>vdo|#*{9Z1!QQYTdGw=glO%@BGlMPaB9$Yv1lH86Jjyu*pIQp;%#Li z#FFwLbuMoQtcnP+6qz7nj8a0Qg2hXe?wN=XYZ@Iw#?&Z>nzFXc zN^3#l?sp8kMfF&_!%Konr)5w8_9JUe9auX@ICYdmy(cTV_hOyepi%K+LbSEzX`r^e z&OeCb+aT%7iaF`ZX^EMxgu;jU6OW6F`v3@L46Rlgj=2>g0EP-`!rs-OApOZH)5R^Fzchbn&yDS1RiQBItZMy=vAd8L^CR*53EkdG?Gn}BOTJU zc^2;@1*}!iCSW5|Nnf%yAZ!A*p$$V1#>|mn=9yEf!lZp!*o3yLYuJ%R8p*-4TJ9gt zL-Izsu6bAxl6p?z5a=`aOM`5q#@kXW5g2-0QPkb+ATkKUNx*YSrD{?cb)=$8J!_G5 z_In>vx{%TlNI^(0$%x4Mp3+vX z5IBu-l0yt=OWm_CcBKtFK#hRc7^H=MTvSx52)r+f-!Vi=B+@d3dyHx#l~Y1R&0>&C zpHW)ypggHD)Ki?aJyhSAiw~BJ2}X?nY=SbS+dHqyiE1KHMH)Pej&k?WtMOx30UMS2 z^EoI5#6ij!>Xd GhResult: + process = subprocess.run( + ["gh", *args], + cwd=cwd, + text=True, + capture_output=True, + ) + return GhResult(process.returncode, process.stdout, process.stderr) + + +def run_gh_command_raw(args: Sequence[str], cwd: Path) -> tuple[int, bytes, str]: + process = subprocess.run( + ["gh", *args], + cwd=cwd, + capture_output=True, + ) + stderr = process.stderr.decode(errors="replace") + return process.returncode, process.stdout, stderr + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser( + description=( + "Inspect failing GitHub PR checks, fetch GitHub Actions logs, and extract a " + "failure snippet." + ), + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + ) + parser.add_argument("--repo", default=".", help="Path inside the target Git repository.") + parser.add_argument( + "--pr", default=None, help="PR number or URL (defaults to current branch PR)." + ) + parser.add_argument("--max-lines", type=int, default=DEFAULT_MAX_LINES) + parser.add_argument("--context", type=int, default=DEFAULT_CONTEXT_LINES) + parser.add_argument("--json", action="store_true", help="Emit JSON instead of text output.") + return parser.parse_args() + + +def main() -> int: + args = parse_args() + repo_root = find_git_root(Path(args.repo)) + if repo_root is None: + print("Error: not inside a Git repository.", file=sys.stderr) + return 1 + + if not ensure_gh_available(repo_root): + return 1 + + pr_value = resolve_pr(args.pr, repo_root) + if pr_value is None: + return 1 + + checks = fetch_checks(pr_value, repo_root) + if checks is None: + return 1 + + failing = [c for c in checks if is_failing(c)] + if not failing: + print(f"PR #{pr_value}: no failing checks detected.") + return 0 + + results = [] + for check in failing: + results.append( + analyze_check( + check, + repo_root=repo_root, + max_lines=max(1, args.max_lines), + context=max(1, args.context), + ) + ) + + if args.json: + print(json.dumps({"pr": pr_value, "results": results}, indent=2)) + else: + render_results(pr_value, results) + + return 1 + + +def find_git_root(start: Path) -> Path | None: + result = subprocess.run( + ["git", "rev-parse", "--show-toplevel"], + cwd=start, + text=True, + capture_output=True, + ) + if result.returncode != 0: + return None + return Path(result.stdout.strip()) + + +def ensure_gh_available(repo_root: Path) -> bool: + if which("gh") is None: + print("Error: gh is not installed or not on PATH.", file=sys.stderr) + return False + result = run_gh_command(["auth", "status"], cwd=repo_root) + if result.returncode == 0: + return True + message = (result.stderr or result.stdout or "").strip() + print(message or "Error: gh not authenticated.", file=sys.stderr) + return False + + +def resolve_pr(pr_value: str | None, repo_root: Path) -> str | None: + if pr_value: + return pr_value + result = run_gh_command(["pr", "view", "--json", "number"], cwd=repo_root) + if result.returncode != 0: + message = (result.stderr or result.stdout or "").strip() + print(message or "Error: unable to resolve PR.", file=sys.stderr) + return None + try: + data = json.loads(result.stdout or "{}") + except json.JSONDecodeError: + print("Error: unable to parse PR JSON.", file=sys.stderr) + return None + number = data.get("number") + if not number: + print("Error: no PR number found.", file=sys.stderr) + return None + return str(number) + + +def fetch_checks(pr_value: str, repo_root: Path) -> list[dict[str, Any]] | None: + primary_fields = ["name", "state", "conclusion", "detailsUrl", "startedAt", "completedAt"] + result = run_gh_command( + ["pr", "checks", pr_value, "--json", ",".join(primary_fields)], + cwd=repo_root, + ) + if result.returncode != 0: + message = "\n".join(filter(None, [result.stderr, result.stdout])).strip() + available_fields = parse_available_fields(message) + if available_fields: + fallback_fields = [ + "name", + "state", + "bucket", + "link", + "startedAt", + "completedAt", + "workflow", + ] + selected_fields = [field for field in fallback_fields if field in available_fields] + if not selected_fields: + print("Error: no usable fields available for gh pr checks.", file=sys.stderr) + return None + result = run_gh_command( + ["pr", "checks", pr_value, "--json", ",".join(selected_fields)], + cwd=repo_root, + ) + if result.returncode != 0: + message = (result.stderr or result.stdout or "").strip() + print(message or "Error: gh pr checks failed.", file=sys.stderr) + return None + else: + print(message or "Error: gh pr checks failed.", file=sys.stderr) + return None + try: + data = json.loads(result.stdout or "[]") + except json.JSONDecodeError: + print("Error: unable to parse checks JSON.", file=sys.stderr) + return None + if not isinstance(data, list): + print("Error: unexpected checks JSON shape.", file=sys.stderr) + return None + return data + + +def is_failing(check: dict[str, Any]) -> bool: + conclusion = normalize_field(check.get("conclusion")) + if conclusion in FAILURE_CONCLUSIONS: + return True + state = normalize_field(check.get("state") or check.get("status")) + if state in FAILURE_STATES: + return True + bucket = normalize_field(check.get("bucket")) + return bucket in FAILURE_BUCKETS + + +def analyze_check( + check: dict[str, Any], + repo_root: Path, + max_lines: int, + context: int, +) -> dict[str, Any]: + url = check.get("detailsUrl") or check.get("link") or "" + run_id = extract_run_id(url) + job_id = extract_job_id(url) + base: dict[str, Any] = { + "name": check.get("name", ""), + "detailsUrl": url, + "runId": run_id, + "jobId": job_id, + } + + if run_id is None: + base["status"] = "external" + base["note"] = "No GitHub Actions run id detected in detailsUrl." + return base + + metadata = fetch_run_metadata(run_id, repo_root) + log_text, log_error, log_status = fetch_check_log( + run_id=run_id, + job_id=job_id, + repo_root=repo_root, + ) + + if log_status == "pending": + base["status"] = "log_pending" + base["note"] = log_error or "Logs are not available yet." + if metadata: + base["run"] = metadata + return base + + if log_error: + base["status"] = "log_unavailable" + base["error"] = log_error + if metadata: + base["run"] = metadata + return base + + snippet = extract_failure_snippet(log_text, max_lines=max_lines, context=context) + base["status"] = "ok" + base["run"] = metadata or {} + base["logSnippet"] = snippet + base["logTail"] = tail_lines(log_text, max_lines) + return base + + +def extract_run_id(url: str) -> str | None: + if not url: + return None + for pattern in (r"/actions/runs/(\d+)", r"/runs/(\d+)"): + match = re.search(pattern, url) + if match: + return match.group(1) + return None + + +def extract_job_id(url: str) -> str | None: + if not url: + return None + match = re.search(r"/actions/runs/\d+/job/(\d+)", url) + if match: + return match.group(1) + match = re.search(r"/job/(\d+)", url) + if match: + return match.group(1) + return None + + +def fetch_run_metadata(run_id: str, repo_root: Path) -> dict[str, Any] | None: + fields = [ + "conclusion", + "status", + "workflowName", + "name", + "event", + "headBranch", + "headSha", + "url", + ] + result = run_gh_command(["run", "view", run_id, "--json", ",".join(fields)], cwd=repo_root) + if result.returncode != 0: + return None + try: + data = json.loads(result.stdout or "{}") + except json.JSONDecodeError: + return None + if not isinstance(data, dict): + return None + return data + + +def fetch_check_log( + run_id: str, + job_id: str | None, + repo_root: Path, +) -> tuple[str, str, str]: + log_text, log_error = fetch_run_log(run_id, repo_root) + if not log_error: + return log_text, "", "ok" + + if is_log_pending_message(log_error) and job_id: + job_log, job_error = fetch_job_log(job_id, repo_root) + if job_log: + return job_log, "", "ok" + if job_error and is_log_pending_message(job_error): + return "", job_error, "pending" + if job_error: + return "", job_error, "error" + return "", log_error, "pending" + + if is_log_pending_message(log_error): + return "", log_error, "pending" + + return "", log_error, "error" + + +def fetch_run_log(run_id: str, repo_root: Path) -> tuple[str, str]: + result = run_gh_command(["run", "view", run_id, "--log"], cwd=repo_root) + if result.returncode != 0: + error = (result.stderr or result.stdout or "").strip() + return "", error or "gh run view failed" + return result.stdout, "" + + +def fetch_job_log(job_id: str, repo_root: Path) -> tuple[str, str]: + repo_slug = fetch_repo_slug(repo_root) + if not repo_slug: + return "", "Error: unable to resolve repository name for job logs." + endpoint = f"/repos/{repo_slug}/actions/jobs/{job_id}/logs" + returncode, stdout_bytes, stderr = run_gh_command_raw(["api", endpoint], cwd=repo_root) + if returncode != 0: + message = (stderr or stdout_bytes.decode(errors="replace")).strip() + return "", message or "gh api job logs failed" + if is_zip_payload(stdout_bytes): + return "", "Job logs returned a zip archive; unable to parse." + return stdout_bytes.decode(errors="replace"), "" + + +def fetch_repo_slug(repo_root: Path) -> str | None: + result = run_gh_command(["repo", "view", "--json", "nameWithOwner"], cwd=repo_root) + if result.returncode != 0: + return None + try: + data = json.loads(result.stdout or "{}") + except json.JSONDecodeError: + return None + name_with_owner = data.get("nameWithOwner") + if not name_with_owner: + return None + return str(name_with_owner) + + +def normalize_field(value: Any) -> str: + if value is None: + return "" + return str(value).strip().lower() + + +def parse_available_fields(message: str) -> list[str]: + if "Available fields:" not in message: + return [] + fields: list[str] = [] + collecting = False + for line in message.splitlines(): + if "Available fields:" in line: + collecting = True + continue + if not collecting: + continue + field = line.strip() + if not field: + continue + fields.append(field) + return fields + + +def is_log_pending_message(message: str) -> bool: + lowered = message.lower() + return any(marker in lowered for marker in PENDING_LOG_MARKERS) + + +def is_zip_payload(payload: bytes) -> bool: + return payload.startswith(b"PK") + + +def extract_failure_snippet(log_text: str, max_lines: int, context: int) -> str: + lines = log_text.splitlines() + if not lines: + return "" + + marker_index = find_failure_index(lines) + if marker_index is None: + return "\n".join(lines[-max_lines:]) + + start = max(0, marker_index - context) + end = min(len(lines), marker_index + context) + window = lines[start:end] + if len(window) > max_lines: + window = window[-max_lines:] + return "\n".join(window) + + +def find_failure_index(lines: Sequence[str]) -> int | None: + for idx in range(len(lines) - 1, -1, -1): + lowered = lines[idx].lower() + if any(marker in lowered for marker in FAILURE_MARKERS): + return idx + return None + + +def tail_lines(text: str, max_lines: int) -> str: + if max_lines <= 0: + return "" + lines = text.splitlines() + return "\n".join(lines[-max_lines:]) + + +def render_results(pr_number: str, results: Iterable[dict[str, Any]]) -> None: + results_list = list(results) + print(f"PR #{pr_number}: {len(results_list)} failing checks analyzed.") + for result in results_list: + print("-" * 60) + print(f"Check: {result.get('name', '')}") + if result.get("detailsUrl"): + print(f"Details: {result['detailsUrl']}") + run_id = result.get("runId") + if run_id: + print(f"Run ID: {run_id}") + job_id = result.get("jobId") + if job_id: + print(f"Job ID: {job_id}") + status = result.get("status", "unknown") + print(f"Status: {status}") + + run_meta = result.get("run", {}) + if run_meta: + branch = run_meta.get("headBranch", "") + sha = (run_meta.get("headSha") or "")[:12] + workflow = run_meta.get("workflowName") or run_meta.get("name") or "" + conclusion = run_meta.get("conclusion") or run_meta.get("status") or "" + print(f"Workflow: {workflow} ({conclusion})") + if branch or sha: + print(f"Branch/SHA: {branch} {sha}") + if run_meta.get("url"): + print(f"Run URL: {run_meta['url']}") + + if result.get("note"): + print(f"Note: {result['note']}") + + if result.get("error"): + print(f"Error fetching logs: {result['error']}") + continue + + snippet = result.get("logSnippet") or "" + if snippet: + print("Failure snippet:") + print(indent_block(snippet, prefix=" ")) + else: + print("No snippet available.") + print("-" * 60) + + +def indent_block(text: str, prefix: str = " ") -> str: + return "\n".join(f"{prefix}{line}" for line in text.splitlines()) + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/.claude/skills/git-commit/SKILL.md b/.claude/skills/git-commit/SKILL.md new file mode 100644 index 0000000..c35f13b --- /dev/null +++ b/.claude/skills/git-commit/SKILL.md @@ -0,0 +1,124 @@ +--- +name: git-commit +description: 'Execute git commit with conventional commit message analysis, intelligent staging, and message generation. Use when user asks to commit changes, create a git commit, or mentions "/commit". Supports: (1) Auto-detecting type and scope from changes, (2) Generating conventional commit messages from diff, (3) Interactive commit with optional type/scope/description overrides, (4) Intelligent file staging for logical grouping' +license: MIT +allowed-tools: Bash +--- + +# Git Commit with Conventional Commits + +## Overview + +Create standardized, semantic git commits using the Conventional Commits specification. Analyze the actual diff to determine appropriate type, scope, and message. + +## Conventional Commit Format + +``` +[optional scope]: + +[optional body] + +[optional footer(s)] +``` + +## Commit Types + +| Type | Purpose | +| ---------- | ------------------------------ | +| `feat` | New feature | +| `fix` | Bug fix | +| `docs` | Documentation only | +| `style` | Formatting/style (no logic) | +| `refactor` | Code refactor (no feature/fix) | +| `perf` | Performance improvement | +| `test` | Add/update tests | +| `build` | Build system/dependencies | +| `ci` | CI/config changes | +| `chore` | Maintenance/misc | +| `revert` | Revert commit | + +## Breaking Changes + +``` +# Exclamation mark after type/scope +feat!: remove deprecated endpoint + +# BREAKING CHANGE footer +feat: allow config to extend other configs + +BREAKING CHANGE: `extends` key behavior changed +``` + +## Workflow + +### 1. Analyze Diff + +```bash +# If files are staged, use staged diff +git diff --staged + +# If nothing staged, use working tree diff +git diff + +# Also check status +git status --porcelain +``` + +### 2. Stage Files (if needed) + +If nothing is staged or you want to group changes differently: + +```bash +# Stage specific files +git add path/to/file1 path/to/file2 + +# Stage by pattern +git add *.test.* +git add src/components/* + +# Interactive staging +git add -p +``` + +**Never commit secrets** (.env, credentials.json, private keys). + +### 3. Generate Commit Message + +Analyze the diff to determine: + +- **Type**: What kind of change is this? +- **Scope**: What area/module is affected? +- **Description**: One-line summary of what changed (present tense, imperative mood, <72 chars) + +### 4. Execute Commit + +```bash +# Single line +git commit -m "[scope]: " + +# Multi-line with body/footer +git commit -m "$(cat <<'EOF' +[scope]: + + + + +EOF +)" +``` + +## Best Practices + +- One logical change per commit +- Present tense: "add" not "added" +- Imperative mood: "fix bug" not "fixes bug" +- Reference issues: `Closes #123`, `Refs #456` +- Keep description under 72 characters + +## Git Safety Protocol + +- NEVER update git config +- NEVER run destructive commands (--force, hard reset) without explicit request +- NEVER skip hooks (--no-verify) unless user asks +- NEVER force push to main/master +- If commit fails due to hooks, fix and create NEW commit (don't amend) diff --git a/.claude/skills/github-issues/SKILL.md b/.claude/skills/github-issues/SKILL.md new file mode 100644 index 0000000..c2e8865 --- /dev/null +++ b/.claude/skills/github-issues/SKILL.md @@ -0,0 +1,132 @@ +--- +name: github-issues +description: 'Create, update, and manage GitHub issues using MCP tools. Use this skill when users want to create bug reports, feature requests, or task issues, update existing issues, add labels/assignees/milestones, or manage issue workflows. Triggers on requests like "create an issue", "file a bug", "request a feature", "update issue X", or any GitHub issue management task.' +--- + +# GitHub Issues + +Manage GitHub issues using the `@modelcontextprotocol/server-github` MCP server. + +## Available MCP Tools + +| Tool | Purpose | +|------|---------| +| `mcp__github__create_issue` | Create new issues | +| `mcp__github__update_issue` | Update existing issues | +| `mcp__github__get_issue` | Fetch issue details | +| `mcp__github__search_issues` | Search issues | +| `mcp__github__add_issue_comment` | Add comments | +| `mcp__github__list_issues` | List repository issues | + +## Workflow + +1. **Determine action**: Create, update, or query? +2. **Gather context**: Get repo info, existing labels, milestones if needed +3. **Structure content**: Use appropriate template from [references/templates.md](references/templates.md) +4. **Execute**: Call the appropriate MCP tool +5. **Confirm**: Report the issue URL to user + +## Creating Issues + +### Required Parameters + +``` +owner: repository owner (org or user) +repo: repository name +title: clear, actionable title +body: structured markdown content +``` + +### Optional Parameters + +``` +labels: ["bug", "enhancement", "documentation", ...] +assignees: ["username1", "username2"] +milestone: milestone number (integer) +``` + +### Title Guidelines + +- Start with type prefix when useful: `[Bug]`, `[Feature]`, `[Docs]` +- Be specific and actionable +- Keep under 72 characters +- Examples: + - `[Bug] Login fails with SSO enabled` + - `[Feature] Add dark mode support` + - `Add unit tests for auth module` + +### Body Structure + +Always use the templates in [references/templates.md](references/templates.md). Choose based on issue type: + +| User Request | Template | +|--------------|----------| +| Bug, error, broken, not working | Bug Report | +| Feature, enhancement, add, new | Feature Request | +| Task, chore, refactor, update | Task | + +## Updating Issues + +Use `mcp__github__update_issue` with: + +``` +owner, repo, issue_number (required) +title, body, state, labels, assignees, milestone (optional - only changed fields) +``` + +State values: `open`, `closed` + +## Examples + +### Example 1: Bug Report + +**User**: "Create a bug issue - the login page crashes when using SSO" + +**Action**: Call `mcp__github__create_issue` with: +```json +{ + "owner": "github", + "repo": "awesome-copilot", + "title": "[Bug] Login page crashes when using SSO", + "body": "## Description\nThe login page crashes when users attempt to authenticate using SSO.\n\n## Steps to Reproduce\n1. Navigate to login page\n2. Click 'Sign in with SSO'\n3. Page crashes\n\n## Expected Behavior\nSSO authentication should complete and redirect to dashboard.\n\n## Actual Behavior\nPage becomes unresponsive and displays error.\n\n## Environment\n- Browser: [To be filled]\n- OS: [To be filled]\n\n## Additional Context\nReported by user.", + "labels": ["bug"] +} +``` + +### Example 2: Feature Request + +**User**: "Create a feature request for dark mode with high priority" + +**Action**: Call `mcp__github__create_issue` with: +```json +{ + "owner": "github", + "repo": "awesome-copilot", + "title": "[Feature] Add dark mode support", + "body": "## Summary\nAdd dark mode theme option for improved user experience and accessibility.\n\n## Motivation\n- Reduces eye strain in low-light environments\n- Increasingly expected by users\n- Improves accessibility\n\n## Proposed Solution\nImplement theme toggle with system preference detection.\n\n## Acceptance Criteria\n- [ ] Toggle switch in settings\n- [ ] Persists user preference\n- [ ] Respects system preference by default\n- [ ] All UI components support both themes\n\n## Alternatives Considered\nNone specified.\n\n## Additional Context\nHigh priority request.", + "labels": ["enhancement", "high-priority"] +} +``` + +## Common Labels + +Use these standard labels when applicable: + +| Label | Use For | +|-------|---------| +| `bug` | Something isn't working | +| `enhancement` | New feature or improvement | +| `documentation` | Documentation updates | +| `good first issue` | Good for newcomers | +| `help wanted` | Extra attention needed | +| `question` | Further information requested | +| `wontfix` | Will not be addressed | +| `duplicate` | Already exists | +| `high-priority` | Urgent issues | + +## Tips + +- Always confirm the repository context before creating issues +- Ask for missing critical information rather than guessing +- Link related issues when known: `Related to #123` +- For updates, fetch current issue first to preserve unchanged fields diff --git a/.gitignore b/.gitignore index bff82c3..4035122 100644 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,6 @@ __pycache__/ *.db *.sqlite3 *.log -site/ \ No newline at end of file +site/ +.aam +.agent-packages/ \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 984ea92..9700088 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,27 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.7.0-draft] — 2026-02-23 + +### Added +- §7 Rules: Full Universal Agent Rule Specification (UARS v1.3) — replaces the previous Cursor-centric stub +- Two CLI namespaces for rules: `aam pkg rules` (package authoring) and `aam convert rules` (standalone conversion) +- `aam pkg rules compile/import/diff/clean/list` sub-commands +- `aam convert rules -s -t ` with standardised flags (`-s`, `-t`, `--type`, `--dry-run`, `--force`, `--verbose`) +- UARS `apply.mode` enum: `always` | `intelligent` | `files` | `manual` +- Mode mapping table and per-platform transformation tables for all four platforms +- Conflict detection with generation guards and `.aam/conflicts/` pending files +- Idempotency guarantee and watch mode for `aam pkg rules compile` +- AGENTS.md merge strategy (directory inference, root aggregation, guard-block management) +- Codex `AGENTS.override.md` support via `agents.codex.override: true` +- Validation rules, error exit codes (0–4), and worked migration examples +- `docs/schemas/rule-frontmatter.schema.json` updated to UARS v1.3 (`name`, `apply.*`, `agents.*`, `refs`) + +### Changed +- `agents.*` override block now limited to platform-specific capabilities only (`enabled`, `alwaysApply`/`priority` for Cursor, `override` for Codex); scope/path fields removed +- Standalone conversion command renamed from `aam converter rules` → `aam convert rules` +- Flags renamed: `--from`/`--to` → `--source-platform`/`-s` and `--target-platform`/`-t` + ## [0.5.0-draft] — 2026-02-22 ### Added From ccd1e4a2c01d21ff1714cceb2d4ecd6e4d01fe43 Mon Sep 17 00:00:00 2001 From: Roman Marek~ Date: Fri, 13 Mar 2026 01:20:59 +0100 Subject: [PATCH 3/4] Enhance documentation and tooling for UAAPS - Updated hooks documentation to clarify Copilot and Cursor event mappings and confidence levels. - Revised compatibility matrix to reflect changes in skills, agents, rules, and hooks support across platforms. - Added migration details for converting UAAPS packages to Codex-native structure, including output layout and artifact mapping. - Improved packaging guidelines to ensure deterministic archive builds and proper file selection. - Expanded dependency management to include tooling groups and feature selectors for enhanced package functionality. - Introduced structured source metadata in lock files for better package resolution tracking. - Strengthened validation rules for package manifests, including checks for extras and dependency groups. - Updated security guidelines to emphasize package contents and permissions management. - Created GitHub issue templates for bug reports, feature requests, and tasks to streamline issue tracking. - Added a pre-commit documentation guard agent to ensure changelog and README accuracy before commits. --- .aam/aam-lock.yaml | 85 +- .../github-issues/references/templates.md | 90 ++ .github/agents/pre-commit-docs.agent.md | 42 + CHANGELOG.md | 45 + README.md | 8 +- docs/changelog.md | 73 ++ docs/schemas/package-lock.schema.json | 91 +- docs/schemas/package-manifest.schema.json | 179 +++- docs/schemas/rule-frontmatter.schema.json | 116 ++- docs/spec/01-package-format.md | 12 +- docs/spec/02-manifest.md | 163 +++- docs/spec/03-skills.md | 16 +- docs/spec/04-commands.md | 6 +- docs/spec/05-agents.md | 2 +- docs/spec/06-rules.md | 872 +++++++++++++++++- docs/spec/07-hooks.md | 6 +- docs/spec/09-compatibility.md | 14 +- docs/spec/10-migration.md | 117 ++- docs/spec/11-packaging.md | 44 +- docs/spec/12-dependencies.md | 201 +++- docs/spec/15-validation.md | 8 +- docs/spec/16-security.md | 60 +- 22 files changed, 2124 insertions(+), 126 deletions(-) create mode 100644 .claude/skills/github-issues/references/templates.md create mode 100644 .github/agents/pre-commit-docs.agent.md diff --git a/.aam/aam-lock.yaml b/.aam/aam-lock.yaml index ca8fece..75fda1d 100644 --- a/.aam/aam-lock.yaml +++ b/.aam/aam-lock.yaml @@ -1,5 +1,5 @@ lockfile_version: 1 -resolved_at: '2026-02-22T12:44:38.250865+00:00' +resolved_at: '2026-03-12T22:21:55.975655+00:00' packages: doc-coauthoring: version: 0.0.0 @@ -13,3 +13,86 @@ packages: skills/doc-coauthoring/SKILL.md: 2e47d78846faeea4a56e9809c52700087a15a2155a3f293a3efbaded81398ef4 source_name: anthropics/skills source_commit: 1ed29a03dc852d30fa6ef2ca53a67dc2c2c2c563 + gh-address-comments: + version: 0.0.0 + source: source + checksum: sha256:a7ab4ea4bc5df6ec452bfe0b540154403b0145083f72b4d932490f2f41ff9067 + dependencies: {} + file_checksums: + algorithm: sha256 + files: + aam.yaml: a7ab4ea4bc5df6ec452bfe0b540154403b0145083f72b4d932490f2f41ff9067 + skills/gh-address-comments/SKILL.md: 77389eefd3fb6584210668ca8e43f4b8de87e7722ddd953474bc9e24cdfaaedd + skills/gh-address-comments/LICENSE.txt: 58d1e17ffe5109a7ae296caafcadfdbe6a7d176f0bc4ab01e12a689b0499d8bd + skills/gh-address-comments/assets/github-small.svg: ee05e92d373d4d84f4612af3c1090ef67698d421bbe30896a4ed5a00cb86a069 + skills/gh-address-comments/assets/github.png: d55a9baa41798d8b5108e3058df3a5bda09914ea9fd73b49929cc38a8832424f + skills/gh-address-comments/agents/openai.yaml: 12cf845a78254dde85e60eefb45a60e6ce2129d321f3e1d691d4ebca9a140861 + skills/gh-address-comments/scripts/fetch_comments.py: d7bc64f6b26f7482f9ca9dd7c923a5b8b20e1ef13491ae2d9052cd208a7c1ad1 + source_name: openai/skills:.curated + source_commit: 2c9ebb321958f64aa9414b46e4240901e2b9e1a9 + gh-fix-ci: + version: 0.0.0 + source: source + checksum: sha256:839458af2ff7fd9d771de9984e7122a1e53d4960433b56618b6022b2f2364ca8 + dependencies: {} + file_checksums: + algorithm: sha256 + files: + aam.yaml: 839458af2ff7fd9d771de9984e7122a1e53d4960433b56618b6022b2f2364ca8 + skills/gh-fix-ci/SKILL.md: 7b326b4a2f0f5f85122144628ec02077e48841e0e0e82efce88b3415bcfb7c26 + skills/gh-fix-ci/LICENSE.txt: 4dd13869245e356246a5b770723247bbb80a8f07a181d1d3d873a1734297cdb9 + skills/gh-fix-ci/assets/github-small.svg: ee05e92d373d4d84f4612af3c1090ef67698d421bbe30896a4ed5a00cb86a069 + skills/gh-fix-ci/assets/github.png: d55a9baa41798d8b5108e3058df3a5bda09914ea9fd73b49929cc38a8832424f + skills/gh-fix-ci/agents/openai.yaml: 36f73b9450504983d1647c960689596f59bf9147c14b1b43162912ed7ada9af6 + skills/gh-fix-ci/scripts/inspect_pr_checks.py: 9459e5b03f86785d184a83e5cd8d621b832e918ec04158a2962d867785812c6c + source_name: openai/skills:.curated + source_commit: 2c9ebb321958f64aa9414b46e4240901e2b9e1a9 + git-commit: + version: 0.0.0 + source: source + checksum: sha256:e89497975d58c71cfbfdae10510ec2b2ff7ad01535f25a69f0085407a8bd8e15 + dependencies: {} + file_checksums: + algorithm: sha256 + files: + aam.yaml: e89497975d58c71cfbfdae10510ec2b2ff7ad01535f25a69f0085407a8bd8e15 + skills/git-commit/SKILL.md: 554d1a3c6d95f15bc1170160659ecdc9a9958b64f377f3988941b672c249b13f + source_name: github/awesome-copilot + source_commit: dc8b0cc5466dcaa482b1bb8b13b529bf8031d25a + github-issues: + version: 0.0.0 + source: source + checksum: sha256:0a86845f53f3bbd67ade423a79c2a2ed23acd8f31b64d710371aa4e6cdf914ff + dependencies: {} + file_checksums: + algorithm: sha256 + files: + aam.yaml: 0a86845f53f3bbd67ade423a79c2a2ed23acd8f31b64d710371aa4e6cdf914ff + skills/github-issues/SKILL.md: dedcc5844bf2fd4f348b418dbc0c0042c7103657cb39cc6f82c084c0b5614235 + skills/github-issues/references/templates.md: 8202c0277a34033acd44ed960a236f31a5b7a74d98769dffac497f2267279fed + source_name: github/awesome-copilot + source_commit: dc8b0cc5466dcaa482b1bb8b13b529bf8031d25a + agentic-eval: + version: 0.0.0 + source: source + checksum: sha256:d53652b2e18af30aebb15cfc074c4161ad13db20b2cc724639e3d4a99e5e76e5 + dependencies: {} + file_checksums: + algorithm: sha256 + files: + aam.yaml: d53652b2e18af30aebb15cfc074c4161ad13db20b2cc724639e3d4a99e5e76e5 + skills/agentic-eval/SKILL.md: fb198a5c9b12e746d01a43c9ff3368598fdc1134bb458766f580186f31001573 + source_name: github/awesome-copilot + source_commit: dc8b0cc5466dcaa482b1bb8b13b529bf8031d25a + agent-governance: + version: 0.0.0 + source: source + checksum: sha256:035b892f51415d20199c825fec48df6b95245563d6dc803d4975f797ccbf1f2c + dependencies: {} + file_checksums: + algorithm: sha256 + files: + aam.yaml: 035b892f51415d20199c825fec48df6b95245563d6dc803d4975f797ccbf1f2c + skills/agent-governance/SKILL.md: aeec5110688463f04f3098184a1b6c19abbda32937832e8506899eb08138971f + source_name: github/awesome-copilot + source_commit: dc8b0cc5466dcaa482b1bb8b13b529bf8031d25a diff --git a/.claude/skills/github-issues/references/templates.md b/.claude/skills/github-issues/references/templates.md new file mode 100644 index 0000000..c05b408 --- /dev/null +++ b/.claude/skills/github-issues/references/templates.md @@ -0,0 +1,90 @@ +# Issue Templates + +Copy and customize these templates for issue bodies. + +## Bug Report Template + +```markdown +## Description +[Clear description of the bug] + +## Steps to Reproduce +1. [First step] +2. [Second step] +3. [And so on...] + +## Expected Behavior +[What should happen] + +## Actual Behavior +[What actually happens] + +## Environment +- Browser: [e.g., Chrome 120] +- OS: [e.g., macOS 14.0] +- Version: [e.g., v1.2.3] + +## Screenshots/Logs +[If applicable] + +## Additional Context +[Any other relevant information] +``` + +## Feature Request Template + +```markdown +## Summary +[One-line description of the feature] + +## Motivation +[Why is this feature needed? What problem does it solve?] + +## Proposed Solution +[How should this feature work?] + +## Acceptance Criteria +- [ ] [Criterion 1] +- [ ] [Criterion 2] +- [ ] [Criterion 3] + +## Alternatives Considered +[Other approaches considered and why they weren't chosen] + +## Additional Context +[Mockups, examples, or related issues] +``` + +## Task Template + +```markdown +## Objective +[What needs to be accomplished] + +## Details +[Detailed description of the work] + +## Checklist +- [ ] [Subtask 1] +- [ ] [Subtask 2] +- [ ] [Subtask 3] + +## Dependencies +[Any blockers or related work] + +## Notes +[Additional context or considerations] +``` + +## Minimal Template + +For simple issues: + +```markdown +## Description +[What and why] + +## Tasks +- [ ] [Task 1] +- [ ] [Task 2] +``` diff --git a/.github/agents/pre-commit-docs.agent.md b/.github/agents/pre-commit-docs.agent.md new file mode 100644 index 0000000..1fdc4ec --- /dev/null +++ b/.github/agents/pre-commit-docs.agent.md @@ -0,0 +1,42 @@ +--- +name: Pre-Commit Docs Guard +description: "Use when reviewing changes before commit, running a pre-commit check, verifying CHANGELOG.md updates, checking whether README.md is still current, or testing an MkDocs build in this UAAPS repository." +tools: [read, search, execute] +argument-hint: "Review the current working tree before commit and report whether CHANGELOG.md, README.md, and the MkDocs build are in a good state." +agents: [] +user-invocable: true +--- +You are a pre-commit documentation guard for the UAAPS specification repository. Your job is to review the current working tree before a commit and report concrete issues that should be fixed first. + +## Constraints +- DO NOT edit files. +- DO NOT stage, commit, or revert changes. +- DO NOT approve changes without checking the actual diff. +- ONLY report findings that are grounded in the repository state or command results. + +## Required Checks +1. Inspect the current Git status and diff to understand what changed. +2. Compare the two changelog files and explain their roles: treat the top-level `CHANGELOG.md` as the canonical project changelog for commit readiness, and treat `docs/changelog.md` as the published documentation changelog page. Identify missing intermediate versions or drift between them. +3. Decide whether the current working tree changes are already reflected in either changelog. If material spec, schema, or documentation changes are not represented, call that out explicitly and say they likely warrant a new draft changelog entry. +4. Check whether README.md is still accurate, especially version or status claims, local build instructions, important links, and top-level project description. When validating the displayed version, compare it against the latest version documented in the top-level `CHANGELOG.md`, not `docs/changelog.md`. +5. Try to build the documentation with `.venv/bin/python -m mkdocs build`. If `.venv/bin/python` does not exist, or MkDocs or a required dependency is missing in that environment, report the check as blocked rather than guessing. +6. Summarize what is missing, what is ahead of or behind the working tree, and what the next sensible changelog action would be. + +## Review Heuristics +- Treat changes in `docs/spec/`, `docs/schemas/`, `README.md`, `mkdocs.yml`, or top-level published-doc files as likely to require top-level changelog review. +- Treat `CHANGELOG.md` as the source of truth for release/version state during review. Use `docs/changelog.md` only as a consistency check for the published site. +- When both changelog files exist, explicitly state that fact and describe the apparent purpose of each file. +- Look for missing intermediate release entries in `CHANGELOG.md` when `docs/changelog.md` contains versions that the root changelog skips. +- If the diff shows large, clearly material changes that are not represented in either changelog, call them out with file paths and approximate added-line magnitude from the diff summary. +- It is acceptable to suggest that the changes likely need a new draft changelog entry, but do not assert an exact next version unless the existing version sequence makes it clear. +- Treat stale setup steps, inaccurate repo descriptions, broken internal links, and mismatched version messaging as README issues. +- If `README.md` and `docs/changelog.md` disagree, do not assume the docs page wins. First check whether `CHANGELOG.md` supports the README value. +- Prefer a small number of high-signal findings over broad stylistic commentary. + +## Output Format +- Start with `Here's what I found:` +- Then `Two changelog files exist:` with one short bullet per file describing its apparent role +- Then `Issues:` with short bullets ordered by severity +- Then `Build check:` with the `.venv/bin/python -m mkdocs build` result or blocked reason +- End with `Summary:` describing whether the changelogs are ahead of, behind, or aligned with the working tree, plus the most sensible next step +- When useful, ask a closing question offering to draft the missing changelog entry or fix the identified file drift \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 9700088..11c9691 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,29 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.8.0-draft] — 2026-03-13 + +### Added +- §3 Manifest: `files` field for archive content selection (packlist control) +- §3 Manifest: `dependencyGroups` — named, non-runtime opt-in dependency sets (`dev`, `docs`, `eval`) +- §3 Manifest: `extras` — optional consumer-facing feature bundles with dependencies, artifacts, and permissions +- §10 Migration: §11.2 Codex native build target spec — full artifact mapping table, skill/command/agent conversion rules +- §12 Packaging: Deterministic archive file selection algorithm and reproducible build spec +- §12 Packaging: Immutability: registries MUST reject duplicate `name@version` publishes +- §13 Dependencies: `dependencyGroups` and `extras` resolution semantics; install flags `--with`, `--only-group`, `--extras` +- §13 Dependencies: Lock file v2 — `source` becomes a structured object with `type`, `registry`, `tarball`, `display` fields +- §15 Validation: New rules for lock file source metadata v2, `files` field, archive completeness, `dependencyGroups`, `extras`, extra artifact refs +- §16 Security: Extra-selected permissions model (union at install time, tool MUST surface delta to user) + +### Changed +- §2 Package Format: Commands directory gains optional `tests/` subdirectory; rules directory supports recursive grouping +- §4 Skills: `context`, `agent`, `disable-model-invocation` frontmatter fields marked as "Inferred" (not confirmed in public Claude Code docs); Copilot skills support corrected to "⚠️ Preview (not GA)" +- §5 Commands: Claude Code invocation syntax clarified (`/package-name:command-name`); Codex updated to generated-skill export path +- §6 Agents: Codex export path updated to `.agents/skills/agent-/` +- §8 Hooks: Added "Inferred" caveat for Copilot hook event names; "Community" caveat for Cursor hook event names +- §10 Compatibility: Copilot skills/hooks/AGENTS.md confidence levels corrected; Cursor rules path corrected to `.cursor/rules/*.mdc`; Codex install target updated to `.agents/skills/` + `AGENTS.md` +- §16 Security: Root authority definition updated to include extra-selected permissions in the effective permission boundary + ## [0.7.0-draft] — 2026-02-23 ### Added @@ -26,6 +49,28 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Standalone conversion command renamed from `aam converter rules` → `aam convert rules` - Flags renamed: `--from`/`--to` → `--source-platform`/`-s` and `--target-platform`/`-t` +## [0.6.0-draft] — 2026-02-23 + +### Added +- §1 Introduction: Specification versioning strategy, forward-compatibility parsing rules, conformance levels (L1–L3), ABNF identifier syntax +- §3 Manifest: YAML round-trip guarantee; `dist-tags`, `resolutions`, `deprecated` fields +- §10 Compatibility: Platform capability negotiation protocol and `.platform-capabilities.json` +- §12 Registry: Structured error codes, pagination, search/deprecation/yank endpoints; package lifecycle states (Published, Deprecated, Yanked) +- §13 Dependencies: Formalized resolution algorithm, `resolverVersion` field, workspace/monorepo support (`workspace.agent.json`) +- §14 Signing: SLSA-aligned provenance attestation (L0–L3) with in-toto format +- §15 Governance: Structured audit log format with JSON schema +- §16 Validation: New rules for resolver version, workspaces, forward-compat, permission grants, provenance +- §17 Security: Formal threat model; transitive permission composition with `permissionGrants` +- §18 Glossary: 12 new terms (Yank, Deprecation, Provenance, Conformance Level, Workspace, etc.) +- All JSON schemas upgraded from Draft-07 to JSON Schema 2020-12 + +### Changed +- §12 Packaging: Subsections reordered to numerical sequence +- Introduction section headings unnumbered + +### Fixed +- Permissions schema in `package-manifest.schema.json` fixed to match prose spec structure + ## [0.5.0-draft] — 2026-02-22 ### Added diff --git a/README.md b/README.md index 94aac64..125815c 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # 📦 UAAPS — Universal Agentic Artifact Package Specification -> **Version:** 0.5.0-draft  |  **Status:** 🌐 Open Standard, seeking collaborators +> **Version:** 0.8.0-draft  |  **Status:** 🌐 Open Standard, seeking collaborators ✍️ **Write once. Deploy to any agent platform.** @@ -111,8 +111,8 @@ The specification is published at **[uaaps.github.io/uaaps_docs](https://uaaps.g The spec source lives in [`docs/spec/`](docs/spec/) — each chapter is a separate Markdown file. To build and preview locally: ```bash -pip install mkdocs-material -mkdocs serve +.venv/bin/python -m pip install mkdocs-material +.venv/bin/python -m mkdocs serve ``` --- @@ -130,7 +130,7 @@ We are looking for collaborators who are: ### 🚀 How to Contribute -1. 📖 **Read the spec** — [docs/SPECIFICATION.md](docs/SPECIFICATION.md) +1. 📖 **Read the spec** — [docs/spec/full.md](docs/spec/full.md) 2. 💬 **Open an issue** — Feedback, edge cases, missing scenarios 3. 🔀 **Submit a PR** — Spec improvements, examples, reference implementations 4. 🧪 **Test the Skills Concentrator** — [How to use remote skills](https://github.com/spazyCZ/agent-package-manager/tree/main?tab=readme-ov-file#quick-start) diff --git a/docs/changelog.md b/docs/changelog.md index ef58b67..014aeb0 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -6,6 +6,79 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). --- +## [0.8.0-draft] — 2026-03-13 + +### Added +- §3 Manifest: `files` field — restricts which files are included in the published `.aam` archive (see §12.1) +- §3 Manifest: `dependencyGroups` — named, non-runtime opt-in dependency sets for workflows (`dev`, `docs`, `eval`) +- §3 Manifest: `extras` — optional consumer-facing feature bundles that can add dependencies, activate artifacts, and extend permissions when selected at install time (`aam install --extras ocr,github`) +- §10 Migration §11.2: Codex native build target — full output layout spec, artifact mapping table, and conversion rules for skills, commands, and agents to `.agents/skills/` structure +- §12.1 Packaging: Deterministic archive file selection algorithm — `files`-field-driven packlist with always-include/always-exclude rules +- §12.1 Packaging: Reproducible archive build spec — lexicographic entry order, normalized owner/group/timestamps, `SOURCE_DATE_EPOCH` support +- §13.1 Dependencies: `dependencyGroups` semantics — non-transitive, `--with`/`--only-group` install flags, system dependency participation +- §13.1 Dependencies: `extras` semantics — additive, root-selected only, artifact activation, permission union at install time, `--extras` and `pkg[extra,…]` syntax +- §13.3 Lock file v2: `source` field becomes a structured object with `type`, `registry`, `name`, `version`, `tarball`, `display`, and optional `resolvedTag` fields +- §15 Validation: New rules — lock file source metadata v2, `files` field format, archive completeness (pack fails if referenced file omitted), `dependencyGroups` key format, `extras` key format, extra artifact ref resolution +- §15 Validation: Extra permission audit — same audit rules MUST apply to effective permission set after extra merge +- §16 Security: Extra-selected permissions model — union semantics, tool MUST surface permission delta at install time, platforms MUST audit effective (not base) permission set +- §16 Security: Package contents security practice — publishers SHOULD use `files` field and review packlist warnings for likely-secret files + +### Changed +- §2 Package Format: Commands directory gains optional `tests/` subdirectory (deterministic command tests); rules directory supports recursive grouping (`group-name/rule-name/` at arbitrary depth) +- §4 Skills: `context`, `agent`, `disable-model-invocation` frontmatter fields annotated as "Inferred" — these fields are not confirmed in public Claude Code documentation +- §4 Skills: GitHub Copilot platform support corrected from "✅ Supported" to "⚠️ Preview (not GA)" +- §5 Commands: Claude Code invocation syntax clarified as `/package-name:command-name`; Codex updated from "⚠️ Via skills" to "⚠️ Via generated skills" with `.agents/skills/cmd-/` path; migration guidance updated for Codex `openai.yaml` policy +- §6 Agents: Codex export path updated from "⚠️ Via skills" to "⚠️ Via generated skills" with `.agents/skills/agent-/` +- §8 Hooks: Added "Inferred" caveat for Copilot hook event names (these are inferred mappings; GitHub Copilot Extensions use a distinct GitHub App webhook mechanism); "Community" caveat for Cursor hook event names +- §10 Compatibility matrix: Copilot skills confidence corrected to "Community"; Copilot/Cursor hooks confidence corrected to "Mixed"; AGENTS.md Copilot footnote clarifies it is read only in Copilot Coding Agent mode; Cursor rules path corrected to `.cursor/rules/*.mdc` +- §10 Migration: Codex install target updated from "`AGENTS.md` composite inject" to "`.agents/skills/` + `AGENTS.md`" +- §12.6 Registry: `DELETE /packages/:name/:version` endpoint removed; index regeneration trigger updated to cover all lifecycle state mutations +- §12.7 Packaging: Immutability guarantee strengthened — registries MUST reject any attempt to publish different archive bytes for an already-published `name@version` +- §16 Security: Root authority definition updated to include extra-selected permissions in the effective permission boundary (base `permissions` + selected extra permissions) + +--- + +## [0.7.0-draft] — 2026-02-23 + +### Added +- §7 Rules: Full Universal Agent Rule Specification (UARS v1.3) replacing the previous stub. + - UARS file format: `.rule.md` with YAML frontmatter (`name`, `version`, `description`, `apply.*`, `agents.*`, `refs`) plus free-form Markdown body. + - `apply.mode` enum: `always` | `intelligent` | `files` | `manual` with defined semantics per platform. + - Mode mapping table: how each `apply.mode` value compiles to Copilot `applyTo`, Cursor `alwaysApply`/`globs`, Claude Code `paths:`, and Codex directory placement. + - `agents.*` per-platform override block limited to platform-specific capabilities: `enabled` (all), `alwaysApply` + `priority` (Cursor), `override` (Codex). + - `refs` field for attaching other rule names or template paths (Cursor `@file` style). + - Compiled output examples for all four platforms (Copilot, Cursor, Claude Code, AGENTS.md). + - Full field reference table for top-level fields and `agents.*` override fields. +- §7 Rules: Converter Specification — normative two-namespace CLI design: + - **`aam pkg rules` namespace** (package authoring): `compile`, `import`, `diff`, `clean`, `list` sub-commands for rules living in `.aam/rules/`. + - **`aam convert rules` namespace** (standalone conversion): direct in-memory platform-to-platform translation without a UAAPS package. +- §7 Rules: `aam pkg rules compile` processing pipeline (9 stages: Discovery → Parse → Validate → Filter → Transform → Group → Merge → Conflict → Write). +- §7 Rules: Per-platform transformation tables for Copilot, Cursor, Claude Code, and Codex. +- §7 Rules: `aam pkg rules import` inverse field mapping and per-platform discovery paths. +- §7 Rules: Conflict Detection — generation guard patterns per platform; `.aam/conflicts/` pending files; `--force` behaviour. +- §7 Rules: Idempotency guarantee — deterministic alphabetical rule ordering, no volatile metadata in generated content. +- §7 Rules: Watch mode (`--watch`) with filesystem event subscription and full-pass recompilation. +- §7 Rules: `aam pkg rules diff` and `aam pkg rules clean` with guard-based deletion safety. +- §7 Rules: `aam pkg rules list` formatted output. +- §7 Rules: Error exit codes `0`–`4` for `aam pkg rules` commands. +- §7 Rules: `aam convert rules` standardised options table (`--source-platform` / `-s`, `--target-platform` / `-t`, `--type`, `--source`, `--out`, `--dry-run`, `--force`, `--verbose`). +- §7 Rules: AGENTS.md merge strategies — directory inference from globs, root aggregation for `always`/`intelligent`, guard-block management for Claude Code. +- §7 Rules: Codex `AGENTS.override.md` support via `agents.codex.override: true`. +- §7 Rules: Export section: UARS → AGENTS.md directory tree with section-splitting and guard comments. +- §7 Rules: Import section: AGENTS.md → UARS reverse pipeline including H1/H2 section splitting. +- §7 Rules: Validation rules (required fields, glob requirement for `files` mode, slug format, semver format). +- §7 Rules: Worked migration examples (Cursor `.mdc` → UARS `.rule.md`, AGENTS.md → UARS). +- `docs/schemas/rule-frontmatter.schema.json`: Updated to UARS v1.3 schema with `name`, `version`, `description`, `apply` (with `mode` + `globs`), `agents` per-platform overrides, and `refs`. Schema authority: informational only; prose spec is normative. + +### Changed +- §7 Rules: Replaced the previous 40-line stub (Cursor-centric `RULE.md` format with `description`, `globs`, `alwaysApply`) with the full UARS specification. +- §7 Rules: `agents.*` override block no longer allows re-defining `scope`, `paths`, `applyTo`, or `globs` (these are controlled by `apply.*`). Only platform-specific features not expressible in `apply:` are permitted in `agents:`. +- `docs/schemas/rule-frontmatter.schema.json`: Schema required fields changed from `["description"]` to `["name", "description", "apply"]`. +- CLI command naming: standalone conversion uses `aam convert rules` (not `aam converter rules`, `aam rules compile`, or `aam rules convert`). +- CLI flag naming: standalone conversion uses `--source-platform` / `-s` and `--target-platform` / `-t` (not `--from` / `--to`). + +--- + ## [0.6.0-draft] — 2026-02-23 ### Added diff --git a/docs/schemas/package-lock.schema.json b/docs/schemas/package-lock.schema.json index 7a19441..df798c3 100644 --- a/docs/schemas/package-lock.schema.json +++ b/docs/schemas/package-lock.schema.json @@ -8,9 +8,9 @@ "additionalProperties": false, "properties": { "lockVersion": { - "description": "Lock file format version. Currently always 1.", + "description": "Lock file format version. Current version is 2. Tools SHOULD continue reading version 1 for backward compatibility.", "type": "integer", - "const": 1 + "const": 2 }, "resolved": { "description": "Map of resolved package name → resolution entry.", @@ -19,6 +19,20 @@ "$ref": "#/$defs/resolvedPackage" } }, + "groups": { + "description": "Selected dependency groups recorded at lock generation time.", + "type": "object", + "additionalProperties": { + "type": "boolean" + } + }, + "selectedExtras": { + "description": "Selected extras recorded at lock generation time.", + "type": "object", + "additionalProperties": { + "type": "boolean" + } + }, "systemChecks": { "description": "Snapshot of system dependency versions recorded at install time.", "type": "object", @@ -55,8 +69,8 @@ "type": "string" }, "source": { - "description": "Source descriptor (e.g. 'registry:https://registry.agentpkg.io', 'github:owner/repo#abc123', 'local:./path').", - "type": "string" + "description": "Structured source descriptor for the resolved artifact.", + "$ref": "#/$defs/sourceObject" }, "integrity": { "description": "SHA-256 hash of the package archive, prefixed with 'sha256-'.", @@ -69,6 +83,75 @@ "additionalProperties": { "type": "string" } } } + }, + "sourceObject": { + "type": "object", + "required": ["type"], + "additionalProperties": false, + "properties": { + "type": { + "type": "string", + "enum": ["registry", "file", "bundle"] + }, + "registry": { + "type": "string", + "format": "uri" + }, + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "resolvedTag": { + "type": "string" + }, + "tarball": { + "type": "string", + "format": "uri" + }, + "path": { + "type": "string" + }, + "target": { + "type": "string" + }, + "display": { + "type": "string" + } + }, + "allOf": [ + { + "if": { + "properties": { + "type": { "const": "registry" } + } + }, + "then": { + "required": ["registry", "name", "version", "tarball"] + } + }, + { + "if": { + "properties": { + "type": { "const": "file" } + } + }, + "then": { + "required": ["path"] + } + }, + { + "if": { + "properties": { + "type": { "const": "bundle" } + } + }, + "then": { + "required": ["path"] + } + } + ] } } } diff --git a/docs/schemas/package-manifest.schema.json b/docs/schemas/package-manifest.schema.json index 311228a..e2473d1 100644 --- a/docs/schemas/package-manifest.schema.json +++ b/docs/schemas/package-manifest.schema.json @@ -87,6 +87,12 @@ } } }, + "files": { + "description": "Glob patterns relative to the package root controlling which files are included in the published .aam archive.", + "type": "array", + "items": { "type": "string" }, + "uniqueItems": true + }, "dependencies": { "description": "Other UAAPS packages required. Keys are package names, values are SemVer ranges.", "$ref": "#/$defs/dependencyMap" @@ -99,46 +105,21 @@ "description": "Packages that must be provided by the host environment. Not auto-installed.", "$ref": "#/$defs/dependencyMap" }, + "dependencyGroups": { + "description": "Named, non-runtime dependency sets for tooling workflows such as dev, docs, and eval.", + "$ref": "#/$defs/dependencyGroupMap" + }, + "extras": { + "description": "Public, consumer-facing feature bundles selected explicitly at install time.", + "$ref": "#/$defs/extraMap" + }, "resolutions": { "description": "Force specific versions for transitive dependencies to resolve conflicts.", "$ref": "#/$defs/dependencyMap" }, "systemDependencies": { "description": "OS-level and runtime requirements. Verified via pre-flight checks; not auto-installed (except pip/npm with --install-system-deps).", - "type": "object", - "additionalProperties": false, - "properties": { - "python": { - "description": "Required Python version (SemVer range, e.g. '>=3.10').", - "type": "string" - }, - "node": { - "description": "Required Node.js version (SemVer range, e.g. '>=18').", - "type": "string" - }, - "packages": { - "description": "Packages to install via package managers.", - "type": "object", - "additionalProperties": false, - "properties": { - "pip": { "type": "array", "items": { "type": "string" } }, - "npm": { "type": "array", "items": { "type": "string" } }, - "brew": { "type": "array", "items": { "type": "string" } }, - "apt": { "type": "array", "items": { "type": "string" } }, - "cargo": { "type": "array", "items": { "type": "string" } } - } - }, - "binaries": { - "description": "Executables that must exist on $PATH.", - "type": "array", - "items": { "type": "string" } - }, - "mcp-servers": { - "description": "MCP server names that must be available.", - "type": "array", - "items": { "type": "string" } - } - } + "$ref": "#/$defs/systemDependenciesObject" }, "env": { "description": "Environment variables required by this package.", @@ -258,7 +239,7 @@ "description": "Human-readable deprecation notice. Should include migration guidance.", "type": "string" }, - "successor": { + "replacement": { "description": "Suggested replacement package name or version range.", "type": "string" } @@ -342,6 +323,134 @@ "type": "object", "additionalProperties": { "type": "string" } }, + "dependencyGroupMap": { + "type": "object", + "propertyNames": { + "pattern": "^[a-z0-9][a-z0-9-]*$" + }, + "additionalProperties": { + "$ref": "#/$defs/dependencyGroup" + } + }, + "dependencyGroup": { + "type": "object", + "additionalProperties": false, + "properties": { + "description": { + "type": "string", + "maxLength": 1024 + }, + "dependencies": { + "$ref": "#/$defs/dependencyMap" + }, + "systemDependencies": { + "$ref": "#/$defs/systemDependenciesObject" + } + }, + "anyOf": [ + { "required": ["dependencies"] }, + { "required": ["systemDependencies"] } + ] + }, + "extraMap": { + "type": "object", + "propertyNames": { + "pattern": "^[a-z0-9][a-z0-9-]*$" + }, + "additionalProperties": { + "$ref": "#/$defs/extra" + } + }, + "extra": { + "type": "object", + "additionalProperties": false, + "properties": { + "description": { + "type": "string", + "maxLength": 1024 + }, + "dependencies": { + "$ref": "#/$defs/dependencyMap" + }, + "systemDependencies": { + "$ref": "#/$defs/systemDependenciesObject" + }, + "artifacts": { + "$ref": "#/$defs/extraArtifactSelection" + }, + "permissions": { + "$ref": "#/$defs/permissionsObject" + } + }, + "anyOf": [ + { "required": ["dependencies"] }, + { "required": ["systemDependencies"] }, + { "required": ["artifacts"] }, + { "required": ["permissions"] } + ] + }, + "extraArtifactSelection": { + "type": "object", + "additionalProperties": false, + "properties": { + "skills": { + "type": "array", + "items": { "type": "string" }, + "uniqueItems": true + }, + "agents": { + "type": "array", + "items": { "type": "string" }, + "uniqueItems": true + }, + "commands": { + "type": "array", + "items": { "type": "string" }, + "uniqueItems": true + } + }, + "anyOf": [ + { "required": ["skills"] }, + { "required": ["agents"] }, + { "required": ["commands"] } + ] + }, + "systemDependenciesObject": { + "type": "object", + "additionalProperties": false, + "properties": { + "python": { + "description": "Required Python version (SemVer range, e.g. '>=3.10').", + "type": "string" + }, + "node": { + "description": "Required Node.js version (SemVer range, e.g. '>=18').", + "type": "string" + }, + "packages": { + "description": "Packages to install via package managers.", + "type": "object", + "additionalProperties": false, + "properties": { + "pip": { "type": "array", "items": { "type": "string" } }, + "npm": { "type": "array", "items": { "type": "string" } }, + "brew": { "type": "array", "items": { "type": "string" } }, + "apt": { "type": "array", "items": { "type": "string" } }, + "cargo": { "type": "array", "items": { "type": "string" } } + } + }, + "binaries": { + "description": "Executables that must exist on $PATH.", + "type": "array", + "items": { "type": "string" } + }, + "mcp-servers": { + "description": "MCP server names that must be available.", + "type": "array", + "items": { "type": "string" } + } + } + }, "permissionsObject": { "description": "A permissions object matching the structure of the top-level permissions field.", "type": "object", diff --git a/docs/schemas/rule-frontmatter.schema.json b/docs/schemas/rule-frontmatter.schema.json index 05af697..eec66cf 100644 --- a/docs/schemas/rule-frontmatter.schema.json +++ b/docs/schemas/rule-frontmatter.schema.json @@ -1,27 +1,115 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://uaaps.github.io/uaaps_docs/schemas/rule-frontmatter.schema.json", - "title": "UAAPS RULE.md Frontmatter", - "description": "Schema for the YAML frontmatter block in rules//RULE.md files.", + "title": "UAAPS Rule Frontmatter (UARS)", + "description": "Schema for the YAML frontmatter block in .aam/rules/.rule.md files (Universal Agent Rule Specification v1.3).", "type": "object", - "required": ["description"], + "required": ["name", "description", "apply"], "additionalProperties": false, "properties": { + "name": { + "description": "Unique slug identifier for the rule (e.g. 'python-standards'). MUST be unique within the package.", + "type": "string", + "pattern": "^[a-z0-9][a-z0-9-]*$" + }, + "version": { + "description": "Semver version string for the rule. Defaults to '1.0.0' when omitted.", + "type": "string", + "pattern": "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$" + }, "description": { - "description": "Purpose of this rule. Used by agents for auto-selection. Be specific about what coding standards or conventions this rule enforces.", + "description": "One-line summary of what this rule enforces. Used by Copilot hover tooltips and Cursor agent auto-selection.", "type": "string" }, - "globs": { - "description": "Glob pattern(s) for automatic attachment. The rule is injected into context whenever a matching file is opened or edited. Example: 'src/**/*.{ts,tsx}'.", - "oneOf": [ - { "type": "string" }, - { "type": "array", "items": { "type": "string" } } - ] + "apply": { + "description": "Applicability configuration controlling when and where the rule is injected.", + "type": "object", + "required": ["mode"], + "additionalProperties": false, + "properties": { + "mode": { + "description": "Injection mode: 'always' (injected into every session), 'intelligent' (agent decides from description + context), 'files' (injected when matched path is in context), 'manual' (only when explicitly referenced).", + "type": "string", + "enum": ["always", "intelligent", "files", "manual"] + }, + "globs": { + "description": "Glob patterns for file-scoped injection. REQUIRED when mode is 'files'. Patterns are relative to workspace root.", + "oneOf": [ + { "type": "string" }, + { "type": "array", "items": { "type": "string" }, "minItems": 1 } + ] + } + }, + "if": { + "properties": { "mode": { "const": "files" } } + }, + "then": { + "required": ["globs"] + } + }, + "agents": { + "description": "Per-platform overrides for features not expressible through the general apply config. Only use for capabilities unique to a given platform.", + "type": "object", + "additionalProperties": false, + "properties": { + "copilot": { + "type": "object", + "additionalProperties": false, + "properties": { + "enabled": { + "description": "When false, no output is emitted for GitHub Copilot.", + "type": "boolean" + } + } + }, + "cursor": { + "type": "object", + "additionalProperties": false, + "properties": { + "enabled": { + "description": "When false, no output is emitted for Cursor.", + "type": "boolean" + }, + "alwaysApply": { + "description": "Pin rule as always-on regardless of apply.mode. Cursor-specific feature.", + "type": "boolean" + }, + "priority": { + "description": "Cursor-specific priority override. Higher values make selection more likely.", + "type": "number" + } + } + }, + "claude": { + "type": "object", + "additionalProperties": false, + "properties": { + "enabled": { + "description": "When false, no output is emitted for Claude Code.", + "type": "boolean" + } + } + }, + "codex": { + "type": "object", + "additionalProperties": false, + "properties": { + "enabled": { + "description": "When false, no output is emitted for Codex / AGENTS.md.", + "type": "boolean" + }, + "override": { + "description": "When true, emits AGENTS.override.md instead of AGENTS.md, fully replacing parent-scope instructions for the target directory. Codex-specific feature.", + "type": "boolean" + } + } + } + } }, - "alwaysApply": { - "description": "When true, the rule is always included in context regardless of the files being worked on. Default: false.", - "type": "boolean", - "default": false + "refs": { + "description": "Other rule names or template file paths to attach. Maps to Cursor @file references when compiling for Cursor.", + "type": "array", + "items": { "type": "string" } } } } diff --git a/docs/spec/01-package-format.md b/docs/spec/01-package-format.md index 0f2d810..3ae50c5 100644 --- a/docs/spec/01-package-format.md +++ b/docs/spec/01-package-format.md @@ -15,14 +15,20 @@ package-name/ │ ├── fixtures/ # Test input files │ └── cases/ # Test cases (YAML) ├── commands/ # Slash commands (optional) -│ └── command-name.md +│ ├── command-name.md +│ └── tests/ # Deterministic command tests (optional) +│ ├── test-config.json # Test runner config +│ └── cases/ # Test cases (YAML) ├── agents/ # Sub-agent definitions (optional) │ └── agent-name/ │ ├── agent.yaml # Agent definition (name, skills, tools, params) │ └── system-prompt.md # Agent system prompt ├── rules/ # Project rules / instructions (optional) -│ └── rule-name/ -│ └── RULE.md +│ ├── rule-name/ +│ │ └── RULE.md +│ └── group-name/ # Recursive grouping (optional, arbitrary depth) +│ └── rule-name/ +│ └── RULE.md ├── hooks/ # Lifecycle hooks (optional) │ ├── hooks.json # Hook definitions │ ├── scripts/ # Shell scripts referenced by hooks diff --git a/docs/spec/02-manifest.md b/docs/spec/02-manifest.md index e2b86ad..975650a 100644 --- a/docs/spec/02-manifest.md +++ b/docs/spec/02-manifest.md @@ -31,10 +31,12 @@ The manifest is the **single required file**. It identifies the package and decl // Declaring artifacts explicitly enables richer registry metadata and description per artifact. "artifacts": { "skills": [ - { "name": "pdf-tools", "path": "skills/pdf-tools/", "description": "Extract text from PDFs" } + { "name": "pdf-tools", "path": "skills/pdf-tools/", "description": "Extract text from PDFs" }, + { "name": "ocr-reader", "path": "skills/ocr-reader/", "description": "OCR support for scanned documents" } ], "agents": [ - { "name": "code-auditor", "path": "agents/code-auditor/", "description": "Security audit agent" } + { "name": "code-auditor", "path": "agents/code-auditor/", "description": "Security audit agent" }, + { "name": "github-reviewer", "path": "agents/github-reviewer/", "description": "Review pull requests on GitHub" } ], "commands": [ { "name": "review", "path": "commands/review.md", "description": "Code review command" }, @@ -42,6 +44,16 @@ The manifest is the **single required file**. It identifies the package and decl ] }, + // === PACKAGE CONTENT SELECTION (optional) === + // Restricts which files are included in the published .aam archive. See §12.1. + "files": [ + "skills/**", + "commands/**", + "agents/**", + "README.md", + "LICENSE" + ], + // === DEPENDENCIES (optional) === "dependencies": { // Other agent packages required "code-review": "^1.0.0", // SemVer range @@ -53,6 +65,61 @@ The manifest is the **single required file**. It identifies the package and decl "peerDependencies": { // Must be provided by host environment "eslint-rules": ">=3.0.0" }, + "dependencyGroups": { // Non-runtime, opt-in dependency sets + "dev": { + "description": "Local authoring and CI tooling", + "dependencies": { + "test-harness": "^2.0.0", + "lint-utils": "^1.4.0" + }, + "systemDependencies": { + "packages": { + "npm": ["markdownlint-cli@^0.41"] + } + } + }, + "docs": { + "dependencies": { + "site-preview": "^1.2.0" + } + } + }, + "extras": { // Public feature bundles selected by consumers + "ocr": { + "description": "OCR support for scanned PDFs", + "dependencies": { + "image-tools": "^2.0.0" + }, + "systemDependencies": { + "packages": { + "apt": ["tesseract-ocr"] + } + }, + "artifacts": { + "skills": ["ocr-reader"] + }, + "permissions": { + "shell": { + "allow": true, + "binaries": ["tesseract"] + } + } + }, + "github": { + "dependencies": { + "gh-integration": "^1.5.0" + }, + "artifacts": { + "agents": ["github-reviewer"] + }, + "permissions": { + "network": { + "hosts": ["api.github.com"], + "schemes": ["https"] + } + } + } + }, "systemDependencies": { // OS-level / runtime requirements "python": ">=3.10", "node": ">=18", @@ -164,10 +231,38 @@ homepage: https://docs.example.com skills: ./skills commands: ./commands agents: ./agents +files: + - skills/** + - commands/** + - README.md dependencies: code-review: "^1.0.0" +dependencyGroups: + dev: + description: Local authoring and CI tooling + dependencies: + test-harness: "^2.0.0" + docs: + dependencies: + site-preview: "^1.2.0" + +extras: + ocr: + description: OCR support for scanned PDFs + dependencies: + image-tools: "^2.0.0" + artifacts: + skills: [ocr-reader] + github: + dependencies: + gh-integration: "^1.5.0" + permissions: + network: + hosts: [api.github.com] + schemes: [https] + x-claude: marketplace: anthropics/skills # x-, value MUST be an object @@ -193,6 +288,70 @@ x-cursor: Tools that convert between JSON and YAML MUST produce output that round-trips without data loss. A manifest that cannot be losslessly converted between formats is non-conformant. +### Package Content Selection + +The `files` field controls which paths are included when building a `.aam` archive. + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `files` | `string[]` | No | Glob patterns relative to the package root. When present, only matching paths are eligible for inclusion, subject to the mandatory inclusions and exclusions in §12.1. | + +**Rules**: +- `files` entries MUST be relative paths using forward slashes. +- `files` entries MUST NOT resolve outside the package root. +- `files` entries MUST NOT use negated patterns (`!foo/**`). +- `aam pack` MUST fail if the computed packlist excludes a file referenced by the manifest or by a declared artifact. +- If `files` is omitted, tools MUST use the default inclusion algorithm defined in §12.1. + +### Dependency Groups + +The `dependencyGroups` field defines named, non-runtime dependency sets for authoring and tooling workflows such as local development, documentation builds, and eval runs. + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `dependencyGroups` | `object` | No | Map of group name to non-runtime dependency declaration. Selected explicitly during install; never installed implicitly for consumers. | + +Each group entry uses this shape: + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `description` | `string` | No | Human-readable purpose of the group. | +| `dependencies` | `object` | No | UAAPS package dependencies used only for the selected workflow. | +| `systemDependencies` | `object` | No | Non-runtime system requirements applied only when the group is selected. | + +**Rules**: +- Group names MUST match `[a-z0-9][a-z0-9-]*`. +- Each group MUST declare at least one of `dependencies` or `systemDependencies`. +- `dependencyGroups` MUST be root-local metadata. Dependencies declared inside a group MUST NOT be exposed transitively when another package depends on this package. +- Tools MUST install group dependencies only when the group is selected explicitly, as defined in §13.1. + +### Extras + +The `extras` field defines public, consumer-facing feature bundles that can be selected at install time. + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `extras` | `object` | No | Map of feature-selector name to an optional install bundle. Extras extend the package only when selected explicitly by the consumer. | + +Each extra entry uses this shape: + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `description` | `string` | No | Human-readable summary of the feature. | +| `dependencies` | `object` | No | UAAPS package dependencies activated by selecting the extra. | +| `systemDependencies` | `object` | No | Additional system requirements activated by selecting the extra. | +| `artifacts` | `object` | No | Additional root-package artifacts activated only when the extra is selected. | +| `permissions` | `object` | No | Additional root-package permissions requested only when the extra is selected. | + +**Rules**: +- Extra names MUST match `[a-z0-9][a-z0-9-]*`. +- Each extra MUST declare at least one of `dependencies`, `systemDependencies`, `artifacts`, or `permissions`. +- Extras are part of the package's public install contract and MAY be selected by consumers as defined in §13.1. +- When an extra declares `artifacts`, the package MUST declare the top-level `artifacts` registry explicitly. Extra-scoped artifact references MUST resolve by artifact `name` within that registry. +- Non-referenced artifacts remain part of the base package. Artifacts referenced by an extra MUST be activated only when that extra is selected. +- Extra-scoped permissions are additive to the root package's `permissions` declaration. They MUST use the same schema and enforcement model as top-level `permissions`. +- Extras MUST NOT change manifest parsing semantics. + ### Vendor Extensions Vendor extensions allow platforms and tools to attach platform-specific metadata to a package manifest without conflicting with the core schema. diff --git a/docs/spec/03-skills.md b/docs/spec/03-skills.md index d0c9180..02f5e6b 100644 --- a/docs/spec/03-skills.md +++ b/docs/spec/03-skills.md @@ -24,9 +24,9 @@ metadata: # Arbitrary key-value extensions # === OPTIONAL (Platform Extensions) === allowed-tools: Read Grep Glob Bash # Pre-approved tools (experimental) -context: fork # Claude Code: run in isolated sub-agent -agent: Explore # Claude Code: sub-agent config -disable-model-invocation: true # Claude Code: user-only invocation +context: fork # Claude Code: run in isolated sub-agent (Inferred) +agent: Explore # Claude Code: sub-agent config (Inferred) +disable-model-invocation: true # Claude Code: user-only invocation (Inferred — not confirmed in public docs) --- # Skill Title @@ -47,10 +47,10 @@ Concrete usage examples... | `license` | `string` | No | agentskills.io | License identifier or filename | | `compatibility` | `map` | No | agentskills.io | Environment requirements | | `metadata` | `map` | No | agentskills.io | Extension key-value pairs | -| `allowed-tools` | `string` | No | Extension | Space-delimited tool whitelist | -| `context` | `string` | No | Claude ext. | `"fork"` for isolated execution | -| `agent` | `string` | No | Claude ext. | Sub-agent config name | -| `disable-model-invocation` | `bool` | No | Claude ext. | Restrict to user-only invocation | +| `allowed-tools` | `string` | No | Extension | Space-delimited tool whitelist. Honored by Claude Code; has no documented effect on Cursor, Copilot, or Codex. | +| `context` | `string` | No | Claude ext. (Inferred) | `"fork"` for isolated execution | +| `agent` | `string` | No | Claude ext. (Inferred) | Sub-agent config name | +| `disable-model-invocation` | `bool` | No | Claude ext. (Inferred) | Restrict to user-only invocation. Not confirmed in public Claude Code docs. | ### Skill Directory @@ -166,6 +166,6 @@ aam test --skill skill-name --case 01-basic # Run a single test case |----------|---------------|--------| | Claude Code | `~/.claude/skills/`, `.claude/skills/`, plugin `skills/` | ✅ Full support | | Cursor | Plugin `skills/`, imported as agent-decided rules | ✅ Supported (v2.2+) | -| GitHub Copilot | `.github/skills/`, `.claude/skills/` (also supported), user profile | ✅ Supported | +| GitHub Copilot | `.github/skills/`, user profile | ⚠️ Preview (not GA) | | OpenAI Codex | `~/.codex/skills/`, `.agents/skills/` | ✅ Supported | | Amp, Goose, OpenCode, Letta | Various paths | ✅ Supported | diff --git a/docs/spec/04-commands.md b/docs/spec/04-commands.md index 15512fa..588c1a8 100644 --- a/docs/spec/04-commands.md +++ b/docs/spec/04-commands.md @@ -77,10 +77,10 @@ Each variable in the `variables` array has: | Platform | Native Support | Location | Invocation | |----------|---------------|----------|------------| -| Claude Code | ✅ Yes | `commands/` (.md files), `.claude/prompts/` | `/plugin:command` | +| Claude Code | ✅ Yes | `commands/` (.md files), `.claude/prompts/` | `/package-name:command-name` (e.g. `/code-review:review`) | | Cursor | ✅ Prompts / ⚠️ Commands via rules | `.cursor/prompts/` | No native `/command` from plugins | | GitHub Copilot | ✅ Yes | `.github/prompts/.prompt.md` | Via prompt picker | -| Codex | ⚠️ Via skills | Skills with `disable-model-invocation` | `$skill-name` | +| Codex | ⚠️ Via generated skills | `.agents/skills/cmd-/` | Explicit skill invocation | ### Command Namespacing & Collision @@ -97,4 +97,4 @@ Plugin/package commands are **always namespaced** as `package-name:command-name` Unnamespaced invocation (e.g., `/review`) resolves to the project or personal scope. If neither scope defines the command, the platform SHOULD prompt the user to disambiguate when multiple packages provide a command with that name. ### Migration Strategy -For platforms without native command support, commands can be **converted to skills** with `disable-model-invocation: true` to achieve similar user-initiated behavior. +For platforms without native command support, commands can be **converted to generated skills**. For Codex exports, the generated skill SHOULD include `agents/openai.yaml` with `policy.allow_implicit_invocation: false` so the command remains an explicit user-invoked action rather than a general implicit skill. diff --git a/docs/spec/05-agents.md b/docs/spec/05-agents.md index 9c8b941..79b90fa 100644 --- a/docs/spec/05-agents.md +++ b/docs/spec/05-agents.md @@ -96,4 +96,4 @@ Provide severity ratings (Critical/High/Medium/Low) with remediation. | Claude Code | ✅ Yes (`agents/` dir) | Direct | | Cursor | ⚠️ No native `agents/` | Convert `system-prompt.md` → RULE.md with `alwaysApply: true` | | GitHub Copilot | ✅ `.github/agents/*.agent.md` | Convert to `.agent.md` | -| Codex | ⚠️ Via skills | Convert to skill | +| Codex | ⚠️ Via generated skills | Convert to `.agents/skills/agent-/` | diff --git a/docs/spec/06-rules.md b/docs/spec/06-rules.md index 3acbdf7..33f7495 100644 --- a/docs/spec/06-rules.md +++ b/docs/spec/06-rules.md @@ -1,41 +1,863 @@ ## 7. Rules (Project Rules / Instructions) -Rules provide **persistent context** — coding standards, architecture conventions, and project-specific guidance. +**Version:** 1.3.0 +**Purpose:** A platform-agnostic rule definition format that can be compiled into agent-specific configuration files for GitHub Copilot, Cursor, Claude Code, and AGENTS.md (OpenAI Codex / open standard). -### Universal Format: `RULE.md` +--- + +### Overview + +A Universal Rule is a single source of truth for coding conventions, project standards, and agent behaviors. A compiler/converter reads UARS files and emits the correct format for each target platform. + +``` +.aam/rules/ + ├── python-standards.rule.md + ├── api-conventions.rule.md + ├── service-patterns.rule.md + └── ... +``` + +--- + +### Rule File Format + +Each rule is a Markdown file with a YAML frontmatter block followed by freeform Markdown content. + +#### File naming + +``` +.rule.md +``` + +#### Full schema + +```yaml +--- +# ─── Identity ─────────────────────────────────────────────────────────────── +name: string # [required] Unique rule identifier (slug-style, e.g. "python-standards") +version: string # [optional] Semver string. Defaults to "1.0.0" +description: string # [recommended] One-line summary shown in UIs and hover tooltips + +# ─── Applicability ────────────────────────────────────────────────────────── +apply: + mode: always | intelligent | files | manual + # always → injected into every session / all files + # intelligent → agent decides based on description + context + # files → applied when any matched path is in context + # manual → only when explicitly referenced by the user + globs: # [required when mode=files] list of glob patterns + - "src/**/*.py" + - "tests/**/*.py" + +# ─── Agent overrides ──────────────────────────────────────────────────────── +# Platform-specific features not expressible in the general apply: config. +# Use these only for capabilities unique to a given platform. +agents: + cursor: + priority: number # [optional] Cursor-specific priority override (higher = more likely to be selected) — Inferred; not in Cursor's public .mdc schema + alwaysApply: false # pin rule as always-on regardless of apply.mode + +# ─── References ───────────────────────────────────────────────────────────── +refs: # [optional] other rule names or template files to attach + - service-template # maps to Cursor's @file references +--- +``` + +#### Rule content (Markdown body) + +The body is plain Markdown. It becomes the instruction content in all target platforms. + +```markdown +# Python Coding Standards + +- Follow the PEP 8 style guide. +- Use type hints for all function signatures. +- Write docstrings for all public functions and classes. +- Use 4 spaces for indentation. +- Prefer `pathlib.Path` over `os.path` for file system operations. +``` + +--- + +### Mode Mapping Table + +| UARS `apply.mode` | Copilot output | Cursor output | Claude Code output | AGENTS.md output | +|---|---|---|---|---| +| `always` | `applyTo: "**"` in frontmatter | `alwaysApply: true` | Listed in `CLAUDE.md` body (no paths filter) | Appended to root `AGENTS.md` | +| `intelligent` | No `applyTo` (manual attach) | `alwaysApply: false`, no `globs` | Listed in `CLAUDE.md` with note for agent | Appended to root `AGENTS.md` under a labelled section | +| `files` | `applyTo: ` | `alwaysApply: false`, `globs: ` | `paths:` list in `.claude/rules/.md` | Appended to inferred or explicit subdirectory `AGENTS.md` | +| `manual` | No `applyTo` (manual attach) | `alwaysApply: false`, no `globs` | Not auto-included; referenced explicitly | Not emitted (AGENTS.md has no manual-only concept) | + +--- + +### Compiled Output Examples + +Given the rule below: + +```yaml +--- +name: python-standards +description: Coding conventions for Python files +apply: + mode: files + globs: + - "**/*.py" +--- +# Python Coding Standards +- Follow PEP 8. +- Use type hints for all function signatures. +- Write docstrings for public functions. +- Use 4 spaces for indentation. +``` + +#### → GitHub Copilot + +**Path:** `.github/instructions/python-standards.instructions.md` ```markdown --- -description: "TypeScript coding standards for this project" -globs: "src/**/*.{ts,tsx}" +description: Coding conventions for Python files +applyTo: '**/*.py' +--- +# Python Coding Standards +- Follow PEP 8. +- Use type hints for all function signatures. +- Write docstrings for public functions. +- Use 4 spaces for indentation. +``` + +#### → Cursor + +**Path:** `.cursor/rules/python-standards.mdc` + +```markdown +--- +description: Coding conventions for Python files +globs: **/*.py alwaysApply: false --- +# Python Coding Standards +- Follow PEP 8. +- Use type hints for all function signatures. +- Write docstrings for public functions. +- Use 4 spaces for indentation. +``` + +#### → Claude Code + +**Path:** `.claude/rules/python-standards.md` + +```markdown +--- +paths: + - "**/*.py" +--- +# Python Coding Standards +- Follow PEP 8. +- Use type hints for all function signatures. +- Write docstrings for public functions. +- Use 4 spaces for indentation. +``` + +#### → AGENTS.md (OpenAI Codex / open standard) + +**Path:** `AGENTS.md` (root — for `always` / `intelligent` modes) +**Path:** `src/AGENTS.md` (subdirectory — compiler infers from `apply.globs` for `files` mode) + +> AGENTS.md has no frontmatter or glob fields. Scope is determined entirely by the **directory** the file is placed in. The compiler groups all rules targeting the same directory into a single `AGENTS.md` file. + +```markdown + +# Python Coding Standards +- Follow PEP 8. +- Use type hints for all function signatures. +- Write docstrings for public functions. +- Use 4 spaces for indentation. +``` + +--- + +### Field Reference + +#### Top-level fields + +| Field | Required | Type | Description | +|---|---|---|---| +| `name` | ✅ | `string` | Unique slug identifier for the rule | +| `version` | ❌ | `string` | Semver version string | +| `description` | ✅ | `string` | One-line summary; used in Copilot hover and Cursor agent decisions | +| `apply.mode` | ✅ | `enum` | `always` \| `intelligent` \| `files` \| `manual` | +| `apply.globs` | ⚠️ | `string[]` | Required when `mode=files`. Glob patterns relative to workspace root | +| `agents` | ❌ | `object` | Per-platform overrides | +| `refs` | ❌ | `string[]` | Rule names or template file paths to attach (Cursor `@file` style) | + +#### `agents.*` override fields + +| Field | Platform | Description | +|---|---|---| +| `enabled` | all | Whether to emit output for this platform | +| `alwaysApply` | `cursor` | Pin rule as always-on regardless of `apply.mode` (Cursor-specific) | +| `override` | `codex` | Emit `AGENTS.override.md` instead of `AGENTS.md` for full subdirectory override (Codex-specific) | + +--- + +### Compiler Behavior + +A UARS compiler (`aam compile` or equivalent) reads `.aam/rules/*.rule.md` files and writes platform-specific outputs. + +#### Output path conventions + +| Platform | Output path pattern | +|---|---| +| Copilot | `.github/instructions/.instructions.md` | +| Cursor | `.cursor/rules/.mdc` | +| Claude Code | `.claude/rules/.md` (+ entry in `CLAUDE.md` for `always` mode) | + +#### Merge strategy for `always` mode rules (Claude Code) + +Rules with `mode: always` are injected directly into `CLAUDE.md` under a generated section header. Rules with `mode: files` or others are written as separate files under `.claude/rules/` and referenced from `CLAUDE.md`. + +```markdown + + +## Always-on Rules + +- [python-standards](.claude/rules/python-standards.md) +- [api-conventions](.claude/rules/api-conventions.md) +``` + +#### Merge strategy for AGENTS.md + +AGENTS.md has no native concept of glob-scoped rules — it applies to all files within the directory it's placed in. The compiler handles this with two behaviors: + +**Directory inference from globs:** When `apply.mode` is `files`, the compiler extracts the longest common directory prefix from `apply.globs`. For example, `src/api/**/*.ts` → `src/api/`. The rule content is appended to `/AGENTS.md`. If multiple rules share the same inferred directory, they are all merged into a single file in insertion order. + +**Root aggregation for `always` / `intelligent`:** Rules with these modes are appended to the root `AGENTS.md`. Each rule contributes a clearly labelled section with an auto-generated comment header so diffs remain readable: + +```markdown + + + +# Python Coding Standards +- Follow PEP 8. +- Use type hints for all function signatures. + + +# General Conventions +- Prefer explicit over implicit. +- Keep functions under 40 lines. +``` + +**Override files:** When `agents.codex.override: true`, the compiler emits `AGENTS.override.md` in the target directory instead of `AGENTS.md`. This is a Codex-specific extension that completely replaces parent-scope instructions for that directory rather than appending to them. + +**Precedence note:** AGENTS.md files are hierarchical — Codex concatenates files from the root down, with files closer to the current directory winning because they appear later in the combined prompt. The compiler respects this by placing scoped rules in the most specific matching directory. + +--- + +### Converter Specification + +This section is the normative specification for the UARS converter — the component that transforms `.aam/rules/*.rule.md` source files into platform-specific instruction artifacts (and back). + +The converter exposes **two distinct command namespaces** for two different use cases: + +| Use case | Command namespace | When to use | +|---|---|---| +| **Package authoring** | `aam pkg rules *` | You are building or maintaining a UAAPS package and want rules to be a first-class part of it, stored in `.aam/rules/` and compiled into the package's distributed artifacts | +| **Standalone conversion** | `aam convert rules *` | You want to translate existing platform-native rules directly from one format to another without creating or managing a UAAPS package | + +--- + +### Use Case 1 — Package Rules (`aam pkg rules`) + +Use these commands when rules live inside a UAAPS package (`.aam/rules/`). This is the full authoring workflow: import → edit → compile → distribute as part of the package. + +#### CLI Command Group + +``` +aam pkg rules compile [--target ] [--dry-run] [--watch] [--force] +aam pkg rules import [--from ] [--source ] [--overwrite] [--dry-run] +aam pkg rules diff [--target ] +aam pkg rules clean [--target ] +aam pkg rules list +``` + +--- + +#### `aam pkg rules compile` + +Reads all `.aam/rules/*.rule.md` files and writes platform-specific outputs to their canonical paths. + +##### Flags + +| Flag | Type | Default | Description | +|------|------|---------|-------------| +| `--target` | `string` | `all` | Platform(s) to emit: `copilot`, `cursor`, `claude`, `codex`, or `all` | +| `--dry-run` | `bool` | `false` | Print what would be written without performing any file I/O | +| `--watch` | `bool` | `false` | Re-compile on any change under `.aam/rules/` | +| `--force` | `bool` | `false` | Overwrite manually-edited output files (see §Conflict Detection) | +| `--out` | `string` | workspace root | Root directory for all relative output paths | + +Source rules are always read from `.aam/rules/` relative to the package root. + +##### Processing pipeline + +The compiler executes the following stages in order for each invocation: + +``` +1. Discovery — glob .aam/rules/**/*.rule.md +2. Parse — YAML frontmatter + Markdown body per file +3. Validate — apply all validation rules (§Validation Rules); abort on error +4. Filter — skip rules where agents..enabled = false +5. Transform — apply per-platform field mapping (see §Per-platform transformations) +6. Group — group transformed outputs by their resolved output file path +7. Merge — concatenate rules sharing the same output path in alphabetical name order +8. Conflict — compare each output path against its on-disk state (see §Conflict Detection) +9. Write — atomic write (write to .tmp, then rename) for each output file +``` + +##### Per-platform transformations + +**Copilot** (`--target copilot`) + +Output path: `.github/instructions/.instructions.md` + +| UARS field | Copilot frontmatter field | Notes | +|---|---|---| +| `name` | `name` | Title-cased: `python-standards` → `Python Standards` | +| `description` | `description` | Verbatim | +| `apply.mode: always` | `applyTo: "**"` | Wildcard — attached to all files | +| `apply.mode: files` | `applyTo: ""` | Multiple globs joined with `,` | +| `apply.mode: intelligent` | _(omitted)_ | User attaches manually in Copilot UI | +| `apply.mode: manual` | _(omitted)_ | Same as `intelligent` | +| `agents.copilot.enabled: false` | _(file not emitted)_ | | + +The Markdown body is passed through verbatim. No merge occurs — one rule produces one file. + +--- + +**Cursor** (`--target cursor`) + +Output path: `.cursor/rules/.mdc` + +| UARS field | Cursor frontmatter field | Notes | +|---|---|---| +| `description` | `description` | Verbatim | +| `apply.mode: always` | `alwaysApply: true` | No `globs` emitted | +| `apply.mode: files` | `alwaysApply: false`, `globs: ` | Multiple globs joined with `,` | +| `apply.mode: intelligent` | `alwaysApply: false` | No `globs`; Cursor uses `description` for agent selection | +| `apply.mode: manual` | `alwaysApply: false` | No `globs` | +| `agents.cursor.alwaysApply` | `alwaysApply` | Overrides the mode-derived value | +| `agents.cursor.priority` | `priority` | Emitted only when explicitly set _(Inferred — not in Cursor's public `.mdc` schema; field may be silently ignored)_ | +| `agents.cursor.enabled: false` | _(file not emitted)_ | | +| `refs` | `# @file ` comments | Prepended to the body as Cursor file references _(Inferred — `@file` syntax is verified in Cursor chat/prompt files; its behavior inside static `.mdc` rule files is unconfirmed)_ | + +--- + +**Claude Code** (`--target claude`) + +Two routing rules applied in order: + +1. Rules with `apply.mode: always` — body is inlined into `CLAUDE.md` inside the compiler-managed guard block; no separate file is written. +2. All other rules — written to `.claude/rules/.md` with a `paths:` frontmatter block; referenced from `CLAUDE.md`. + +| UARS field | Claude output | Notes | +|---|---|---| +| `apply.mode: always` | Body inlined in `CLAUDE.md` guard block | No separate file | +| `apply.mode: files` | `paths:` list in `.claude/rules/.md` | Each glob is a separate list entry | +| `apply.mode: intelligent` | `description:` frontmatter in `.claude/rules/.md` | No `paths:` | +| `apply.mode: manual` | _(not emitted)_ | | +| `agents.claude.enabled: false` | _(not emitted)_ | | + +`CLAUDE.md` is managed with a **block-replace strategy**. The compiler owns only the content between guard markers and leaves everything outside them untouched: + +```markdown + + +## Always-on Rules + + +# Python Coding Standards +- Follow PEP 8. +- Use type hints for all function signatures. + + +## File-scoped Rules + +- [api-conventions](.claude/rules/api-conventions.md) + + +``` + +- If `CLAUDE.md` does not exist, the compiler creates it containing only the guard block. +- If `CLAUDE.md` exists but has no guard markers, the compiler appends the guard block at the end and emits a warning. +- On each compile run the compiler replaces only the text between `` and ``. + +--- + +**Codex / AGENTS.md** (`--target codex`) + +Fully specified in §Compiler Behavior and §Export: UARS → AGENTS.md Directory Tree. Additional normative requirements: + +- The compiler MUST create all intermediate directories required by the resolved placement path. +- All rules targeting the same directory MUST be sorted by `name` (ascending lexicographic) before concatenation, ensuring deterministic output. +- The generation guard comment `` blocks in `CLAUDE.md` | One file → one rule; each inline block → one rule | +| `codex` | All `AGENTS.md` / `AGENTS.override.md` files in the directory tree | H1/H2 headings within each file (see §Section Splitting) | + +##### Inverse field mapping (platform → UARS) + +For `copilot`, `cursor`, and `claude`, existing frontmatter fields map back to UARS fields using the inverse of the per-platform transformation tables above. Unrecognized frontmatter fields are preserved verbatim under `agents..*` to ensure round-trip fidelity. + +If `--overwrite` is not set and a `.rule.md` file with the same `name` already exists in `.aam/rules/`, the importer skips that rule and emits a warning. With `--overwrite`, the existing file is replaced. + +--- + +#### `aam pkg rules diff` + +Compares compiled output currently on disk against what `aam pkg rules compile --dry-run` would produce, and prints a unified diff for each output file that would change. + +```bash +aam pkg rules diff # diff all targets +aam pkg rules diff --target copilot # diff only .github/instructions/ +``` + +**Exit codes:** `0` = no changes; `1` = changes exist. Suitable for CI pre-commit hooks. + +--- + +#### `aam pkg rules clean` + +Deletes all compiler-generated output files. A file is only deleted if it carries the generation guard comment that proves it was compiler-managed; manually-edited files are never deleted. + +```bash +aam pkg rules clean # remove generated outputs for all targets +aam pkg rules clean --target cursor # remove only .cursor/rules/*.mdc +``` + +--- + +#### `aam pkg rules list` + +Prints all rules currently in the package with their `name`, `apply.mode`, and the set of platforms they will be emitted to. + +```bash +aam pkg rules list +# name mode targets +# python-standards files copilot, cursor, claude, codex +# api-conventions files copilot, cursor, codex +# general-conventions always copilot, cursor, claude, codex +``` + +--- + +#### Error Codes (`aam pkg rules`) + +| Exit code | Meaning | +|-----------|---------| +| `0` | Success — all files written (or no changes in dry-run) | +| `1` | Validation error in one or more rule files — no files written | +| `2` | Conflict detected — one or more output files were manually edited; skipped | +| `3` | I/O error — missing directory, permission denied, or disk full | +| `4` | Dry-run with pending changes — signals a non-empty diff without writing files | + +--- + +### Use Case 2 — Standalone Converter (`aam convert rules`) -# TypeScript Standards +Use these commands when you want to translate rules directly between platforms **without managing a UAAPS package**. No `.aam/rules/` directory is created or required — the converter reads from the source platform's native paths and writes directly to the target platform's native paths in one operation. -- Use strict mode -- Prefer `const` over `let` -- Use early returns -- Named exports only -- Co-locate tests as `*.test.ts` +This is the right tool for: +- Migrating an existing project from Cursor to GitHub Copilot (or any other pair) +- One-off cross-platform sync without committing to the full UAAPS package structure +- CI pipelines that need to keep platform files in sync from a single authoritative source + +#### CLI Command Group + +``` +aam convert rules -s -t [--type ] [--source ] [--out ] [--dry-run] [--force] [--verbose] +aam convert rules diff -s -t [--type ] [--source ] ``` -### Frontmatter Fields +#### `aam convert rules` -| Field | Type | Required | Description | -|-------|------|----------|-------------| -| `description` | `string` | **Yes** | Purpose of rule. Used for agent auto-selection. | -| `globs` | `string` | No | File patterns for auto-attachment. | -| `alwaysApply` | `boolean` | No | Always include in context. Default: `false`. | +Reads all rules from the source platform's native paths, converts them through the UARS intermediate representation, and writes the results to the target platform's native paths. + +##### Options + +| Option | Short | Required | Description | +|--------|-------|----------|-------------| +| `--source-platform` | `-s` | **Yes** | Source platform: `cursor`, `copilot`, `claude`, `codex` | +| `--target-platform` | `-t` | **Yes** | Target platform: `cursor`, `copilot`, `claude`, `codex` | +| `--type` | | No | Filter by artifact type: `instruction`, `agent`, `prompt`, `skill`, `rule`, `hooks`, `mcp` | +| `--source` | | No | Root directory to discover source files from (default: workspace root) | +| `--out` | | No | Root directory to write target files to (default: workspace root) | +| `--dry-run` | | No | Show what would be converted without writing files | +| `--force` | | No | Overwrite existing target files (creates `.bak` backup) | +| `--verbose` | | No | Show detailed workaround instructions for warnings | + +##### Processing pipeline + +``` +1. Discovery — find source platform files under --source (or workspace root) +2. Parse — read each file and extract frontmatter + body +3. Normalise — convert to UARS intermediate representation (in-memory; no files written) +4. Validate — apply all UARS validation rules; abort on error +5. Transform — apply per-platform output transformation for --to target +6. Conflict — check target paths for manually-edited files +7. Write — atomic write to target platform paths under --out +``` + +The UARS intermediate layer is never written to disk. It exists only to decouple the source and target platform formats. + +##### Example — Cursor → Codex + +```bash +# Dry-run to preview what would be written +aam convert rules -s cursor -t codex --dry-run + +# Execute the conversion +aam convert rules -s cursor -t codex +``` + +Source discovery: `.cursor/rules/*.mdc` +Output: `AGENTS.md` (root) or `/AGENTS.md` (per-directory, inferred from glob prefixes) + +##### Example — Cursor → Copilot + +```bash +aam convert rules -s cursor -t copilot +``` -### Platform Mapping +Source discovery: `.cursor/rules/*.mdc` +Output: `.github/instructions/.instructions.md` -| Platform | Native Format | Migration | -|----------|--------------|-----------| -| Claude Code | `CLAUDE.md` (freeform) | Copy content into `CLAUDE.md` | -| Cursor | `.mdc` / `RULE.md` in `.cursor/rules/` | Copy to `.cursor/rules/` | -| Codex | `AGENTS.md` | Copy into `AGENTS.md` sections | -| Universal | `AGENTS.md` (project root) | Direct (supported everywhere) | +##### Example — filter by artifact type -### `AGENTS.md` as Universal Fallback -`AGENTS.md` at the project root is the **most portable instruction format** — supported by Claude Code, Cursor, Codex, Copilot, Gemini CLI, Jules, Amp, and others. Every universal package SHOULD include an `AGENTS.md` for maximum compatibility. +```bash +# Convert only rule artifacts (skip skills, prompts, etc.) +aam convert rules -s cursor -t copilot --type rule +``` + +##### Example — convert from a different source directory + +```bash +aam convert rules -s cursor -t codex --source /path/to/other-project +``` + +#### `aam convert rules diff` + +Prints a unified diff showing what the converter **would** change in the target platform's files, without writing anything. + +```bash +aam convert rules diff -s cursor -t codex +``` + +**Exit codes:** `0` = target files already up to date; `1` = changes would be made. + +#### Error Codes (`aam convert rules`) + +| Exit code | Meaning | +|-----------|----------| +| `0` | Success — all target files written (or no changes in dry-run) | +| `1` | No source platform files found at the expected paths | +| `2` | Parse error in one or more source files | +| `3` | Conflict — one or more target files were manually edited; use `--force` to overwrite | +| `4` | I/O error — permission denied, disk full, or invalid output path | + +--- + +### Import from AGENTS.md + +`aam import --from codex` (or `aam import --source `) walks the project tree, discovers all `AGENTS.md` and `AGENTS.override.md` files, parses each one into discrete rules, and writes them as `.aam/rules/*.rule.md` files. The source directory is encoded in `apply.globs` so a subsequent `aam compile --target codex` reconstructs the same file tree. + +#### Discovery walk + +The importer scans from the project root downward, collecting every file named `AGENTS.md` or `AGENTS.override.md`. Files are processed from the root toward leaves so that parent context is available when inferring `apply.mode`. + +``` +project/ + AGENTS.md → scope: "" (root) + src/ + AGENTS.md → scope: "src/" + api/ + AGENTS.md → scope: "src/api/" + AGENTS.override.md → scope: "src/api/", override: true + tests/ + AGENTS.md → scope: "tests/" +``` + +#### Section splitting + +Each `AGENTS.md` file is split into rules at **H1 (`#`) and H2 (`##`) headings**. Content before the first heading is treated as a single rule with an auto-generated name derived from the file's scope. Each resulting rule gets: + +- `name` — slugified heading text, with a numeric suffix if the slug would collide (e.g., `api-rules`, `api-rules-2`) +- `description` — first non-blank line of the section body (truncated to 120 chars) +- `apply.mode` — inferred (see table below) +- `agents.codex.override` — `true` if sourced from `AGENTS.override.md` + +#### Mode inference during import + +The importer cannot read glob intent from freeform Markdown, so it applies conservative defaults: + +| Source directory | Inferred `apply.mode` | +|---|---| +| Root (`AGENTS.md`) | `always` | +| Any subdirectory | `files` + `apply.globs` auto-set to `"**"` | + +The user can manually tighten glob patterns after import. The `description` and content are preserved verbatim so that `intelligent` mode can be set manually when appropriate. + +#### Import example + +Given this project layout: + +``` +AGENTS.md +--- +# General Conventions +- Prefer explicit over implicit. +- Keep functions under 40 lines. + +## Working agreements +- Always run `npm test` after modifying JavaScript files. +``` + +``` +src/api/AGENTS.md +--- +# API Development Rules +- All endpoints must include input validation. +- Use standard error response format. +``` + +Running `aam import --from codex` produces: + +**`.aam/rules/general-conventions.rule.md`** +```yaml +--- +name: general-conventions +description: Prefer explicit over implicit. +apply: + mode: always +--- +# General Conventions +- Prefer explicit over implicit. +- Keep functions under 40 lines. +``` + +**`.aam/rules/working-agreements.rule.md`** +```yaml +--- +name: working-agreements +description: Always run `npm test` after modifying JavaScript files. +apply: + mode: always +--- +## Working agreements +- Always run `npm test` after modifying JavaScript files. +``` + +**`.aam/rules/api-development-rules.rule.md`** +```yaml +--- +name: api-development-rules +description: All endpoints must include input validation. +apply: + mode: files + globs: + - "src/api/**" +--- +# API Development Rules +- All endpoints must include input validation. +- Use standard error response format. +``` + +--- + +### Export: UARS → AGENTS.md Directory Tree + +`aam compile --target codex` reads all `.aam/rules/*.rule.md` files whose `agents.codex.enabled` is `true` (or unset) and reconstructs the `AGENTS.md` file tree. The placement authority chain is: + +``` +apply.globs (inferred: longest common directory prefix) + └─ "." (fallback: root AGENTS.md) +``` + +#### Directory grouping + +Rules are grouped by their resolved target directory. All rules sharing the same target are concatenated in alphabetical `name` order and written to a single `AGENTS.md`: + +``` +.aam/rules/ + general-conventions.rule.md → scope: "" ┐ + working-agreements.rule.md → scope: "" ├─ → AGENTS.md + git-workflow.rule.md → scope: "" ┘ + + api-development-rules.rule.md → scope: "src/api/" ┐ + api-auth-rules.rule.md → scope: "src/api/" ┘─ → src/api/AGENTS.md + + test-conventions.rule.md → scope: "tests/" ──→ tests/AGENTS.md +``` + +#### Generated file structure + +Each generated `AGENTS.md` contains a header guard comment so the file is clearly machine-managed, followed by rule sections separated by blank lines. The rule name is recorded in an inline comment to enable targeted re-import and diff tracking: + +```markdown + + + + +# General Conventions +- Prefer explicit over implicit. +- Keep functions under 40 lines. + +## Working agreements +- Always run `npm test` after modifying JavaScript files. +``` + +#### Override files + +Rules with `agents.codex.override: true` are written to `AGENTS.override.md` in their target directory. If both regular and override rules exist for the same directory, the compiler writes two separate files. + +#### Import → compile workflow + +``` +aam import --from codex # existing AGENTS.md files → .aam/rules/*.rule.md +aam compile --target codex # .aam/rules/*.rule.md → AGENTS.md file tree +``` + +The generated AGENTS.md tree mirrors the original structure because the importer sets `apply.globs` to `/**`, which the compiler uses to derive the same placement. Section order within a generated file follows alphabetical rule name order; manually adjust rule names to control ordering if needed. + +--- + +### AGENTS.md Platform Notes + +AGENTS.md is an open standard originally introduced by OpenAI for Codex and adopted as a de facto cross-platform convention by Cursor, Amp, Jules from Google, Factory, and others. It is not formally stewarded by any foundation. Unlike the other platforms in this spec, AGENTS.md: + +- Has **no frontmatter schema** — the entire file is freeform Markdown parsed as plain instructions. +- Uses **directory placement** as its only scoping mechanism (no glob patterns, no `applyTo`). +- Supports a **hierarchical chain** from `~/.codex/AGENTS.md` (global) → project root → subdirectories, all concatenated together at runtime. +- Supports an **override variant** (`AGENTS.override.md`) that suppresses parent-level instructions for a given directory tree. +- Has a **size cap**: Codex stops adding files once the combined size reaches the limit defined by `project_doc_max_bytes` (32 KiB by default). + +--- + +### Validation Rules + +A valid UARS file must satisfy: + +1. `name` is present, non-empty, and matches `^[a-z0-9][a-z0-9-]*$` +2. `description` is present and non-empty +3. `apply.mode` is one of `always | intelligent | files | manual` +4. If `apply.mode` is `files`, at least one entry in `apply.globs` must exist +5. Markdown body must be non-empty +6. `refs` entries must resolve to another `*.rule.md` file or an existing file path in the repository + +--- + +### Complete Example + +```yaml +--- +name: api-conventions +version: 1.2.0 +description: Standards for REST API endpoints in TypeScript and Python + +apply: + mode: files + globs: + - "src/api/**/*.ts" + - "src/api/**/*.py" + +agents: + copilot: + enabled: true + cursor: + enabled: true + alwaysApply: false + claude: + enabled: true + codex: + enabled: true + +refs: + - service-template +--- + +# API Development Rules + +- All endpoints must include input validation using the project's `validate()` helper. +- Use the standard error response format: `{ error: string, code: string, details?: object }`. +- Include OpenAPI documentation comments on every route handler. +- Prefer pagination over returning unbounded lists. +- Log all mutations at `INFO` level with the actor's user ID. +``` diff --git a/docs/spec/07-hooks.md b/docs/spec/07-hooks.md index 8be304b..edd092b 100644 --- a/docs/spec/07-hooks.md +++ b/docs/spec/07-hooks.md @@ -63,7 +63,11 @@ Hooks execute shell commands at specific points in the agent lifecycle. They run | `pre-compact` | `PreCompact` | N/A | N/A | No | Before context compaction | | `notification` | `Notification` | N/A | N/A | No | System notification | -> **Copilot hook gaps**: Copilot currently supports 4 hook events vs Claude Code's 10. The `stop`, `post-tool-use`, `permission-request`, `sub-agent-end`, `pre-compact`, and `notification` events have no Copilot equivalent. A community feature request on the Copilot CLI GitHub repository tracks parity efforts. +> **Copilot hook gaps**: Copilot currently supports 4 hook events vs Claude Code's 10. The `stop`, `post-tool-use`, `permission-request`, `sub-agent-end`, `pre-compact`, and `notification` events have no Copilot equivalent. +> +> **Copilot hook confidence — Inferred**: The Copilot event names listed here (`preToolUse`, `userPromptSubmitted`, `sessionStart`, `sessionEnd`) are inferred mappings and do not correspond to a publicly documented `hooks.json`-style API in GitHub Copilot. GitHub Copilot Extensions use a distinct GitHub App webhook mechanism. Treat these event names as aspirational until official documentation confirms them. +> +> **Cursor hook confidence — Community**: Cursor hook event names (`beforeShellCommand`, `beforeMcpCall`, `afterFileEdit`, `beforeSubmitPrompt`, `stop`) are documented by the community and are not part of an official public API specification. Treat them as subject to change across Cursor releases. ### Hook Types diff --git a/docs/spec/09-compatibility.md b/docs/spec/09-compatibility.md index 95a8573..a716cd9 100644 --- a/docs/spec/09-compatibility.md +++ b/docs/spec/09-compatibility.md @@ -4,19 +4,19 @@ | Artifact | Claude Code | Cursor | Copilot | Codex | Standard | Confidence | |----------|------------|--------|---------|-------|----------|------------| -| **Skills (SKILL.md)** | ✅ Native | ✅ v2.2+ | ✅ `.github/skills/` | ✅ CLI+App | agentskills.io | Official | +| **Skills (SKILL.md)** | ✅ Native | ✅ v2.2+ | ⚠️ `.github/skills/` (Preview) | ✅ CLI+App | agentskills.io | Community | | **Commands** | ✅ Native | ⚠️ Via rules | ✅ `.prompt.md` | ⚠️ Via skills | Package-defined | Official | -| **Agents** | ✅ Native (`agents/*.md`) | ⚠️ Via rules | ✅ `.agent.md` | ⚠️ Via skills | Package-defined | Official | -| **Rules / Instructions** | ✅ `CLAUDE.md` | ✅ `.mdc`/`RULE.md` | ✅ `.instructions.md` | ⚠️ `AGENTS.md` | Package-defined | Official | -| **AGENTS.md** | ✅ | ✅ | ✅ | ✅ | agents.md | Official | -| **Hooks** | ✅ 10 events | ✅ 5 events | ✅ 4 events | ❌ | Package-defined | Official | +| **Agents** | ✅ Native (`agents//` dir) | ⚠️ Via rules | ✅ `.agent.md` | ⚠️ Via skills | Package-defined | Official | +| **Rules / Instructions** | ✅ `CLAUDE.md` | ✅ `.cursor/rules/*.mdc` | ✅ `.instructions.md` | ⚠️ `AGENTS.md` | Package-defined | Official | +| **AGENTS.md** | ✅ | ✅ | ✅† | ✅ | agents.md | Official | +| **Hooks** | ✅ 10 events | ⚠️ 5 events (Community) | ⚠️ 4 events (Inferred) | ❌ | Package-defined | Mixed | | **MCP Servers** | ✅ Native | ✅ Native | ✅ CCA + VS Code | ⚠️ Limited | MCP Protocol | Official | ### Instruction File Compatibility | File | Claude Code | Cursor | Copilot | Codex | Jules | Confidence | |------|------------|--------|---------|-------|-------|------------| -| `AGENTS.md` | ✅ | ✅ | ✅ | ✅ | ✅ | Official | +| `AGENTS.md` | ✅ | ✅ | ✅† | ✅ | ✅ | Official | | `CLAUDE.md` | ✅ | ✅* | ❌ | ❌ | ❌ | Official | | `GEMINI.md` | ❌ | ✅* | ❌ | ❌ | ❌ | Community | | `.cursor/rules/*.mdc` | ❌ | ✅ | ❌ | ❌ | ❌ | Official | @@ -25,6 +25,8 @@ | `.github/agents/*.agent.md` | ❌ | ❌ | ✅ | ❌ | ❌ | Official | > *Cursor reads `CLAUDE.md` and `GEMINI.md` as agent-specific instruction files alongside `AGENTS.md`. +> +> †GitHub Copilot reads `AGENTS.md` only in **Copilot Coding Agent** (autonomous agent) mode. Standard Copilot chat does not read this file. ### GitHub Copilot Artifact Types diff --git a/docs/spec/10-migration.md b/docs/spec/10-migration.md index 05ef585..5ddf4db 100644 --- a/docs/spec/10-migration.md +++ b/docs/spec/10-migration.md @@ -103,7 +103,7 @@ When `installMode` resolves to `"plugin"` for a given platform, the `aam` CLI tr | `claude-code` | `.claude/` (skills, commands, agents, hooks) | | `cursor` | `.cursor/rules/` (rules as `.mdc`), `.cursor/skills/` | | `copilot` | `.github/instructions/`, `.github/agents/`, `.github/skills/` | -| `codex` | `AGENTS.md` composite inject | +| `codex` | `.agents/skills/` + `AGENTS.md` | ### CLI Override @@ -130,3 +130,118 @@ Once a platform vendor implements UAAPS natively, `"plugin"` mode for that platf ``` Package authors SHOULD remove per-platform `plugin` overrides once the target platform is listed as UAAPS-native in the compatibility matrix (§10). + +## 11.2 Future Build Target — UAAPS Package → Codex Native Structure + +This section defines the intended conversion model for a future toolchain that exports a UAAPS package into a Codex-native repository layout. + +The primary entry points are expected to be: + +```bash +aam pkg build --target codex +aam install ./my-package --install-mode plugin --platform codex +``` + +The Codex export is a **lossy platform build target**. The tool MUST preserve the portable meaning of supported artifacts where possible, and MUST emit warnings for UAAPS concepts that Codex does not natively model. + +### Output Layout + +A Codex-native export SHOULD produce a repository tree shaped like: + +```text +/ +├── .agents/ +│ └── skills/ +│ ├── / +│ │ ├── SKILL.md +│ │ ├── scripts/ +│ │ ├── references/ +│ │ ├── assets/ +│ │ └── agents/ +│ │ └── openai.yaml +│ ├── cmd-/ +│ │ ├── SKILL.md +│ │ └── agents/ +│ │ └── openai.yaml +│ └── agent-/ +│ ├── SKILL.md +│ └── agents/ +│ └── openai.yaml +└── AGENTS.md +``` + +`AGENTS.md` is OPTIONAL and is emitted only when the package contains root instructions or rules that can be compiled into Codex instruction files. + +### Artifact Mapping + +| UAAPS artifact | Codex-native output | Conversion behavior | +|----------------|---------------------|---------------------| +| `skills//SKILL.md` | `.agents/skills//SKILL.md` | Copy skill body and supported frontmatter. | +| `skills//scripts/`, `references/`, `assets/` | Same relative path under `.agents/skills//` | Copy verbatim. | +| UAAPS skill vendor metadata | `.agents/skills//agents/openai.yaml` | Emit only when Codex-specific metadata is available or derived. | +| `commands/.md` | `.agents/skills/cmd-/SKILL.md` | Convert command to a generated skill for explicit user invocation. | +| `agents//agent.yaml` + `system-prompt.md` | `.agents/skills/agent-/SKILL.md` | Convert agent definition into a generated skill persona. | +| `rules/` + root `AGENTS.md` | `AGENTS.md` | Compile/merge into Codex instruction file(s). | +| `hooks/` | No native output | Emit warning: unsupported on Codex. | +| `mcp/servers.json` | No direct 1:1 file | Emit Codex-side metadata only when exact mapping exists; otherwise warn. | +| `tests/`, `evals/` | No runtime output | Omit from native runtime layout; MAY remain in bundle metadata. | +| `package.agent.json` | Not consumed by Codex runtime | MAY be retained in bundle/reference metadata, but MUST NOT be required at runtime. | + +### Skill Conversion Rules + +For a UAAPS skill exported to Codex: + +1. The tool MUST preserve `name`, `description`, `license`, `metadata`, and `allowed-tools` when they are representable in Codex. +2. The tool SHOULD preserve the `SKILL.md` body unchanged except for path rewrites needed to keep relative references valid. +3. The tool MUST copy `scripts/`, `references/`, and `assets/` directories when present. +4. The tool SHOULD emit `agents/openai.yaml` only for Codex-specific metadata and policy, not for portable fields already represented in `SKILL.md`. + +### Command Conversion Rules + +Codex does not define a native slash-command artifact. A UAAPS command export therefore uses a generated skill: + +1. Each command MUST be converted into a generated skill directory named `cmd-` unless the user requests a different naming strategy. +2. The generated `SKILL.md` MUST carry a description explaining that it is the Codex-native form of the source UAAPS command. +3. The command template body MUST become the skill instruction body. +4. Command variables SHOULD be rendered into the generated instructions as explicit fill-in guidance or parameter prompts. +5. The exporter SHOULD write `agents/openai.yaml` with `policy.allow_implicit_invocation: false` so the generated skill behaves like an explicit user-invoked action rather than an always-eligible implicit skill. + +### Agent Conversion Rules + +Codex does not define a native `agents/` artifact separate from skills. A UAAPS agent export therefore uses a generated skill: + +1. Each agent MUST be converted into a generated skill directory named `agent-` unless the user requests a different naming strategy. +2. The generated `SKILL.md` MUST combine: + - the agent description from `agent.yaml` + - the behavioral instructions from `system-prompt.md` +3. Referenced UAAPS skills SHOULD remain separate skills and SHOULD be mentioned in the generated instructions only when needed for discovery or orchestration guidance. +4. Structured fields from `agent.yaml` that have no Codex-native equivalent MUST trigger a warning and MAY be serialized into vendor metadata for informational use only. + +### Rules And `AGENTS.md` + +Rules and repository-wide instructions SHOULD be compiled into Codex instruction files using the Codex rules conversion pipeline defined in §7. + +When both are present: + +1. Root package `AGENTS.md` content SHOULD appear first. +2. Converted rule content SHOULD be appended in deterministic order. +3. If a rule targets a subdirectory-specific Codex instruction file, the exporter SHOULD emit the appropriate `AGENTS.md` or `AGENTS.override.md` file in that directory. + +### Unsupported Or Partial Features + +The Codex export tool MUST emit a `WARN` for each artifact or field that cannot be represented faithfully. At minimum: + +| UAAPS feature | Codex export behavior | +|---------------|-----------------------| +| Hooks | Warn and omit. | +| Hook tests | Warn and omit. | +| Package permissions | Warn if the package depends on host-enforced permissions not available in Codex native export. | +| MCP server config without Codex metadata equivalent | Warn and omit direct conversion. | +| Agent tool whitelist fields with no Codex equivalent | Warn and inline guidance only if safe. | +| Any unknown vendor extension affecting runtime behavior | Warn and preserve only as reference metadata if possible. | + +Warnings MUST identify the source path and the reason the information could not be preserved exactly. + +### Determinism + +For the same package input, platform target, and build options, the Codex export tool MUST produce byte-for-byte stable generated file content except for explicitly declared build metadata such as bundle timestamps. diff --git a/docs/spec/11-packaging.md b/docs/spec/11-packaging.md index ad820ab..bd7f6f8 100644 --- a/docs/spec/11-packaging.md +++ b/docs/spec/11-packaging.md @@ -52,6 +52,44 @@ my-package-1.0.0.aam # produced by: aam pkg pack - No absolute paths - SHA-256 checksum stored in `package.agent.lock` post-install +#### Archive File Selection + +The archive packlist MUST be deterministic. + +**Selection algorithm:** +1. If `package.agent.json` defines `files`, start from the union of those glob matches. +2. Otherwise, start from all files under the package root. +3. Always include `package.agent.json`. +4. Always include `README`, `README.md`, `LICENSE`, `LICENSE.md`, `CHANGELOG`, and `CHANGELOG.md` when present. +5. Always exclude these paths from the candidate set: + - `.git/**`, `.hg/**`, `.svn/**` + - `.agent-packages/**`, `node_modules/**` + - `.venv/**`, `venv/**`, `__pycache__/**`, `*.pyc` + - `.DS_Store`, `Thumbs.db` + - `package.agent.lock` + - `evals/reports/**` + +**Packlist rules:** +- Archive entries MUST use forward-slash paths relative to the package root. +- Archive entries MUST be sorted lexicographically by path before tar creation. +- `aam pack` MUST fail if the final packlist omits a file referenced by the manifest, a declared artifact entry, or a configured hook/MCP path. +- Tools SHOULD warn when the packlist includes likely-secret files such as `.env`, `.env.*`, or credential exports. + +#### Reproducible Archive Build + +To support provenance and byte-for-byte rebuilds, archive creation MUST be reproducible. + +| Property | Requirement | +|----------|-------------| +| Entry order | MUST be lexicographic by archive path | +| Path format | MUST use `/` separators only | +| Tar owner/group IDs | MUST be normalized to `0` | +| Tar owner/group names | MUST be empty strings | +| Modification time | MUST be `SOURCE_DATE_EPOCH` when set, otherwise `0` | +| Gzip header timestamp | MUST match the normalized archive timestamp | + +File mode bits MAY be preserved, but implementations MUST normalize any platform-specific metadata not representable across POSIX tar readers. + ### 12.2 Scoped Package Names Following npm convention, packages support a `@scope/name` format for namespacing under an organization or author: @@ -192,7 +230,7 @@ A local filesystem registry is a **directory tree** on disk following a defined #### `index.json` -A flat list of all packages in the registry. Implementations MUST regenerate this file after every publish or unpublish operation. +A flat list of all packages in the registry. Implementations MUST regenerate this file after every publish or lifecycle state mutation. ```json { @@ -433,7 +471,7 @@ The `replacement` field is OPTIONAL and, when provided, MUST be a valid package ### 12.7 Package Lifecycle -Published packages transition through a defined set of states that control visibility and resolution behavior. UAAPS follows an **immutability-first** model: once an archive is published, it is permanent. +Published packages transition through a defined set of states that control visibility and resolution behavior. UAAPS follows an **immutability-first** model: once an archive is published, its bytes and version identity are permanent. #### State Machine @@ -494,3 +532,5 @@ The `message` field is REQUIRED when `deprecated` is present. The `replacement` #### Immutability Guarantee > Unlike npm's `unpublish`, UAAPS follows Cargo's immutability model: published archives are permanent. This guarantees that existing lock files always resolve successfully, preventing supply-chain breakage. + +Registries MUST reject any attempt to publish different archive bytes for an already-published `name@version`, even if the prior version is deprecated or yanked. diff --git a/docs/spec/12-dependencies.md b/docs/spec/12-dependencies.md index 8bd6fb7..41885d8 100644 --- a/docs/spec/12-dependencies.md +++ b/docs/spec/12-dependencies.md @@ -9,8 +9,103 @@ Dependency resolution is a core differentiator of UAAPS over raw vendor plugin s | **Direct** | `dependencies` | **Yes** — install fails | Other agent packages this package requires to function. | | **Optional** | `optionalDependencies` | No — install succeeds | Enhances functionality if present. Graceful degradation if absent. | | **Peer** | `peerDependencies` | **Yes** — warn/fail | MUST be provided by the host project or another top-level install. Not auto-installed. | +| **Tooling group** | `dependencyGroups` | No — opt-in only | Named, non-runtime dependency sets for workflows such as `dev`, `docs`, or `eval`. | +| **Feature selector** | `extras` | No — opt-in only | Public, consumer-facing feature bundles such as `ocr`, `github`, or `enterprise`. | | **System** | `systemDependencies` | **Yes** — pre-flight check | OS binaries, language runtimes, pip/npm packages, MCP servers. | +#### Non-runtime Dependency Groups + +Dependency groups let package authors declare workflow-specific dependencies without making them part of the runtime dependency contract. + +```jsonc +{ + "dependencyGroups": { + "dev": { + "description": "Local authoring and CI tooling", + "dependencies": { + "test-harness": "^2.0.0", + "lint-utils": "^1.4.0" + } + }, + "docs": { + "dependencies": { + "site-preview": "^1.2.0" + }, + "systemDependencies": { + "packages": { + "npm": ["markdownlint-cli@^0.41"] + } + } + } + } +} +``` + +**Semantics:** +- `aam install` MUST install only runtime dependencies by default. +- `aam install --with dev,docs` MUST include the selected dependency groups in the root requirement set. +- `aam install --only-group docs` MUST install only the named dependency group plus the root package itself. +- Group dependencies MUST be non-transitive package metadata. When another package depends on this package, its `dependencyGroups` MUST be ignored unless the consumer selects matching groups locally. +- When a selected group declares `systemDependencies`, those checks MUST participate in the same pre-flight flow as top-level `systemDependencies`. +- Tooling group names are author-defined, but `dev`, `docs`, and `eval` are RECOMMENDED conventions. + +#### Extras — Optional Feature Bundles + +Extras let package authors publish consumer-facing feature bundles without making them part of the base install. + +```jsonc +{ + "extras": { + "ocr": { + "description": "OCR support for scanned PDFs", + "dependencies": { + "image-tools": "^2.0.0" + }, + "systemDependencies": { + "packages": { + "apt": ["tesseract-ocr"] + } + }, + "artifacts": { + "skills": ["ocr-reader"] + }, + "permissions": { + "shell": { + "allow": true, + "binaries": ["tesseract"] + } + } + }, + "github": { + "dependencies": { + "gh-integration": "^1.5.0" + }, + "artifacts": { + "agents": ["github-reviewer"] + }, + "permissions": { + "network": { + "hosts": ["api.github.com"], + "schemes": ["https"] + } + } + } + } +} +``` + +**Semantics:** +- `aam install` MUST install the base package without extras unless extras are requested explicitly. +- `aam install --extras ocr,github` MUST include the selected extras for the root package. +- `aam install @myorg/pdf-tools[ocr,github]` MUST add the package as a direct dependency with those extras selected. +- Extras MUST be additive. Selecting an extra MUST NOT remove or replace base dependencies. +- Extras MUST be root-selected only. A package manifest MUST NOT request extras of its transitive dependencies. +- When a selected extra declares `artifacts`, those artifact names MUST be activated for the root package in addition to the base artifact set. +- When a selected extra declares `permissions`, those permissions MUST be unioned with the root package's base `permissions` request before platform enforcement is applied. +- Tools MUST surface extra-selected permissions to the user at install time so the requested capability expansion is reviewable. +- In this version of the spec, extras MAY affect dependency resolution, system dependency checks, root-package artifact activation, and root-package permissions. They MUST NOT alter manifest parsing semantics or the dependency metadata of transitive packages. +- Extra names are author-defined, but short capability names such as `ocr`, `github`, and `enterprise` are RECOMMENDED. + ### 13.2 Version Constraint Syntax UAAPS uses **SemVer 2.0** with npm-style range operators: @@ -30,11 +125,18 @@ After resolution, the solver writes a deterministic **lock file** pinning exact ```jsonc { - "lockVersion": 1, + "lockVersion": 2, "resolved": { "code-review": { "version": "1.3.2", - "source": "marketplace:anthropics/skills", + "source": { + "type": "registry", + "registry": "https://aamregistry.io", + "name": "code-review", + "version": "1.3.2", + "tarball": "https://aamregistry.io/packages/code-review/1.3.2/tarball", + "display": "registry:code-review" + }, "integrity": "sha256-abc123...", "dependencies": { "git-utils": "1.0.1" @@ -42,12 +144,27 @@ After resolution, the solver writes a deterministic **lock file** pinning exact }, "testing-utils": { "version": "2.4.0", - "source": "npm:@aamregistry/testing-utils", + "source": { + "type": "registry", + "registry": "https://aamregistry.io", + "name": "testing-utils", + "version": "2.4.0", + "resolvedTag": "latest", + "tarball": "https://aamregistry.io/packages/testing-utils/2.4.0/tarball", + "display": "registry:testing-utils" + }, "integrity": "sha256-def456..." }, "git-utils": { "version": "1.0.1", - "source": "marketplace:anthropics/skills", + "source": { + "type": "registry", + "registry": "https://aamregistry.io", + "name": "git-utils", + "version": "1.0.1", + "tarball": "https://aamregistry.io/packages/git-utils/1.0.1/tarball", + "display": "registry:git-utils" + }, "integrity": "sha256-ghi789..." } }, @@ -65,10 +182,36 @@ After resolution, the solver writes a deterministic **lock file** pinning exact } ``` +#### Structured Source Metadata + +Each resolved package entry MUST store `source` as a structured object in lock file version `2` and later. + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `type` | `string` | **Yes** | Source kind. Supported values in this version: `registry`, `file`, `bundle`. | +| `registry` | `string` | For `registry` | Canonical registry base URL used for resolution. | +| `name` | `string` | For `registry` | Canonical package identifier. | +| `version` | `string` | For `registry` | Exact resolved package version. | +| `resolvedTag` | `string` | No | Dist-tag requested by the user, if resolution originated from a tag such as `stable` or `latest`. | +| `tarball` | `string` | For `registry` | Exact artifact URL fetched by the resolver. | +| `path` | `string` | For `file` or `bundle` | Canonical local path or URI of the selected artifact. | +| `target` | `string` | For `bundle` | Target platform encoded by the bundle, if applicable. | +| `display` | `string` | No | Backward-compatible human-readable shorthand. Informational only. | + +**Rules**: +- The `source` object MUST be authoritative. Tools MUST NOT derive canonical resolution semantics from `display`. +- Tools writing lock file version `2` MUST emit the structured object form. +- Tools SHOULD continue reading legacy lock file version `1` entries where `source` is a string. +- A tool that rewrites a legacy lock file SHOULD upgrade it to `lockVersion: 2` and preserve the old string only in `source.display` when useful for diagnostics. + +When dependency groups are selected during lock generation, the lock file SHOULD record them in a top-level `groups` object so the selected workflow inputs remain reproducible. +When extras are selected during lock generation, the lock file SHOULD record them in a top-level `selectedExtras` object so feature selections remain reproducible. When an extra contributes artifacts or permissions, the lock file SHOULD record those effective additions as derived install metadata. + **Behavior**: - If `package.agent.lock` exists, install uses **locked versions** (reproducible builds). - `aam install` reads the lock file; `aam update` regenerates it. - Lock files SHOULD be committed to version control for deterministic environments. +- Tools MUST support reading lock file version `1` and `2`. Tools writing new lock files SHOULD write version `2`. ### 13.4 Resolution Algorithm @@ -78,7 +221,7 @@ Resolution proceeds through five named phases: **Phase 1: Parse** — Read root `package.agent.json`. If `package.agent.lock` exists and `--frozen` is set, skip to Phase 5 (locked install). -**Phase 2: Build requirement graph** — Collect all `dependencies`, `peerDependencies`, and `optionalDependencies` into an initial requirement DAG. Each edge carries a version constraint. +**Phase 2: Build requirement graph** — Collect all `dependencies`, `peerDependencies`, and `optionalDependencies` into an initial requirement DAG. If the user selected any `dependencyGroups`, collect those groups' `dependencies` and `systemDependencies` into the same root requirement set. If the user selected any `extras`, collect those extras' `dependencies` and `systemDependencies` into the same root requirement set. Each dependency edge carries a version constraint. **Phase 3: Resolve** — For each unresolved requirement, in topological order: @@ -143,6 +286,48 @@ The `resolverVersion` field is OPTIONAL. When absent, version `1` is assumed. | **Latest** | `--latest` | Ignore constraints, install latest of everything (dangerous). | | **Dry-run** | `--dry-run` | Resolve and display tree without installing. | +#### Group Selection Flags + +| Flag | Behavior | +|------|----------| +| `--with [,...]` | Include one or more named dependency groups in addition to runtime dependencies. | +| `--only-group [,...]` | Install only the selected dependency groups for the root package, skipping runtime dependencies. | + +If a requested group name does not exist in `package.agent.json`, the tool MUST fail before resolution begins. + +#### Extra Selection Syntax + +| Form | Behavior | +|------|----------| +| `aam install --extras [,...]` | Select extras for the root package in the current workspace. | +| `aam install [,]` | Add a direct dependency with explicit extras selected for that package. | + +If a requested extra name does not exist for the targeted package, the tool MUST fail before resolution begins. + +#### Extra-scoped Artifact Activation + +Extras MAY activate additional artifacts from the root package, but only through the explicit top-level `artifacts` registry. + +| Rule | Requirement | +|------|-------------| +| Extra artifact references are by artifact `name` | MUST | +| Referenced artifact names MUST exist in the top-level `artifacts` registry | MUST | +| Selected extras MAY activate additional `skills`, `agents`, or `commands` | MAY | +| Selected extras MUST NOT deactivate base artifacts | MUST NOT | +| Artifact activation by extras applies only to the root package in this version | MUST | + +#### Extra-scoped Permissions + +Extras MAY request additional permissions for the root package. + +| Rule | Requirement | +|------|-------------| +| Extra `permissions` MUST use the same schema as top-level `permissions` | MUST | +| Effective root permissions are the union of base `permissions` and all selected extra permissions | MUST | +| Tools MUST display extra-added permissions to the user before install completes | MUST | +| Platforms MUST enforce the effective root permission set at runtime | MUST | +| Extra-added permissions apply only to the root package in this version | MUST | + ### 13.6 Package Dependencies (Agent-to-Agent) Agent packages can depend on other agent packages. This enables composition: @@ -292,6 +477,10 @@ When two installed packages export skills with the same `name`: | Command | Description | |---------|-------------| | `aam install` | Install all dependencies from manifest (or lock file). | +| `aam install --with dev,docs` | Install runtime dependencies plus the selected dependency groups. | +| `aam install --only-group eval` | Install only the selected non-runtime dependency group for the root package. | +| `aam install --extras ocr,github` | Install the root package with the selected optional feature bundles. | +| `aam install @myorg/pdf-tools[ocr]` | Add a direct dependency with explicit extras selected. | | `aam install ` | Add a package and resolve. | | `aam update` | Re-resolve all dependencies, update lock file. | | `aam update ` | Update a single package within constraints. | @@ -422,7 +611,7 @@ All install modes share a **download cache** at `~/.aam/cache/`. The cache store | `aam cache clean` | Entire cache cleared | | `aam cache clean @` | Single entry evicted | | `aam install --no-cache` | Cache bypassed for this run; result not cached | -| Registry republish of same version | Cache is **not** invalidated automatically — use `--no-cache` or bump version | +| Registry serves different bytes for same `name@version` | Client MUST treat this as an integrity violation and reject the artifact | Cached archives MUST be verified against their `sha256` integrity hash before extraction. A corrupted or tampered archive MUST be rejected and the cache entry evicted. diff --git a/docs/spec/15-validation.md b/docs/spec/15-validation.md index 17ee13a..be97980 100644 --- a/docs/spec/15-validation.md +++ b/docs/spec/15-validation.md @@ -15,6 +15,12 @@ | Peer dependencies | MUST be satisfied by root or ancestor package | | System dependencies | Pre-flight check MUST pass (or explicit `--skip-checks`) | | Lock file integrity | Hash MUST match on `--frozen` install | +| Lock file source metadata | `package.agent.lock` version `2` entries MUST store `source` as an object with required fields for the selected source `type` | +| Manifest `files` | Entries MUST be relative, use forward slashes, and MUST NOT use negated patterns | +| Archive completeness | `aam pack` MUST fail if the computed packlist omits a referenced artifact, hook, or MCP file | +| Dependency groups | `dependencyGroups` keys MUST match `[a-z0-9][a-z0-9-]*` and each group MUST declare at least one of `dependencies` or `systemDependencies` | +| Extras | `extras` keys MUST match `[a-z0-9][a-z0-9-]*` and each extra MUST declare at least one of `dependencies`, `systemDependencies`, `artifacts`, or `permissions` | +| Extra artifact refs | `extras.*.artifacts` entries MUST resolve to names declared in the top-level `artifacts` registry | | Namespace uniqueness | No two skills with same `name` within a single package | | Resolution overrides | `resolutions` entries MUST reference packages in the tree | | Resolver version | `resolverVersion` MUST be a positive integer. Unknown versions are a fatal error. | @@ -38,7 +44,7 @@ The following conditions do not fail validation but MUST produce a `WARN`-level | `network.hosts` contains `*` (bare wildcard) | `⚠ Network host wildcard allows any host. Specify explicit hosts or scoped wildcards (e.g. *.example.com).` | | `network.schemes` contains `http` | `⚠ Plain HTTP is declared as an allowed network scheme. Prefer https unless the target endpoint requires it.` | -These warnings MUST be displayed to the user and MUST be included in the output of `aam validate --json` under a `warnings` array. They MUST NOT block installation or publication. +These warnings MUST be displayed to the user and MUST be included in the output of `aam validate --json` under a `warnings` array. They MUST NOT block installation or publication. When extras are selected during install, the same audit rules MUST be applied to the effective permission set after selected extra permissions are merged. ### Vendor Extension Validation diff --git a/docs/spec/16-security.md b/docs/spec/16-security.md index cd9d95e..c840fda 100644 --- a/docs/spec/16-security.md +++ b/docs/spec/16-security.md @@ -5,13 +5,14 @@ 3. **Use `allowed-tools`** to restrict skill capabilities where possible. 4. **Hook commands** run as the user — apply least-privilege principles. 5. **MCP servers** SHOULD NOT expose secrets; use environment variable injection. -6. **Environment Variables**: Packages MUST explicitly declare required environment variables in the `env` manifest field. Host platforms SHOULD prompt users securely for these values rather than storing them in plaintext. -7. **Permissions Model**: The `permissions` field in `package.agent.json` is OPTIONAL. When it is present, platforms MUST enforce it as defined in §17.1 Permissions Model below. A platform that ignores a declared `permissions` field is non-compliant. When the field is absent, platforms MAY apply their own default restrictions. -8. **Sandbox** untrusted packages before production deployment. -9. **Verify lock file integrity** — `package.agent.lock` hashes prevent supply-chain tampering. -10. **Run `aam audit`** regularly to check dependencies for known vulnerabilities. -11. **System dependency auto-install** never uses `sudo` without explicit `--allow-sudo` flag. -12. **Transitive dependencies** SHOULD be audited — `aam tree` reveals the full graph. +6. **Package Contents**: Publishers SHOULD minimize archive contents with the `files` manifest field and review packlist warnings for likely-secret files before publishing. +7. **Environment Variables**: Packages MUST explicitly declare required environment variables in the `env` manifest field. Host platforms SHOULD prompt users securely for these values rather than storing them in plaintext. +8. **Permissions Model**: The `permissions` field in `package.agent.json` is OPTIONAL. When it is present, platforms MUST enforce it as defined in §17.1 Permissions Model below. A platform that ignores a declared `permissions` field is non-compliant. When the field is absent, platforms MAY apply their own default restrictions. +9. **Sandbox** untrusted packages before production deployment. +10. **Verify lock file integrity** — `package.agent.lock` hashes prevent supply-chain tampering. +11. **Run `aam audit`** regularly to check dependencies for known vulnerabilities. +12. **System dependency auto-install** never uses `sudo` without explicit `--allow-sudo` flag. +13. **Transitive dependencies** SHOULD be audited — `aam tree` reveals the full graph. --- @@ -110,6 +111,47 @@ $ aam install @myorg/data-exporter The `aam validate --permissions` flag runs the audit in isolation without a full package validation pass. +#### Extra-selected Permissions + +When selected extras declare `permissions`, those declarations extend the root package's permission request for that install. + +| Rule | Requirement | +|------|-------------| +| Selected extra permissions are unioned with root `permissions` before enforcement | MUST | +| Unselected extras MUST NOT contribute permissions | MUST | +| Tools MUST show the permission delta introduced by selected extras at install time | MUST | +| Platforms MUST audit the effective permission set, not only the base manifest value | MUST | + +Example effective request: + +```jsonc +// Base manifest +{ + "permissions": { + "fs": { "read": ["src/**"] } + }, + "extras": { + "github": { + "permissions": { + "network": { + "hosts": ["api.github.com"], + "schemes": ["https"] + } + } + } + } +} +``` + +Installing with `--extras github` produces an effective root permission request of: + +```json +{ + "fs": { "read": ["src/**"] }, + "network": { "hosts": ["api.github.com"], "schemes": ["https"] } +} +``` + --- ### 17.2 Threat Model @@ -183,7 +225,7 @@ When a package depends on other packages, each with their own `permissions` decl | Rule | Description | |------|-------------| -| **Root authority** | The root package's `permissions` field (the package installed directly by the user) is the maximum permission boundary. No dependency can exceed it. | +| **Root authority** | The root package's effective permission set (base `permissions` plus selected extra permissions) is the maximum permission boundary. No dependency can exceed it. | | **Dependency permissions** | Each dependency's `permissions` are checked against the root's permissions. If a dependency requests a capability not granted by the root, the platform MUST deny it at runtime. | | **Absent permissions** | If the root package omits `permissions`, platform defaults apply to the entire dependency tree. If a dependency declares `permissions` but the root does not, the dependency's permissions are advisory only. | | **Union within root boundary** | The effective permission set is the union of all dependencies' declared permissions, intersected with the root's permission boundary. | @@ -221,7 +263,7 @@ If a root package needs to explicitly grant broader permissions to a specific de effective(dep) = declared(dep) ∩ (declared(root) ∪ permissionGrants(dep)) ``` -Where `∩` is the intersection (least-privilege) and `∪` is the union of explicit grants. If `declared(dep)` is absent, the dependency inherits the root's permissions boundary. +Where `∩` is the intersection (least-privilege) and `∪` is the union of explicit grants. `declared(root)` refers to the effective root permission set after selected extra permissions are merged. If `declared(dep)` is absent, the dependency inherits the root's permissions boundary. #### Example From 491f8ea2de99790f0aaf62e3a3211e3e3e253940 Mon Sep 17 00:00:00 2001 From: Roman Marek~ Date: Fri, 13 Mar 2026 01:34:56 +0100 Subject: [PATCH 4/4] fix: address PR review feedback on command naming and doc consistency MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Unify `aam pack` → `aam pkg pack` across manifest, packaging, and validation specs - Unify `aam rules compile` → `aam pkg rules compile` in rules spec - Clarify lock schema validates v2 only (v1 backward-compat is a tool requirement, not schema) - Fix heading levels in migration guide (## → ### for nested subsections) Co-Authored-By: Claude Opus 4.6 --- docs/schemas/package-lock.schema.json | 2 +- docs/spec/02-manifest.md | 2 +- docs/spec/06-rules.md | 4 ++-- docs/spec/10-migration.md | 4 ++-- docs/spec/11-packaging.md | 2 +- docs/spec/15-validation.md | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/schemas/package-lock.schema.json b/docs/schemas/package-lock.schema.json index df798c3..af5d28e 100644 --- a/docs/schemas/package-lock.schema.json +++ b/docs/schemas/package-lock.schema.json @@ -8,7 +8,7 @@ "additionalProperties": false, "properties": { "lockVersion": { - "description": "Lock file format version. Current version is 2. Tools SHOULD continue reading version 1 for backward compatibility.", + "description": "Lock file format version. Current version is 2. This schema validates v2 lock files only. Tools SHOULD continue reading version 1 for backward compatibility, but v1 files will not validate against this schema.", "type": "integer", "const": 2 }, diff --git a/docs/spec/02-manifest.md b/docs/spec/02-manifest.md index 975650a..37b97a5 100644 --- a/docs/spec/02-manifest.md +++ b/docs/spec/02-manifest.md @@ -300,7 +300,7 @@ The `files` field controls which paths are included when building a `.aam` archi - `files` entries MUST be relative paths using forward slashes. - `files` entries MUST NOT resolve outside the package root. - `files` entries MUST NOT use negated patterns (`!foo/**`). -- `aam pack` MUST fail if the computed packlist excludes a file referenced by the manifest or by a declared artifact. +- `aam pkg pack` MUST fail if the computed packlist excludes a file referenced by the manifest or by a declared artifact. - If `files` is omitted, tools MUST use the default inclusion algorithm defined in §12.1. ### Dependency Groups diff --git a/docs/spec/06-rules.md b/docs/spec/06-rules.md index 33f7495..7d312b8 100644 --- a/docs/spec/06-rules.md +++ b/docs/spec/06-rules.md @@ -371,7 +371,7 @@ Two routing rules applied in order: `CLAUDE.md` is managed with a **block-replace strategy**. The compiler owns only the content between guard markers and leaves everything outside them untouched: ```markdown - + ## Always-on Rules @@ -427,7 +427,7 @@ With `--force`, the compiler overwrites the file unconditionally and deletes any #### Idempotency -Running `aam rules compile` twice with no source changes MUST produce bit-identical output files: +Running `aam pkg rules compile` twice with no source changes MUST produce bit-identical output files: - Rule ordering within a merged file is always alphabetical by `name`. - The compiler MUST NOT embed build timestamps or volatile metadata in generated file content. diff --git a/docs/spec/10-migration.md b/docs/spec/10-migration.md index 5ddf4db..dcc726a 100644 --- a/docs/spec/10-migration.md +++ b/docs/spec/10-migration.md @@ -61,7 +61,7 @@ The formats are functionally identical. Only the file extension changes. --- -## 11.1 Install Mode — Smooth Adoption Before Full Vendor Implementation +### 11.1 Install Mode — Smooth Adoption Before Full Vendor Implementation The `installMode` field in `package.agent.json` allows a package author to declare how the package SHOULD be deployed on platforms that have not yet natively implemented the UAAPS standard. This enables smooth adoption: one package can target both UAAPS-native environments and platforms still using their own plugin system. @@ -131,7 +131,7 @@ Once a platform vendor implements UAAPS natively, `"plugin"` mode for that platf Package authors SHOULD remove per-platform `plugin` overrides once the target platform is listed as UAAPS-native in the compatibility matrix (§10). -## 11.2 Future Build Target — UAAPS Package → Codex Native Structure +### 11.2 Future Build Target — UAAPS Package → Codex Native Structure This section defines the intended conversion model for a future toolchain that exports a UAAPS package into a Codex-native repository layout. diff --git a/docs/spec/11-packaging.md b/docs/spec/11-packaging.md index bd7f6f8..ee25509 100644 --- a/docs/spec/11-packaging.md +++ b/docs/spec/11-packaging.md @@ -72,7 +72,7 @@ The archive packlist MUST be deterministic. **Packlist rules:** - Archive entries MUST use forward-slash paths relative to the package root. - Archive entries MUST be sorted lexicographically by path before tar creation. -- `aam pack` MUST fail if the final packlist omits a file referenced by the manifest, a declared artifact entry, or a configured hook/MCP path. +- `aam pkg pack` MUST fail if the final packlist omits a file referenced by the manifest, a declared artifact entry, or a configured hook/MCP path. - Tools SHOULD warn when the packlist includes likely-secret files such as `.env`, `.env.*`, or credential exports. #### Reproducible Archive Build diff --git a/docs/spec/15-validation.md b/docs/spec/15-validation.md index be97980..7de4fc3 100644 --- a/docs/spec/15-validation.md +++ b/docs/spec/15-validation.md @@ -17,7 +17,7 @@ | Lock file integrity | Hash MUST match on `--frozen` install | | Lock file source metadata | `package.agent.lock` version `2` entries MUST store `source` as an object with required fields for the selected source `type` | | Manifest `files` | Entries MUST be relative, use forward slashes, and MUST NOT use negated patterns | -| Archive completeness | `aam pack` MUST fail if the computed packlist omits a referenced artifact, hook, or MCP file | +| Archive completeness | `aam pkg pack` MUST fail if the computed packlist omits a referenced artifact, hook, or MCP file | | Dependency groups | `dependencyGroups` keys MUST match `[a-z0-9][a-z0-9-]*` and each group MUST declare at least one of `dependencies` or `systemDependencies` | | Extras | `extras` keys MUST match `[a-z0-9][a-z0-9-]*` and each extra MUST declare at least one of `dependencies`, `systemDependencies`, `artifacts`, or `permissions` | | Extra artifact refs | `extras.*.artifacts` entries MUST resolve to names declared in the top-level `artifacts` registry |