diff --git a/.agents/skills/commit-message/SKILL.md b/.agents/skills/commit-message/SKILL.md new file mode 100644 index 000000000..3a27937e1 --- /dev/null +++ b/.agents/skills/commit-message/SKILL.md @@ -0,0 +1,339 @@ +--- +name: commit-message +description: This skill should be used when the user asks to "create a commit", "generate commit message", "commit changes", "make a commit", mentions "conventional commits", or discusses commit message formatting. Provides guided workflow for creating properly formatted commit messages with line length validation and required trailers. +version: 0.2.0 +--- + +# Conventional Commit Message Creation + +Create properly formatted conventional commit messages following project standards with line length validation and required trailers. + +## Purpose + +Generate commit messages that: + +- Follow conventional commits format (`type(scope): description`) +- Use component names or GitHub issue numbers as scope +- Respect line length limits (50 for subject, 72 for body) +- Include required trailers (Signed-off-by, Assisted-by) +- Match [Tekton commit message standards](https://github.com/tektoncd/community/blob/master/standards.md#commit-messages) + +## Quick Workflow + +1. **Analyze changes**: Run git status and git diff to understand modifications +2. **Determine scope**: Use component name from changed files, or GitHub issue number if available +3. **Generate message**: Create conventional commit message with proper formatting +4. **Add trailers**: Include Signed-off-by and Assisted-by trailers +5. **Confirm with user**: Display message and wait for approval before committing + +**CRITICAL**: Never commit without explicit user confirmation. + +## Conventional Commit Format + +### Structure + +```text +(): + +[optional body] + +Signed-off-by: +Assisted-by: (via Cursor) +``` + +### Type Selection + +Choose the appropriate commit type based on changes: + +| Type | Description | Example | +| ------ | ------------- | --------- | +| `feat` | New features | `feat(pipeline): add start --output flag` | +| `fix` | Bug fixes | `fix(taskrun): resolve log streaming race` | +| `docs` | Documentation | `docs(README): update installation steps` | +| `refactor` | Code refactoring | `refactor(actions): simplify CRUD helpers` | +| `test` | Test changes | `test(pipelinerun): add describe unit tests` | +| `chore` | Maintenance | `chore(deps): update go dependencies` | +| `build` | Build system | `build(Makefile): add vendor target` | +| `ci` | CI/CD changes | `ci(github): add golangci-lint action` | +| `perf` | Performance | `perf(log): optimize pod log streaming` | +| `style` | Code style | `style(format): run goimports formatter` | +| `revert` | Revert commit | `revert: undo breaking CLI flag change` | + +For complete type reference, see `references/commit-types.md`. + +### Scope Rules + +#### Priority 1: Component from changed files + +Analyze staged files to identify the primary component: + +```bash +git diff --cached --name-only +``` + +| File pattern | Scope | Example commit | +| ------------ | ----- | -------------- | +| `pkg/cmd/pipeline/*` | `pipeline` | `feat(pipeline): add start --dry-run flag` | +| `pkg/cmd/task/*` | `task` | `fix(task): resolve list filtering` | +| `pkg/cmd/pipelinerun/*` | `pipelinerun` | `feat(pipelinerun): add cancel subcommand` | +| `pkg/cmd/taskrun/*` | `taskrun` | `fix(taskrun): correct log streaming` | +| `pkg/actions/*` | `actions` | `refactor(actions): simplify CRUD` | +| `pkg/formatted/*` | `formatted` | `feat(formatted): add JSON output` | +| `pkg/log/*` | `log` | `fix(log): handle container restart` | +| `pkg/pods/*` | `pods` | `fix(pods): resolve container lookup` | +| `pkg/cli/*` | `cli` | `refactor(cli): extract client setup` | +| `pkg/flags/*` | `flags` | `feat(flags): add --output flag` | +| `pkg/plugins/*` | `plugins` | `feat(plugins): add discovery logic` | +| `pkg/version/*` | `version` | `fix(version): correct server detection` | +| `docs/*` | `docs` or filename | `docs(README): update steps` | +| `test/*` | component being tested | `test(pipeline): add E2E tests` | +| `cmd/*` | command name | `feat(tkn): add new subcommand` | +| Root files | filename | `chore(Makefile): add target` | +| `AGENTS.md`, `CLAUDE.md` | `docs` | `docs(AGENTS.md): update conventions` | + +#### Priority 2: GitHub issue number (optional) + +If the work is tracked in a GitHub issue and the user provides one, it can be used as the scope: + +```text +# Branch: fix-123-log-race +# Scope: #123 or component name +# Result: fix(log): resolve streaming race condition + +Fixes #123 +``` + +Add `Fixes #NNN` or `Closes #NNN` in the commit body (not the scope) — this is the standard GitHub convention for auto-closing issues. + +#### Priority 3: Ask user + +If changed files span multiple components or scope is unclear, ask the user which component is the primary focus. + +## Line Length Requirements + +### Subject Line + +- **Target**: 50 characters maximum +- **Hard limit**: 72 characters +- **Format**: `type(scope): description` counts toward limit +- **Tips**: Use present tense, no period at end + +```text +# Good (40 chars) +feat(pipeline): add start --dry-run + +# Too long - exceeds 72 char hard limit +feat(pipeline): add comprehensive dry-run support with preview output for pipeline start command +``` + +### Body + +- **Wrap at 72 characters per line** +- **Blank line** required between subject and body +- **Content**: Explain why, not what (code shows what) +- **Format**: Wrap manually or use heredoc in git commit + +```text +feat(pipeline): add start --dry-run + +Add dry-run flag to pipeline start command that previews the +PipelineRun YAML without creating it. Useful for validating +parameters and workspace bindings before submission. + +Signed-off-by: Developer Name +Assisted-by: Claude Sonnet 4.5 (via Cursor) +``` + +## Required Trailers + +### Signed-off-by + +**Always include**: `Signed-off-by: ` + +This certifies the Developer Certificate of Origin (DCO) — required by tektoncd upstream. + +**Detection priority order**: + +1. Environment variables: `$GIT_AUTHOR_NAME` and `$GIT_AUTHOR_EMAIL` +2. Git config: `git config user.name` and `git config user.email` +3. If neither configured, ask user to provide details + +```bash +echo "$GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL>" +git config user.name +git config user.email +``` + +If neither is configured, ask the user to provide their name and email. + +### Assisted-by + +**Always include**: `Assisted-by: (via Cursor)` + +**Format examples**: + +```text +Assisted-by: Claude Sonnet 4.5 (via Cursor) +Assisted-by: Claude Opus 4.5 (via Cursor) +``` + +Use the actual model name (Claude Sonnet 4.5, Claude Opus 4.5, etc.). + +## User Confirmation Requirement + +**CRITICAL RULE**: Always ask for user confirmation before executing `git commit`. + +### Confirmation Workflow + +1. **Generate** the commit message following all rules above +2. **Display** the complete message to the user with separator +3. **Ask**: "Should I commit with this message? (y/n)" +4. **Wait** for user response +5. **Commit** only if user confirms (yes/y/affirmative) + +### Example Interaction + +```text +Generated commit message: +--- +feat(pipeline): add JSON output support + +Add --output=json flag to pipeline list and describe commands. +Uses the existing formatted package JSON helpers. + +Signed-off-by: Developer Name +Assisted-by: Claude Sonnet 4.5 (via Cursor) +--- + +Should I commit with this message? (y/n) +``` + +Wait for user response before proceeding. + +## Commit Execution + +Use heredoc format for proper multi-line handling: + +```bash +git commit -m "$(cat <<'EOF' +feat(pipeline): add JSON output support + +Add --output=json flag to pipeline list and describe commands. + +Signed-off-by: Developer Name +Assisted-by: Claude Sonnet 4.5 (via Cursor) +EOF +)" +``` + +**Never use**: + +- `--no-verify` (skips pre-commit hooks) +- `--no-gpg-sign` (skips signing) +- `--amend` (unless explicitly requested and safe) + +## Complete Examples + +### Feature with component scope + +```text +feat(pipeline): add start --dry-run + +Add dry-run flag to pipeline start command that previews the +PipelineRun YAML without creating it on the cluster. + +Signed-off-by: Jane Developer +Assisted-by: Claude Sonnet 4.5 (via Cursor) +``` + +### Bug fix closing a GitHub issue + +```text +fix(log): resolve concurrent streaming panic + +Prevent nil pointer dereference when multiple containers stream +logs simultaneously during a TaskRun. + +Fixes #789 + +Signed-off-by: John Developer +Assisted-by: Claude Sonnet 4.5 (via Cursor) +``` + +### Documentation update + +```text +docs(AGENTS.md): update architecture conventions + +Add command tree structure and clarify golden-file testing +rules for contributors. + +Signed-off-by: Jane Developer +Assisted-by: Claude Sonnet 4.5 (via Cursor) +``` + +### Breaking change + +```text +feat(cli)!: remove deprecated --namespace flag + +Remove the deprecated short-form --namespace flag from all +commands. Use -n instead. Deprecated since v0.30. + +BREAKING CHANGE: Removed --namespace long flag from all +commands. Use -n or --ns instead. + +Signed-off-by: John Developer +Assisted-by: Claude Sonnet 4.5 (via Cursor) +``` + +## Validation Rules + +Every commit message must pass these checks: + +1. **Subject line format**: Must match `type(scope): description` (conventional commits) +2. **Subject line length**: Target 50 chars, hard limit 72 chars +3. **No trailing punctuation**: Subject must not end with `.` `!` `?` `,` `:` `;` (exception: `!` for breaking changes like `feat!:`) +4. **No trailing whitespace**: On any line +5. **Blank line after subject**: Required before body text +6. **Body line length**: Wrap at 72 characters per line +7. **Valid commit type**: Must be one of: `feat`, `fix`, `docs`, `style`, `refactor`, `test`, `chore`, `build`, `ci`, `perf`, `revert` +8. **Signed-off-by trailer**: Required — `Signed-off-by: Name ` +9. **Assisted-by trailer**: Required when AI assists — `Assisted-by: Model (via Tool)` +10. **Trailer spacing**: Blank line before trailers, no blank lines between them, no trailing blank lines + +## Format standards + +Follow [Tekton commit message standards](https://github.com/tektoncd/community/blob/master/standards.md#commit-messages): + +- Conventional commit format (`type(scope): description`) +- Subject line: target 50 characters, hard limit 72 +- Body lines wrapped at 72 characters +- Required `Signed-off-by` trailer (DCO) +- No trailing punctuation on the subject + +## Auto-Detection Summary + +When generating commit messages: + +1. Run `git status` (without -uall flag) +2. Run `git diff` for staged and unstaged changes +3. Identify primary component from staged file paths +4. If scope unclear, ask user +5. If user mentions a GitHub issue number, add `Fixes #NNN` to body +6. Analyze staged files to determine commit type +7. Generate appropriate scope and description +8. Detect author info from environment variables or git config +9. Ensure subject line is ≤50 characters (max 72) +10. Wrap body text at 72 characters per line +11. Add required trailers (Signed-off-by and Assisted-by) +12. Format according to conventional commits standard +13. **Display message and ask for user confirmation** +14. Only commit after receiving confirmation + +## Additional Resources + +For detailed information: + +- **`references/commit-types.md`** - Complete commit type reference with descriptions +- **`references/trailer-detection.md`** - Author detection logic and priority order diff --git a/.agents/skills/commit-message/references/commit-types.md b/.agents/skills/commit-message/references/commit-types.md new file mode 100644 index 000000000..27fba0840 --- /dev/null +++ b/.agents/skills/commit-message/references/commit-types.md @@ -0,0 +1,274 @@ +# Commit Types Reference + +Complete reference for conventional commit types used in the Tekton CLI project. + +## Scope and issue references + +- Prefer **component names** as scope (e.g. `pipeline`, `task`, `log`, `actions`). +- To link work to a GitHub issue, add `Fixes #NNN` or `Closes #NNN` in the **commit body** (not the subject scope). Merging the PR then closes the issue automatically. + +## Standard Types + +### feat - New Features + +Use for new features or functionality added to the codebase. + +**Examples**: + +- `feat(pipeline): add start --dry-run flag` +- `feat(task): add describe --output=json` +- `feat(plugins): add plugin discovery mechanism` +- `feat(flags): expose new --output format` + +**When to use**: + +- Adding new capabilities +- Introducing new commands or sub-commands +- Adding new CLI flags or output formats +- Adding new configuration options + +### fix - Bug Fixes + +Use for bug fixes that resolve incorrect behavior. + +**Examples**: + +- `fix(log): resolve streaming race condition` +- `fix(taskrun): correct describe output format` +- `fix(actions): handle missing resource gracefully` +- `fix(cli): prevent nil pointer on missing kubeconfig` + +**When to use**: + +- Fixing crashes or errors +- Resolving incorrect behavior +- Correcting logic errors + +**CVE / security fixes**: Use scope `cve` and cite the advisory in the subject (see examples on `main`). Routine dependency bumps from Dependabot use `chore(deps):`, not `fix`. + +### docs - Documentation + +Use for documentation-only changes. + +**Examples**: + +- `docs(README): update installation steps` +- `docs(cmd): add pipeline start examples` +- `docs(AGENTS.md): update architecture conventions` +- `docs(contributing): add code review guidelines` + +**When to use**: + +- Updating README files +- Adding or improving code comments +- Updating CLI reference documentation +- Improving developer guides + +### refactor - Code Refactoring + +Use for code changes that neither fix bugs nor add features, but improve code structure. + +**Examples**: + +- `refactor(actions): extract common CRUD logic` +- `refactor(formatted): simplify table renderer` +- `refactor(cli): consolidate client setup` +- `refactor(cmd): reorganize package structure` + +**When to use**: + +- Improving code readability +- Simplifying complex logic +- Reorganizing code structure +- Extracting common functionality + +**Must not** change observable behavior. If behavior changes, use `fix` (bug) or `feat` (new capability) instead. + +### test - Testing + +Use for adding or updating tests. + +**Examples**: + +- `test(pipeline): add describe unit tests` +- `test(taskrun): improve golden file coverage` +- `test(log): add streaming edge case tests` +- `test(e2e): add pipeline start E2E test` + +**When to use**: + +- Adding new test cases +- Improving test coverage +- Fixing flaky tests +- Adding integration or E2E tests + +### chore - Maintenance Tasks + +Use for routine maintenance tasks and tooling. + +**Examples**: + +- `chore(deps): update go dependencies` +- `chore(vendor): run go mod vendor` +- `chore(tools): update pre-commit hooks` +- `chore(golden): regenerate golden files` + +**When to use**: + +- Dependency updates +- Tooling configuration +- Repository maintenance +- Build script updates (minor) + +### build - Build System + +Use for changes to how the project is built (not routine dependency bumps — those are `chore(deps):`). + +**Examples**: + +- `build(Makefile): add vendor target` +- `build(goreleaser): update release config` +- `build(docker): update container base image` +- `build(go.mod): bump Go version to 1.24` + +**When to use**: + +- Changes to the root `Makefile` or build scripts +- Container or build image configuration +- GoReleaser configuration +- Bumping the Go version in `go.mod` + +### ci - CI/CD Changes + +Use for changes to continuous integration and release automation. + +**Examples**: + +- `ci(.github/workflows): add golangci-lint to CI` +- `ci(.github/workflows): update e2e matrix workflow` +- `ci(.tekton): update release pipeline` + +**When to use**: + +- Changes under `.github/workflows/` +- Changes under `.tekton/` + +### perf - Performance Improvements + +Use for changes that improve performance. + +**Examples**: + +- `perf(log): optimize pod log streaming` +- `perf(actions): reduce API call count` +- `perf(list): implement pagination` +- `perf(cli): lazy-load Tekton clients` + +**When to use**: + +- Optimization work +- Reducing latency +- Improving throughput +- Memory usage improvements + +### style - Code Style + +Use for formatting and style changes that don't affect code behavior. + +**Examples**: + +- `style(format): run goimports formatter` +- `style(lint): fix golangci-lint warnings` +- `style(markdown): fix markdownlint issues` +- `style(naming): apply consistent naming` + +**When to use**: + +- Running code formatters +- Fixing linter warnings (style-only) +- Applying consistent naming +- Whitespace/indentation fixes + +### revert - Revert Previous Commit + +Use for reverting previous commits. + +**Examples**: + +- `revert: undo breaking CLI flag change` +- `revert(pipeline): revert start refactoring` +- `revert: "feat(task): add new flag"` + +**When to use**: + +- Undoing problematic changes +- Rolling back breaking changes +- Reverting commits that caused issues + +**Format**: Include reference to original commit in body + +## Breaking Changes + +For any commit type, add `!` after type/scope to indicate breaking change: + +**Examples**: + +- `feat(cli)!: change default output format` +- `fix(flags)!: remove deprecated --namespace flag` +- `refactor(actions)!: restructure client interface` + +**Body should include**: + +```markdown +BREAKING CHANGE: +``` + +## Type Selection Guide + +### Decision Tree + +1. **Does it add new functionality?** → `feat` +2. **Does it fix a bug?** → `fix` +3. **Is it documentation only?** → `docs` +4. **Does it change code structure without behavior change?** → `refactor` +5. **Is it test-related?** → `test` +6. **Is it dependency/maintenance?** → `chore` +7. **Is it build system related?** → `build` +8. **Is it CI/CD related?** → `ci` +9. **Does it improve performance?** → `perf` +10. **Is it formatting/style only?** → `style` +11. **Does it revert a previous commit?** → `revert` + +### Common Mistakes + +**Wrong**: `chore(cli): add new subcommand` +**Right**: `feat(cli): add new subcommand` +*Reason: Adding a subcommand is a feature, not maintenance* + +**Wrong**: `feat(tests): add test coverage` +**Right**: `test(pipeline): add test coverage` +*Reason: Tests have their own type* + +**Wrong**: `fix(docs): update README` +**Right**: `docs(README): update installation steps` +*Reason: Documentation has its own type* + +**Wrong**: `chore(ci): update GitHub Actions workflow` +**Right**: `ci(.github/workflows): update test workflow` +*Reason: CI changes have their own type; scope the path touched* + +## Multiple Changes in One Commit + +When a commit includes multiple types of changes, choose the most significant: + +**Example**: Adding feature + tests + +- **Choose**: `feat(pipeline): add start --dry-run flag` +- **Body mentions**: "Includes unit tests" + +**Example**: Bug fix + refactoring + +- **Choose**: `fix(log): resolve streaming race condition` +- **Body mentions**: "Refactored log reader for clarity" + +**Guideline**: If changes are too diverse, consider splitting into multiple commits. diff --git a/.agents/skills/commit-message/references/trailer-detection.md b/.agents/skills/commit-message/references/trailer-detection.md new file mode 100644 index 000000000..d55b5132c --- /dev/null +++ b/.agents/skills/commit-message/references/trailer-detection.md @@ -0,0 +1,419 @@ +# Author Detection and Trailer Generation + +Complete guide for detecting author information and generating required commit trailers. + +## Overview + +Every commit must include two trailers: + +1. **Signed-off-by**: Author's name and email +2. **Assisted-by**: AI model information (when AI assists) + +This document describes the complete detection logic and trailer generation process. + +## Signed-off-by Trailer + +### Purpose + +The `Signed-off-by` trailer indicates who created the commit and certifies that the contributor has the right to submit the code. + +**Format**: `Signed-off-by: Full Name ` + +### Detection Priority Order + +Author information is detected using this priority: + +#### Priority 1: Environment Variables (Highest) + +Check environment variables first (common in dev containers and CI): + +```bash +# Check if both variables are set +if [ -n "$GIT_AUTHOR_NAME" ] && [ -n "$GIT_AUTHOR_EMAIL" ]; then + author_name="$GIT_AUTHOR_NAME" + author_email="$GIT_AUTHOR_EMAIL" +fi +``` + +**Environment variables**: + +- `$GIT_AUTHOR_NAME` - Author's full name +- `$GIT_AUTHOR_EMAIL` - Author's email address + +**Example**: + +```bash +export GIT_AUTHOR_NAME="Jane Developer" +export GIT_AUTHOR_EMAIL="jane.developer@example.com" +``` + +**Result**: + +```text +Signed-off-by: Jane Developer +``` + +**When used**: + +- Development containers (devcontainers) +- CI/CD pipelines +- Containerized environments +- Explicitly set environments + +#### Priority 2: Git Configuration (Fallback) + +If environment variables not set, check git configuration: + +```bash +# Get author name from git config +author_name=$(git config user.name) +author_email=$(git config user.email) + +if [ -n "$author_name" ] && [ -n "$author_email" ]; then + echo "Using git config: $author_name <$author_email>" +fi +``` + +**Git config sources** (in priority order): + +1. **Repository config** (`.git/config`): `git config user.name` +2. **Global config** (`~/.gitconfig`): `git config --global user.name` +3. **System config** (`/etc/gitconfig`): `git config --system user.name` + +**Example**: + +```bash +# Set globally +git config --global user.name "John Developer" +git config --global user.email "john.developer@example.com" + +# Or per-repository +git config user.name "John Developer" +git config user.email "john.developer@example.com" +``` + +**Result**: + +```text +Signed-off-by: John Developer +``` + +#### Priority 3: Ask User (Last Resort) + +If neither environment variables nor git config available: + +**Prompt**: + +```text +Git author information not configured. Please provide: + +Full Name: _ +Email: _ +``` + +**Validation**: + +- Name: Non-empty, contains at least first and last name +- Email: Valid email format (`user@domain.com`) + +**Store for session**: +After user provides information, optionally ask: + +```text +Would you like to save this information? +1. For this repository only (git config) +2. Globally for all repositories (git config --global) +3. Just for this commit (don't save) +``` + +### Complete Detection Logic + +```bash +detect_author() { + local author_name="" + local author_email="" + + # Priority 1: Environment variables + if [ -n "$GIT_AUTHOR_NAME" ] && [ -n "$GIT_AUTHOR_EMAIL" ]; then + author_name="$GIT_AUTHOR_NAME" + author_email="$GIT_AUTHOR_EMAIL" + echo "Using environment variables" >&2 + + # Priority 2: Git config + elif git config user.name >/dev/null && git config user.email >/dev/null; then + author_name=$(git config user.name) + author_email=$(git config user.email) + echo "Using git config" >&2 + + # Priority 3: Ask user + else + echo "Git author information not configured." >&2 + read -p "Full Name: " author_name + read -p "Email: " author_email + + if [ -z "$author_name" ] || [ -z "$author_email" ]; then + echo "Error: Name and email are required" >&2 + return 1 + fi + fi + + echo "Signed-off-by: $author_name <$author_email>" +} +``` + +### Common Scenarios + +#### Scenario 1: DevContainer with Environment Variables + +```json +{ + "containerEnv": { + "GIT_AUTHOR_NAME": "Developer Name", + "GIT_AUTHOR_EMAIL": "developer@example.com" + } +} +``` + +**Detection**: Uses environment variables (Priority 1) +**Result**: `Signed-off-by: Developer Name ` + +#### Scenario 2: Local Development with Git Config + +```ini +# User's ~/.gitconfig +[user] + name = John Developer + email = john@example.com +``` + +**Detection**: Uses git config (Priority 2) +**Result**: `Signed-off-by: John Developer ` + +#### Scenario 3: Multiple Git Identities + +User has different identities for different projects: + +```bash +# Global config (personal) +git config --global user.name "Jane Doe" +git config --global user.email "jane@personal.com" + +# Repository config (work) +cd /work/project +git config user.name "Jane Developer" +git config user.email "jane.developer@company.com" +``` + +**Detection**: Uses repository config (overrides global) +**Result**: `Signed-off-by: Jane Developer ` + +## Assisted-by Trailer + +### Purpose + +The `Assisted-by` trailer credits AI assistance in creating the commit, following open source contribution practices. + +**Format**: `Assisted-by: Model Name (via Tool Name)` + +### Model Name Detection + +Determine the current AI model being used: + +**Available models**: + +- Claude Sonnet 4.5 +- Claude Opus 4.5 +- (Other Claude models) + +**Detection method**: Check model identifier or configuration + +**Examples**: + +```text +Assisted-by: Claude Sonnet 4.5 (via Cursor) +Assisted-by: Claude Opus 4.5 (via Cursor) +``` + +### Tool Name + +Use the tool that is driving the session (e.g., `Cursor`, `Claude Code`, etc.). + +### Format Rules + +1. **Model name first**: Full model name (e.g., `Claude Sonnet 4.5`) +2. **Tool in parentheses**: `(via Tool Name)` +3. **Consistent casing**: Proper names capitalized +4. **No abbreviations**: Use full names + +**Correct**: + +```text +Assisted-by: Claude Sonnet 4.5 (via Cursor) +``` + +**Incorrect**: + +```text +Assisted-by: claude-sonnet (via cursor) # Wrong casing +Assisted-by: Sonnet (via C) # Abbreviations +Assisted-by: (via Cursor) Claude Sonnet 4.5 # Wrong order +Assisted-by: Claude Sonnet 4.5 # Missing tool +``` + +### When to Include + +**Always include** when: + +- AI generated commit message +- AI assisted with commit message +- AI helped analyze changes +- AI suggested improvements + +## Complete Trailer Generation + +### Both Trailers Together + +Always include both trailers in this order: + +1. Signed-off-by (required) +2. Assisted-by (when AI assists) + +**Format**: + +```text +Signed-off-by: Full Name +Assisted-by: Model Name (via Tool Name) +``` + +### Complete Example + +```text +feat(pipeline): add start --dry-run flag + +Add dry-run flag to pipeline start command that previews the +PipelineRun YAML without creating it on the cluster. + +Signed-off-by: Jane Developer +Assisted-by: Claude Sonnet 4.5 (via Cursor) +``` + +### Spacing Rules + +- **Blank line before trailers**: Separate body from trailers +- **No blank lines between trailers**: Trailers are consecutive +- **No trailing blank lines**: End commit message after last trailer + +**Correct**: + +```text +feat(pipeline): add start handler + +Implements pipeline start support. + +Signed-off-by: Jane Developer +Assisted-by: Claude Sonnet 4.5 (via Cursor) +``` + +**Incorrect**: + +```text +feat(pipeline): add start handler + +Implements pipeline start support. +Signed-off-by: Jane Developer + +Assisted-by: Claude Sonnet 4.5 (via Cursor) + +``` + +(Missing blank line before trailers, extra blank lines between/after) + +## Trailer Generation Function + +Complete implementation: + +```bash +generate_trailers() { + local author_name="" + local author_email="" + local model_name="Claude Sonnet 4.5" # Detect actual model + local tool_name="Cursor" + + # Detect author (Priority 1: env vars) + if [ -n "$GIT_AUTHOR_NAME" ] && [ -n "$GIT_AUTHOR_EMAIL" ]; then + author_name="$GIT_AUTHOR_NAME" + author_email="$GIT_AUTHOR_EMAIL" + + # Priority 2: git config + elif git config user.name >/dev/null && git config user.email >/dev/null; then + author_name=$(git config user.name) + author_email=$(git config user.email) + + # Priority 3: ask user + else + read -p "Full Name: " author_name + read -p "Email: " author_email + fi + + # Generate trailers + echo "" # Blank line before trailers + echo "Signed-off-by: $author_name <$author_email>" + echo "Assisted-by: $model_name (via $tool_name)" +} +``` + +## Troubleshooting + +### Issue: "user.name" not set + +**Error**: `fatal: unable to auto-detect email address` + +**Fix**: + +```bash +git config --global user.name "Your Name" +git config --global user.email "your.email@example.com" +``` + +### Issue: Wrong author in devcontainer + +**Cause**: Environment variables override git config + +**Check**: + +```bash +echo "$GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL>" +``` + +**Fix**: Set correct environment variables in `.devcontainer/devcontainer.json` + +### Issue: Trailers in wrong order + +**Wrong**: + +```text +Assisted-by: Claude Sonnet 4.5 (via Cursor) +Signed-off-by: Jane Developer +``` + +**Correct**: + +```text +Signed-off-by: Jane Developer +Assisted-by: Claude Sonnet 4.5 (via Cursor) +``` + +**Fix**: Always put Signed-off-by first, Assisted-by second + +## Summary + +Trailer detection priority: + +1. Environment variables (`$GIT_AUTHOR_NAME`, `$GIT_AUTHOR_EMAIL`) +2. Git configuration (`git config user.name`, `git config user.email`) +3. Ask user (last resort) + +Required trailers (in order): + +1. `Signed-off-by: Name ` +2. `Assisted-by: Model Name (via Tool Name)` diff --git a/.agents/skills/release-notes/SKILL.md b/.agents/skills/release-notes/SKILL.md new file mode 100644 index 000000000..2d85420fd --- /dev/null +++ b/.agents/skills/release-notes/SKILL.md @@ -0,0 +1,221 @@ +--- +name: release-notes +description: This skill should be used when the user asks to "create release note", "generate release notes", "release notes", "release changelog", "update GitHub release", or wants to generate categorized release notes between tags. Gathers PR/commit data via gh CLI, categorizes changes, and outputs formatted release notes. The user can optionally specify a release version (e.g. "create release note v0.45.0") to bypass auto-detection. +version: 0.1.0 +--- + +# Release Notes Generation + +Generate categorized release notes for Tekton CLI releases by gathering PR/commit data between two git tags and producing formatted markdown output. + +## Purpose + +Skill that: + +- Auto-detects current and previous tags +- Gathers PR and commit data via `gh` CLI +- Uses intelligent categorization +- Outputs release notes matching the project's established format +- Optionally updates the GitHub release + +## Workflow + +### Step 1: Pull latest tags + +```bash +git pull origin --tags +``` + +Ensure all tags are available locally before detection. + +### Step 2: Detect repository info + +```bash +gh repo view --json owner,name +``` + +Extract `owner` and `repo` name for API calls. + +### Step 3: Detect tags + +**If the user specified a version** (e.g. "create release note v0.45.0"), use that directly as the current tag — skip auto-detection. Validate that the tag exists locally: + +```bash +git tag --list 'v0.45.0' +``` + +If the tag doesn't exist locally, error and ask the user to verify. + +**Otherwise, auto-detect current tag:** + +```bash +git tag --points-at HEAD +``` + +Filter for `v*` prefixed tags. If no tag points at HEAD, list recent tags and ask the user which tag to use: + +```bash +git tag --list 'v*' --sort=-version:refname | head -10 +``` + +**Previous tag:** + +```bash +git tag --list 'v*' --sort=-version:refname +``` + +Find the entry immediately after the current tag in the version-sorted list. + +**CRITICAL**: Confirm both tags with the user before proceeding. Display: + +```text +Current tag: v0.45.0 +Previous tag: v0.44.1 + +Proceed with these tags? (y/n) +``` + +### Step 4: Verify prerequisites + +Check `gh` authentication: + +```bash +gh auth status +``` + +If not authenticated, instruct the user to run `gh auth login`. + +**Validate that both tags exist on GitHub:** + +```bash +gh api repos/{owner}/{repo}/git/ref/tags/{current_tag} +gh api repos/{owner}/{repo}/git/ref/tags/{previous_tag} +``` + +If either tag does not exist on GitHub, **stop and report the error** — do not proceed. + +**Check GitHub release status for the current tag:** + +```bash +gh release view {current_tag} --json isDraft,isPrerelease,tagName +``` + +- If **no release exists** for the tag: warn the user, but allow proceeding (notes can be generated for preview, but cannot be uploaded). +- If the release is a **draft** (`isDraft: true`): proceed normally — this is the expected state for generating release notes. +- If the release is **published** (not draft, not prerelease): **ask for explicit confirmation** before proceeding, since updating will override an already-published release. + +### Step 5: Gather PR/commit data + +Use `gh api` to collect data between the two tags: + +**Compare commits:** + +```bash +gh api repos/{owner}/{repo}/compare/{previous_tag}...{current_tag} --jq '.commits[].sha' +``` + +**For each commit, find associated PRs:** + +```bash +gh api repos/{owner}/{repo}/commits/{sha}/pulls +``` + +**Deduplicate by PR number.** For each PR/commit, extract: + +- PR number, title, body, author, URL, labels +For commits not associated with any PR, include them as standalone commit entries with their SHA, message, author, and URL. + +**IMPORTANT**: This step involves many API calls. Process commits in reasonable batches and report progress to the user. + +### Step 6: Categorize changes + +Using the gathered PR/commit data, categorize each entry into exactly these sections (skip empty ones): + +- `## ✨ Major changes and Features` +- `## 🐛 Bug Fixes` +- `## 📚 Documentation Updates` +- `## ⚙️ Chores` + +Use the entry format specified in `references/release-notes-format.md`. + +**Categorization guidelines:** + +- New capabilities, enhancements, significant behavior changes → Features +- Bug fixes, error corrections, regression fixes → Bug Fixes +- Documentation-only changes (docs/, README, comments) → Documentation Updates +- Dependencies, CI/CD, refactoring, formatting, test-only changes → Chores +**Internal vs user-facing detection:** + +Changes that match ANY of these patterns are internal and belong in Chores, NOT Features — regardless of `feat:` prefix: + +- CI/CD pipeline changes (`.tekton/`, `.github/workflows/`, Makefile targets) +- Release infrastructure (release scripts, goreleaser config) +- Test infrastructure (test helpers, e2e framework, test configuration) +- Build system changes (Dockerfile, build scripts) +- Developer tooling (linter config, pre-commit hooks, code generation) +- Internal refactoring that doesn't change user-visible behavior + +Only classify as Features when the change is **visible to end users**: new CLI commands, new flags, new output formats, new plugin capabilities, new resource support. + +### Step 7: Assemble complete release notes + +Combine all sections in this order: + +1. **Header** (see `references/release-notes-format.md` for template) +2. **Categorized sections** from Step 6 +3. **Installation section** (see `references/release-notes-format.md` for template) +4. **GitHub auto-generated changelog**: + +```bash +gh api repos/{owner}/{repo}/releases/generate-notes -f tag_name="{current_tag}" -f previous_tag_name="{previous_tag}" +``` + +Extract the `body` field from the response. + +### Step 8: Output, save, and optional GitHub release update + +1. **Always write** the complete release notes to `/tmp/release-notes-{current_tag}.md` and tell the user the file path. +2. **Display** the complete release notes to the user. +3. **Ask** if they want to update the GitHub release: + +```text +Release notes saved to /tmp/release-notes-{current_tag}.md + +Would you like to update the GitHub release for {current_tag} with these notes? (y/n) +``` + +4. If yes, update via: + +```bash +gh release edit {current_tag} --notes-file /tmp/release-notes-{current_tag}.md +``` + +If the release has a TODO placeholder (matching pattern `TODO: XXXXX.*?see older releases for some example`), replace only that placeholder in the existing release body rather than overwriting the entire body. + +### Step 9: Slack announcement (optional) + +After Step 8, ask the user if they want a Slack announcement message. + +If yes: + +1. Generate a friendly summary with a few emojis (not excessive), listing the top 5-7 most important features and/or bug fixes. Max 7 items total. +2. Save to `/tmp/release-notes-slack-{current_tag}.txt` and tell the user the file path so they can copy-paste. +3. Append the GitHub release URL at the end of the message. + +## Error Handling + +| Scenario | Action | +| --- | --- | +| No tag at HEAD | List recent tags, ask user to pick one | +| User-specified tag doesn't exist locally | Error and ask user to verify the tag name | +| Tag doesn't exist on GitHub | Stop and report error — do not proceed | +| `gh` not authenticated | Instruct user to run `gh auth login` | +| No previous tag found | Ask user to provide one manually | +| No GitHub release for tag | Warn but allow generating notes for preview | +| Release is a draft | Proceed normally (expected state) | +| Release is already published | Ask for explicit confirmation before overriding | +| API rate limiting | Report the error, suggest waiting or using a different token | + +## User Confirmation Requirements + +**CRITICAL**: Always confirm tags before gathering data. Always confirm before updating a GitHub release. Never update a release without explicit user approval. diff --git a/.agents/skills/release-notes/references/release-notes-format.md b/.agents/skills/release-notes/references/release-notes-format.md new file mode 100644 index 000000000..95fc6ad17 --- /dev/null +++ b/.agents/skills/release-notes/references/release-notes-format.md @@ -0,0 +1,115 @@ +# Release Notes Format Reference + +## Entry Format + +Each release note entry MUST follow this exact format. The Link line MUST be indented with two spaces so it renders as a nested sub-bullet: + +```markdown +* **Bold title:** One-sentence description of the change. + * Link: +``` + +### Rules + +- The first bullet MUST start with `*` (no indent) with a bold title followed by a colon and a description. +- The Link line MUST start with `* Link:` (two-space indent) with the PR or commit URL. +- Do NOT add a Contributors section. + +## Header Template + +```markdown +# Tekton CLI {tag} + +Tekton CLI {tag} has been released 🎉 +``` + +## Section Headers + +Use exactly these section headers (skip empty ones): + +```markdown +## ✨ Major changes and Features +## 🐛 Bug Fixes +## 📚 Documentation Updates +## ⚙️ Chores +``` + +## Installation Section Template + +```markdown +## Installation + +Install `tkn` using one of the following methods: + +### Homebrew (macOS/Linux) + +\`\`\`shell +brew install tektoncd-cli +\`\`\` + +### Release binary + +Download the binary for your platform from the [GitHub release page](https://github.com/tektoncd/cli/releases/tag/{tag}). + +### From source + +\`\`\`shell +go install github.com/tektoncd/cli/cmd/tkn@{tag} +\`\`\` + +### Documentation + +https://github.com/tektoncd/cli/tree/{tag}/docs +``` + +## Complete Example + +```markdown +# Tekton CLI v0.45.0 + +Tekton CLI v0.45.0 has been released 🎉 + +## ✨ Major changes and Features + +* **Add pipeline start --dry-run flag:** Preview PipelineRun YAML without creating it on the cluster. + * Link: https://github.com/tektoncd/cli/pull/1234 +* **Support JSON output for task describe:** Add --output=json flag to task describe command. + * Link: https://github.com/tektoncd/cli/pull/1230 + +## 🐛 Bug Fixes + +* **Fix log streaming race condition:** Corrected concurrent container log streaming that caused nil pointer panics. + * Link: https://github.com/tektoncd/cli/pull/1220 + +## ⚙️ Chores + +* **Bump go.opentelemetry.io/otel from 1.28.0 to 1.29.0:** Updated OpenTelemetry dependency to latest version. + * Link: https://github.com/tektoncd/cli/pull/1215 + +## Installation + +Install `tkn` using one of the following methods: + +### Homebrew (macOS/Linux) + +\`\`\`shell +brew install tektoncd-cli +\`\`\` + +### Release binary + +Download the binary for your platform from the [GitHub release page](https://github.com/tektoncd/cli/releases/tag/v0.45.0). + +### From source + +\`\`\`shell +go install github.com/tektoncd/cli/cmd/tkn@v0.45.0 +\`\`\` + +### Documentation + +https://github.com/tektoncd/cli/tree/v0.45.0/docs + +## What's Changed + +``` diff --git a/.claude/.gitignore b/.claude/.gitignore new file mode 100644 index 000000000..dda3f58d8 --- /dev/null +++ b/.claude/.gitignore @@ -0,0 +1 @@ +*.local.json diff --git a/.claude/skills b/.claude/skills new file mode 120000 index 000000000..2b7a412b8 --- /dev/null +++ b/.claude/skills @@ -0,0 +1 @@ +../.agents/skills \ No newline at end of file diff --git a/.cursor/skills b/.cursor/skills new file mode 120000 index 000000000..2b7a412b8 --- /dev/null +++ b/.cursor/skills @@ -0,0 +1 @@ +../.agents/skills \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 000000000..9959d9294 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,147 @@ +# Tekton CLI (`tkn`) + +Command-line client for [Tekton Pipelines](https://tekton.dev). Provides +sub-commands to create, list, describe, start, cancel, delete, and log +Tekton resources (Tasks, Pipelines, PipelineRuns, etc.). + +--- + +## Build & Test Commands + +```bash +# Build +go build ./cmd/tkn +make bin/tkn + +# Cross-platform binaries +make cross + +# Test — runs without a cluster +make test +make test-unit +make test-unit-race + +# Lint — must pass before every PR +make lint # golangci-lint + goimports + yamllint +make lint-go # Go only, all packages +make lint-go PKG=./pkg/pods/... # single package (fast) + +# Code formatting +make fmt # gofmt +make goimports # goimports + +# Update golden files (test snapshots) +make update-golden + +# Regenerate docs and formatting +make generated # update-golden + docs + fmt + +# Dependency update — required after go.mod changes +go mod vendor +``` + +E2E tests require a live cluster with Tekton Pipelines installed. +See [DEVELOPMENT.md](./DEVELOPMENT.md) for cluster setup. + +--- + +## Key Conventions + +1. **Cobra command tree.** Every user-facing command lives under `pkg/cmd/`. + The root command is wired in `cmd/tkn/main.go`. Each resource type + (pipeline, task, pipelinerun, …) has its own sub-package with standard + verbs: `create`, `delete`, `describe`, `list`, `start`, `logs`. + +2. **`pkg/` holds the shared logic.** Packages like `pkg/actions/`, + `pkg/formatted/`, `pkg/log/`, and `pkg/pods/` do the real work (API calls, + output formatting, log streaming). The command files in `pkg/cmd/` are mostly wiring, and should + only handle flag parsing and call into these packages — don't put core + logic directly in command handlers. + +3. **Golden-file tests.** Many commands use golden files for expected output. + After changing command output, run `make update-golden` and commit the + updated `.golden` files. + +4. **Vendored dependencies.** This project vendors (`go mod vendor`). + Always commit `vendor/` changes when updating `go.mod`. + +5. **`-mod=vendor` everywhere.** Builds and lints use `-mod=vendor`. + Do not remove this flag. + +6. **Version injection via ldflags.** The client version is injected at build + time through `LDFLAGS`. See the `Makefile` for `VERSIONLDFLAG`. + +--- + +## Architecture + +``` +cmd/ + tkn/ # main entry point + docs/ # doc generation binary + +pkg/ + cmd/ # Cobra command implementations (one sub-package per resource) + pipeline/ # tkn pipeline create|delete|describe|list|start|logs + task/ # tkn task create|delete|describe|list|start|logs + pipelinerun/ + taskrun/ + ... + actions/ # generic Tekton resource CRUD via dynamic client + cli/ # shared CLI params, streams, clients + flags/ # common flag definitions + formatted/ # output formatters (table, YAML, JSON) + log/ # log streaming for PipelineRun/TaskRun + pods/ # logic for finding the right container in a pod, pod/container log helpers + params/ # parameter parsing and merging + plugins/ # tkn plugin discovery + version/ # client/server version reporting + +docs/ # auto-generated CLI reference (markdown + man pages) +test/ # E2E test framework and scripts +hack/ # helper scripts (golden-file updates, etc.) +third_party/ # vendored non-Go assets +``` + +**Key patterns:** + +- Each command sub-package typically has: `.go` (command definition), + `_test.go` (unit tests), and `testdata/` with `.golden` files. +- `pkg/cli/Clients` bundles the Tekton, Kubernetes, and dynamic clients. + Commands receive this via their `Params` interface. + +--- + +## PR Conventions + +- Pull requests must follow the repository PR template defined in `.github/pull_request_template.md`. +- `make lint` must pass with zero issues. +- `make test` must pass with zero failures. +- Run `make generated` and commit generated/golden files when command output changes. +- Commit messages follow [Tekton community standards](https://github.com/tektoncd/community/blob/master/standards.md#commit-messages). + +--- + +## Windows checkout + +`CLAUDE.md` points to `AGENTS.md`, and `.claude/skills` points to `.agents/skills`. +This works on Linux, macOS, and GitHub; on Windows, enable symlinks when cloning: + +```bash +git clone -c core.symlinks=true https://github.com/tektoncd/cli.git +``` + +Alternatively, set `core.symlinks=true` in your git config before checkout. + +--- + +## Skills + +For complex workflows, use these repo-local skills: + +- **Commit messages**: Conventional commits with component scopes, line length + validation, DCO Signed-off-by, and Assisted-by trailers. Trigger: "create + commit", "commit changes", "generate commit message" +- **Release notes**: Gather PRs between tags, categorize, output formatted + markdown, optionally update GitHub release. Trigger: "create release note", + "generate release notes", "release changelog" diff --git a/CLAUDE.md b/CLAUDE.md new file mode 120000 index 000000000..47dc3e3d8 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +AGENTS.md \ No newline at end of file diff --git a/Makefile b/Makefile index e39f736a4..31d7044d5 100644 --- a/Makefile +++ b/Makefile @@ -102,8 +102,8 @@ $(BIN)/golangci-lint: ; $(info $(M) getting golangci-lint $(GOLANGCI_VERSION)) GOTOOLCHAIN=go$$(grep '^go ' go.mod | awk '{print $$2}') GOBIN=$(BIN) $(GO) install -mod=mod github.com/golangci/golangci-lint/v2/cmd/golangci-lint@$(GOLANGCI_VERSION) .PHONY: lint-go -lint-go: | $(GOLANGCILINT) ; $(info $(M) running golangci-lint…) @ ## Run golangci-lint - $Q $(GOLANGCILINT) run --modules-download-mode=vendor --max-issues-per-linter=0 --max-same-issues=0 --timeout 10m +lint-go: | $(GOLANGCILINT) ; $(info $(M) running golangci-lint…) @ ## Run golangci-lint (PKG=./pkg/pods/... for single package) + $Q $(GOLANGCILINT) run --modules-download-mode=vendor --max-issues-per-linter=0 --max-same-issues=0 --timeout 10m $(PKG) GOIMPORTS = $(BIN)/goimports $(GOIMPORTS): ; $(info $(M) getting goimports ) @@ -125,8 +125,8 @@ test-unit-race: ARGS=-race test-unit-verbose-and-race: ARGS=-v -race $(TEST_UNIT_TARGETS): test-unit .PHONY: $(TEST_UNIT_TARGETS) test-unit -test-unit: ; $(info $(M) running unit tests…) ## Run unit tests - $(GO) test -timeout $(TIMEOUT_UNIT) $(ARGS) $(shell go list ./... | grep -v third_party/) +test-unit: ; $(info $(M) running unit tests…) ## Run unit tests (PKG=./pkg/pods/... for single package) + $(GO) test -timeout $(TIMEOUT_UNIT) $(ARGS) $(PKGS) .PHONY: update-golden update-golden: ./vendor ; $(info $(M) Running unit tests to update golden files…) ## run unit tests (updating golden files)