# gc-cartography

This skill should be used when the user says "clean up flows", "merge flows", "gc cartography", "garbage collect flows", "deduplicate flows", "consolidate cartography", "too many flow files", "overlapping flows", "duplicate cartography", "reduce flow count", or wants to identify and merge overlapping cartography files, remove stale references, and reduce duplication in the grimoire/cartography/ directory.

- **Kind:** skill
- **Source:** https://github.com/JoranHonig/grimoire
- **Page:** https://forefy.com/skills/3b533709-b78c-435c-b209-9880fed97f19
- **API (JSON + files):** https://forefy.com/api/asr/3b533709-b78c-435c-b209-9880fed97f19

---

## SKILL.md

---
name: gc-cartography
description: >-
  This skill should be used when the user says "clean up flows",
  "merge flows", "gc cartography", "garbage collect flows",
  "deduplicate flows", "consolidate cartography", "too many flow files",
  "overlapping flows", "duplicate cartography", "reduce flow count",
  or wants to identify and merge overlapping cartography files, remove
  stale references, and reduce duplication in the grimoire/cartography/
  directory.
user_invocable: true
---

# GC Cartography

Identify overlapping or duplicated cartography files, merge them where appropriate, and clean
up stale cross-references. Garbage collection for your flow maps.

## Philosophy

**Cartography files accumulate.** As a security engagement progresses, researchers map flows
independently. Over time this creates overlap — two files documenting slightly different views
of the same flow, or three files that share most of their components. This bloat degrades agent
performance by splitting context across multiple files. GC consolidates without losing signal.

## 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. Inventory all flows — run index, read all files, track referenced paths per flow
- [ ] 2. Detect overlap — compare component sets, flag pairs with significant overlap
- [ ] 3. Propose merge plan — present candidates to user with rationale
- [ ] 4. Execute merges — combine files, deduplicate, create conditional sections
- [ ] 5. Clean up cross-references — fix stale links in all files
- [ ] 6. Update index — re-run script, present before/after summary
```

---

### 1. Inventory All Flows

Run the indexing script:

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

Read every cartography file in `grimoire/cartography/`. For each file, extract:
- The `name` and `description` from frontmatter
- The list of file paths referenced in Entry Points, Key Components, and Flow Sequence
- The `related` and `tags` fields from frontmatter

Build an in-memory map of flow → referenced file paths.

### 2. Detect Overlap

Run `scripts/gc-cartography/scripts/detect-overlaps.sh` or compare manually. Consult
`references/overlap-metrics.md` for the overlap formula and tier thresholds.

Flag pairs that meet any of these criteria:

- **Component overlap >40%** — more than 40% of one flow's referenced file paths also appear
  in the other flow
- **Similar descriptions** — descriptions that describe the same activity in different words
- **Subset flows** — one flow's components are entirely contained within another flow's
  components

For each flagged pair, note:
- Which files are shared vs unique to each flow
- Whether the flows represent the same activity from different angles, or genuinely different
  activities that happen to share infrastructure

### 3. Propose Merge Plan

Consult `references/merge-decisions.md` for the merge-vs-keep decision framework.

Present merge candidates to the user. For each candidate pair:
- Show both flow names and descriptions
- Show the overlap percentage and shared components
- Explain why merging makes sense (or flag if it's ambiguous)
- Suggest which flow should be the primary (keep its filename) and which should be absorbed

**Never auto-merge.** Always present the plan and wait for user approval. The user may have
reasons to keep flows separate that aren't apparent from the file contents alone.

If no overlaps are detected, report a clean bill of health and skip to step 6.

### 4. Execute Merges

For each approved merge:

1. **Combine frontmatter** — keep the primary flow's `name`. Merge `tags` (union). Merge
   `related` (union, removing references to the absorbed flow). Update `description` if the
   merged flow's scope has broadened. Set `updated` to today's date.

2. **Merge Entry Points** — union of both flows' entry points, deduplicated.

3. **Merge Key Components** — union of both flows' components. Where both flows list the same
   file, keep the more descriptive role annotation.

4. **Merge Flow Sequence** — this is the hardest part. If both flows describe the same sequence
   with minor variations, unify into one sequence. If they describe genuinely different paths
   through shared components, keep the primary flow's sequence as the main body and move the
   absorbed flow's sequence into a conditional section.

5. **Merge Security Notes** — union of all security notes. Preserve every note from both files.
   Deduplicate only when two notes say exactly the same thing.

6. **Unique content becomes conditional** — any content from the absorbed flow that doesn't
   fit naturally into the primary flow's main body should become a conditional section.

7. **Delete the absorbed flow's file** after confirming the merge is correct.

Consult `skills/cartography/references/cartography-format.md` for format details.

### 5. Clean Up Cross-References

After merges, scan all remaining cartography files for stale references:

- Find `[[cartography/...]]` links that point to deleted files
- Update them to point to the merged flow's file
- Check `related` fields in frontmatter for deleted slugs and update them
- Ensure all `related` references are reciprocal

Also check `GRIMOIRE.md` and files in `grimoire/tomes/` for stale cartography links.

### 6. Update Index

Re-run the indexing script:

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

Present to the user:
- **Before:** number of flows, list of names
- **After:** number of flows, list of names
- Which flows were merged and which were kept
- Any stale references that were fixed

Suggest [[review-cartography]] to verify the merged flows are accurate and complete.

---

## Guidelines

- **Never auto-merge.** Always present the plan and get user approval. Merging destroys
  information if done carelessly.
- **Preserve all security notes.** When merging, keep every security observation from both
  files. Security notes are the highest-value content in a cartography file.
- **Use conditional sections for divergent paths.** When two flows share entry points and
  components but diverge in their sequences, use conditional sections rather than trying to
  force them into one linear sequence.
- **Check beyond cartography/.** Stale `[[cartography/...]]` links can appear in GRIMOIRE.md,
  tomes, and other documents. Clean them all up.
- **Overlap is not always bad.** Two flows might legitimately share 50% of their components
  and still be better as separate files. Respect the user's judgment on what to merge.

## examples

```

