Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 26 additions & 10 deletions .claude/commands/api-lint-diff.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ description: Validate API issues using kube-api-linter with diff-aware analysis

# API Lint Diff

Validates API issues in `api/` directory using kube-api-linter with diff-aware analysis that distinguishes between NEW and PRE-EXISTING issues.
Validates API issues in `api/` directory using kube-api-linter with diff-aware analysis that distinguishes between FIXED, NEW, and PRE-EXISTING issues.

## Instructions for Claude AI

Expand All @@ -19,6 +19,7 @@ When this command is invoked, you MUST:

2. **Understand the shell script's output**:
- **False positives (IGNORED)**: Standard CRD scaffolding patterns that kube-api-linter incorrectly flags
- **FIXED issues (SUCCESS)**: Issues that existed in baseline but were resolved in current branch → Celebrate! 🎉
- **NEW issues (ERRORS)**: Introduced in current branch → MUST fix
- **PRE-EXISTING issues (WARNINGS)**: Existed before changes → Can fix separately

Expand Down Expand Up @@ -132,23 +133,38 @@ When this command is invoked, you MUST:
# API Lint Diff Analysis Report

**Generated:** [date]
**Baseline:** main branch
**Current:** [branch name]
**Baseline:** main branch (X issues)
**Current:** [branch name] (Y issues)
**Status:** [status icon and message based on logic below]

**Status Logic:**
- ✅ PASSED: 0 real issues (after filtering false positives)
- ✅ PASSED: 0 new issues (fixed issues are OK)
- ⚠️ WARN: 0 new issues but has pre-existing issues
- ❌ FAIL: Has new issues that must be fixed

## Executive Summary
- Total issues: X
- False positives (IGNORED): Y
- Real issues (NEED FIXING): Z
- NEW issues: N
- PRE-EXISTING issues: P
- Baseline issues: X
- Current issues: Y
- **FIXED**: F (issues resolved in this branch)
- **NEW**: N (issues introduced in this branch)
- **PRE-EXISTING**: P (issues that still remain)
- False positives (IGNORED): Z

## REAL ISSUES - FIXES NEEDED (Z issues)
## FIXED ISSUES (F issues)

[List of issues that were fixed in this branch - show the baseline line numbers]

## NEW ISSUES (N issues)

[List of issues introduced in this branch - these MUST be fixed]

## PRE-EXISTING ISSUES (P issues)

[List of issues that existed before and still exist - can be fixed separately]

---

## DETAILED ANALYSIS FOR ISSUES NEEDING FIXES

### Category 1: [Issue Type] (N issues) - [BREAKING/NON-BREAKING]

Expand Down
114 changes: 104 additions & 10 deletions hack/api-lint-diff/run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -257,13 +257,14 @@ get_changed_files() {
grep -v 'zz_generated' || true
}

