Files
tendril/.github/workflows/kb-lint.yml
Gitea Actions 1423ee1f04 fix(workflows): resolve git revision error in KB lint workflow
- Fix HEAD~1...HEAD error for shallow clones by using github.event.before/after
- Add proper error suppression with 2>/dev/null
- Add fallback logic for edge cases (initial commits, force pushes)
- Archive resolved runner setup issue to docs/issues/archive/resolved/
- Add archive documentation and next steps guide

Fixes workflow execution on test/runner-validation branch.
Resolves issue with 'fatal: bad revision' error in logs.
2025-11-11 14:46:26 -07:00

177 lines
7.3 KiB
YAML

name: KB Lint
on:
push:
paths:
- 'kb/**/*.md'
pull_request:
paths:
- 'kb/**/*.md'
permissions:
contents: read
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Validate KB Files
run: |
set -e
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
echo "Validating KB files..."
# Find all KB files that changed
if [ "${{ github.event_name }}" = "pull_request" ]; then
# For pull requests, check changed files
changed_files=$(git diff --name-only --diff-filter=ACMR origin/${{ github.base_ref }}...HEAD -- 'kb/**/*.md' 2>/dev/null || true)
else
# For push events, use commit SHAs from event (works with shallow clones)
# Fallback to finding all KB files if commit comparison fails
if [ -n "${{ github.event.before }}" ] && [ "${{ github.event.before }}" != "0000000000000000000000000000000000000000" ]; then
changed_files=$(git diff --name-only --diff-filter=ACMR ${{ github.event.before }}...${{ github.event.after }} -- 'kb/**/*.md' 2>/dev/null || true)
else
# If no before commit (e.g., initial commit or force push), check all KB files
changed_files=$(find kb -type f -name "*.md" 2>/dev/null || true)
fi
# If git diff failed or returned empty, fallback to finding all KB files
if [ -z "$changed_files" ]; then
changed_files=$(find kb -type f -name "*.md" 2>/dev/null || true)
fi
fi
if [ -z "$changed_files" ]; then
echo "No KB files changed, skipping validation"
exit 0
fi
errors=0
# Process each changed file
while IFS= read -r file; do
# Skip empty lines
[ -z "$file" ] && continue
# Skip if file doesn't exist (deleted files)
[ ! -f "$file" ] && continue
# Skip special directories and files
if [[ "$file" == *"/_guides/"* ]] || \
[[ "$file" == *"/_templates/"* ]] || \
[[ "$file" == "kb/README.md" ]] || \
[[ "$file" == "kb/_index.md" ]] || \
[[ "$file" == "kb/CHANGELOG.md" ]]; then
echo -e "${YELLOW}⏭️ Skipping: $file (excluded from validation)${NC}"
continue
fi
filename=$(basename "$file")
relative_path="${file#kb/}"
file_errors=0
echo -e "\n${GREEN}Validating: $file${NC}"
# Validate filename pattern
if ! [[ "$filename" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}--[a-z0-9-]+--(idea|note|spec|decision|howto|retro|meeting)(--p[0-9]+)?\.md$ ]]; then
echo -e "${RED}❌ ERROR: Invalid filename pattern${NC}"
echo " Expected: YYYY-MM-DD--slug--type.md"
echo " Got: $filename"
file_errors=$((file_errors + 1))
errors=$((errors + 1))
fi
# Extract date and type from filename
filename_date=$(echo "$filename" | sed -E 's/^([0-9]{4}-[0-9]{2}-[0-9]{2})--.*/\1/')
filename_type=$(echo "$filename" | sed -E 's/.*--([a-z]+)(--p[0-9]+)?\.md$/\1/')
# Check frontmatter exists
if ! grep -q "^---$" "$file"; then
echo -e "${RED}❌ ERROR: Missing frontmatter delimiter${NC}"
echo " File must start with '---'"
file_errors=$((file_errors + 1))
errors=$((errors + 1))
fi
# Extract frontmatter
frontmatter=$(sed -n '/^---$/,/^---$/p' "$file" | sed '1d;$d')
if [ -z "$frontmatter" ]; then
echo -e "${RED}❌ ERROR: Empty frontmatter${NC}"
file_errors=$((file_errors + 1))
errors=$((errors + 1))
fi
# Check required fields (14 base fields)
REQUIRED_FIELDS=("title" "date" "author" "source" "project" "topics" "tags" "type" "status" "routing_hint" "proposed_path" "routing_confidence" "related" "summary")
for field in "${REQUIRED_FIELDS[@]}"; do
if ! echo "$frontmatter" | grep -q "^${field}:"; then
echo -e "${RED}❌ ERROR: Missing required field: $field${NC}"
file_errors=$((file_errors + 1))
errors=$((errors + 1))
fi
done
# Validate date matches
frontmatter_date=$(echo "$frontmatter" | grep "^date:" | sed -E 's/^date:[[:space:]]*["'\'']?([^"'\'']+)["'\'']?.*/\1/' | tr -d ' ' | head -1)
if [ "$frontmatter_date" != "$filename_date" ]; then
echo -e "${RED}❌ ERROR: Date mismatch${NC}"
echo " Filename date: $filename_date"
echo " Frontmatter date: $frontmatter_date"
file_errors=$((file_errors + 1))
errors=$((errors + 1))
fi
# Validate type matches
frontmatter_type=$(echo "$frontmatter" | grep "^type:" | sed -E 's/^type:[[:space:]]*["'\'']?([^"'\'']+)["'\'']?.*/\1/' | tr -d ' ' | head -1)
if [ "$frontmatter_type" != "$filename_type" ]; then
echo -e "${RED}❌ ERROR: Type mismatch${NC}"
echo " Filename type: $filename_type"
echo " Frontmatter type: $frontmatter_type"
file_errors=$((file_errors + 1))
errors=$((errors + 1))
fi
# Validate routing_confidence
routing_confidence=$(echo "$frontmatter" | grep "^routing_confidence:" | sed -E 's/^routing_confidence:[[:space:]]*([0-9.]+).*/\1/' | tr -d ' ' | head -1)
if ! awk -v conf="$routing_confidence" 'BEGIN {if (conf < 0.0 || conf > 1.0 || conf == "") exit 1}' 2>/dev/null; then
echo -e "${RED}❌ ERROR: Invalid routing_confidence value${NC}"
echo " Value: $routing_confidence"
echo " Must be numeric between 0.00 and 1.00"
file_errors=$((file_errors + 1))
errors=$((errors + 1))
fi
# Enforce review queue policy
if awk -v conf="$routing_confidence" 'BEGIN {exit !(conf < 0.60)}' 2>/dev/null; then
if [[ "$file" != *"/_review_queue/"* ]]; then
echo -e "${RED}❌ ERROR: File has routing_confidence < 0.60 but is not in kb/_review_queue/${NC}"
echo " routing_confidence: $routing_confidence"
echo " File path: $file"
file_errors=$((file_errors + 1))
errors=$((errors + 1))
fi
fi
if [ $file_errors -eq 0 ]; then
echo -e "${GREEN}✅ Valid: $file${NC}"
fi
done <<< "$changed_files"
if [ $errors -gt 0 ]; then
echo -e "\n${RED}Validation failed with $errors error(s)${NC}"
exit 1
else
echo -e "\n${GREEN}All KB files validated successfully${NC}"
exit 0
fi