# review-cartography

This skill should be used when the user says "review a flow", "improve a flow", "verify a flow", "refine cartography", "check a cartography file", "verify flow accuracy", "are my flows still accurate", or wants to verify and improve an existing cartography file against the actual codebase. It cross-references documented flows with real code, fills gaps, fixes stale paths, adds related flow links, and introduces conditional sections where needed.

- **Kind:** skill
- **Source:** https://github.com/JoranHonig/grimoire
- **Page:** https://forefy.com/skills/8427e80e-1190-4da4-8729-06258d5839b1
- **API (JSON + files):** https://forefy.com/api/asr/8427e80e-1190-4da4-8729-06258d5839b1

---

## SKILL.md

---
name: review-cartography
description: >-
  This skill should be used when the user says "review a flow",
  "improve a flow", "verify a flow", "refine cartography",
  "check a cartography file", "verify flow accuracy",
  "are my flows still accurate", or wants to verify and improve an existing
  cartography file against the actual codebase. It cross-references
  documented flows with real code, fills gaps, fixes stale paths,
  adds related flow links, and introduces conditional sections where needed.
user_invocable: true
---

# Review Cartography

Verify and refine an existing cartography file against the actual codebase. Fill gaps, fix
inaccuracies, add cross-references, and introduce conditional sections where needed.

## Philosophy

**First drafts are starting points.** A cartography file created during initial exploration
captures what was found, not everything that exists. Code changes, flows evolve, and initial
exploration misses things. Review is how cartography files become reliable navigation maps that
agents can trust.

## Workflow

When this skill is activated, create a todo list from the following steps. Mark each task
in_progress before starting it and completed when done.

```
- [ ] 1. Select flow — run index, identify target cartography file
- [ ] 2. Verify against codebase — spawn subagents to check each section independently
- [ ] 3. Extend missing pieces — add what subagents found missing
- [ ] 4. Cross-reference related flows — compare with index, add reciprocal related links
- [ ] 5. Add conditional sections — separate independent sub-flows if file is too large
- [ ] 6. Update file and index — write changes, update date, re-run index
```

---

### 1. Select Flow

Run the indexing script to list available flows:

```bash
bash skills/cartography/scripts/index-cartography.sh grimoire/cartography/
```

If the user specified a flow, match it against the index. If not, present the list and ask
which flow to review.

Read the target cartography file in full.

### 2. Verify Against Codebase

Spawn subagents to independently verify each section of the cartography file against the actual
codebase. Each subagent should check one area:

**Entry points subagent:** For each listed entry point, verify:
- Does the file exist?
- Does the symbol (function/method/handler) exist in that file?
- Are there other entry points into this flow that were missed?

**Key components subagent:** For each listed component, verify:
- Does the file exist?
- Is the one-line role description accurate?
- Are there other files that participate in this flow but aren't listed?

**Flow sequence subagent:** Trace the documented sequence through the code:
- Does execution actually follow the documented order?
- Are there steps missing between documented steps?
- Are file references at each step correct?

**Security notes subagent:** Review the security observations:
- Are the noted trust boundaries still accurate?
- Have any validation gaps been fixed since the file was written?
- Are there new security-relevant observations to add?

Collect all subagent results before proceeding.

### 3. Extend Missing Pieces

Based on subagent findings, update the cartography file:

- Add missing entry points, components, or sequence steps
- Fix incorrect file paths or symbol names
- Remove references to files or symbols that no longer exist
- Add new security notes discovered during verification

Maintain the format defined in the cartography skill's
`references/cartography-format.md`. Keep entries as pointers — don't add code explanations.

### 4. Cross-Reference Related Flows

Compare the current flow against all other flows in the index:

- Read the frontmatter of all other cartography files
- Identify flows that share components, entry points, or security concerns
- Add missing entries to the `related` field in frontmatter
- Add `[[cartography/...]]` links in the Related Flows section
- **Make links reciprocal** — if flow A references flow B, update flow B to reference flow A

