Skip to content
Draft
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
161 changes: 161 additions & 0 deletions .github/workflows/catalog-maintenance.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
name: 📅 Catalog Maintenance

on:
schedule:
# Runs every Monday at 9:00 AM UTC
- cron: "0 9 * * 1"
workflow_dispatch:

permissions:
contents: read
issues: write

jobs:
check-staleness:
name: 🔍 Check for Stale Content
runs-on: ubuntu-latest
steps:
- name: 📥 Checkout
uses: actions/checkout@v4

- name: 🔧 Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '22'

- name: 🔍 Run Staleness Check
id: check
run: |
node scripts/check-staleness.js || true

# Read the report
if [ -f stale-content-report.json ]; then
echo "REPORT_EXISTS=true" >> $GITHUB_OUTPUT
STALE_COUNT=$(jq '.staleEntries | length' stale-content-report.json)
echo "STALE_COUNT=$STALE_COUNT" >> $GITHUB_OUTPUT

# Export report for next step
cat stale-content-report.json > /tmp/report.json
else
echo "REPORT_EXISTS=false" >> $GITHUB_OUTPUT
echo "STALE_COUNT=0" >> $GITHUB_OUTPUT
fi

- name: 📝 Create Issues for Stale Content
if: steps.check.outputs.REPORT_EXISTS == 'true' && steps.check.outputs.STALE_COUNT > 0
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
echo "📋 Processing stale content notifications..."

# Read stale entries
STALE_ENTRIES=$(jq -c '.staleEntries[]' /tmp/report.json)

# Process each stale entry
while IFS= read -r entry; do
FILE=$(echo "$entry" | jq -r '.file')
TITLE=$(echo "$entry" | jq -r '.title')
OWNER=$(echo "$entry" | jq -r '.owner // "N/A"')
DAYS=$(echo "$entry" | jq -r '.daysSinceUpdate')
SEVERITY=$(echo "$entry" | jq -r '.severity')

echo "Creating issue for: $FILE ($DAYS days stale)"

# Create issue title
ISSUE_TITLE="📅 Catalog Review Needed: $TITLE"

# Create issue body
ISSUE_BODY="## Stale Content Notification

This catalog entry has not been updated in **$DAYS days** and needs your attention.

