---
description: Track command sizes, detect bloat, compare against baselines and thresholds
argument-hint: "[diff | baseline | full | cost]"
install: internal
---
<!-- SPDX-FileCopyrightText: Copyright (c) RapierCraft Studios -->
<!-- SPDX-License-Identifier: AGPL-3.0-or-later -->

# /forge-stats — Prompt Bloat Tracker

**Input**: $ARGUMENTS (default: `diff`; modes: `diff`, `baseline`, `full`, `cost`)

Tracks the size of every Forge command file over time. Detects bloat by comparing current sizes against the git-tracked baseline and per-command thresholds.

## Config

Read project home from `forge.yaml` before running any mode:

```bash
CONFIG_FILE="${FORGE_CONFIG:-forge.yaml}"
FORGE_HOME=$(yq e '.paths.root' "$CONFIG_FILE")
echo "Forge home: $FORGE_HOME"
```

**FORGE_HOME**: `$FORGE_HOME` (set from `forge.yaml` → `paths.root`)

---

## Thresholds

Commands are classified by role. Each role has a max line count — beyond this, the prompt is too large for Sonnet to reliably follow all instructions.

| Role | Max Lines | Rationale |
|------|-----------|-----------|
| Orchestrator (work-on, orchestrate, review-pr) | 800 | Delegates to sub-agents; needs full pipeline logic |
| Agent catalog (review-pr-agents, review-pr-staging) | 1000 | Reference file read on demand, not fed whole to one agent |
| Specialist (milestone, analytics, sync-ecosystem, validate, etc.) | 400 | Single-purpose or multi-subcommand; Sonnet attention degrades past this |
| Utility (cleanup, forge-stats) | 250 | Simple tasks; should stay lean |
| Sub-file (quality-gate, pipeline-health) | 350 | Invoked by parent; shares context budget |

**Current overages (v1.1.0 baseline):**
- `work-on.md`: 289 lines (threshold 800) — OK (compressed from 1,565)
- `review-pr-staging.md`: 218 lines (threshold 1,000) — OK (compressed from 1,201)
- `qa-sweep.md`: 196 lines (threshold 400) — OK (compressed from 1,058)
- `review-pr-agents.md`: 815 lines (threshold 1,000) — OK
- `review-pr.md`: 796 lines (threshold 800) — OK
- `validate.md`: 97 lines (threshold 400) — OK (compressed from 679)
- `orchestrate.md`: 654 lines (threshold 800) — OK
- `analytics.md`: 856 lines (threshold 400) — **114% over**
- `sync-ecosystem.md`: 799 lines (threshold 400) — **99% over**

---

## Mode: `diff` (default)

Compare current line counts against the last git commit. Shows what grew and what shrank.

```bash
cd "$FORGE_HOME"

echo "=== Forge Command Sizes — $(date +%Y-%m-%d) ==="
echo ""
printf "%-35s %6s %6s %6s %s\n" "Command" "Now" "Prev" "Delta" "Status"
printf "%-35s %6s %6s %6s %s\n" "-------" "---" "----" "-----" "------"

for cmd in commands/*.md; do
    name=$(basename "$cmd")
    current=$(wc -l < "$cmd")

    # Get line count from last commit
    previous=$(git show HEAD:"$cmd" 2>/dev/null | wc -l || echo 0)

    delta=$((current - previous))

    # Determine threshold based on filename
    case "$name" in
        work-on.md|orchestrate.md|review-pr.md) threshold=800 ;;
        review-pr-agents.md|review-pr-staging.md) threshold=1000 ;;
        cleanup.md|forge-stats.md) threshold=250 ;;
        quality-gate.md|pipeline-health.md) threshold=350 ;;
        *) threshold=400 ;;
    esac

    # Status
    if [ "$current" -gt "$threshold" ]; then
        overage=$(( (current - threshold) * 100 / threshold ))
        status="⚠ ${overage}% over (max ${threshold})"
    else
        headroom=$(( (threshold - current) * 100 / threshold ))
        status="OK (${headroom}% headroom)"
    fi

    # Delta formatting
    if [ "$delta" -gt 0 ]; then
        delta_str="+${delta}"
    elif [ "$delta" -eq 0 ]; then
        delta_str="="
    else
        delta_str="${delta}"
    fi

    printf "%-35s %6d %6d %6s %s\n" "$name" "$current" "$previous" "$delta_str" "$status"
done

echo ""
echo "Total: $(wc -l commands/*.md | tail -1 | awk '{print $1}') lines across $(ls commands/*.md | wc -l) commands"
```