Run the overlap detection script to check for significant duplication:

```bash
bash skills/review-cartography/scripts/find-overlaps.sh grimoire/cartography/
```

If significant overlap is detected (>40% shared components between two flows), note this to
the user and suggest [[gc-cartography]] for potential merging. Consult
`references/overlap-detection.md` for details on how overlap is calculated and when merging
is appropriate.

### 5. Add Conditional Sections

If the cartography file body exceeds ~80 lines, look for opportunities to separate independent
sub-flows into conditional sections:

- Identify parts of the flow that are only relevant for specific investigations
- Extract them into `## Conditional: [Sub-flow Name]` sections
- Add a `<!-- condition: load only when [topic] -->` comment
- Move the sub-flow's entry points, components, sequence, and security notes under the
  conditional section

The condition comment should describe when an agent should load this section. Be specific
enough that an agent can match it against a user's question.

If the file is already lean (<80 lines body), skip this step.

### 6. Update File and Index

Write all changes to the cartography file:

- Update the `updated` field in frontmatter to today's date
- Preserve the original `created` date

Validate the updated file:

```bash
bash skills/review-cartography/scripts/validate-cartography.sh grimoire/cartography/<flow-slug>.md
```

Re-run the indexing script:

```bash
bash skills/cartography/scripts/index-cartography.sh grimoire/cartography/
```

Present to the user:
- A summary of what was changed (added, removed, corrected)
- Any security notes that were added or updated
- Any related flow links that were added
- Suggest [[gc-cartography]] if overlap with other flows was detected

---

## Guidelines

- **Independent verification.** Subagents should check the code themselves, not just validate
  that file paths exist. Read the actual code to confirm role descriptions and flow sequences.
- **Don't bloat the file.** Review should make the file more accurate, not longer. If adding
  new content pushes the file past ~80 lines, use conditional sections.
- **Reciprocal links.** Every `related` reference should go both ways. If you add flow B to
  flow A's related list, also add flow A to flow B's.
- **Preserve security notes.** Never remove a security note unless you can confirm the issue
  has been resolved in the code. When in doubt, keep it.
- **Update, don't recreate.** This skill refines existing files. If the flow needs to be
  rewritten from scratch, use [[cartography]] instead.

## examples

```

```

## examples/cartography-review-example.md

# Example: Cartography Review Cycle

A worked example showing the review of a "Secret Retrieval" cartography file from the
VaultBridge project. Demonstrates finding and correcting four common issues: a missing entry
point, an outdated file path, a missing cross-reference, and a new security note.

## Original Flow File (Before Review)

```markdown
---
name: Secret Retrieval
description: How secrets are fetched, decrypted, and returned to the client
created: 2026-02-16
updated: 2026-02-16
tags: [crypto, data-flow, kms]
---

## Overview

The secret retrieval flow handles authenticated requests to read stored secrets. It involves
permission checks, KMS key unwrapping, and client-side decryption.

## Entry Points

- `gateway/src/routes/secrets.ts:getSecret` — HTTP GET /api/v1/vaults/:id/secrets/:name
- `core/src/handlers/secret_handler.rs:handle_get_secret` — gRPC handler

## Key Components

- `core/src/authz/policy.rs` — RBAC permission evaluation
- `core/src/crypto/envelope.rs` — DEK unwrapping and envelope decryption
- `core/src/crypto/kms_client.rs` — AWS KMS integration for KEK operations
- `core/src/storage/vault_store.rs` — fetches encrypted blob and wrapped DEK

## Flow Sequence

1. Client sends GET request (`gateway/src/routes/secrets.ts:getSecret`)
2. Gateway auth middleware validates session (`gateway/src/middleware/auth.ts:validateSession`)
3. Core evaluates RBAC policy (`core/src/authz/policy.rs:evaluate`)
4. Core fetches encrypted blob + wrapped DEK (`core/src/storage/vault_store.rs:get_secret`)
5. Core unwraps DEK via KMS (`core/src/crypto/kms_client.rs:unwrap_key`)
6. Core returns encrypted blob + unwrapped DEK to client

## Security Notes

- Trust boundary between gateway authz and core authz — are they consistent?
- DEK is in plaintext in transit from core to client (relies on TLS)
- TOCTOU: permission check (step 3) and data fetch (step 4) are separate queries
```