```

## examples/gc-before-after.md

# GC Cartography Example: Before and After

This example shows two overlapping cartography flows being merged into one consolidated flow.

## Scenario

During an audit of a vault service, two flows were mapped independently:

- **Secret Retrieval** — mapped from the HTTP API perspective
- **Vault Read Path** — mapped from the storage layer perspective

They share 5 of 8 components (62.5% overlap) and describe the same logical operation.

---

## Before: Two Overlapping Flows

### Flow A: `grimoire/cartography/secret-retrieval.md`

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

## Overview

The secret retrieval flow handles authenticated requests to read stored secrets. It involves
permission checks, KMS key unwrapping, and client-side decryption. Security-critical because
it is the primary path to exfiltrating stored credentials.

## Entry Points

- `gateway/src/routes/secrets.ts:getSecret` — HTTP GET /api/v1/vaults/:id/secrets/:name

## Key Components

- `gateway/src/middleware/auth.ts` — session validation and JWT verification
- `core/src/authz/policy.rs` — RBAC permission evaluation for vault access
- `core/src/handlers/secret_handler.rs` — request routing and validation
- `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 from PostgreSQL
- `core/src/audit/logger.rs` — audit trail for secret access events
- `gateway/src/middleware/rate_limit.ts` — per-user rate limiting on read operations

## Flow Sequence

1. Client sends GET request to gateway (`gateway/src/routes/secrets.ts:getSecret`)
2. Gateway validates session (`gateway/src/middleware/auth.ts:validateSession`)
3. Gateway applies rate limit (`gateway/src/middleware/rate_limit.ts:check`)
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 decrypts using DEK (`core/src/crypto/envelope.rs:decrypt`)
9. Core logs access event (`core/src/audit/logger.rs:log_access`)
10. Core returns decrypted secret to client

## Security Notes

- Trust boundary between gateway authz and core authz — are they consistent?
- DEK is in plaintext in memory between steps 7-8 — window for memory dump
- Rate limiting (step 3) is per-user but not per-IP — credential stuffing risk
- Audit log (step 9) happens after decryption — if step 8 fails, is access still logged?
```

### Flow B: `grimoire/cartography/vault-read-path.md`

```markdown
---
name: Vault Read Path
description: Storage layer read path for retrieving encrypted vault entries
created: 2026-02-14
updated: 2026-02-14
tags: [storage, crypto, database]
related: [vault-write-path]
---

## Overview

The read path through the vault storage layer. Covers how encrypted entries are fetched from
the database, decrypted, and returned. Focus on the storage and crypto layers.

## Entry Points

- `core/src/handlers/secret_handler.rs:handle_get_secret` — gRPC handler for read requests

## Key Components

- `core/src/handlers/secret_handler.rs` — request routing and input validation
- `core/src/authz/policy.rs` — permission checks before data access
- `core/src/storage/vault_store.rs` — PostgreSQL queries for encrypted blobs
- `core/src/storage/cache.rs` — LRU cache for frequently accessed secrets
- `core/src/crypto/envelope.rs` — envelope decryption with DEK
- `core/src/crypto/kms_client.rs` — KMS key operations
- `core/src/crypto/key_cache.rs` — DEK cache to reduce KMS calls

## Flow Sequence

1. Handler receives gRPC request (`core/src/handlers/secret_handler.rs:handle_get_secret`)
2. Handler checks permissions (`core/src/authz/policy.rs:evaluate`)
3. Handler checks secret cache (`core/src/storage/cache.rs:get`)
4. On cache miss: fetch from database (`core/src/storage/vault_store.rs:get_secret`)
5. Check DEK cache (`core/src/crypto/key_cache.rs:get_dek`)
6. On DEK cache miss: unwrap via KMS (`core/src/crypto/kms_client.rs:unwrap_key`)
7. Decrypt envelope (`core/src/crypto/envelope.rs:decrypt`)
8. Populate caches (`core/src/storage/cache.rs:put`, `core/src/crypto/key_cache.rs:put_dek`)
9. Return decrypted secret

## Security Notes

- Secret cache (step 3) stores decrypted values in memory — cache poisoning vector?
- DEK cache (step 5) keeps key material in memory — how long? What eviction policy?
- Cache population (step 8) happens after successful decrypt — but is cache write atomic?
- No audit logging in this path — relies on caller to log
```

### Overlap Analysis

**Shared components (5):**
- `core/src/handlers/secret_handler.rs`
- `core/src/authz/policy.rs`
- `core/src/storage/vault_store.rs`
- `core/src/crypto/envelope.rs`
- `core/src/crypto/kms_client.rs`

**Unique to Flow A (3):** `gateway/src/middleware/auth.ts`, `core/src/audit/logger.rs`,
`gateway/src/middleware/rate_limit.ts`

**Unique to Flow B (2):** `core/src/storage/cache.rs`, `core/src/crypto/key_cache.rs`

**Overlap:** 5 / max(8, 7) = 5 / 8 = **62.5%** — exceeds 40% threshold

**Decision:** Merge. Both flows describe the same read operation. Flow A covers the full
gateway-to-response path. Flow B adds storage-layer detail (caching). Flow A is the primary
because it has more components, more security notes, and broader scope. Flow B's caching
detail becomes a conditional section.

---

## After: Merged Flow

### `grimoire/cartography/secret-retrieval.md`

```markdown
---
name: Secret Retrieval
description: How secrets are fetched, decrypted, and returned — from gateway through storage layer caching
created: 2026-02-10
updated: 2026-03-10
tags: [crypto, data-flow, kms, storage, database]
related: [secret-creation, key-rotation, vault-write-path]
---

## Overview

The secret retrieval flow handles authenticated requests to read stored secrets. Covers the
full path from HTTP gateway through RBAC, storage, KMS key unwrapping, envelope decryption,
and caching. Security-critical because it is the primary path to exfiltrating stored
credentials.