**After running, flag any command where:**
1. Delta > +50 lines in a single commit → "Significant growth — review if this is necessary"
2. Current > threshold → "Over limit — consider splitting into sub-files or rewriting"
3. Total across all commands > 10,000 lines → "Pipeline total is growing — review for consolidation"

---

## Mode: `baseline`

Record the current sizes as the new baseline. Run this after a deliberate refactor or rewrite.

```bash
cd "$FORGE_HOME"

echo "# Forge Command Baselines — $(date +%Y-%m-%d)" > .baselines
echo "# Auto-generated by /forge-stats baseline" >> .baselines
echo "" >> .baselines

for cmd in commands/*.md; do
    name=$(basename "$cmd")
    lines=$(wc -l < "$cmd")
    echo "${name}:${lines}" >> .baselines
done

git add .baselines
git commit -s -m "stats: update command baselines — $(date +%Y-%m-%d)"
```

---

## Mode: `full`

Full analysis with git history trend. Shows how each command has grown over time.

```bash
cd "$FORGE_HOME"

echo "=== Forge Command Growth History ==="
echo ""

for cmd in commands/*.md; do
    name=$(basename "$cmd")
    echo "--- $name ---"

    # Get size at each commit that touched this file
    git log --oneline --follow -- "$cmd" | while read sha msg; do
        lines=$(git show "${sha}:${cmd}" 2>/dev/null | wc -l || echo "?")
        echo "  ${sha} | ${lines} lines | ${msg}"
    done
    echo ""
done
```

Also compute:
- **Growth rate**: lines added per commit (average)
- **Bloat ratio**: current_lines / baseline_lines
- **Attention budget**: For commands that spawn sub-agents, estimate how much of Sonnet's context window the prompt consumes (rough: 1 line ≈ 15 tokens, Sonnet effective window ≈ 150K tokens, but attention quality degrades past ~30K tokens of instructions)

```
Attention budget estimate:
  work-on.md:  1,565 lines × 15 ≈ 23,475 tokens — 78% of quality attention budget
  orchestrate: 654 lines × 15 ≈ 9,810 tokens — 33% of quality attention budget
  review-pr:   796 lines × 15 ≈ 11,940 tokens — 40% of quality attention budget
```

Commands consuming >50% of the quality attention budget should be split into orchestrator + sub-files.

---

## Bloat Reduction Patterns

When a command exceeds its threshold, use these patterns to reduce without losing functionality:

### 1. Extract sub-files (preferred)
Move reference material into a separate file loaded on demand:
```
review-pr.md (orchestrator, 400 lines)
  → reads review-pr-agents.md (agent catalog, on demand)
  → reads review-pr-staging.md (staging mode, on demand)
```

### 2. Externalize templates
Move large output templates (report formats, comment templates) into `docs/templates/`.

### 3. Deduplicate cross-command content
If 3+ commands repeat the same instruction block (e.g., "how to create a worktree"), extract to `docs/WORKFLOW.md` and reference it.

### 4. Remove motivational padding
Emojis, ALL CAPS warnings, "YOU MUST", "CRITICAL", "NO EXCEPTIONS" — these are noise. Sonnet follows clear instructions, not emphasis. Replace 5 lines of warnings with 1 line of instruction.

---

## Mode: `cost`

Report raw cost-per-issue, cost-per-stage, and cost-per-agent-tier by reading `FORGE:DECISION_RECORD` annotations from merged PRs. **Raw numbers only** — no trend dashboards, no automated regression-flagging, no visualization. Respects the open-core boundary defined in `devdocs/project/architecture.md` (trend analytics are a Platform value-add).