## Review Findings

Each subagent reports independently:

- **Entry points subagent:** Found `cli/src/commands/get_secret.rs:run` — a CLI entry point
  added after initial mapping. File exists, handler dispatches to the same core gRPC path.
- **Key components subagent:** `core/src/crypto/envelope.rs` was renamed to
  `core/src/crypto/decrypt.rs` in a recent refactor. Current path is stale.
- **Flow sequence subagent:** Sequence is missing a step — a request validation middleware
  (`gateway/src/middleware/validate.ts:validateRequest`) was added between auth and core
  dispatch.
- **Security notes subagent:** New audit logging was added at step 5, but it logs the secret
  name in plaintext — potential information disclosure via log access.

## Corrected Flow File (After Review)

```markdown
---
name: Secret Retrieval
description: How secrets are fetched, decrypted, and returned to the client via the retrieval API
created: 2026-02-16
updated: 2026-03-10
tags: [crypto, data-flow, kms, retrieval]
related: [secret-creation, key-rotation]
---

## Overview

The secret retrieval flow handles authenticated requests to read stored secrets from a vault.
It spans the API gateway, CLI, core service, and AWS KMS. This is the primary path through
which stored credentials can be exfiltrated if any step is compromised.

## Entry Points

- `gateway/src/routes/secrets.ts:getSecret` — HTTP GET /api/v1/vaults/:id/secrets/:name
- `core/src/handlers/secret_handler.rs:handle_get_secret` — gRPC handler invoked by gateway
- `cli/src/commands/get_secret.rs:run` — CLI command, dispatches to same gRPC handler

## Key Components

- `gateway/src/middleware/auth.ts` — session validation
- `gateway/src/middleware/validate.ts` — request schema validation
- `core/src/authz/policy.rs` — RBAC permission evaluation for vault access
- `core/src/crypto/decrypt.rs` — DEK unwrapping and envelope decryption
- `core/src/crypto/kms_client.rs` — AWS KMS integration for KEK operations
- `core/src/storage/vault_store.rs` — fetches encrypted blob and wrapped DEK

## Flow Sequence

1. Client sends authenticated GET request (`gateway/src/routes/secrets.ts:getSecret`)
2. Gateway auth middleware validates session (`gateway/src/middleware/auth.ts:validateSession`)
3. Gateway validates request schema (`gateway/src/middleware/validate.ts:validateRequest`)
4. Gateway forwards to core via gRPC (`core/src/handlers/secret_handler.rs:handle_get_secret`)
5. Core evaluates RBAC policy (`core/src/authz/policy.rs:evaluate`)
6. Core fetches encrypted blob + wrapped DEK (`core/src/storage/vault_store.rs:get_secret`)
7. Core unwraps DEK via KMS (`core/src/crypto/kms_client.rs:unwrap_key`)
8. Core returns encrypted blob + unwrapped DEK to client

## Security Notes

- Trust boundary between gateway authz and core authz — are they consistent?
- DEK is in plaintext in transit from core to client (relies on TLS)
- TOCTOU: permission check (step 5) and data fetch (step 6) are separate queries
- Audit logger at step 6 logs secret name in plaintext — information disclosure via log access

## Related Flows

- [[cartography/secret-creation]] — the write path; same authz checks
- [[cartography/key-rotation]] — KEK rotation affects the unwrap step
```

## Why This Review Works

- **Independent verification.** Each subagent checked actual code, not just path existence.
  The renamed file (`envelope.rs` to `decrypt.rs`) was caught because the subagent read the
  directory listing, not just tested `[ -f path ]`.
