---
name: fp-context-score
description: Score the quality and richness of *domain context* in this repo on six dimensions (doc inventory, why-density, decision links, docstring richness, vocabulary consistency, freshness). Read-only. Outputs a per-dimension grade and the top-3 most actionable fixes — each tied to a fastpace skill that closes the gap. Use after /fp-discover, before a stakeholder review, or when an AI session feels under-grounded. Triggers on /fp-context-score.
---

# fp-context-score

A read-only audit. Answers "how legible is this codebase's *domain* to a future engineer or AI session?" — not code quality. The output is a compact grade card plus three specific, ranked remediations.

## Why this exists

Code quality (lint, complexity, coverage) tells you the code works. Context quality tells you whether the next reader can understand *what the business problem is*, *why decisions were made*, and *what vocabulary the team uses* — without interviewing a human. Low context = AI sessions hallucinate, new engineers ramp slowly, decisions get re-litigated.

## Preflight

1. Confirm `fastpace/fastpace.config.yaml` exists. If not, tell the user to run `fastpace` first and stop.
2. Respect `.gitignore`. Skip `node_modules/`, `dist/`, `build/`, `.next/`, `.cache/`, `coverage/`, `.git/`, `vendor/`, `venv/`, `__pycache__/`.
3. **Sample, don't scan.** Cap reads at ~60 source files, sampled across top-level modules (largest by file count first, then one per remaining module). Caps below are calibrated to that sample size.

## The six dimensions

Each scored 0–100. Letter grade: A ≥ 85, B ≥ 70, C ≥ 55, D ≥ 40, F < 40. Total = weighted sum.

### 1. Doc inventory · weight 20%

Are the canonical context files present and non-trivial?

| File | Required | Min lines (non-empty, non-heading) | Worth |
|---|---|---|---|
| `README.md` | ✓ | 15 | 15 |
| `CLAUDE.md` | ✓ | 10 | 10 |
| `fastpace/context/architecture.md` | ✓ | 12 | 15 |
| `fastpace/context/stack.md` | ✓ | 6 | 10 |
| `fastpace/context/conventions.md` | ✓ | 6 | 10 |
| `fastpace/context/glossary.md` | ✓ | 5 (≥ 3 terms defined) | 15 |
| `fastpace/context/decisions.md` | bonus | ≥ 1 entry | 10 |
| `fastpace/context/learnings.md` | bonus | ≥ 1 entry | 5 |
| `fastpace/docs/adr/` (any file) | bonus | n/a | 10 |

Score = sum(present-and-meets-threshold) capped at 100. Half-credit for present-but-thin.

**Fix tag:** `/fp-discover` (if foundation missing), `/fp-teach <area>` (if thin).

### 2. Why-density · weight 20%

Across sampled source files, what fraction of comments explain WHY?

- A comment is **WHY** if it matches (case-insensitive, word-boundary): `because|why|since\b|reason|invariant|must|cannot|edge case|workaround|gotcha|hack|warning|caution|important|note:|XXX|FIXME\(.+\)|TODO\(.+\)|see (decision|ADR|RFC|issue|#\d+)`.
- A comment is **NOISE** if it's < 4 words OR is a banner (`/* ===== */`, `// ----`) OR matches the next line's identifier verbatim (e.g., `// getUser` above `function getUser`).
- Everything else counts as neutral.

Score formula:
```
why_pct = why_count / max(1, why_count + neutral_count)
score   = clamp(why_pct / 0.25, 0, 1) * 100
```
A repo where 25% of substantive comments are WHY-comments scores 100. Linear below.

**Fix tag:** Enrich the bottom-3 files (highest comment count, lowest WHY-pct).

### 3. Decision-link density · weight 15%

References to tickets / PRs / RFCs / ADRs / issues per 1k lines, across sampled source + last 100 commit messages.

