diff --git a/.agents/skills b/.agents/skills new file mode 120000 index 0000000000..454b8427cd --- /dev/null +++ b/.agents/skills @@ -0,0 +1 @@ +../.claude/skills \ No newline at end of file diff --git a/.claude/.gitignore b/.claude/.gitignore new file mode 100644 index 0000000000..dda3f58d8a --- /dev/null +++ b/.claude/.gitignore @@ -0,0 +1 @@ +*.local.json diff --git a/.claude/skills/commit-message/SKILL.md b/.claude/skills/commit-message/SKILL.md new file mode 100644 index 0000000000..ee0ba882bd --- /dev/null +++ b/.claude/skills/commit-message/SKILL.md @@ -0,0 +1,327 @@ +--- +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) +- Pass gitlint validation + +## 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) +```text + +### Type Selection + +Choose the appropriate commit type based on changes: + +| Type | Description | Example | +| ------ | ------------- | --------- | +| `feat` | New features | `feat(tektonpipeline): add HA support` | +| `fix` | Bug fixes | `fix(reconciler): resolve race condition` | +| `docs` | Documentation | `docs(README): update installation steps` | +| `refactor` | Code refactoring | `refactor(installerset): simplify logic` | +| `test` | Test changes | `test(tektonchain): add 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(cache): optimize lookup speed` | +| `style` | Code style | `style(format): run fumpt formatter` | +| `revert` | Revert commit | `revert: undo breaking API 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 +# Get list of staged files +git diff --cached --name-only +```text + +| File pattern | Scope | Example commit | +| ------------ | ----- | -------------- | +| `pkg/reconciler/kubernetes/tektonpipeline/*` | `tektonpipeline` | `fix(tektonpipeline): resolve status update` | +| `pkg/reconciler/kubernetes/tektonchain/*` | `tektonchain` | `feat(tektonchain): add OCI support` | +| `pkg/reconciler/openshift/*` | component name | `fix(tektondashboard): fix route creation` | +| `pkg/reconciler/common/*` | `common` | `refactor(common): simplify installerset` | +| `pkg/apis/*` | `api` | `feat(api): add new CRD field` | +| `docs/*` | `docs` or filename | `docs(README): update steps` | +| `test/*` | component being tested | `test(tektonchain): add unit tests` | +| `cmd/*` | command name | `feat(operator): add new flag` | +| 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-installerset-race +# Scope: #123 or component name +# Result: fix(installerset): resolve race condition + +Fixes #123 +```text + +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 (gitlint enforced) +- **Format**: `type(scope): description` counts toward limit +- **Tips**: Use present tense, no period at end + +```text +# Good (44 chars) +feat(tektonpipeline): add HA support + +# Too long - will fail gitlint +feat(tektonpipeline): add comprehensive high-availability support for pipeline controller +```text + +### 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(tektonpipeline): add HA support + +Enable leader election in the pipeline reconciler to support +high-availability deployments. This prevents split-brain issues +when multiple operator replicas are running. + +Signed-off-by: Developer Name +Assisted-by: Claude Sonnet 4.5 (via Cursor) +```text + +## 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 +# Check environment variables first +echo "$GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL>" + +# Fallback to git config +git config user.name +git config user.email +```text + +For complete detection logic, see `references/trailer-detection.md`. + +### 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) +```text + +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(tektonchain): add OCI bundle support + +Enable storing Tekton Chains signatures in OCI registries. This +allows signing artifacts without requiring a separate storage +backend. + +Signed-off-by: Developer Name +Assisted-by: Claude Sonnet 4.5 (via Cursor) +--- + +Should I commit with this message? (y/n) +```text + +Wait for user response before proceeding. + +## Commit Execution + +Use heredoc format for proper multi-line handling: + +```bash +git commit -m "$(cat <<'EOF' +feat(tektonchain): add OCI bundle support + +Enable storing Tekton Chains signatures in OCI registries. + +Signed-off-by: Developer Name +Assisted-by: Claude Sonnet 4.5 (via Cursor) +EOF +)" +```text + +**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(tektonpipeline): add HA leader election + +Enable leader election in the pipeline reconciler to support +high-availability deployments with multiple operator replicas. + +Signed-off-by: Jane Developer +Assisted-by: Claude Sonnet 4.5 (via Cursor) +```text + +### Bug fix closing a GitHub issue + +```text +fix(installerset): resolve concurrent reconcile panic + +Prevent nil pointer dereference when two reconcile loops run +concurrently on the same InstallerSet object. + +Fixes #789 + +Signed-off-by: John Developer +Assisted-by: Claude Sonnet 4.5 (via Cursor) +```text + +### Documentation update + +```text +docs(AGENTS.md): update architecture conventions + +Add InstallerSet naming patterns and clarify platform split +rules for contributors. + +Signed-off-by: Jane Developer +Assisted-by: Claude Sonnet 4.5 (via Cursor) +```text + +### Breaking change + +```text +feat(api)!: remove deprecated TektonConfig fields + +Remove fields deprecated in v0.60 from TektonConfig spec. +Operators upgrading from v0.60 must migrate before upgrading. + +BREAKING CHANGE: Removed spec.targetNamespace and spec.profile +from TektonConfig. Use spec.config instead. See migration guide. + +Signed-off-by: John Developer +Assisted-by: Claude Sonnet 4.5 (via Cursor) +```text + +## Gitlint Integration + +This project uses gitlint to enforce commit message format. Ensure all commit messages pass gitlint validation. + +**Common gitlint rules**: + +- Conventional commit format required +- Subject line length limits (50 soft, 72 hard) +- Required trailers (Signed-off-by) +- No trailing whitespace +- Body line wrapping at 72 characters + +For complete gitlint rules, see `references/gitlint-rules.md`. + +## 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/gitlint-rules.md`** - Gitlint validation rules and configuration +- **`references/trailer-detection.md`** - Author detection logic and priority order diff --git a/.claude/skills/commit-message/references/commit-types.md b/.claude/skills/commit-message/references/commit-types.md new file mode 100644 index 0000000000..c81f60cb3d --- /dev/null +++ b/.claude/skills/commit-message/references/commit-types.md @@ -0,0 +1,269 @@ +# Commit Types Reference + +Complete reference for conventional commit types used in the Tekton Operator project. + +## Standard Types + +### feat - New Features + +Use for new features or functionality added to the codebase. + +**Examples**: + +- `feat(webhook): add GitHub App support` +- `feat(SRVKP-123): implement GitLab integration` +- `feat(controller): add pipeline caching mechanism` +- `feat(api): expose webhook configuration endpoint` + +**When to use**: + +- Adding new capabilities +- Introducing new components +- Implementing new APIs or endpoints +- Adding new configuration options + +### fix - Bug Fixes + +Use for bug fixes that resolve incorrect behavior. + +**Examples**: + +- `fix(controller): resolve pipeline race condition` +- `fix(SRVKP-456): correct webhook payload parsing` +- `fix(matcher): handle edge case in regex matching` +- `fix(api): prevent nil pointer dereference` + +**When to use**: + +- Fixing crashes or errors +- Resolving incorrect behavior +- Patching security vulnerabilities +- Correcting logic errors + +### docs - Documentation + +Use for documentation-only changes. + +**Examples**: + +- `docs(README): update installation steps` +- `docs(api): add webhook endpoint documentation` +- `docs(SRVKP-789): document GitLab setup process` +- `docs(contributing): add code review guidelines` + +**When to use**: + +- Updating README files +- Adding or improving code comments +- Updating API documentation +- Improving developer guides + +### refactor - Code Refactoring + +Use for code changes that neither fix bugs nor add features, but improve code structure. + +**Examples**: + +- `refactor(controller): extract reconciliation logic` +- `refactor(matcher): simplify regex patterns` +- `refactor(webhook): consolidate handler functions` +- `refactor(SRVKP-234): reorganize package structure` + +**When to use**: + +- Improving code readability +- Simplifying complex logic +- Reorganizing code structure +- Extracting common functionality + +### test - Testing + +Use for adding or updating tests. + +**Examples**: + +- `test(webhook): add integration tests` +- `test(controller): improve coverage for edge cases` +- `test(SRVKP-567): add E2E test for GitLab` +- `test(matcher): add regex validation tests` + +**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 make vendor` +- `chore(tools): update pre-commit hooks` +- `chore(SRVKP-890): update golangci-lint version` + +**When to use**: + +- Dependency updates +- Tooling configuration +- Repository maintenance +- Build script updates (minor) + +### build - Build System + +Use for changes to build system or external dependencies. + +**Examples**: + +- `build(Makefile): add vendor target` +- `build(docker): update container base image` +- `build(ko): configure image build settings` +- `build(SRVKP-345): add arm64 build support` + +**When to use**: + +- Makefile changes +- Docker/container build changes +- Dependency management (major) +- Build configuration updates + +### ci - CI/CD Changes + +Use for changes to CI/CD configuration and scripts. + +**Examples**: + +- `ci(github): add golangci-lint action` +- `ci(actions): update test workflow` +- `ci(SRVKP-678): add GitLab CI configuration` +- `ci(pre-commit): add new validation hooks` + +**When to use**: + +- GitHub Actions updates +- GitLab CI configuration +- Pre-commit hook changes +- CI pipeline modifications + +### perf - Performance Improvements + +Use for changes that improve performance. + +**Examples**: + +- `perf(cache): optimize lookup speed` +- `perf(controller): reduce reconciliation time` +- `perf(SRVKP-901): implement pipeline caching` +- `perf(matcher): improve regex compilation` + +**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 fumpt formatter` +- `style(lint): fix golangci-lint warnings` +- `style(markdown): fix markdownlint issues` +- `style(SRVKP-123): 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 API change` +- `revert(SRVKP-456): revert webhook refactoring` +- `revert: "feat(api): add new endpoint"` + +**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(api)!: change webhook payload format` +- `fix(controller)!: remove deprecated reconciliation mode` +- `refactor(webhook)!: restructure event handlers` + +**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(api): add new endpoint` +**Right**: `feat(api): add new endpoint` +*Reason: Adding endpoint is a feature, not maintenance* + +**Wrong**: `feat(tests): add test coverage` +**Right**: `test(controller): 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` +**Right**: `ci(github): update test workflow` +*Reason: CI changes have their own type* + +## Multiple Changes in One Commit + +When a commit includes multiple types of changes, choose the most significant: + +**Example**: Adding feature + tests + +- **Choose**: `feat(webhook): add GitHub App support` +- **Body mentions**: "Includes integration tests" + +**Example**: Bug fix + refactoring + +- **Choose**: `fix(controller): resolve race condition` +- **Body mentions**: "Refactored reconciliation logic for clarity" + +**Guideline**: If changes are too diverse, consider splitting into multiple commits. diff --git a/.claude/skills/commit-message/references/gitlint-rules.md b/.claude/skills/commit-message/references/gitlint-rules.md new file mode 100644 index 0000000000..551d670790 --- /dev/null +++ b/.claude/skills/commit-message/references/gitlint-rules.md @@ -0,0 +1,541 @@ +# Gitlint Integration and Rules + +Complete reference for gitlint validation rules enforced in this project. + +## Overview + +Gitlint is a commit message linter that enforces conventional commits format and other quality standards. All commits must pass gitlint validation before being accepted. + +## Installation and Usage + +### Running Gitlint Locally + +```bash +# Install gitlint (if not installed) +pip install gitlint + +# Lint last commit +gitlint + +# Lint specific commit +gitlint --commit abc123 + +# Lint commit message from stdin +echo "feat: add new feature" | gitlint + +# Lint range of commits +gitlint --commits "origin/main..HEAD" +```text + +### Pre-commit Integration + +Gitlint runs automatically via pre-commit hooks: + +```bash +# Install pre-commit hooks +pre-commit install + +# Gitlint runs on git push +git push + +# Skip gitlint (not recommended) +git push --no-verify + +# Skip specific hook +SKIP=gitlint git push +```text + +## Core Gitlint Rules + +### T1: Title Max Length + +**Rule**: Subject line must not exceed 72 characters (hard limit) +**Recommendation**: Target 50 characters for better readability + +**Examples**: + +✓ **Valid**: + +```text +feat(SRVKP-123): add webhook controller +```text + +(45 characters) + +✗ **Invalid**: + +```text +feat(SRVKP-123): add comprehensive webhook controller with GitHub integration support +```text + +(78 characters, exceeds limit) + +**How to fix**: + +- Use concise, present-tense descriptions +- Move details to commit body +- Remove unnecessary words + +✓ **Fixed**: + +```text +feat(SRVKP-123): add webhook controller + +Implements comprehensive GitHub integration with support for +push events, pull requests, and issue comments. +```text + +### T2: Title Trailing Whitespace + +**Rule**: Subject line must not have trailing whitespace + +**Examples**: + +✗ **Invalid**: + +```text +feat(webhook): add handler +```text + +(trailing spaces) + +✓ **Valid**: + +```text +feat(webhook): add handler +```text + +**How to fix**: Remove trailing whitespace from subject line + +### T3: Title Trailing Punctuation + +**Rule**: Subject line must not end with punctuation (`.`, `!`, `?`, `,`, `:`, `;`) + +**Exception**: `!` is allowed for breaking changes (e.g., `feat!:`) + +**Examples**: + +✗ **Invalid**: + +```text +feat(webhook): add handler. +fix(controller): resolve bug! +```text + +✓ **Valid**: + +```text +feat(webhook): add handler +fix(controller): resolve bug +feat(api)!: change endpoint format +```text + +**How to fix**: Remove ending punctuation from subject line + +### T5: Body Line Length + +**Rule**: Each line in commit body must not exceed 72 characters + +**Examples**: + +✗ **Invalid**: + +```text +feat(webhook): add handler + +This commit implements a comprehensive webhook handler for GitHub push events with support for validation. +```text + +(Line 2 is 119 characters) + +✓ **Valid**: + +```text +feat(webhook): add handler + +Implement comprehensive webhook handler for GitHub push events. +Includes validation and error handling. +```text + +(Each line ≤ 72 characters) + +**How to fix**: Wrap body text at 72 characters per line + +### T6: Body Missing Blank Line + +**Rule**: Must have blank line between subject and body + +**Examples**: + +✗ **Invalid**: + +```text +feat(webhook): add handler +This implements webhook support. +```text + +✓ **Valid**: + +```text +feat(webhook): add handler + +This implements webhook support. +```text + +**How to fix**: Add blank line after subject + +### T7: Body Match Regex + +**Rule**: Enforces conventional commits format in subject line + +**Pattern**: `^(feat|fix|docs|style|refactor|test|chore|build|ci|perf|revert)(\(.+\))?: .+` + +**Requirements**: + +- Start with valid type +- Optional scope in parentheses +- Colon and space after type/scope +- Non-empty description + +**Examples**: + +✓ **Valid**: + +```text +feat(webhook): add handler +fix: resolve bug +docs(README): update steps +```text + +✗ **Invalid**: + +```text +Add handler (no type) +feat add handler (missing colon) +feat: add handler. (trailing period - T3 violation) +feature(webhook): add handler (invalid type) +```text + +**How to fix**: Follow conventional commits format exactly + +## Additional Rules + +### B1: Body Max Line Length + +**Rule**: Enforces 72-character limit for all body lines + +Same as T5, but applies to entire body. + +### B3: Body First Line Empty + +**Rule**: First line after subject must be blank + +Same as T6. + +## Conventional Commits Validation + +### Valid Type Enforcement + +Gitlint enforces specific commit types: + +- `feat` - Features +- `fix` - Bug fixes +- `docs` - Documentation +- `style` - Formatting +- `refactor` - Code refactoring +- `test` - Tests +- `chore` - Maintenance +- `build` - Build system +- `ci` - CI/CD +- `perf` - Performance +- `revert` - Reverts + +**Invalid types will fail validation**: + +```text +feature(webhook): add handler # Use 'feat', not 'feature' +bugfix(api): resolve issue # Use 'fix', not 'bugfix' +documentation: update README # Use 'docs', not 'documentation' +```text + +### Scope Format + +**Optional but if present**: + +- Must be in parentheses +- Must be after type +- Must be followed by colon + +✓ **Valid**: + +```text +feat(webhook): add handler (with scope) +feat: add handler (without scope) +```text + +✗ **Invalid**: + +```text +feat webhook: add handler (missing parentheses) +(webhook)feat: add handler (wrong position) +feat(webhook) add handler (missing colon) +```text + +## Trailer Validation + +### Required Trailers + +**Signed-off-by**: Required in this project + +**Format**: `Signed-off-by: Name ` + +**Examples**: + +✓ **Valid**: + +```text +Signed-off-by: John Developer +```text + +✗ **Invalid**: + +```text +Signed-off-by: John Developer (missing email) +Signed-off-by: john@redhat.com (missing name) +signed-off-by: John Developer (lowercase, invalid email) +```text + +### Recommended Trailers + +**Assisted-by**: Recommended when AI assists + +**Format**: `Assisted-by: Model Name (via Tool)` + +**Examples**: + +✓ **Valid**: + +```text +Assisted-by: Claude Sonnet 4.5 (via Claude Code) +Assisted-by: Claude Opus 4.5 (via Claude Code) +```text + +## Breaking Changes + +### Indicating Breaking Changes + +**Method 1**: Add `!` after type/scope: + +```text +feat(api)!: change endpoint format +fix(controller)!: remove deprecated mode +```text + +**Method 2**: Include `BREAKING CHANGE:` in footer: + +```text +feat(api): update webhook payload + +BREAKING CHANGE: Webhook payload structure changed. See migration +guide for update instructions. +```text + +**Both methods can be combined for maximum clarity**. + +## Common Gitlint Errors and Fixes + +### Error: "Title exceeds max length (72>72)" + +**Cause**: Subject line too long + +**Fix**: Shorten description or move details to body + +```text +# Before (78 chars) +feat(SRVKP-123): add comprehensive webhook controller with GitHub support + +# After (45 chars) +feat(SRVKP-123): add webhook controller + +Add comprehensive GitHub integration with push event support. +```text + +### Error: "Title has trailing punctuation (.)" + +**Cause**: Subject line ends with period or other punctuation + +**Fix**: Remove trailing punctuation + +```text +# Before +feat(webhook): add handler. + +# After +feat(webhook): add handler +```text + +### Error: "Body line exceeds max length (85>72)" + +**Cause**: Body line too long + +**Fix**: Wrap text at 72 characters + +```text +# Before +This commit implements a comprehensive webhook handler for GitHub with validation. + +# After +This commit implements a comprehensive webhook handler for GitHub +with validation and error handling. +```text + +### Error: "Title does not match regex" + +**Cause**: Not following conventional commits format + +**Fix**: Use valid type, scope, colon, and description + +```text +# Before +Add webhook handler +Feature: add webhook handler +feat add webhook handler + +# After +feat(webhook): add handler +```text + +### Error: "Body message missing required signature" + +**Cause**: Missing Signed-off-by trailer + +**Fix**: Add required trailer + +```text +feat(webhook): add handler + +Add webhook support for GitHub push events. + +Signed-off-by: John Developer +```text + +## Gitlint Configuration + +### Project Configuration + +Gitlint configuration is typically in `.gitlint` or `setup.cfg`: + +```ini +[general] +ignore=body-is-missing + +[title-max-length] +line-length=72 + +[title-must-not-contain-word] +words=wip,WIP,todo,TODO + +[body-max-line-length] +line-length=72 + +[body-min-length] +min-length=0 +```text + +### Custom Rules + +This project may have custom gitlint rules for: + +- Conventional commits enforcement +- Required trailers (Signed-off-by) +- Scope validation +- Jira issue format validation + +## CI/CD Integration + +### GitHub Actions + +Gitlint runs in CI pipeline: + +```yaml +- name: Lint commit messages + run: gitlint --commits "origin/${{ github.base_ref }}..HEAD" +```text + +**All commits in PR must pass gitlint before merge**. + +### Pre-push Hooks + +Configured via `.pre-commit-config.yaml`: + +```yaml +- repo: https://github.com/jorisroovers/gitlint + rev: v0.19.1 + hooks: + - id: gitlint + stages: [commit-msg] +```text + +## Best Practices + +1. **Check before committing**: Run `gitlint` locally before push +2. **Install pre-commit hooks**: Catch issues early with `pre-commit install` +3. **Follow 50/72 rule**: 50 chars for subject (soft), 72 for body (hard) +4. **Use conventional commits**: Always start with valid type +5. **Include required trailers**: Signed-off-by is mandatory +6. **No trailing punctuation**: Subject line ends without period +7. **Blank line separation**: Always separate subject and body +8. **Wrap body text**: Keep all lines under 72 characters + +## Troubleshooting + +### Gitlint Not Running + +**Check**: + +1. Pre-commit hooks installed: `pre-commit install --install-hooks` +2. Gitlint in hooks config: Check `.pre-commit-config.yaml` +3. Hook not skipped: Avoid `--no-verify` + +### Gitlint Failing in CI but Passing Locally + +**Possible causes**: + +1. Different gitlint versions +2. Different configuration files +3. CI checks additional commits + +**Fix**: Match CI environment locally + +### Need to Skip Gitlint Temporarily + +**Not recommended**, but for emergency: + +```bash +# Skip all hooks +git push --no-verify + +# Skip gitlint only +SKIP=gitlint git push +```text + +**Document reason in commit or PR**. + +## Resources + +- [Conventional Commits Specification](https://www.conventionalcommits.org/) +- [Gitlint Documentation](https://jorisroovers.com/gitlint/) +- [Pre-commit Framework](https://pre-commit.com/) + +## Summary + +Gitlint enforces: + +- ✓ Conventional commits format (type, scope, description) +- ✓ Line length limits (72 chars for subject and body) +- ✓ Proper spacing (blank line between subject and body) +- ✓ No trailing punctuation (except `!` for breaking changes) +- ✓ Required trailers (Signed-off-by) +- ✓ Valid commit types (feat, fix, docs, etc.) + +Follow these rules to ensure commits pass validation in both local and CI environments. diff --git a/.claude/skills/commit-message/references/trailer-detection.md b/.claude/skills/commit-message/references/trailer-detection.md new file mode 100644 index 0000000000..2db47e3bcd --- /dev/null +++ b/.claude/skills/commit-message/references/trailer-detection.md @@ -0,0 +1,554 @@ +# 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 +```bash + +**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@redhat.com" +```bash + +**Result**: + +```bash +Signed-off-by: Jane Developer +```bash + +**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 + # Use git config values + echo "Using git config: $author_name <$author_email>" +fi +```bash + +**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@redhat.com" + +# Or per-repository +git config user.name "John Developer" +git config user.email "john.developer@redhat.com" +```bash + +**Result**: + +```bash +Signed-off-by: John Developer +```bash + +**When used**: + +- Standard git installations +- User's local machine +- Normal development workflow + +#### Priority 3: Ask User (Last Resort) + +If neither environment variables nor git config available: + +**Prompt**: + +```bash +Git author information not configured. Please provide: + +Full Name: _ +Email: _ +```bash + +**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: + +```bash +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) +```bash + +**Example interaction**: + +```bash +Git author information not configured. Please provide: + +Full Name: Sarah Developer +Email: sarah@example.com + +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) + +Choice: 2 + +Saving globally... +git config --global user.name "Sarah Developer" +git config --global user.email "sarah@example.com" +Done! +```bash + +### 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 + + # Validate + if [ -z "$author_name" ] || [ -z "$author_email" ]; then + echo "Error: Name and email are required" >&2 + return 1 + fi + + # Offer to save + echo "Would you like to save this information?" >&2 + echo "1. For this repository only (git config)" >&2 + echo "2. Globally for all repositories (git config --global)" >&2 + echo "3. Just for this commit (don't save)" >&2 + read -p "Choice: " choice + + case $choice in + 1) + git config user.name "$author_name" + git config user.email "$author_email" + echo "Saved to repository config" >&2 + ;; + 2) + git config --global user.name "$author_name" + git config --global user.email "$author_email" + echo "Saved to global config" >&2 + ;; + 3) + echo "Using for this commit only" >&2 + ;; + esac + fi + + # Generate Signed-off-by trailer + echo "Signed-off-by: $author_name <$author_email>" +} +```bash + +### Email Validation + +Ensure email is in valid format: + +**Valid formats**: + +- `user@example.com` +- `first.last@company.com` +- `user+tag@example.com` +- `user123@subdomain.example.com` + +**Invalid formats**: + +- `user@` (missing domain) +- `@example.com` (missing user) +- `user@.com` (missing domain name) +- `user example.com` (missing @) + +**Validation regex**: `^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$` + +### Common Scenarios + +#### Scenario 1: DevContainer with Environment Variables + +```bash +# In .devcontainer/devcontainer.json +{ + "containerEnv": { + "GIT_AUTHOR_NAME": "Developer Name", + "GIT_AUTHOR_EMAIL": "developer@redhat.com" + } +} +```bash + +**Detection**: Uses environment variables (Priority 1) +**Result**: `Signed-off-by: Developer Name ` + +#### Scenario 2: Local Development with Git Config + +```bash +# User's ~/.gitconfig +[user] + name = John Developer + email = john@example.com +```bash + +**Detection**: Uses git config (Priority 2) +**Result**: `Signed-off-by: John Developer ` + +#### Scenario 3: Fresh Git Installation + +No environment variables, no git config set. + +**Detection**: Asks user (Priority 3) +**Interaction**: + +```bash +Git author information not configured. Please provide: + +Full Name: Sarah Developer +Email: sarah@example.com + +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) + +Choice: 2 +```bash + +**Result**: `Signed-off-by: Sarah Developer ` + +#### Scenario 4: 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@redhat.com" +```bash + +**Detection**: Uses repository config (overrides global) +**Result**: `Signed-off-by: Jane Developer ` + +## Assisted-by Trailer + +### Assisted-by 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 +- Claude Haiku +- (Other Claude models) + +**Detection method**: Check model identifier or configuration + +**Examples**: + +```bash +Assisted-by: Claude Sonnet 4.5 (via Claude Code) +Assisted-by: Claude Opus 4.5 (via Claude Code) +Assisted-by: Claude Haiku (via Claude Code) +```bash + +### Tool Name + +Always use: `Claude Code` (for this project) + +Other projects might use: + +- `Cursor` +- `Continue` +- `Codex CLI` +- Other AI coding tools + +### 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**: + +```bash +Assisted-by: Claude Sonnet 4.5 (via Claude Code) +```bash + +**Incorrect**: + +```bash +Assisted-by: claude-sonnet (via claude code) # Wrong casing +Assisted-by: Sonnet (via CC) # Abbreviations +Assisted-by: (via Claude Code) Claude Sonnet 4.5 # Wrong order +Assisted-by: Claude Sonnet 4.5 # Missing tool +```bash + +### When to Include + +**Always include** when: + +- AI generated commit message +- AI assisted with commit message +- AI helped analyze changes +- AI suggested improvements + +**Optional** (user preference) when: + +- AI was consulted but not directly used +- Commit message manually written after AI consultation + +**Standard practice**: Always include for transparency and credit. + +## 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**: + +```bash +Signed-off-by: Full Name +Assisted-by: Model Name (via Tool Name) +```bash + +### Complete Example + +```bash +feat(SRVKP-456): ensure webhook logs output to stdout + +Configure webhook controller to direct all logs to stdout for +container compatibility. This resolves logging issues in Kubernetes +environments where logs are collected from stdout. + +Signed-off-by: Jane Developer +Assisted-by: Claude Sonnet 4.5 (via Claude Code) +```bash + +### 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**: + +```bash +feat(webhook): add handler + +Implements webhook support. + +Signed-off-by: Jane Developer +Assisted-by: Claude Sonnet 4.5 (via Claude Code) +```bash + +**Incorrect**: + +```bash +feat(webhook): add handler + +Implements webhook support. +Signed-off-by: Jane Developer + +Assisted-by: Claude Sonnet 4.5 (via Claude Code) + +```bash + +(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="Claude Code" + + # 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)" +} +```bash + +## 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" +```bash + +### Issue: Wrong author in devcontainer + +**Cause**: Environment variables override git config + +**Check**: + +```bash +echo "$GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL>" +```bash + +**Fix**: Set correct environment variables in `.devcontainer/devcontainer.json`: + +```json +{ + "containerEnv": { + "GIT_AUTHOR_NAME": "Correct Name", + "GIT_AUTHOR_EMAIL": "correct@email.com" + } +} +```bash + +### Issue: Trailers in wrong order + +**Wrong**: + +```bash +Assisted-by: Claude Sonnet 4.5 (via Claude Code) +Signed-off-by: Jane Developer +```bash + +**Correct**: + +```bash +Signed-off-by: Jane Developer +Assisted-by: Claude Sonnet 4.5 (via Claude Code) +```bash + +**Fix**: Always put Signed-off-by first, Assisted-by second + +## Best Practices + +1. **Set git config globally**: Configure once for all projects +2. **Use environment variables in containers**: Consistent in devcontainers +3. **Validate email format**: Ensure valid email before using +4. **Always include both trailers**: Transparency and compliance +5. **Consistent model naming**: Use full model names +6. **Proper spacing**: Blank line before trailers, none between +7. **Check detection priority**: Understand which source is used + +## 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)` + +Following this process ensures consistent, compliant commit trailers across all environments. diff --git a/.claude/skills/release-notes/SKILL.md b/.claude/skills/release-notes/SKILL.md new file mode 100644 index 0000000000..79ee1297c4 --- /dev/null +++ b/.claude/skills/release-notes/SKILL.md @@ -0,0 +1,233 @@ +--- +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.13.0") to bypass auto-detection. +version: 0.1.0 +--- + +# Release Notes Generation + +Generate categorized release notes for Tekton Operator releases by gathering PR/commit data between two git tags and producing formatted markdown output. + +## Purpose + +Claude-native skill that: + +- Auto-detects current and previous tags +- Gathers PR and commit data via `gh` CLI +- Uses Claude itself for intelligent categorization (no external AI API needed) +- 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.31.0"), use that directly as the current tag — skip auto-detection. Validate that the tag exists locally: + +```bash +git tag --list 'v0.31.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.31.0 +Previous tag: v0.30.0 + +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: + +```text +⚠️ The release for {current_tag} is already published (not a draft). +Updating it will override the existing release notes. +Are you sure you want to proceed? (y/n) +``` + +Only continue if the user confirms. + +### 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 +- JIRA tickets using regex pattern: `SRVKP-\d+` (search in PR title, PR body, and commit message) + +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 +- Within each section, entries with JIRA tickets go FIRST, before entries without JIRA tickets + +**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, release pipeline tasks, goreleaser config) +- Test infrastructure (test helpers, e2e framework, test configuration) +- Build system changes (Dockerfile, ko config, 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 flags, new API fields, new provider capabilities, new webhook behaviors, new configuration options, new user-facing commands. + +### 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) +``` + +1. 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/.claude/skills/release-notes/references/release-notes-format.md b/.claude/skills/release-notes/references/release-notes-format.md new file mode 100644 index 0000000000..91fee607b8 --- /dev/null +++ b/.claude/skills/release-notes/references/release-notes-format.md @@ -0,0 +1,126 @@ +# Release Notes Format Reference + +## Entry Format + +Each release note entry MUST follow this exact format. The Link and Jira lines MUST be indented with two spaces so they render as nested sub-bullets: + +```markdown +* **Bold title:** One-sentence description of the change. + * Link: + * Jira: [SRVKP-XXXX](https://issues.redhat.com/browse/SRVKP-XXXX) +``` + +### 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. +- The Jira line MUST start with `* Jira:` (two-space indent). Include it ONLY if the entry has JIRA tickets. If there are multiple tickets, list each as a separate markdown link comma-separated. +- Within each section, list entries that have JIRA tickets FIRST, before entries without JIRA tickets. +- Do NOT add a Contributors section. + +## Header Template + +```markdown +# Tekton Operator {tag} + +Tekton Operator {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 + +To install this version apply the release manifest for your platform: + +### Kubernetes + +\`\`\`shell +kubectl apply -f https://github.com/{owner}/{repo}/releases/download/{tag}/release.yaml +\`\`\` + +### OpenShift + +\`\`\`shell +kubectl apply -f https://github.com/{owner}/{repo}/releases/download/{tag}/release.yaml +\`\`\` + +### Documentation + +https://github.com/{owner}/{repo}/tree/{tag}/docs +``` + +## JIRA Ticket Format + +JIRA tickets matching `SRVKP-\d+` should be linked as: + +```markdown +[SRVKP-XXXX](https://issues.redhat.com/browse/SRVKP-XXXX) +``` + +Multiple tickets are comma-separated: + +```markdown +[SRVKP-1234](https://issues.redhat.com/browse/SRVKP-1234), [SRVKP-5678](https://issues.redhat.com/browse/SRVKP-5678) +``` + +## Complete Example + +```markdown +# Tekton Operator v0.13.0 + +Tekton Operator v0.13.0 has been released 🎉 + +## ✨ Major changes and Features + +* **Add TektonScheduler component support:** Introduced new TektonScheduler CRD for managing scheduler deployments via the operator. + * Link: https://github.com/tektoncd/operator/pull/1234 + * Jira: [SRVKP-1234](https://issues.redhat.com/browse/SRVKP-1234) +* **Support StatefulSet ordinals for TektonPipeline:** Added opt-in StatefulSet ordinals mode for pipeline controller deployments. + * Link: https://github.com/tektoncd/operator/pull/1230 + +## 🐛 Bug Fixes + +* **Fix reconciler requeue on namespace mismatch:** Corrected requeue logic when target namespace changes between reconcile loops. + * Link: https://github.com/tektoncd/operator/pull/1220 + * Jira: [SRVKP-5678](https://issues.redhat.com/browse/SRVKP-5678) + +## ⚙️ 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/operator/pull/1215 + +## Installation + +To install this version apply the release manifest for your platform: + +### Kubernetes + +\`\`\`shell +kubectl apply -f https://github.com/tektoncd/operator/releases/download/v0.13.0/release.yaml +\`\`\` + +### OpenShift + +\`\`\`shell +kubectl apply -f https://github.com/tektoncd/operator/releases/download/v0.13.0/release.yaml +\`\`\` + +### Documentation + +https://github.com/tektoncd/operator/tree/v0.13.0/docs + +## What's Changed + +``` diff --git a/.gitlint b/.gitlint new file mode 100644 index 0000000000..974b565a72 --- /dev/null +++ b/.gitlint @@ -0,0 +1,8 @@ +[general] +# https://jorisroovers.github.io/gitlint/configuration/general_options/#regex-style-search +regex-style-search=True + +# Dependabot tends to generate lines that exceed the default 80 char limit. +[ignore-by-author-name] +regex=dependabot +ignore=all diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000000..195fd6b183 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,144 @@ +# Tekton Operator + +Installs and manages Tekton components (Pipeline, Triggers, Chains, Dashboard, +Results, PAC…) on Kubernetes and OpenShift via custom CRDs and controllers. + +--- + +## Build & Test Commands + +```bash +# Build +make bin/kubernetes/operator +make bin/openshift/operator +make bin/kubernetes/webhook + +# Test — runs without a cluster +make test +make test-unit +make test-unit-race + +# Lint — must pass before every PR +make lint # golangci-lint + yamllint (all packages) +make lint-go # Go only, all packages +make lint-go PKG=./pkg/reconciler/kubernetes/tektonchain/... # single package (fast) + +# Code generation — required after any API type change +./hack/update-codegen.sh + +# Dependency update — required after go.mod changes +./hack/update-deps.sh +``` + +E2E tests require a live cluster and are tagged `//go:build e2e`. +See [DEVELOPMENT.md](./DEVELOPMENT.md) for cluster setup. + +--- + +## Key Conventions + +1. **Most components have both platform reconcilers, but not all.** `tektonaddon` is + OpenShift-only; `tektondashboard` is Kubernetes-only. Before adding a new component, + decide explicitly whether it is cross-platform or platform-specific. + +2. **InstallerSet is the only way to apply manifests.** Never call `kubectl apply` or + direct client writes inside a reconciler. Use the appropriate set type: + - `MainSet` — all core resources. **Automatically splits** into two sets on the cluster: + `-main-static-` (RBAC, CRDs, ConfigMaps) applied first, then + `-main-deployment-` (Deployments + Services). `` = component name + lowercased with `Tekton` prefix stripped (e.g. `TektonPipeline` → `pipeline`). + - `PreSet` — resources that must exist *before* the main set (e.g. namespaces) + - `PostSet` — resources applied *after* the main set is ready + - `CustomSet("name", ...)` — independently versioned named set; produces + `--` on the cluster (e.g. `chain-config-…`, `chain-secret-…`) + + Every InstallerSet carries labels: `operator.tekton.dev/created-by` (Go struct name, + e.g. `TektonChain`), `operator.tekton.dev/type` (set type), and for main sub-sets + `operator.tekton.dev/installType` (`static` | `deployment` | `statefulset`). + +3. **One CR instance per component type.** `Validate()` enforces + `GetName() != ComponentResourceName` — do not relax this check. + +4. **Use `MarkXXX()` for status, never write `.Status.Conditions` directly.** + All status transitions live in the generated `*_lifecycle.go` files. + +5. **`v1alpha1.REQUEUE_EVENT_AFTER` means "retry later".** Return it for transient + errors (e.g. dependency not yet ready). Return `nil` for permanent non-fatal states. + +--- + +## Architecture (non-obvious parts) + +**Built on Knative controller runtime.** Reconcilers implement `ReconcileKind` / +`FinalizeKind` from `knative.dev/pkg/reconciler`. Status conditions use +`knative.dev/pkg/apis` (`condSet.Manage(status).MarkTrue/MarkFalse`). Do not use +`controller-runtime` — it is not in this project. + +**API layout** — all CRD types live in `pkg/apis/operator/`. Each component +requires four files: + +``` +tektonexample_types.go # struct + compile-time interface assertions +tektonexample_defaults.go # SetDefaults(ctx) +tektonexample_lifecycle.go # MarkXXX status helpers +tektonexample_validation.go # Validate(ctx) +``` + +The struct must carry these markers to drive `./hack/update-codegen.sh`: +```go +// +genclient +// +genreconciler:krshapedlogic=false +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +genclient:nonNamespaced +// +kubebuilder:resource:scope=Cluster +``` + +**Platform split** — shared behaviour lives in `pkg/reconciler/common/` and +`pkg/reconciler/shared/`; platform-specific overrides implement the `Extension` +interface (`PreReconcile`, `PostReconcile`, `Finalize`). + +``` +pkg/reconciler/ + kubernetes// # Kubernetes-only reconciler + openshift// # OpenShift-only reconciler + shared// # reconcile logic used by both platforms + common/ # transformers, utilities, init-controller +``` + +**Init controller** — `pkg/reconciler/common/initcontroller.go` bootstraps manifests +at startup via `fetchSourceManifests`; every new component must be registered there. + + +--- + +## Pattern References for Common Changes + +| Change | Canonical example to follow | +|--------|----------------------------| +| Reconciler structure | `pkg/reconciler/kubernetes/tektonpipeline/` | +| OpenShift extension | `pkg/reconciler/openshift/tektonpipeline/` | +| Manifest transformer | `pkg/reconciler/common/transformers.go` | +| API type + lifecycle | `pkg/apis/operator/v1alpha1/tektonpipeline_types.go` + `tektonpipeline_lifecycle.go` | +| Validation | `pkg/apis/operator/v1alpha1/tektonpipeline_validation.go` | +| E2E test helper | `test/resources/tektonpipeline.go` | + +--- + +## 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 `./hack/update-codegen.sh` and commit generated files whenever API types change. +- One CR instance per new component type — enforce in `Validate()`. + +--- + +## 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 0000000000..47dc3e3d86 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +AGENTS.md \ No newline at end of file diff --git a/Makefile b/Makefile index dea6ce5ace..b9987d2ea5 100644 --- a/Makefile +++ b/Makefile @@ -44,7 +44,11 @@ $(BIN)/kustomize: | $(BIN) ; $(info $(M) getting kustomize) GOLANGCILINT = $(or ${GOLANGCILINT_BIN},${GOLANGCILINT_BIN},$(BIN)/golangci-lint) $(BIN)/golangci-lint: | $(BIN) ; $(info $(M) getting golangci-lint $(GOLANGCI_VERSION)) - @curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(BIN) $(GOLANGCI_VERSION) + @if [ -f $(BIN)/golangci-lint ] && $(BIN)/golangci-lint --version 2>/dev/null | grep -qF "$(GOLANGCI_VERSION)"; then \ + echo "golangci-lint $(GOLANGCI_VERSION) already installed"; \ + else \ + curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(BIN) $(GOLANGCI_VERSION); \ + fi ##@ Clean .PHONY: clean-cluster @@ -192,13 +196,14 @@ test-unit: ## Run unit tests .PHONY: lint lint: lint-go lint-yaml ## run all linters +PKG ?= ./... .PHONY: lint-go -lint-go: | $(GOLANGCILINT) ## runs go linter on all go files +lint-go: | $(GOLANGCILINT) ## runs go linter; set PKG=./some/pkg/... to lint a single package @echo "Linting go files..." - @$(GOLANGCILINT) run ./... --modules-download-mode=vendor \ - --max-issues-per-linter=0 \ - --max-same-issues=0 \ - --timeout 5m + @$(GOLANGCILINT) run $(PKG) --modules-download-mode=vendor \ + --max-issues-per-linter=0 \ + --max-same-issues=0 \ + --timeout 5m YAML_FILES := $(shell find . -type f -regex ".*y[a]ml" -print) .PHONY: lint-yaml diff --git a/pkg/reconciler/kubernetes/tektonchain/tektonchain.go b/pkg/reconciler/kubernetes/tektonchain/tektonchain.go index 8b2267c894..2245f33654 100644 --- a/pkg/reconciler/kubernetes/tektonchain/tektonchain.go +++ b/pkg/reconciler/kubernetes/tektonchain/tektonchain.go @@ -46,8 +46,6 @@ import ( ) const ( - resourceKind = v1alpha1.KindTektonChain - // Chains ConfigMap ChainsConfig = "chains-config" // Chains Container Name diff --git a/pkg/reconciler/kubernetes/tektonpipeline/reconcile.go b/pkg/reconciler/kubernetes/tektonpipeline/reconcile.go index 76e030a80b..8c8bf26f42 100644 --- a/pkg/reconciler/kubernetes/tektonpipeline/reconcile.go +++ b/pkg/reconciler/kubernetes/tektonpipeline/reconcile.go @@ -32,8 +32,6 @@ import ( ) const ( - resourceKind = v1alpha1.KindTektonPipeline - proxyLabel = "operator.tekton.dev/disable-proxy=true" ) diff --git a/pkg/reconciler/kubernetes/tektontrigger/reconcile.go b/pkg/reconciler/kubernetes/tektontrigger/reconcile.go index a8d28be432..9808d20026 100644 --- a/pkg/reconciler/kubernetes/tektontrigger/reconcile.go +++ b/pkg/reconciler/kubernetes/tektontrigger/reconcile.go @@ -34,8 +34,6 @@ import ( pkgreconciler "knative.dev/pkg/reconciler" ) -const resourceKind = v1alpha1.KindTektonTrigger - // Reconciler implements controller.Reconciler for TektonTrigger resources. type Reconciler struct { // kube client to interact with core k8s resources