---
name: docs-freshness
description: "Detect stale documentation, missing coverage for recent features, and doc-vs-code drift"
type: operational
cadence: weekly
---

# Docs Freshness

## Objetivo

Detect stale documentation, missing coverage for recently shipped features, and drift between documentation claims and actual codebase state. This runbook never modifies documentation -- it reports findings and creates task work items for human authors.

## Precondiciones

- `git` available for commit history and diff analysis.
- `gh` or `az` CLI authenticated for merged PR queries and work item creation.
- Scan targets exist: `README.md`, `.ai-engineering/README.md`, `CHANGELOG.md`, `docs/solution-intent.md`.
- `.ai-engineering/manifest.yml` present for count verification in Step 4.

## Procedimiento

### Step 1 -- Check doc file last-modified dates

For each scan target, retrieve the last commit date and compute days since modification:

```bash
for DOC in README.md .ai-engineering/README.md CHANGELOG.md docs/solution-intent.md; do
  LAST_EPOCH=$(git log -1 --format="%at" -- "$DOC")
  NOW_EPOCH=$(date +%s)
  DAYS_STALE=$(( (NOW_EPOCH - LAST_EPOCH) / 86400 ))
  echo "$DOC | last_modified=$(git log -1 --format='%ai' -- "$DOC") | days_inactive=$DAYS_STALE"
done
```

Any file where `days_inactive >= 30` is flagged as stale.

### Step 2 -- Compare against staleness threshold

For each flagged file, measure how much the codebase changed since the doc was last touched:

```bash
LAST_DOC_SHA=$(git log -1 --format="%H" -- "$DOC")
git diff --stat "$LAST_DOC_SHA"..HEAD -- . ':!*.md'
```

If more than 5 non-doc files changed, the finding severity is elevated from `info` to `warning`.

### Step 3 -- Cross-reference specs with documentation

Detect features that shipped without corresponding doc updates:

```bash
git log --oneline "$LAST_DOC_SHA"..HEAD -- .ai-engineering/specs/_history.md
git show HEAD:.ai-engineering/specs/_history.md | grep -E '^\|.*spec-[0-9]+'
```

For each spec entry that postdates the last doc update, search scan targets for any mention of that feature. A spec with zero doc references is a coverage gap.

### Step 4 -- Verify counts and statistics in READMEs

Check that numeric claims match the actual codebase:

```bash
# Manifest totals
MANIFEST_SKILLS=$(grep -A1 '^skills:' .ai-engineering/manifest.yml | grep 'total:' | awk '{print $2}')
MANIFEST_AGENTS=$(grep -A1 '^agents:' .ai-engineering/manifest.yml | grep 'total:' | awk '{print $2}')

# README claims
grep -nE '[0-9]+ (skills|agents|contexts|stacks)' README.md .ai-engineering/README.md
```

Flag any discrepancy between manifest totals and documented counts. Record the file, line number, claimed value, and actual value.

### Step 5 -- Check CHANGELOG against recent merged PRs

```bash
# GitHub
gh pr list --state merged --limit 20 \
  --json number,title,mergedAt \
  --jq '.[] | select(.mergedAt > (now - 2592000 | strftime("%Y-%m-%dT%H:%M:%SZ"))) | "#\(.number) \(.title)"'

# Azure DevOps
az repos pr list --status completed --top 20 --output json \
  | jq '.[] | select(.closedDate > (now - 2592000 | strftime("%Y-%m-%dT%H:%M:%SZ"))) | "#\(.pullRequestId) \(.title)"'
```

For each merged PR, search `CHANGELOG.md` for the PR number or a keyword match on the title. Missing entries are flagged as coverage gaps.

### Step 6 -- Verify solution-intent architecture alignment

Check that `docs/solution-intent.md` references the current directory structure:

```bash
for DIR in $(ls -d */ 2>/dev/null); do
  DIR_NAME=$(basename "$DIR")
  if ! grep -q "$DIR_NAME" docs/solution-intent.md; then
    echo "DRIFT: directory '$DIR_NAME' not referenced in solution-intent.md"
  fi
done
```

Also verify that component names, agent names, and integration points mentioned in the doc still correspond to existing files. Flag references to deleted or renamed components.

### Step 7 -- Map findings and deduplicate via handler

Map each finding to the Finding contract and route through the shared dedup handler.

**Finding mapping:**

```yaml
domain_label: "docs-stale"
title: "docs-freshness: $DOC is $DAYS_STALE days stale"
file_path: $DOC
rule_id: null
symbol: null
severity: $SEVERITY (warning if >5 non-doc files changed, info otherwise)
body: |
  ## Finding
  - **File:** `$DOC`
  - **Days since last update:** $DAYS_STALE
  - **Code changes since:** $CHANGED_FILES files
  - **Severity:** $SEVERITY

  ## What changed
  $CODE_DIFF_SUMMARY

  ## Suggested update
  $SUGGESTION

  ---
  *Auto-generated by docs-freshness runbook.*
```

Follow `handlers/dedup-check.md` to process all findings through the dedup cascade (max 10 per run). The handler labels new issues with `docs-stale` and `chore`.

### Step 8 -- Output summary to stdout

Print a one-paragraph summary of the run to stdout (no file writes):

```
Run complete. Targets checked: <N>. Findings: <warning_count> warnings, <info_count> info.
Work items created: <N>/10 — <list issue URLs>.
```

This is the only output. No local files are written.

## Output

Freshness report to stdout. Work items created for stale or missing docs. No local files are written.

## Guardrails

- **Never modifies documentation.** This runbook only reads docs and creates work items. No repository file is written or overwritten.
- **Mutation cap.** Maximum 10 work item creations per run. Excess findings are reported in the summary but not materialized. Remaining findings carry over to the next run.
- **Protected labels.** Work items carrying `p1-critical` or `pinned` are never relabeled or commented on.
- **Protected states.** Items in `closed` or `resolved` state are never reopened or modified.
- **Mutations enabled by default.** All qualifying work items are created automatically.
- **Idempotent.** Deduplication is delegated to the shared handler (`handlers/dedup-check.md`), which checks consolidated issues first, then individual issues, before creating new items.
- **No hierarchy escalation.** Creates tasks only. Never creates or mutates features, epics, or user stories.