- **No bloat.** Four issues found, four things fixed. The file gained a few lines from the new
  entry point and sequence step but stayed well under 80 lines.
- **Reciprocal links.** Adding `related: [secret-creation, key-rotation]` here means also
  updating those two files to include `secret-retrieval` in their `related` lists.
- **Security notes preserved.** All three original notes were kept. One new note was added
  based on the audit logging change discovered during verification.

## references

```

```

## references/cartography-format.md

../../cartography/references/cartography-format.md

## references/overlap-detection.md

# Overlap Detection

When reviewing a cartography file, overlaps with other flows indicate potential duplication
that degrades agent performance by splitting context across multiple files.

## What Counts as Overlap

### Shared Components

A "shared component" is an exact file path match between two flows' **Key Components** sections.
Only the path matters — role descriptions are ignored for matching purposes.

Entry Points and Flow Sequence file references are not counted. Only Key Components paths,
because these represent the core files that define a flow.

### Calculating Overlap Percentage

```
overlap = shared_components / max(components_A, components_B)
```

Use `max()` not `union()` — this catches subset flows where a small flow is entirely
contained within a larger one.

**Example:** Flow A has 8 components, Flow B has 5. They share 3 components.

```
overlap = 3 / max(8, 5) = 3 / 8 = 37.5% — below threshold
```

## Thresholds

| Percentage | Action |
|------------|--------|
| <20% | Normal. Flows share some infrastructure (database client, auth middleware). No action needed. |
| 20-40% | Note in review summary. Significant shared infrastructure but may represent genuinely different activities. |
| >40% | Flag to user. Suggest `[[gc-cartography]]` for potential merging. |

## When Overlap Is Acceptable

Not all overlap warrants merging. Overlap is expected when:

- Flows share a common entry point (e.g., API gateway) but diverge immediately
- Flows share infrastructure components (database client, auth middleware) used by many flows
- Flows represent different security concerns in the same codebase area

## When to Suggest gc-cartography

Overlap likely indicates duplication when:

- Two flows describe the same activity from different perspectives
- One flow is a strict subset of another (all components contained)
- Flows share >40% of components AND have similar descriptions
- Three or more flows form an overlap cluster (A overlaps B, B overlaps C)

## Detection in Practice

During step 4 of the review-cartography workflow:

1. Read frontmatter of all other cartography files in the index
2. For the flow under review, extract its Key Components paths
3. For each other flow, extract its Key Components paths
4. Calculate pairwise overlap percentage using the formula above
5. Report any pairs exceeding 40% as candidates for `[[gc-cartography]]`
6. Note 20-40% pairs in the review summary as informational

The `scripts/find-overlaps.sh` script automates steps 2-6 across all flows.

## scripts

```

```

## scripts/find-overlaps.sh