```bash
cd "$FORGE_HOME"

CONFIG_FILE="${FORGE_CONFIG:-forge.yaml}"
GH_REPO=$(yq e '.project.owner + "/" + .project.repo' "$CONFIG_FILE")

echo "=== Forge Cost Report — $(date +%Y-%m-%d) ==="
echo "Repo: $GH_REPO"
echo ""

# Fetch all merged PRs (last 50) and extract their FORGE:DECISION_RECORD JSON blocks
RECORDS=$(gh pr list -R "$GH_REPO" --state merged --limit 50 --json number \
  --jq '.[].number' 2>/dev/null | while read pr; do
    gh api "repos/$GH_REPO/issues/$pr/comments" \
      --jq '.[] | select(.body | contains("FORGE:DECISION_RECORD")) | .body' 2>/dev/null
done)

if [ -z "$RECORDS" ]; then
  echo "No FORGE:DECISION_RECORD annotations found in the last 50 merged PRs."
  echo "(Older issues predate the cost block — no data to report.)"
  exit 0
fi

# Extract cost blocks via Python (handles missing cost block gracefully)
echo "$RECORDS" | python3 - <<'PYEOF'
import sys, re, json

records = []
for block in sys.stdin.read().split("<!-- FORGE:DECISION_RECORD -->"):
    m = re.search(r'```json\s*(\{.*?\})\s*```', block, re.DOTALL)
    if not m:
        continue
    try:
        data = json.loads(m.group(1))
    except json.JSONDecodeError:
        continue
    records.append(data)

if not records:
    print("No parseable FORGE:DECISION_RECORD JSON found.")
    sys.exit(0)

print(f"Parsed {len(records)} FORGE:DECISION_RECORD annotation(s).\n")

has_cost = [r for r in records if r.get("cost")]
no_cost  = [r for r in records if not r.get("cost")]

print(f"  With cost block   : {len(has_cost)}")
print(f"  Without cost block: {len(no_cost)} (older annotations — no data)")
print()

if not has_cost:
    print("No cost data available. Cost blocks are written starting with #1296.")
    sys.exit(0)

# Per-issue raw cost
print("--- Cost Per Issue ---")
print(f"{'Issue':<10} {'PR':<8} {'Total ($)':<12} {'Stages'}")
print(f"{'-----':<10} {'--':<8} {'---------':<12} {'------'}")
for r in sorted(has_cost, key=lambda x: x.get("issue", 0)):
    cost = r.get("cost", {})
    total = cost.get("total_usd", "n/a")
    stages = cost.get("stages", {})
    stage_str = ", ".join(f"{k}: ${v}" for k, v in stages.items()) if stages else "n/a"
    print(f"  #{r.get('issue','?'):<8} #{r.get('pr','?'):<6} {str(total):<12} {stage_str}")

print()

# Per-stage aggregate
stage_totals = {}
for r in has_cost:
    for stage, val in (r.get("cost", {}).get("stages") or {}).items():
        try:
            stage_totals[stage] = stage_totals.get(stage, 0.0) + float(val)
        except (TypeError, ValueError):
            pass

if stage_totals:
    print("--- Cost Per Stage (aggregate) ---")
    for stage, total in sorted(stage_totals.items()):
        print(f"  {stage:<20}: ${total:.4f}")
    print()

# Per-agent-tier aggregate
tier_totals = {}
for r in has_cost:
    for tier, val in (r.get("cost", {}).get("agent_tiers") or {}).items():
        try:
            tier_totals[tier] = tier_totals.get(tier, 0.0) + float(val)
        except (TypeError, ValueError):
            pass

if tier_totals:
    print("--- Cost Per Agent Tier (aggregate) ---")
    for tier, total in sorted(tier_totals.items()):
        print(f"  {tier:<20}: ${total:.4f}")
    print()

# Summary
if has_cost:
    totals = [float(r["cost"]["total_usd"]) for r in has_cost if r.get("cost", {}).get("total_usd") not in (None, "unavailable")]
    if totals:
        print(f"Issues with cost data: {len(totals)}")
        print(f"  Total cost (window) : ${sum(totals):.4f}")
        print(f"  Average per issue   : ${sum(totals)/len(totals):.4f}")
        print(f"  Min / Max           : ${min(totals):.4f} / ${max(totals):.4f}")
PYEOF
```

**Scope constraint**: This mode reports raw numbers sourced from `FORGE:DECISION_RECORD` annotations only. No new data source is invented. Trend visualization, regression-flagging, and alerting logic are Platform features — do not add them here. If a cost block is absent (older annotations predating #1296), report "no data" rather than erroring.

<!-- Added: forge#1297 -->