## 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 for read requests

## Key Components

- `gateway/src/middleware/auth.ts` — session validation and JWT verification
- `gateway/src/middleware/rate_limit.ts` — per-user rate limiting on read operations
- `core/src/handlers/secret_handler.rs` — request routing and validation
- `core/src/authz/policy.rs` — RBAC permission evaluation for vault access
- `core/src/storage/vault_store.rs` — PostgreSQL queries for encrypted blobs
- `core/src/storage/cache.rs` — LRU cache for frequently accessed secrets
- `core/src/crypto/envelope.rs` — DEK unwrapping and envelope decryption
- `core/src/crypto/kms_client.rs` — AWS KMS integration for KEK operations
- `core/src/crypto/key_cache.rs` — DEK cache to reduce KMS calls
- `core/src/audit/logger.rs` — audit trail for secret access events

## Flow Sequence

1. Client sends GET request to gateway (`gateway/src/routes/secrets.ts:getSecret`)
2. Gateway validates session (`gateway/src/middleware/auth.ts:validateSession`)
3. Gateway applies rate limit (`gateway/src/middleware/rate_limit.ts:check`)
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 decrypts using DEK (`core/src/crypto/envelope.rs:decrypt`)
9. Core logs access event (`core/src/audit/logger.rs:log_access`)
10. Core returns decrypted secret to client

## Conditional: Storage Layer Caching

<!-- condition: load only when investigating cache behavior, cache poisoning, key material caching, or performance-related security concerns -->

The storage layer includes two caches that short-circuit the main flow sequence:

1. **Secret cache** (`core/src/storage/cache.rs`) — LRU cache of decrypted secrets. On hit,
   skips steps 6-8 entirely. Checked before database fetch.
2. **DEK cache** (`core/src/crypto/key_cache.rs`) — caches unwrapped DEKs to reduce KMS
   calls. On hit, skips step 7. Checked before KMS unwrap.

Cached flow sequence:
1. Handler receives request (step 4 in main flow)
2. Handler checks permissions (step 5)
3. Check secret cache (`core/src/storage/cache.rs:get`) — if hit, skip to step 7
4. On miss: fetch from database (step 6)
5. Check DEK cache (`core/src/crypto/key_cache.rs:get_dek`) — if hit, skip to step 7
6. On miss: unwrap via KMS (step 7 in main flow)
7. Decrypt and return (steps 8-10)
8. Populate caches (`core/src/storage/cache.rs:put`, `core/src/crypto/key_cache.rs:put_dek`)

## Security Notes

- Trust boundary between gateway authz and core authz — are they consistent?
- DEK is in plaintext in memory between steps 7-8 — window for memory dump
- Rate limiting (step 3) is per-user but not per-IP — credential stuffing risk
- Audit log (step 9) happens after decryption — if step 8 fails, is access still logged?
- Secret cache stores decrypted values in memory — cache poisoning vector?
- DEK cache keeps key material in memory — how long? What eviction policy?
- Cache population happens after successful decrypt — but is cache write atomic?
- No audit logging in cached path — relies on caller to log

## Related Flows

- [[cartography/secret-creation]] — the write path for secrets
- [[cartography/key-rotation]] — KEK rotation affects the unwrap step in this flow
- [[cartography/vault-write-path]] — the write counterpart to this read path
```

### What Changed

| Aspect | Before | After |
|--------|--------|-------|
| Files | 2 (`secret-retrieval.md`, `vault-read-path.md`) | 1 (`secret-retrieval.md`) |
| Components | 8 + 7 (5 shared) | 10 (deduplicated union) |
| Entry Points | 1 + 1 | 2 (union) |
| Security Notes | 4 + 4 | 8 (all preserved) |
| Tags | 3 + 3 | 5 (union) |
| Related | 2 + 1 | 3 (union, absorbed slug removed) |
| Conditional sections | 0 | 1 (caching detail from absorbed flow) |

### Cross-Reference Cleanup

After the merge, `vault-read-path.md` was deleted. Any file referencing it needs updating:

- `grimoire/cartography/vault-write-path.md`: `related: [vault-read-path]` updated to
  `related: [secret-retrieval]`
- `GRIMOIRE.md`: `[[cartography/vault-read-path]]` link updated to
  `[[cartography/secret-retrieval]]`

## references

```

```

## references/merge-decisions.md

# Merge Decisions

When gc-cartography flags overlapping flows, the decision to merge is not automatic. This
reference formalizes the decision framework.

## Decision Criteria

### Merge When

- **Same activity, different perspectives.** Two flows document the same logical operation
  (e.g., "secret retrieval" and "vault read") from different starting points or at different
  granularities. Merging unifies the picture.

- **Strict subset.** One flow's components are entirely contained within another flow. The
  smaller flow adds no unique components — it's a slice of the larger flow. Absorb it.

- **>40% overlap with similar descriptions.** High component overlap combined with
  descriptions that describe the same activity (even in different words) strongly indicates
  duplication. The 40% threshold is calibrated against the overlap formula in
  `overlap-metrics.md`.

- **Overlap cluster.** Three or more flows form a chain (A overlaps B, B overlaps C) with
  each pair exceeding 40%. This usually means the flows grew organically from different
  starting investigations and should be consolidated.

### Keep Separate When

- **Shared infrastructure, different concerns.** Flows share components like auth middleware,
  database clients, or API gateways but investigate completely different security properties.
  A payment flow and a user registration flow might both pass through the same gateway — that
  shared infrastructure doesn't make them the same flow.

- **Different trust boundaries.** Even with high component overlap, if the flows cross
  different trust boundaries or represent different threat models, they serve distinct
  purposes. An admin API flow and a public API flow through the same handlers are separate
  security concerns.

- **User says so.** The researcher may have reasons that aren't apparent from the file
  contents. Never override the user's decision to keep flows separate.

## Choosing the Primary Flow

When merging, one flow keeps its filename (primary) and the other is absorbed. Choose the
primary based on:

1. **More components** — the larger flow is usually the more complete picture
2. **More security notes** — richer annotations indicate deeper investigation
3. **Broader scope** — a flow covering the full operation beats a partial view
4. **Established references** — if other files link to one flow more often, keep that one to
   minimize reference updates

If both flows are roughly equal, prefer the one created first (earlier `created` date) since
it's more likely to be referenced elsewhere.

## Merge Execution Principles

### Preserve All Security Notes

Security notes are the highest-value content in a cartography file. When merging:

- Keep every unique note from both files
- Deduplicate only when two notes say exactly the same thing
- When notes conflict, keep both and annotate the conflict for the researcher to resolve
- Never summarize or compress security notes — they are observations, not documentation

### Use Conditional Sections for Divergent Paths

When two flows share entry points and components but diverge in their sequences:

- Keep the primary flow's sequence as the main body
- Move the absorbed flow's divergent sequence into a conditional section
- Format: `## Conditional: [Sub-flow Name]` with a condition comment