```bash
#!/usr/bin/env bash
# find-overlaps.sh — Detect overlapping cartography files by comparing Key Components.
# Outputs tab-separated: flow_A\tflow_B\toverlap_%\tshared_files
# Usage: find-overlaps.sh [directory] [threshold]
# Default directory: grimoire/cartography/  Default threshold: 40
# Exits 0 if no overlaps exceed threshold, 1 if any do.

set -euo pipefail

dir="${1:-grimoire/cartography/}"
threshold="${2:-40}"

if [ ! -d "$dir" ]; then
  echo "Directory not found: $dir" >&2
  exit 1
fi

tmpdir="${TMPDIR:-/tmp}/find-overlaps.$$"
mkdir -p "$tmpdir"
trap 'rm -rf "$tmpdir"' EXIT

# --- Extract Key Components from each cartography file ---
extract_components() {
  local file="$1"
  local outfile="$2"
  local in_components=0

  while IFS= read -r line; do
    # Enter Key Components section
    case "$line" in
      "## Key Components"*)
        in_components=1
        continue
        ;;
      "## "*)
        # Any other h2 heading exits the section
        if [ "$in_components" -eq 1 ]; then
          break
        fi
        continue
        ;;
    esac

    if [ "$in_components" -eq 1 ]; then
      # Extract file path from lines like: - `path/to/file.rs` — description
      # or: - `path/to/file.rs:symbol` — description
      case "$line" in
        "- \`"*)
          path="${line#- \`}"
          # Remove everything after the closing backtick
          path="${path%%\`*}"
          # Remove symbol suffix if present (e.g., :function_name)
          path="${path%%:*}"
          if [ -n "$path" ]; then
            echo "$path"
          fi
          ;;
      esac
    fi
  done < "$file" | sort -u > "$outfile"
}

# Extract name from frontmatter
extract_name() {
  local file="$1"
  local in_fm=0
  while IFS= read -r line; do
    if [ "$line" = "---" ]; then
      if [ "$in_fm" -eq 0 ]; then
        in_fm=1
        continue
      else
        break
      fi
    fi
    if [ "$in_fm" -eq 1 ]; then
      case "$line" in
        name:*)
          local name="${line#name:}"
          name="${name# }"
          name="${name#\"}"
          name="${name%\"}"
          echo "$name"
          return
          ;;
      esac
    fi
  done < "$file"
}

# --- Build component lists for all files ---
files=()
for file in "$dir"/*.md; do
  [ -e "$file" ] || continue
  basename=$(basename "$file")
  [ "$basename" = "_index.md" ] && continue

  slug="${basename%.md}"
  extract_components "$file" "$tmpdir/$slug.paths"
  files+=("$file")
done

count=${#files[@]}
if [ "$count" -lt 2 ]; then
  echo "Need at least 2 cartography files to compare. Found: $count" >&2
  exit 0
fi

# --- Pairwise comparison ---
found_overlap=0
pairs_checked=0
overlaps_found=0

for ((i=0; i<count; i++)); do
  for ((j=i+1; j<count; j++)); do
    file_a="${files[$i]}"
    file_b="${files[$j]}"
    slug_a=$(basename "$file_a" .md)
    slug_b=$(basename "$file_b" .md)

    paths_a="$tmpdir/$slug_a.paths"
    paths_b="$tmpdir/$slug_b.paths"

    # Skip if either has no components
    count_a=$(wc -l < "$paths_a" | tr -d ' ')
    count_b=$(wc -l < "$paths_b" | tr -d ' ')
    if [ "$count_a" -eq 0 ] || [ "$count_b" -eq 0 ]; then
      continue
    fi

    # Find shared components
    shared=$(comm -12 "$paths_a" "$paths_b" | wc -l | tr -d ' ')
    pairs_checked=$((pairs_checked + 1))

    if [ "$shared" -eq 0 ]; then
      continue
    fi

    # Calculate overlap: shared / max(count_a, count_b)
    if [ "$count_a" -gt "$count_b" ]; then
      max=$count_a
    else
      max=$count_b
    fi

    pct=$((shared * 100 / max))

    if [ "$pct" -gt "$threshold" ]; then
      name_a=$(extract_name "$file_a")
      name_b=$(extract_name "$file_b")
      shared_files=$(comm -12 "$paths_a" "$paths_b" | paste -sd ',' -)
      printf '%s\t%s\t%d%%\t%s\n' "${name_a:-$slug_a}" "${name_b:-$slug_b}" "$pct" "$shared_files"
      found_overlap=1
      overlaps_found=$((overlaps_found + 1))
    elif [ "$pct" -ge 20 ]; then
      name_a=$(extract_name "$file_a")
      name_b=$(extract_name "$file_b")
      printf 'INFO\t%s\t%s\t%d%%\n' "${name_a:-$slug_a}" "${name_b:-$slug_b}" "$pct" >&2
    fi
  done
done

# --- Summary to stderr ---
echo "" >&2
echo "Checked $pairs_checked pairs across $count flows (threshold: ${threshold}%)" >&2
if [ "$overlaps_found" -gt 0 ]; then
  echo "Found $overlaps_found pair(s) exceeding ${threshold}% overlap — consider gc-cartography" >&2
else
  echo "No overlaps exceed ${threshold}% threshold" >&2
fi

exit "$found_overlap"
```