**Details:**
- 📄 File: \`src/content/ip/$FILE\`
- 👤 Owner: $OWNER
- ⏰ Days since update: $DAYS
- 🔴 Severity: $SEVERITY

**Action Required:**

Please review this content and take one of the following actions:

1. **Update the content** if changes are needed
2. **Refresh** the \`last_updated\` field to confirm review (even if no changes)
3. **Set status to \`deprecated\`** if no longer relevant

### How to Update

Edit \`src/content/ip/$FILE\` and update the frontmatter:

\`\`\`yaml
---
last_updated: $(date +%Y-%m-%d) # Update to today's date
status: ready # Or 'deprecated' if outdated
---
\`\`\`

For more information, see [CATALOG_LIFECYCLE.md](https://github.com/DevExpGbb/devexpgbb.github.io/blob/main/CATALOG_LIFECYCLE.md).

---
*This issue was created automatically by the Catalog Maintenance workflow.*"

# Check if issue already exists for this file
EXISTING_ISSUE=$(gh issue list \
--label "catalog-maintenance" \
--search "\"$TITLE\" in:title" \
--json number,title \
--jq ".[0].number // empty" || echo "")

if [ -n "$EXISTING_ISSUE" ]; then
echo " Issue already exists: #$EXISTING_ISSUE"
# Update the existing issue with a comment
gh issue comment "$EXISTING_ISSUE" \
--body "⏰ Still stale after $DAYS days. Please review and update."
else
# Create new issue
ASSIGNEE=""
if [ "$OWNER" != "N/A" ] && [ -n "$OWNER" ]; then
OWNER_CLEAN=$(echo "$OWNER" | sed 's/@//')
ASSIGNEE="--assignee $OWNER_CLEAN"
fi

gh issue create \
--title "$ISSUE_TITLE" \
--body "$ISSUE_BODY" \
--label "catalog-maintenance,needs-review" \
$ASSIGNEE || echo " Failed to assign to $OWNER (user may not have repo access)"
fi

done <<< "$STALE_ENTRIES"

echo "✅ Processed ${{ steps.check.outputs.STALE_COUNT }} stale entries"

- name: 📊 Summary
if: always()
run: |
echo "## Catalog Maintenance Summary" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY

if [ "${{ steps.check.outputs.REPORT_EXISTS }}" == "true" ]; then
TOTAL=$(jq '.totalEntries' /tmp/report.json)
STALE=$(jq '.staleEntries | length' /tmp/report.json)
HEALTHY=$(jq '.healthyEntries | length' /tmp/report.json)
MISSING=$(jq '.missingMetadata | length' /tmp/report.json)

echo "- 📚 Total entries: $TOTAL" >> $GITHUB_STEP_SUMMARY
echo "- ⚠️ Stale entries: $STALE" >> $GITHUB_STEP_SUMMARY
echo "- ✅ Healthy entries: $HEALTHY" >> $GITHUB_STEP_SUMMARY
echo "- ❓ Missing metadata: $MISSING" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY

if [ "$STALE" -gt 0 ]; then
echo "### Stale Content" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "| File | Title | Owner | Days Stale |" >> $GITHUB_STEP_SUMMARY
echo "|------|-------|-------|------------|" >> $GITHUB_STEP_SUMMARY

jq -r '.staleEntries[] | "| \(.file) | \(.title) | \(.owner // "N/A") | \(.daysSinceUpdate) |"' /tmp/report.json >> $GITHUB_STEP_SUMMARY
fi
else
echo "❌ No report generated" >> $GITHUB_STEP_SUMMARY
fi
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ yarn-debug.log*
yarn-error.log*
pnpm-debug.log*

# staleness reports
stale-content-report.json

# environment variables
.env
Expand Down
89 changes: 89 additions & 0 deletions CATALOG_LIFECYCLE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# Catalog Lifecycle Management

This document describes the automated lifecycle management process for IP Atlas catalog entries.

## Metadata Fields

Each catalog entry in `src/content/ip/*.md` should include these lifecycle metadata fields:

```yaml
---
title: "Your IP Title"
summary: "Brief description"
category: "Category"
owner: "@githubhandle" # GitHub handle of the content owner
status: "ready" # wip | ready | deprecated
last_updated: 2026-02-04 # Date of last content update
---
```

### Field Definitions

- **owner**: GitHub handle (e.g., `@username`) or email of the person responsible for this content
- **status**: Current lifecycle status
- `wip` - Work in progress, not yet ready for catalog
- `ready` - Ready for catalog, actively maintained
- `deprecated` - No longer maintained, scheduled for removal
- **last_updated**: ISO date of last meaningful content update

## Lifecycle Rules

### Staleness Threshold

- **Stale after**: 90 days (3 months) without updates
- **First reminder**: When asset becomes stale
- **Second reminder**: 2 weeks after first reminder
- **Escalation**: 4 weeks after first reminder (team lead notification)
- **Auto-deprecation**: 180 days (6 months) stale (optional, requires team approval)

### Status Transitions

```
wip → ready → deprecated → (removed)
↑ ↓
└──────┘ (can return to wip for major updates)
```

## Automated Processes

### Staleness Detection

A scheduled GitHub Action runs weekly to:

1. Parse all catalog entries
2. Identify assets not updated within 90 days
3. Generate reminders for owners
4. Create GitHub issues for stale content

### Owner Responsibilities

When you receive a staleness reminder:

1. **Review** the content for accuracy and relevance
2. **Update** if changes are needed
3. **Refresh** `last_updated` field even if no content changes (confirms review)
4. **Deprecate** if no longer relevant (set `status: deprecated`)

### Team Rituals

**Monthly Review Meeting** (suggested):
- Review newly added assets
- Discuss recently updated content
- Confirm deprecations
- Celebrate new IP contributions

## Success Criteria

- ≥90% of assets have `owner`, `status`, and `last_updated` fields
- Automated reminders sent on schedule
- Reduced manual catalog maintenance
- Regular review cadence established

## Getting Started

1. **Update existing content**: Add lifecycle metadata to your IP entries
2. **Set yourself as owner**: Use your GitHub handle
3. **Mark status**: Start with `ready` for published content, `wip` for drafts
4. **Set last_updated**: Use the date of your last content update

For questions or issues, please open a GitHub issue or contact the IP Atlas maintainers.
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,23 @@ published: true
date: 2026-02-04
author: "Team"
link: "https://optional-link"
# Lifecycle metadata (for automated maintenance)
owner: "@githubhandle" # Your GitHub handle
status: "ready" # wip | ready | deprecated
last_updated: 2026-02-04 # Last content update date
---
```

### Automated Catalog Maintenance

IP Atlas includes automated lifecycle management to keep content fresh and relevant:

- **Staleness Detection**: Runs weekly to identify content not updated in 90+ days
- **Owner Notifications**: Creates GitHub issues to remind owners to review stale content
- **Lifecycle Tracking**: Uses `status` field to track content state (wip/ready/deprecated)

See [CATALOG_LIFECYCLE.md](CATALOG_LIFECYCLE.md) for complete details on the automated maintenance process.

## 🧞 Commands

| Command | Action |
Expand Down
Loading