This prevents context pollution — an agent loading the file gets the main flow by default
and only loads the conditional section when investigating that specific sub-flow.

### Update Scope Indicators

After absorbing a flow, the primary flow's scope may have broadened:

- Update `description` if the merged flow now covers more than the original described
- Merge `tags` as a union — keep all tags from both flows
- Merge `related` as a union — remove the absorbed flow's slug, add any new related flows
  the absorbed flow referenced
- Set `updated` to today's date

## Ambiguous Cases

When the decision isn't clear-cut, present both options to the user with:

1. The overlap percentage and shared components
2. What each flow contributes uniquely
3. A recommendation (merge or keep separate) with reasoning
4. What would be lost or gained by merging

Let the user decide. The cost of an unnecessary merge (lost context, harder to navigate) is
higher than the cost of keeping a borderline-redundant flow (slightly more files to manage).

## references/overlap-metrics.md

# Overlap Metrics

Formalization of how gc-cartography measures overlap between cartography flows. Extends the
detection thresholds defined in review-cartography's `overlap-detection.md` with merge-specific
metrics.

## Core Overlap Formula

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

- **shared_components**: count of file paths appearing in both flows' Key Components sections
- **components_A / components_B**: total component count for each flow
- **max()**: using the larger count catches subset flows where a small flow is entirely
  contained within a larger one

Only Key Components paths are counted. Entry Points and Flow Sequence file references are
excluded because they represent access patterns, not flow identity.

### Path Matching

Paths are compared as exact strings after stripping the `:symbol` suffix. This means:

- `core/src/crypto/kms_client.rs:unwrap_key` and `core/src/crypto/kms_client.rs:wrap_key`
  both resolve to `core/src/crypto/kms_client.rs` and count as a match
- Relative vs absolute path differences would not match — but cartography files should
  consistently use project-relative paths

## Overlap Tiers

| Tier | Range | Interpretation | GC Action |
|------|-------|----------------|-----------|
| Low | <20% | Normal shared infrastructure | No action |
| Notable | 20-40% | Significant overlap worth noting | Report as informational |
| High | >40% | Likely duplication | Flag as merge candidate |
| Subset | 100% of smaller flow | One flow entirely within another | Strong merge candidate |

## Subset Detection

A flow is a **strict subset** when every one of its components appears in another flow:

```
subset = (shared_components == components_smaller_flow)
```

Subsets are always merge candidates regardless of the percentage from the larger flow's
perspective. A 5-component flow entirely contained in a 20-component flow is only 25% overlap
by the standard formula, but it's still a strict subset.

## Cluster Detection

An **overlap cluster** exists when three or more flows form a connected graph where each edge
represents >40% overlap:

```
A --52%--> B --45%--> C
```

Clusters indicate that a single logical flow was mapped multiple times from different starting
points. The merge strategy for clusters:

1. Identify the flow with the most components as the primary
2. Absorb flows one at a time, starting with the highest-overlap pair
3. Re-check overlap after each merge — the merged flow's component set changes

## Metric Limitations

### False Positives

High overlap does not always mean duplication:

- **Hub components**: Files like `auth/middleware.rs` or `db/connection.rs` appear in many
  flows. If two flows share only hub components, they're probably independent flows through
  shared infrastructure.
- **Same area, different concerns**: Two flows in the same codebase area investigating
  different vulnerability classes (e.g., access control vs injection) may share components
  but serve distinct purposes.

### False Negatives

Low overlap does not always mean independence:

- **Renamed files**: If the codebase refactored between mapping sessions, the same logical
  components may have different paths.
- **Different granularity**: One flow may reference `src/auth/` while another references
  individual files within that directory.

These cases require human judgment — the metrics provide candidates, not decisions.

## Script Integration

The `scripts/detect-overlaps.sh` script implements these metrics by wrapping
review-cartography's `find-overlaps.sh` and adding:

- Subset detection (flags when smaller flow is 100% contained)
- Cluster detection (groups connected overlapping pairs)
- Primary flow suggestion based on component count and security note count

## scripts

```

```

## scripts/detect-overlaps.sh