- Pattern (case-sensitive on prefixes): `\b(#\d{2,}|[A-Z]{2,8}-\d{2,}|ADR[- ]?\d+|RFC[- ]?\d+)\b|https?://(github|gitlab|jira|linear)\.[\w./-]+|see (decision|ADR|RFC)`.
- Count unique refs per file/commit (don't double-count).

Score formula (log-scale):
```
density = unique_refs / (loc / 1000)
score   = clamp(log10(1 + density) / log10(6), 0, 1) * 100
```
0 refs/kLOC → 0. 1/kLOC → ~37. 5/kLOC → 100.

**Fix tag:** `/fp-write-adr` for big calls, `/fp-remember` for small ones.

### 4. Public-API docstring richness · weight 20%

Of exported symbols (functions/classes), what fraction have **rich** docstrings?

Detect exports per language:
- **TS/JS:** `export (function|class|const|default)`; also re-exports.
- **Python:** module-level `def `/`class ` not prefixed with `_`.
- **Go:** functions/types with capitalized first letter.
- **Rust:** `pub fn`/`pub struct`/`pub enum`.

A docstring is **rich** if all hold:
- ≥ 2 sentences OR ≥ 1 sentence + (`@param|@return|Args:|Returns:|Raises:|Example:`).
- Mentions at least one domain noun (a noun also present in `glossary.md` OR a non-stopword identifier from the surrounding module).
- Is **not** a pure type-echo (e.g., `Returns the user.` for `getUser(): User`).

Score = % rich, capped at 100. Files in `test/`, `__tests__/`, `*.test.*`, `*.spec.*` are excluded.

**Fix tag:** Docstring sweep on the top-3 modules with the most undocumented exports.

### 5. Vocabulary consistency · weight 15%

Are the most-used domain nouns *defined* somewhere a reader can find them?

1. Extract identifiers from sampled source: split camelCase / snake_case / kebab-case; lowercase; drop programming stopwords (`get|set|is|has|run|do|make|create|update|delete|new|util|helper|manager|handler|data|item|result|value|temp|tmp|foo|bar`); drop language keywords; drop type primitives.
2. Rank by frequency. Take the top 20 → call this `top_terms`.
3. For each term, check whether it (or a clear stem-equivalent: `order/orders`, `invoice/invoicing`) appears as a heading or **bold** in `fastpace/context/glossary.md`.
4. Soft-flag synonyms: terms whose stems are very close (Levenshtein ≤ 2) and both appear ≥ 3 times → potential synonym pair worth disambiguating.

Score = (defined_count / 20) × 100, minus 5 per soft-flagged synonym pair (floor at 0).

**Fix tag:** `/fp-teach glossary` or `/fp-remember "<term> means …"`.

### 6. Context freshness · weight 10%

How stale is the context relative to the code it describes?

- For each `fastpace/context/*.md`, get its last-modified date via `git log -1 --format=%cs -- <file>`.
- Get the median last-mod date of files in `src/` (or top-level source dirs).
- `gap_days = max(0, code_median_days_ago - context_days_ago)` — i.e., context older than the code it covers.
- Stale-TODO ratio: % of `TODO|FIXME` comments without a ticket link AND > 90 days since the file's last touch.

Score:
```
freshness_penalty = clamp(gap_days / 180, 0, 1) * 60
todo_penalty      = stale_todo_pct * 40
score             = 100 - freshness_penalty - todo_penalty
```

**Fix tag:** `/fp-discover` re-run (additive), or `/fp-teach <stale area>`.

## Process

1. Resolve repo root and load `fastpace/context/` index. If missing, the doc-inventory dimension will score low — don't bail; it's the diagnosis.
2. Sample source files (cap ~60). Use `git ls-files` if it's a git repo; else `find` with the ignore list.
3. Compute each dimension. Show progress as you go (one line per dimension): `[1/6] doc inventory… 72`.
4. Compute weighted total and letter grade.
5. Rank fixes by `(points_to_recover_to_next_grade × inverse_estimated_effort)` — pick the top 3. Effort heuristic:
   - Low (< 30 min): glossary stub, link-the-TODOs, write 1 ADR
   - Medium (30 min – 2 h): docstring sweep on a module, write architecture.md
   - High (> 2 h): full `/fp-discover` re-run, broad docstring backfill
6. Print the report.

## Output format

```
⚡ fastpace context score · grade B (74) · 6 dimensions

  ✓ doc inventory          88   A   (7/9 files present, glossary thin)
  ✓ why-density            71   B   (18% of comments explain why; target 25%)
  ! decision links         42   D   (0.4 refs/kLOC — sparse)
  ✓ docstring richness     79   B   (61/77 exports rich)
  ! vocabulary             55   C   (11/20 top terms in glossary; 1 synonym pair)
  ✓ freshness              92   A   (context within 12d of code)

  weighted total: 74 / 100  ·  grade B

top-3 fixes (ranked by impact / effort)
  1 ▸ +6 pts · /fp-teach glossary             — define 9 missing top-terms (~20 min)
  2 ▸ +5 pts · /fp-write-adr <decision>       — capture the 3 large untracked calls (~45 min)
  3 ▸ +3 pts · docstring sweep · src/billing/ — 9 of 11 exports are bare (~30 min)

  next: /fp-context-score again after fixes — re-grade in seconds
```

For repos that score F: lead the report with `→ run /fp-discover first` instead of the per-dimension breakdown — the ranking is meaningless until L2 is reached.

## Rules

- **Read-only.** Never write context files; never modify code. Suggest skills that do.
- **Sample, don't scan.** Cap at ~60 file reads. The grade is an estimate, not an audit.
- **Don't fabricate.** If a dimension can't be computed (no git, no source files), print `n/a` and exclude from the weighted total — recompute weights so the rest still sum to 1.
- **No grade inflation.** A repo with no glossary cannot score above 60 on dimension 5; a repo with no context/ folder cannot score above 55 on dimension 1. Caps prevent the score from masking the gap.
- **Three fixes max.** More than that becomes ignorable. Pick the highest impact-per-effort.
- **Cite files.** When suggesting a fix, name the specific file or module that's worst.
- **Don't moralize.** No "your codebase needs love" prose. Just the numbers and the fixes.

## Integration

- Available from the terminal too: `fastpace context-score` (or alias `fastpace repo-quality`). Add `--explain` to print the rubric table without scoring, `--json` for machine output.
- Recommend running **after** `/fp-discover` to verify the seed produced a useful baseline.
- Recommend running **before** a stakeholder demo or `/fp-status-report` to show context-debt alongside feature progress.
- Pairs with `/fp-doctor` (install/config health) — together they cover "is fastpace working?" and "is the *content* worth working with?".
- Each fix line ties to a real skill: `/fp-discover`, `/fp-teach`, `/fp-write-adr`, `/fp-remember`. The user should never need to figure out *what* to do next.

## Exit criteria

- One ≤ 14-line report printed.
- Six dimension scores OR `n/a` for each.
- Exactly three fix lines, each tied to a skill and a file/area.
- No files modified.