## scripts/validate-cartography.sh

```bash
#!/usr/bin/env bash
# validate-cartography.sh — Validate a cartography file against the format spec.
# Checks frontmatter fields, required body sections, and reciprocal related links.
# Usage: validate-cartography.sh <cartography-file>
# Exits 0 if valid, 1 if errors found. Prints results to stderr.

set -euo pipefail

file="${1:-}"
if [ -z "$file" ] || [ ! -f "$file" ]; then
  echo "Usage: validate-cartography.sh <cartography-file>" >&2
  exit 1
fi

errors=0
warnings=0
passes=0

pass() {
  printf 'PASS\t%s\n' "$1" >&2
  passes=$((passes + 1))
}

fail() {
  printf 'FAIL\t%s\t%s\n' "$1" "$2" >&2
  errors=$((errors + 1))
}

warn() {
  printf 'WARN\t%s\t%s\n' "$1" "$2" >&2
  warnings=$((warnings + 1))
}

# --- Parse frontmatter ---
name=""
description=""
created=""
updated=""
related_raw=""
in_frontmatter=0
frontmatter_closed=0

# Section tracking
has_overview=0
has_entry_points=0
has_key_components=0
has_flow_sequence=0
has_security_notes=0
has_conditional=0
body_lines=0
current_section=""
referenced_paths=""

while IFS= read -r line; do
  if [ "$line" = "---" ]; then
    if [ "$in_frontmatter" -eq 0 ]; then
      in_frontmatter=1
      continue
    else
      frontmatter_closed=1
      continue
    fi
  fi

  if [ "$in_frontmatter" -eq 1 ] && [ "$frontmatter_closed" -eq 0 ]; then
    case "$line" in
      name:*)
        name="${line#name:}"
        name="${name# }"
        name="${name#\"}"
        name="${name%\"}"
        name="${name#\'}"
        name="${name%\'}"
        ;;
      description:*)
        description="${line#description:}"
        description="${description# }"
        description="${description#\"}"
        description="${description%\"}"
        description="${description#\'}"
        description="${description%\'}"
        ;;
      created:*)
        created="${line#created:}"
        created="${created# }"
        ;;
      updated:*)
        updated="${line#updated:}"
        updated="${updated# }"
        ;;
      related:*)
        related_raw="${line#related:}"
        related_raw="${related_raw# }"
        ;;
    esac
  fi

  if [ "$frontmatter_closed" -eq 1 ]; then
    # Count non-empty body lines
    if [ -n "$line" ]; then
      body_lines=$((body_lines + 1))
    fi

    # Check for required sections
    case "$line" in
      "## Overview"*) has_overview=1; current_section="overview" ;;
      "## Entry Points"*) has_entry_points=1; current_section="entry-points" ;;
      "## Key Components"*) has_key_components=1; current_section="key-components" ;;
      "## Flow Sequence"*) has_flow_sequence=1; current_section="flow-sequence" ;;
      "## Security Notes"*) has_security_notes=1; current_section="security-notes" ;;
      "## Conditional:"*) has_conditional=1; current_section="conditional" ;;
      "## "*) current_section="other" ;;
    esac

    # Collect file paths from Entry Points and Key Components
    if [ "$current_section" = "entry-points" ] || [ "$current_section" = "key-components" ]; then
      case "$line" in
        "- \`"*)
          path="${line#- \`}"
          path="${path%%\`*}"
          path="${path%%:*}"
          if [ -n "$path" ]; then
            referenced_paths="$referenced_paths $path"
          fi
          ;;
      esac
    fi
  fi
done < "$file"

# --- Validate frontmatter ---
if [ "$in_frontmatter" -eq 0 ]; then
  fail "frontmatter" "No frontmatter found (missing opening ---)"
elif [ "$frontmatter_closed" -eq 0 ]; then
  fail "frontmatter" "Frontmatter not closed (missing closing ---)"
else
  pass "frontmatter-structure"
fi

# Required fields
if [ -n "$name" ]; then
  pass "name"
else
  fail "name" "Missing required field: name"
fi

if [ -n "$description" ]; then
  pass "description"
else
  fail "description" "Missing required field: description"
fi

if [ -n "$created" ]; then
  if echo "$created" | grep -qE '^[0-9]{4}-[0-9]{2}-[0-9]{2}$'; then
    pass "created"
  else
    fail "created" "Invalid date format: '$created'. Expected YYYY-MM-DD"
  fi
else
  fail "created" "Missing required field: created"
fi

if [ -n "$updated" ]; then
  if echo "$updated" | grep -qE '^[0-9]{4}-[0-9]{2}-[0-9]{2}$'; then
    pass "updated"
  else
    fail "updated" "Invalid date format: '$updated'. Expected YYYY-MM-DD"
  fi
else
  fail "updated" "Missing required field: updated"
fi

# --- Validate sections ---
if [ "$has_overview" -eq 1 ]; then
  pass "overview-section"
else
  fail "overview-section" "Missing required section: ## Overview"
fi

if [ "$has_entry_points" -eq 1 ]; then
  pass "entry-points-section"
else
  fail "entry-points-section" "Missing required section: ## Entry Points"
fi

if [ "$has_key_components" -eq 1 ]; then
  pass "key-components-section"
else
  fail "key-components-section" "Missing required section: ## Key Components"
fi

if [ "$has_flow_sequence" -eq 1 ]; then
  pass "flow-sequence-section"
else
  fail "flow-sequence-section" "Missing required section: ## Flow Sequence"
fi

if [ "$has_security_notes" -eq 1 ]; then
  pass "security-notes-section"
else
  fail "security-notes-section" "Missing required section: ## Security Notes"
fi

# --- Validate referenced file paths ---
if [ -n "$referenced_paths" ]; then
  for path in $referenced_paths; do
    if [ -f "$path" ]; then
      pass "file-exists:$path"
    else
      warn "file-exists:$path" "Referenced file not found: $path"
    fi
  done
fi

# --- Validate reciprocal related links ---
if [ -n "$related_raw" ]; then
  # Extract slugs from flow-style [a, b] or bare list
  cleaned="${related_raw#\[}"
  cleaned="${cleaned%\]}"
  dir=$(dirname "$file")

  IFS=',' read -ra slugs <<< "$cleaned"
  for slug in "${slugs[@]}"; do
    slug=$(echo "$slug" | tr -d ' ')
    [ -z "$slug" ] && continue

    related_file="$dir/$slug.md"
    if [ -f "$related_file" ]; then
      # Check if the related file references this file back
      this_slug=$(basename "$file" .md)
      if grep -q "$this_slug" "$related_file" 2>/dev/null; then
        pass "reciprocal-link:$slug"
      else
        warn "reciprocal-link:$slug" "Related flow '$slug' does not link back to '$(basename "$file" .md)'"
      fi
    else
      warn "related-file:$slug" "Related flow file not found: $related_file"
    fi
  done
fi

# --- Body length warning ---
if [ "$body_lines" -gt 80 ] && [ "$has_conditional" -eq 0 ]; then
  warn "body-length" "Body has $body_lines non-empty lines (>80) with no conditional sections. Consider extracting sub-flows."
fi

# --- Summary ---
total=$((passes + errors + warnings))
echo "" >&2
if [ "$errors" -gt 0 ]; then
  echo "FAIL: $passes/$total passed, $errors error(s), $warnings warning(s) — $file" >&2
  exit 1
else
  if [ "$warnings" -gt 0 ]; then
    echo "PASS with $warnings warning(s): $passes/$total passed — $file" >&2
  else
    echo "PASS: $passes/$total passed — $file" >&2
  fi
  exit 0
fi
```