```bash
#!/usr/bin/env bash
# detect-overlaps.sh — Detect overlapping cartography files with merge recommendations.
# Wraps review-cartography's find-overlaps.sh and adds:
#   - Subset detection (smaller flow 100% contained in larger)
#   - Cluster detection (groups of connected overlapping flows)
#   - Primary flow suggestion (based on component count)
#
# Usage: detect-overlaps.sh [directory] [threshold]
# Default directory: grimoire/cartography/  Default threshold: 40
# Exits 0 if no merge candidates, 1 if candidates found.

set -euo pipefail

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

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

# Locate find-overlaps.sh relative to this script
script_dir="$(cd "$(dirname "$0")" && pwd)"
find_overlaps="$script_dir/../../review-cartography/scripts/find-overlaps.sh"

if [ ! -f "$find_overlaps" ]; then
  echo "Error: find-overlaps.sh not found at $find_overlaps" >&2
  echo "This script requires review-cartography/scripts/find-overlaps.sh" >&2
  exit 1
fi

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

# --- Step 1: Run base overlap detection ---
# Capture stdout (overlap lines) and stderr (info/summary) separately
bash "$find_overlaps" "$dir" "$threshold" > "$tmpdir/overlaps.tsv" 2> "$tmpdir/info.txt" || true

if [ ! -s "$tmpdir/overlaps.tsv" ]; then
  echo "No overlaps exceed ${threshold}% threshold." >&2
  cat "$tmpdir/info.txt" >&2
  exit 0
fi

# --- Step 2: Count components per flow for primary suggestion ---
count_components() {
  local file="$1"
  local count=0
  local in_components=0
  while IFS= read -r line; do
    case "$line" in
      "## Key Components"*) in_components=1; continue ;;
      "## "*) [ "$in_components" -eq 1 ] && break; continue ;;
    esac
    if [ "$in_components" -eq 1 ]; then
      case "$line" in
        "- \`"*) count=$((count + 1)) ;;
      esac
    fi
  done < "$file"
  echo "$count"
}

count_security_notes() {
  local file="$1"
  local count=0
  local in_notes=0
  while IFS= read -r line; do
    case "$line" in
      "## Security Notes"*) in_notes=1; continue ;;
      "## "*) [ "$in_notes" -eq 1 ] && break; continue ;;
    esac
    if [ "$in_notes" -eq 1 ]; then
      case "$line" in
        "- "*) count=$((count + 1)) ;;
      esac
    fi
  done < "$file"
  echo "$count"
}

# Build a lookup of slug -> file path
declare -A slug_to_file
for file in "$dir"/*.md; do
  [ -e "$file" ] || continue
  basename=$(basename "$file")
  [ "$basename" = "_index.md" ] && continue
  slug="${basename%.md}"
  slug_to_file["$slug"]="$file"
done

# --- Step 3: Enrich overlap data with merge recommendations ---
echo "=== GC-Cartography Merge Candidates ===" >&2
echo "" >&2

candidates=0
while IFS=$'\t' read -r name_a name_b pct shared_files; do
  candidates=$((candidates + 1))

  # Find files by matching name in frontmatter
  file_a=""
  file_b=""
  for slug in "${!slug_to_file[@]}"; do
    f="${slug_to_file[$slug]}"
    # Extract name from frontmatter
    fm_name=$(sed -n '/^---$/,/^---$/{ /^name:/{ s/^name: *//; s/^"//; s/"$//; p; } }' "$f")
    if [ "$fm_name" = "$name_a" ] && [ -z "$file_a" ]; then
      file_a="$f"
    elif [ "$fm_name" = "$name_b" ] && [ -z "$file_b" ]; then
      file_b="$f"
    fi
  done

  # Fallback: try slug matching if name matching failed
  if [ -z "$file_a" ] || [ -z "$file_b" ]; then
    for slug in "${!slug_to_file[@]}"; do
      f="${slug_to_file[$slug]}"
      if [ -z "$file_a" ] && [[ "$(basename "$f" .md)" == *"${name_a// /-}"* ]]; then
        file_a="$f"
      fi
      if [ -z "$file_b" ] && [[ "$(basename "$f" .md)" == *"${name_b// /-}"* ]]; then
        file_b="$f"
      fi
    done
  fi

  # Get component and note counts
  comp_a=0; comp_b=0; notes_a=0; notes_b=0
  if [ -n "$file_a" ] && [ -f "$file_a" ]; then
    comp_a=$(count_components "$file_a")
    notes_a=$(count_security_notes "$file_a")
  fi
  if [ -n "$file_b" ] && [ -f "$file_b" ]; then
    comp_b=$(count_components "$file_b")
    notes_b=$(count_security_notes "$file_b")
  fi

  # Detect subset
  shared_count=$(echo "$shared_files" | tr ',' '\n' | wc -l | tr -d ' ')
  is_subset=""
  if [ "$comp_a" -gt 0 ] && [ "$shared_count" -eq "$comp_a" ]; then
    is_subset="$name_a is a subset of $name_b"
  elif [ "$comp_b" -gt 0 ] && [ "$shared_count" -eq "$comp_b" ]; then
    is_subset="$name_b is a subset of $name_a"
  fi

  # Suggest primary
  if [ "$comp_a" -gt "$comp_b" ]; then
    primary="$name_a"
    absorbed="$name_b"
  elif [ "$comp_b" -gt "$comp_a" ]; then
    primary="$name_b"
    absorbed="$name_a"
  elif [ "$notes_a" -ge "$notes_b" ]; then
    primary="$name_a"
    absorbed="$name_b"
  else
    primary="$name_b"
    absorbed="$name_a"
  fi

  # Output enriched record
  printf '%s\t%s\t%s\t%s\n' "$name_a" "$name_b" "$pct" "$shared_files"

  # Enriched report to stderr
  echo "--- Candidate $candidates ---" >&2
  echo "  Flow A: $name_a ($comp_a components, $notes_a security notes)" >&2
  echo "  Flow B: $name_b ($comp_b components, $notes_b security notes)" >&2
  echo "  Overlap: $pct" >&2
  echo "  Shared: $shared_files" >&2
  if [ -n "$is_subset" ]; then
    echo "  Subset: $is_subset" >&2
  fi
  echo "  Suggestion: keep \"$primary\" as primary, absorb \"$absorbed\"" >&2
  echo "" >&2
done < "$tmpdir/overlaps.tsv"

# --- Step 4: Detect clusters ---
# A cluster exists when 3+ flows are connected through overlapping pairs
if [ "$candidates" -gt 1 ]; then
  echo "--- Cluster Analysis ---" >&2
  # Collect all flow names involved in overlaps
  all_flows=$(cut -f1,2 "$tmpdir/overlaps.tsv" | tr '\t' '\n' | sort -u)
  flow_count=$(echo "$all_flows" | wc -l | tr -d ' ')
  if [ "$flow_count" -ge 3 ]; then
    echo "  $flow_count flows involved in overlapping pairs — possible overlap cluster" >&2
    echo "  Consider consolidating into fewer flows, starting with highest-overlap pair" >&2
  else
    echo "  No clusters detected (overlapping pairs are independent)" >&2
  fi
  echo "" >&2
fi

# --- Summary ---
echo "Found $candidates merge candidate(s) exceeding ${threshold}% threshold" >&2

exit 1
```