# Categorize issues as NEW or PRE-EXISTING
# Categorize issues as NEW, PRE-EXISTING, or FIXED
categorize_issues() {
local current_file="$1"
local baseline_file="$2"
local changed_files_file="$3"
local new_issues_file="$4"
local preexisting_issues_file="$5"
local fixed_issues_file="$6"

# Read changed files into array
local changed_files=()
Expand Down Expand Up @@ -306,17 +307,81 @@ categorize_issues() {
# Compare without line numbers since line numbers can change when code is added/removed
# Format is: file:line:col:linter:message
# We'll compare: file:linter:message
# Use f1,4,5- to capture field 5 and all remaining fields (handles colons in messages)
# Extract file (field 1), linter (field 4), and message (field 5+) from current issue
local file_linter_msg
file_linter_msg=$(echo "${line}" | cut -d: -f1,4,5-)

if grep -Fq "${file_linter_msg}" "${baseline_file}" 2>/dev/null; then
# Check if baseline has a matching issue (same file, linter, message but possibly different line number)
# We need to extract the same fields from baseline and compare
local found=false
if [[ -f "${baseline_file}" ]]; then
while IFS= read -r baseline_line; do
[[ -z "${baseline_line}" ]] && continue
local baseline_file_linter_msg
baseline_file_linter_msg=$(echo "${baseline_line}" | cut -d: -f1,4,5-)
if [[ "${file_linter_msg}" == "${baseline_file_linter_msg}" ]]; then
found=true
break
fi
done < "${baseline_file}"
fi

if $found; then
echo "${line}" >> "${preexisting_issues_file}"
else
echo "${line}" >> "${new_issues_file}"
fi
done < "${current_file}"
fi

# Find FIXED issues - issues in baseline that are NOT in current
if [[ -f "${baseline_file}" && -s "${baseline_file}" ]]; then
while IFS= read -r baseline_line; do
[[ -z "${baseline_line}" ]] && continue

local file
file=$(echo "${baseline_line}" | cut -d: -f1)

# Only check files that were changed
if [[ ${#changed_files[@]} -gt 0 ]]; then
local file_changed=false
for changed_file in "${changed_files[@]}"; do
if [[ "${file}" == "${changed_file}" ]]; then
file_changed=true
break
fi
done

# Skip if file wasn't changed
if ! $file_changed; then
continue
fi
fi

# Extract file:linter:message from baseline
local baseline_file_linter_msg
baseline_file_linter_msg=$(echo "${baseline_line}" | cut -d: -f1,4,5-)

# Check if this issue still exists in current
local still_exists=false
if [[ -f "${current_file}" ]]; then
while IFS= read -r current_line; do
[[ -z "${current_line}" ]] && continue
local current_file_linter_msg
current_file_linter_msg=$(echo "${current_line}" | cut -d: -f1,4,5-)
if [[ "${baseline_file_linter_msg}" == "${current_file_linter_msg}" ]]; then
still_exists=true
break
fi
done < "${current_file}"
fi

# If issue doesn't exist in current, it was fixed
if ! $still_exists; then
echo "${baseline_line}" >> "${fixed_issues_file}"
fi
done < "${baseline_file}"
fi
}

# Output issue (basic format)
Expand All @@ -328,20 +393,41 @@ output_issue() {
generate_report() {
local new_issues_file="$1"
local preexisting_issues_file="$2"
local fixed_issues_file="$3"
local baseline_file="$4"

local new_count=0
local preexisting_count=0
local fixed_count=0
local baseline_count=0

[[ -f "${new_issues_file}" ]] && new_count=$(wc -l < "${new_issues_file}" | tr -d ' ')
[[ -f "${preexisting_issues_file}" ]] && preexisting_count=$(wc -l < "${preexisting_issues_file}" | tr -d ' ')
[[ -f "${fixed_issues_file}" ]] && fixed_count=$(wc -l < "${fixed_issues_file}" | tr -d ' ')
[[ -f "${baseline_file}" ]] && baseline_count=$(wc -l < "${baseline_file}" | tr -d ' ')

local current_total=$((new_count + preexisting_count))

# Simple summary
# Summary header
echo "API Lint Diff Results"
echo "Baseline: ${BASELINE_BRANCH}"
echo "====================="
echo "Baseline (${BASELINE_BRANCH}): ${baseline_count} issues"
echo "Current branch: ${current_total} issues"
echo ""
echo "FIXED: ${fixed_count}"
echo "NEW: ${new_count}"
echo "PRE-EXISTING: ${preexisting_count}"
echo ""

# Show FIXED issues
if [[ ${fixed_count} -gt 0 ]]; then
echo "=== FIXED ISSUES ==="
while IFS= read -r line; do
output_issue "${line}"
done < "${fixed_issues_file}"
echo ""
fi

# Show NEW issues
if [[ ${new_count} -gt 0 ]]; then
echo "=== NEW ISSUES ==="
Expand All @@ -362,13 +448,17 @@ generate_report() {

# Exit based on NEW issues count
if [[ ${new_count} -eq 0 ]]; then
echo -e "${GREEN}NO NEW ISSUES found. Lint check passed.${NC}"
if [[ ${fixed_count} -gt 0 ]]; then
echo -e "${GREEN}SUCCESS: Fixed ${fixed_count} issue(s), no new issues introduced.${NC}"
else
echo -e "${GREEN}NO NEW ISSUES found. Lint check passed.${NC}"
fi
if [[ ${preexisting_count} -gt 0 ]]; then
echo -e "${YELLOW}WARNING: Pre-existing issues detected. Please address them separately.${NC}"
echo -e "${YELLOW}WARNING: ${preexisting_count} pre-existing issue(s) remain. Please address them separately.${NC}"
fi
return 0
else
echo -e "${RED}FAILED: ${new_count} new issue(s)${NC}"
echo -e "${RED}FAILED: ${new_count} new issue(s) introduced${NC}"
return 1
fi
}
Expand Down Expand Up @@ -414,18 +504,22 @@ main() {
# Categorize issues
touch "${TEMP_DIR}/new_issues.txt"
touch "${TEMP_DIR}/preexisting_issues.txt"
touch "${TEMP_DIR}/fixed_issues.txt"

categorize_issues \
"${TEMP_DIR}/current_parsed.txt" \
"${TEMP_DIR}/baseline_parsed.txt" \
"${TEMP_DIR}/changed_files.txt" \
"${TEMP_DIR}/new_issues.txt" \
"${TEMP_DIR}/preexisting_issues.txt"
"${TEMP_DIR}/preexisting_issues.txt" \
"${TEMP_DIR}/fixed_issues.txt"

# Generate report
generate_report \
"${TEMP_DIR}/new_issues.txt" \
"${TEMP_DIR}/preexisting_issues.txt"
"${TEMP_DIR}/preexisting_issues.txt" \
"${TEMP_DIR}/fixed_issues.txt" \
"${TEMP_DIR}/baseline_parsed.txt"

return $?
}
Expand Down