## scripts/merge-flows.sh

```bash
#!/usr/bin/env bash
# merge-flows.sh — Merge two cartography files by combining their sections.
# Produces a merged file from a primary and absorbed flow.
#
# Usage: merge-flows.sh <primary-file> <absorbed-file> [output-file]
# If output-file is omitted, writes to primary-file (in-place merge).
#
# The script performs structural merging:
#   - Combines frontmatter (union of tags, related; primary name kept)
#   - Unions entry points and key components (deduplicated)
#   - Keeps primary flow sequence as main body
#   - Moves absorbed flow's unique content into a conditional section
#   - Unions security notes (preserves all)
#
# Does NOT delete the absorbed file — that's a manual step after review.
# Exits 0 on success, 1 on error.

set -euo pipefail

primary="${1:-}"
absorbed="${2:-}"
output="${3:-$primary}"

if [ -z "$primary" ] || [ -z "$absorbed" ]; then
  echo "Usage: merge-flows.sh <primary-file> <absorbed-file> [output-file]" >&2
  exit 1
fi

if [ ! -f "$primary" ]; then
  echo "Primary file not found: $primary" >&2
  exit 1
fi

if [ ! -f "$absorbed" ]; then
  echo "Absorbed file not found: $absorbed" >&2
  exit 1
fi

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

today=$(date +%Y-%m-%d)

# --- Parse frontmatter from a file ---
parse_frontmatter() {
  local file="$1"
  local prefix="$2"
  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:*) echo "${prefix}_name=${line#name: }" ;;
        description:*) echo "${prefix}_description=${line#description: }" ;;
        created:*) echo "${prefix}_created=${line#created: }" ;;
        updated:*) echo "${prefix}_updated=${line#updated: }" ;;
        tags:*) echo "${prefix}_tags=${line#tags: }" ;;
        related:*) echo "${prefix}_related=${line#related: }" ;;
      esac
    fi
  done < "$file"
}

# --- Extract a body section from a file ---
extract_section() {
  local file="$1"
  local section="$2"
  local in_section=0
  local past_frontmatter=0
  local fm_count=0

  while IFS= read -r line; do
    if [ "$line" = "---" ]; then
      fm_count=$((fm_count + 1))
      if [ "$fm_count" -eq 2 ]; then past_frontmatter=1; fi
      continue
    fi
    [ "$past_frontmatter" -eq 0 ] && continue

    case "$line" in
      "## $section"*)
        in_section=1
        continue
        ;;
      "## "*)
        if [ "$in_section" -eq 1 ]; then break; fi
        continue
        ;;
    esac
    if [ "$in_section" -eq 1 ]; then
      echo "$line"
    fi
  done < "$file"
}

# --- Extract list items (lines starting with "- ") ---
extract_list_items() {
  while IFS= read -r line; do
    case "$line" in
      "- "*) echo "$line" ;;
    esac
  done
}

# --- Parse tags from [a, b, c] format ---
parse_tags() {
  local raw="$1"
  raw="${raw#\[}"
  raw="${raw%\]}"
  echo "$raw" | tr ',' '\n' | sed 's/^ *//;s/ *$//' | sort -u
}

# --- Parse primary and absorbed frontmatter ---
eval "$(parse_frontmatter "$primary" "p")"
eval "$(parse_frontmatter "$absorbed" "a")"

# Merge tags (union)
{
  parse_tags "${p_tags:-}"
  parse_tags "${a_tags:-}"
} | sort -u | grep -v '^$' > "$tmpdir/merged_tags.txt"
merged_tags=$(paste -sd ', ' "$tmpdir/merged_tags.txt")

# Merge related (union, removing absorbed flow's slug)
absorbed_slug=$(basename "$absorbed" .md)
{
  parse_tags "${p_related:-}"
  parse_tags "${a_related:-}"
} | sort -u | grep -v '^$' | grep -v "^${absorbed_slug}$" > "$tmpdir/merged_related.txt"
merged_related=$(paste -sd ', ' "$tmpdir/merged_related.txt")

# --- Extract sections from both files ---
extract_section "$primary" "Overview" > "$tmpdir/p_overview.txt"
extract_section "$absorbed" "Overview" > "$tmpdir/a_overview.txt"
extract_section "$primary" "Entry Points" > "$tmpdir/p_entry.txt"
extract_section "$absorbed" "Entry Points" > "$tmpdir/a_entry.txt"
extract_section "$primary" "Key Components" > "$tmpdir/p_components.txt"
extract_section "$absorbed" "Key Components" > "$tmpdir/a_components.txt"
extract_section "$primary" "Flow Sequence" > "$tmpdir/p_sequence.txt"
extract_section "$absorbed" "Flow Sequence" > "$tmpdir/a_sequence.txt"
extract_section "$primary" "Security Notes" > "$tmpdir/p_notes.txt"
extract_section "$absorbed" "Security Notes" > "$tmpdir/a_notes.txt"

# --- Merge entry points (union, deduplicated by path) ---
{
  extract_list_items < "$tmpdir/p_entry.txt"
  extract_list_items < "$tmpdir/a_entry.txt"
} | sort -u > "$tmpdir/merged_entry.txt"

# --- Merge key components (union, deduplicated by path) ---
# When both have the same path, keep the longer (more descriptive) line
declare -A component_lines
while IFS= read -r line; do
  case "$line" in
    "- \`"*)
      path="${line#- \`}"
      path="${path%%\`*}"
      path="${path%%:*}"
      existing="${component_lines[$path]:-}"
      if [ -z "$existing" ] || [ "${#line}" -gt "${#existing}" ]; then
        component_lines["$path"]="$line"
      fi
      ;;
  esac
done < <(cat "$tmpdir/p_components.txt" "$tmpdir/a_components.txt")

# Output components sorted by path
for path in $(echo "${!component_lines[@]}" | tr ' ' '\n' | sort); do
  echo "${component_lines[$path]}"
done > "$tmpdir/merged_components.txt"

# --- Merge security notes (union, deduplicated) ---
{
  extract_list_items < "$tmpdir/p_notes.txt"
  extract_list_items < "$tmpdir/a_notes.txt"
} | sort -u > "$tmpdir/merged_notes.txt"

# --- Build absorbed flow's unique content as conditional section ---
# Find components unique to absorbed flow
a_unique_components=""
while IFS= read -r line; do
  case "$line" in
    "- \`"*)
      path="${line#- \`}"
      path="${path%%\`*}"
      path="${path%%:*}"
      if ! grep -qF "$path" "$tmpdir/p_components.txt" 2>/dev/null; then
        a_unique_components="${a_unique_components}${line}\n"
      fi
      ;;
  esac
done < "$tmpdir/a_components.txt"

# Check if absorbed flow has a meaningfully different sequence
absorbed_name="${a_name:-$(basename "$absorbed" .md)}"
absorbed_name="${absorbed_name#\"}"
absorbed_name="${absorbed_name%\"}"

# --- Write merged output ---
{
  # Frontmatter
  echo "---"
  echo "name: ${p_name:-$(basename "$primary" .md)}"

  # Update description if absorbed flow adds scope
  echo "description: ${p_description:-}"

  echo "created: ${p_created:-$today}"
  echo "updated: $today"

  if [ -n "$merged_tags" ]; then
    echo "tags: [$merged_tags]"
  fi
  if [ -n "$merged_related" ]; then
    echo "related: [$merged_related]"
  fi
  echo "---"

  # Overview — keep primary's overview
  echo ""
  echo "## Overview"
  echo ""
  cat "$tmpdir/p_overview.txt"

  # Entry Points — merged
  echo ""
  echo "## Entry Points"
  echo ""
  cat "$tmpdir/merged_entry.txt"

  # Key Components — merged
  echo ""
  echo "## Key Components"
  echo ""
  cat "$tmpdir/merged_components.txt"

  # Flow Sequence — primary's sequence
  echo ""
  echo "## Flow Sequence"
  echo ""
  cat "$tmpdir/p_sequence.txt"

  # Conditional section for absorbed flow's sequence (if non-empty)
  if [ -s "$tmpdir/a_sequence.txt" ]; then
    echo ""
    echo "## Conditional: $absorbed_name"
    echo ""
    echo "<!-- condition: load only when investigating ${absorbed_name,,} specifics -->"
    echo ""
    cat "$tmpdir/a_sequence.txt"
  fi

  # Security Notes — merged
  echo ""
  echo "## Security Notes"
  echo ""
  cat "$tmpdir/merged_notes.txt"

} > "$tmpdir/merged.md"

# Remove excessive blank lines (3+ consecutive -> 2)
sed '/^$/N;/^\n$/N;/^\n\n$/d' "$tmpdir/merged.md" > "$output"

echo "Merged: $primary + $absorbed -> $output" >&2
echo "Components: $(wc -l < "$tmpdir/merged_components.txt" | tr -d ' ') (deduplicated union)" >&2
echo "Security notes: $(wc -l < "$tmpdir/merged_notes.txt" | tr -d ' ') (all preserved)" >&2
echo "" >&2
echo "Next steps:" >&2
echo "  1. Review the merged file and adjust description/overview if scope broadened" >&2
echo "  2. Review the conditional section — edit or restructure as needed" >&2
echo "  3. Delete the absorbed file: rm $absorbed" >&2
echo "  4. Run: bash skills/gc-cartography/scripts/update-references.sh $(basename "$absorbed" .md) $(basename "$primary" .md)" >&2
echo "  5. Run: bash skills/gc-cartography/scripts/validate-gc.sh $output" >&2

exit 0
```

## scripts/update-references.sh

```bash
#!/usr/bin/env bash
# update-references.sh — Fix stale cartography references after a merge.
# Scans all markdown files for references to a deleted flow and updates them
# to point to the merged flow.
#
# Usage: update-references.sh <old-slug> <new-slug> [search-dir]
# Default search-dir: . (current directory, typically repo root)
#
# Updates:
#   - [[cartography/old-slug]] links -> [[cartography/new-slug]]
#   - related: [..., old-slug, ...] entries -> new-slug
#   - Any other markdown references to cartography/old-slug
#
# Exits 0 on success (even if no references found). Prints changes to stderr.

set -euo pipefail

old_slug="${1:-}"
new_slug="${2:-}"
search_dir="${3:-.}"

if [ -z "$old_slug" ] || [ -z "$new_slug" ]; then
  echo "Usage: update-references.sh <old-slug> <new-slug> [search-dir]" >&2
  exit 1
fi

updated=0
checked=0

echo "Scanning for references to '$old_slug' (replacing with '$new_slug')..." >&2
echo "" >&2

# Find all markdown files that reference the old slug
while IFS= read -r file; do
  [ -f "$file" ] || continue
  checked=$((checked + 1))

  # Skip the old file itself if it still exists
  if [ "$(basename "$file" .md)" = "$old_slug" ]; then
    continue
  fi

  if grep -q "$old_slug" "$file" 2>/dev/null; then
    # Perform replacements
    # 1. [[cartography/old-slug]] -> [[cartography/new-slug]]
    # 2. related: [...old-slug...] -> new-slug
    # 3. cartography/old-slug -> cartography/new-slug (in any context)
    sed -i '' \
      -e "s|cartography/${old_slug}|cartography/${new_slug}|g" \
      -e "s|${old_slug}|${new_slug}|g" \
      "$file"

    echo "  Updated: $file" >&2
    updated=$((updated + 1))
  fi
done < <(find "$search_dir" -name '*.md' -not -path '*/node_modules/*' -not -path '*/.git/*')

# --- Check for reciprocal related links ---
# The new (merged) flow should have reciprocal links to all flows that reference it
merged_file=""
if [ -f "$search_dir/grimoire/cartography/$new_slug.md" ]; then
  merged_file="$search_dir/grimoire/cartography/$new_slug.md"
fi

if [ -n "$merged_file" ]; then
  echo "" >&2
  echo "Checking reciprocal links in $merged_file..." >&2

  # Find all cartography files that now reference the new slug in their related field
  for file in "$search_dir"/grimoire/cartography/*.md; do
    [ -f "$file" ] || continue
    [ "$file" = "$merged_file" ] && continue
    slug=$(basename "$file" .md)
    [ "$slug" = "_index" ] && continue

    # Check if this file has the new slug in its related field
    if sed -n '/^---$/,/^---$/p' "$file" | grep -q "$new_slug"; then
      # Check if merged file has reciprocal link
      if ! sed -n '/^---$/,/^---$/p' "$merged_file" | grep -q "$slug"; then
        echo "  Warning: $slug references $new_slug but $new_slug does not reference $slug back" >&2
        echo "           Add '$slug' to the related field in $merged_file" >&2
      fi
    fi
  done
fi

# --- Summary ---
echo "" >&2
echo "Checked $checked files, updated $updated reference(s)." >&2
if [ "$updated" -eq 0 ]; then
  echo "No stale references found for '$old_slug'." >&2
fi

exit 0
```

## scripts/validate-gc.sh

```bash
#!/usr/bin/env bash
# validate-gc.sh — Verify merge result integrity after gc-cartography.
# Runs validate-cartography.sh on the merged file, then checks for dangling
# references to any deleted flows.
#
# Usage: validate-gc.sh <merged-file> [deleted-slug...]
# Example: validate-gc.sh grimoire/cartography/secret-retrieval.md vault-read-path
#
# Checks:
#   1. Merged file passes standard cartography validation
#   2. No remaining files reference deleted slugs
#   3. Merged file has more components than either original (sanity check)
#   4. All security notes from originals are preserved (if originals provided)
#
# Exits 0 if all checks pass, 1 if any fail.

set -euo pipefail

merged="${1:-}"
shift || true
deleted_slugs=("$@")

if [ -z "$merged" ] || [ ! -f "$merged" ]; then
  echo "Usage: validate-gc.sh <merged-file> [deleted-slug...]" >&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))
}

# --- Check 1: Standard cartography validation ---
script_dir="$(cd "$(dirname "$0")" && pwd)"
validate_script="$script_dir/../../review-cartography/scripts/validate-cartography.sh"

if [ -f "$validate_script" ]; then
  echo "--- Running standard cartography validation ---" >&2
  if bash "$validate_script" "$merged" 2>&1 >&2; then
    pass "cartography-validation"
  else
    fail "cartography-validation" "Merged file failed standard validation (see above)"
  fi
  echo "" >&2
else
  warn "cartography-validation" "validate-cartography.sh not found at $validate_script — skipping"
fi

# --- Check 2: No dangling references to deleted slugs ---
if [ "${#deleted_slugs[@]}" -gt 0 ]; then
  echo "--- Checking for dangling references ---" >&2
  cart_dir=$(dirname "$merged")

  for slug in "${deleted_slugs[@]}"; do
    # Ensure the deleted file is actually gone
    if [ -f "$cart_dir/$slug.md" ]; then
      warn "deleted-file:$slug" "File still exists: $cart_dir/$slug.md (expected deleted)"
    fi

    # Search for remaining references in all markdown files
    dangling=$(grep -rl "$slug" "$cart_dir"/*.md 2>/dev/null | grep -v "$(basename "$merged")" || true)
    if [ -n "$dangling" ]; then
      fail "dangling-ref:$slug" "Files still reference deleted slug '$slug': $dangling"
    else
      pass "no-dangling-refs:$slug"
    fi
  done
  echo "" >&2
fi

# --- Check 3: Merged file has expected sections ---
echo "--- Checking merge completeness ---" >&2

# Count components in merged file
comp_count=0
in_components=0
while IFS= read -r line; do
  case "$line" in
    "## Key Components"*) in_components=1; continue ;;
    "## "*) [ "$in_components" -eq 1 ] && break; continue ;;
  esac
  if [ "$in_components" -eq 1 ]; then
    case "$line" in
      "- \`"*) comp_count=$((comp_count + 1)) ;;
    esac
  fi
done < "$merged"

if [ "$comp_count" -gt 0 ]; then
  pass "has-components ($comp_count)"
else
  fail "has-components" "Merged file has no Key Components"
fi

# Count security notes
note_count=0
in_notes=0
while IFS= read -r line; do
  case "$line" in
    "## Security Notes"*) in_notes=1; continue ;;
    "## "*) [ "$in_notes" -eq 1 ] && break; continue ;;
  esac
  if [ "$in_notes" -eq 1 ]; then
    case "$line" in
      "- "*) note_count=$((note_count + 1)) ;;
    esac
  fi
done < "$merged"

if [ "$note_count" -gt 0 ]; then
  pass "has-security-notes ($note_count)"
else
  warn "has-security-notes" "Merged file has no security notes"
fi

# Check for conditional sections (expected after merge)
if grep -q "## Conditional:" "$merged" 2>/dev/null; then
  pass "has-conditional-section"
else
  warn "has-conditional-section" "No conditional section found — absorbed flow's unique content may be missing"
fi

# Check updated date is recent
updated=$(sed -n '/^---$/,/^---$/{ /^updated:/{ s/^updated: *//; p; } }' "$merged")
if [ -n "$updated" ]; then
  pass "updated-date ($updated)"
else
  fail "updated-date" "No updated date in frontmatter"
fi

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

