# AGENTS.md — working in the expectation-fit repo

Guidance for any AI agent (or human) contributing to this repository. Read this
before editing. `CLAUDE.md` points here; this is the single source of truth for how
the repo is laid out and the rules that keep the plugin installable and internally
consistent.

> This repo *develops* the **expectation-fit** Claude Code plugin. The plugin's
> own end-user docs live in [`plugins/expectation-fit/README.md`](plugins/expectation-fit/README.md);
> the design lives in `PLAN.md`, current state in `STATUS.md`, and change history in `CHANGELOG.md`.

---

## What this repo is

A Claude Code **plugin** that enforces *expectation fit*: software that behaves the
way a reasonable developer or user already expects. It applies established design
principles as review **lenses** (parallel sub-agents) across four contexts — planning,
plan validation, code review, codebase audit — producing scored, deduplicated,
confidence-gated findings with concrete fixes.

The plugin is **data + prompts, not executable code.** Skills and agents are markdown
that the model follows; the knowledge base and contract files are markdown/JSON/YAML.
There is no build step and no runtime. "Correctness" here means the prose contracts,
schemas, and cross-references stay mutually consistent.

---

## Load-bearing rules (break these and the plugin breaks)

1. **`agents/` gitignore trap.** Some environments globally ignore `agents/` *everywhere*
   (several AI coding tools add it to a global gitignore). The repo `.gitignore` MUST
   keep the `!plugins/expectation-fit/agents/` + `!plugins/expectation-fit/agents/**`
   negation, or on such a machine all five lens agents silently leave git and a cloned
   plugin has **zero agents**. It's defensive — keep it so the plugin is portable
   regardless of a contributor's global config. After touching anything under `agents/`,
   verify with `git ls-files plugins/expectation-fit/agents/` (expect 5 files). The
   same lesson is why `.idea/` is listed in the repo `.gitignore` too.
2. **Self-contained plugin.** Everything the installed plugin needs lives **under**
   `plugins/expectation-fit/` — `agents/`, `skills/`, `references/`, `config/`,
   `resources/`. A Claude Code plugin only ships what's inside its plugin dir. Never
   move `resources/` or `references/` to the repo root. Edit them in place; they are the
   single source of truth. Optional Grok runtimes live in repo-root `.grok/workflows/`
   and must not be moved into the plugin dir. Shipped files must not cite repo-only
   paths, such as `docs/`, `wip/`, `scripts/`, `.grok/`, `PLAN.md` or `STATUS.md`. An
   installed plugin does not have them. Skills and references may still name
   `docs/expectation-fit/` as the report folder in the user's repo.
3. **Runtime paths use `${CLAUDE_PLUGIN_ROOT}`.** Every cross-file reference inside a
   skill or agent must address shipped files as `${CLAUDE_PLUGIN_ROOT}/<dir>/<file>` —
   never a bare filename or repo-relative path. A lens runs in isolation and can only
   resolve `${CLAUDE_PLUGIN_ROOT}` paths.
4. **Read-only by default; never push.** Lenses are read-only except for the Layer A
   run-scratch JSON write. Orchestrators write the report under the resolved
   `artifacts.report_dir` (and clean up run scratch). In `mode:agent` they write no
   report file unless the caller passes `out:`; the JSON reply is the deliverable. Only `/fit-review` (interactive)
   mutates product code — applies safe fixes, commits on a clean tree — and `/fit-setup`
   writes under `.expectation-fit/` (optional `.gitignore` append for runs). Nothing ever pushes,
   opens PRs, or files tickets.
5. **Two-layer artifacts, out of git by default.** Run scratch →
   `.expectation-fit/runs/<run-id>/` (lens JSON; deleted after the report step when
   `cleanup_runs: true`). Report → `.expectation-fit/reports/<stamp>-<skill>[-scope].md`.
   Both default folders ignore themselves. Analysis and run files never go to a tracked
   path by default; a project opts in with `artifacts.report_dir`. Configure via
   `artifacts.*` in `ways-of-working.yaml`. Do not reintroduce plugin defaults under
   `wip/`, `.wip/` or `docs/`.
6. **`ie-` prefix** for every skill and agent. Project config dir is **`.expectation-fit/`**.

---

## Repo map

```
expectation-fit/                       dev repo + marketplace
  AGENTS.md  CLAUDE.md  README.md  LICENSE
  PLAN.md                                 design + phase detail
  STATUS.md                               current-state snapshot
  CHANGELOG.md                            dated change history + decisions
  .claude-plugin/marketplace.json         marketplace entry (install from repo root)
  scripts/check-contracts.rb              contract-integrity check (the one automated check)
  docs/intent-engineering/             this repo's committed dogfood reports (runs pass out:)
  docs/index.html                         GitHub Pages site
  .expectation-fit/runs/ + reports/    self-ignoring run scratch and local reports
  .grok/workflows/                        optional Grok runtime (not shipped in the plugin)
  plugins/expectation-fit/             THE INSTALLABLE PLUGIN (self-contained)
    .claude-plugin/plugin.json            name, version, keywords, license
    README.md                             end-user usage + lens details
    agents/      ie-{predictability,convention,simplicity,experience,architecture}-reviewer.md
    skills/      ie-{init,plan-assist,validate-plan,review,audit,from-pr-learnings}/SKILL.md
    references/  findings-schema.json, subagent-template.md, lens-catalog.md,
                 stack-catalog.md, report-template.md, scoring-rubric.md,
                 principle-index.md, config-resolution.md   (the shared contract layer)
    config/defaults/  ways-of-working.yaml, patterns.yaml, thresholds.yaml
    resources/   principles/  frameworks/  agnostic/  patterns/   (knowledge base)
```

---

## How a run works (skills → lenses → report)

`fit-validate-plan`, `fit-review`, and `fit-audit` are orchestrators with the same shape:

1. **Load resolved config** (`references/config-resolution.md`): discover nearest
   `.expectation-fit/` via walk-up (or `config:` / `EXPECTATION_FIT_CONFIG_DIR`), then deep-merge over
   `${CLAUDE_PLUGIN_ROOT}/config/defaults/`. Always before lens selection; always record
   the Config source in Coverage.
2. **Select lenses** (`references/lens-catalog.md`): predictability + simplicity are
   always-on; convention is on for essentially all code; experience only on user-facing
   surfaces; architecture only when `stack-catalog.md` has Arch pack ✅ for a detected
   stack, code/audit only.
   Resolved `lenses:` toggles (`on`/`off`/`auto`) override the defaults.
3. **Dispatch** each lens in parallel using `references/subagent-template.md`, binding
   `run_artifact_dir = $RUN` (Layer A). Each lens reads its `resources/` heuristic docs,
   returns compact JSON per the schema, and writes full detail to `$RUN/{lens}.json`.
4. **Merge / dedup / gate** (`references/report-template.md` "Merge and gate"): repair
   off-schema findings, merge findings on one defect within 3 lines (titles may differ),
   note agreeing lenses (agreement does not raise confidence), apply `severity_align`
   then explicit severity overrides and pattern policy (incl. `preferred`), then suppress
   below the confidence gate (default anchor 75; P0 survives 50+). Every dropped finding
   is listed under Rejected with its reason.
5. **Report** to `$REPORT_PATH` under the resolved `artifacts.report_dir` (or a JSON
   reply in `mode:agent`, written to a file only with `out:`), then delete `$RUN` when
   `cleanup_runs` is true.

`fit-plan-assist` is the lightweight exception: inline advisory checklist, no sub-agents,
no artifacts, prose (not findings JSON). `fit-setup` is the setup/upgrade wizard for
`.expectation-fit/` (fresh, upgrade, calibrate; multi-repo placement) and may offer a
`.gitignore` line for `.expectation-fit/runs/`.

**Grok optional runtime.** `.grok/workflows/fit-review.rhai` is a Grok script, not a
Claude skill. It is a subset: agent-resolved config, selected lenses, skeptics,
script-owned verdict, report in run scratch. It must not move into
`plugins/expectation-fit/`. See `.grok/workflows/README.md`.

**Shared tokens** (review/audit/validate-plan): `mode:agent` (JSON, and for review skips
the apply stage), `out:<path>` (override the report path; pass a folder),
`lenses:<list>` (run only the listed lenses; overrides auto-selection and config
toggles; Coverage marks the others `not_selected`), and `prior:<report-path>` (an
earlier report of the same target; its `fixed` and `declined` rows reach every lens
through the `<prior>` slot in `references/subagent-template.md`, and it never changes
the diff range). Path resolution:
`references/config-resolution.md` → Artifact paths. Defaults: run
`.expectation-fit/runs/<run-id>/`, report `.expectation-fit/reports/<stamp>-<skill>[-scope].md`.
Run-id format is identical across the three: `$(date +%Y%m%d-%H%M%S)-<4-byte hex>` —
keep them in sync if you change one.

---

## The five lenses (`agents/`)

| Agent | Principles | Model | When it runs |
|-------|-----------|-------|--------------|
| `fit-predictability-reviewer` | least-astonishment, DWIM, WYSIWYG | inherit | always-on |
| `fit-simplicity-reviewer` | Occam, KISS, YAGNI | inherit | always-on |
| `fit-convention-reviewer` | convention-over-config, framework idiom | sonnet | code (almost always) |
| `fit-experience-reviewer` | HIG, look-and-feel, UX | sonnet | user-facing surfaces |
| `fit-architecture-reviewer` | structural quality, design patterns | sonnet | supported framework, code/audit only |

**Agent contract (every lens):**
- Frontmatter: `name` (= filename stem **and** the `lens` enum in `findings-schema.json`
  **and** the `lens-catalog.md` row), `description`, `model`, `tools`, `color`.
- `tools` is uniformly `Read, Grep, Glob, Bash, Write`. `Write` exists **only** to emit
  `{run_artifact_dir}/{lens}.json`. `Bash` is for measurement (architecture metrics;
  optional read-only `reek`/`flog` probes). Adding `Edit`/`MultiEdit` breaks
  the read-only contract.
- `model: inherit` for the two always-on lenses (session model for high-stakes
  reasoning); `model: sonnet` for the three conditional lenses. Don't "standardize" all
  five to one model without updating `subagent-template.md` — it changes cost/quality for
  the always-on lenses.
- Findings conform to `references/findings-schema.json`. `principle` is one of the schema
  enum (17 values incl. `architecture`); `severity` ∈ P0–P3; `confidence` ∈ {0,25,50,75,100}.
- **Confidence anchors are fixed and shared**: 100 = provable from code/doc alone;
  75 = traceable end-to-end and a normal user/dev hits it; 50 = depends on unseen context
  (advisory); ≤25 = speculative, suppress. Reuse this exact language.
- Every finding names the **broken expectation** — a surprise you can't tie to a specific
  expectation is not a finding. When two principles conflict, or a principle conflicts
  with a settled decision (name it, for example plan KTD-6), set the `tension` field and
  present the trade-off; don't dogmatize. A tension finding is never auto-applied.
- `smell` and `pattern` fields are **architecture-lens-only**. `scores` are returned only
  in audit/plan contexts, keyed by the canonical snake_case ids in `scoring-rubric.md`.
- Local conventions win: every lens reads repo `CLAUDE.md`/`AGENTS.md` paths and the
  resolved `conventions.notes` first. `conventions.sources` and `conventions.auto` go to the
  convention lens only. Authority order is fixed in `config-resolution.md`:
  `.expectation-fit/*.yaml` > repo `CLAUDE.md`/`AGENTS.md` > sibling code > plugin defaults/framework docs.

---

## The contract layer (`references/`)

Single source of truth, read at runtime by skills and lenses. Do not duplicate these
rules into skills/agents — reference them.

- `findings-schema.json` — the machine-checkable finding contract. The `lens` enum must
  equal exactly the five `agents/fit-*-reviewer.md` basenames.
- `subagent-template.md` — the dispatch prompt skeleton + the shared confidence rubric.
- `lens-catalog.md` — the five lenses, their resource docs, and selection rules.
- `stack-catalog.md` — the **stack registry**: every known stack, its detection signals,
  the packs it loads (convention doc, architecture doc, pattern catalog, threshold
  namespace), and whether the architecture lens supports it. Skills + the architecture lens
  + `fit-setup` read this instead of hardcoding detection, so adding a stack is data + a
  catalog row, not skill edits.
- `scoring-rubric.md` — audit/plan posture dimensions per lens (canonical snake_case keys).
- `report-template.md` — synthesized output shape (markdown tables + `mode:agent` JSON).
- `principle-index.md` — maps each principle/topic to its resource doc and owning lens.
- `config-resolution.md` — how `.expectation-fit/` config merges over defaults; authority order.

Note: findings cite **principles** (the schema enum) while posture rates **dimensions**
(the scoring-rubric keys). These are two intentionally distinct taxonomies — don't assume
one should mirror the other.

---

## Config system (`config/defaults/` + `.expectation-fit/`)

The plugin works out of the box with defaults; a repo tunes it via committable
`.expectation-fit/*.yaml` at its root.

- `ways-of-working.yaml` — lens toggles (turn an agent `off`), external-tool preference
  (`tools.architecture`: `enrich`/`prefer`/`report`/`off`), severity overrides,
  **`severity_align`**, local conventions (`notes`, `sources`, **`auto`** discovery),
  confidence gate, and **`artifacts.*`** (`run_dir`, `report_dir`, `cleanup_runs`;
  see rule 5 and `references/config-resolution.md`).
- `patterns.yaml` — design-pattern policy: `preferred` (A instead of B) / `allowed` /
  `blocked` (no new use) / `approved` (grandfathered paths). Keys reference
  **snake_case pattern ids** from `resources/patterns/<stack>.yaml`.
- `thresholds.yaml` — architecture metric limits, namespaced `rails.<unit>.<metric>`.

**Merge rule:** project overrides global. Nested maps merge recursively at every depth. Only lists replace, unless the block sets `extends: true`.
An `extends: true` list appends to the global list. The `conventions` block carries an `extends` flag; the `patterns.*` lists are
**replace-only** (no `extends` knob today). A threshold is a *signal*, not a verdict — the
lens judges responsibilities, not just the number.

**Two id namespaces, deliberately distinct casing:** structural-smell ids are
**kebab-case** (`fat-model`, `god-object`, `callback-hell`, …); design-pattern ids are
**snake_case** (`interactor`, `service_object`, …). Pattern ids are an API — keep them
stable; anything in `.expectation-fit/patterns.yaml` must exist in the matching catalog.

---

## Knowledge base (`resources/`)

Researched, citation-backed docs the lenses read at runtime. Three families plus the
pattern catalog:

- `principles/` — one doc per design principle (definition, origin, core tenets,
  **violation smells**, good/bad examples, how-to-apply, **Sources**).
- `frameworks/` — per-stack convention docs (rails, ruby, react, typescript, python,
  laravel, express, phoenix, swift-ios) plus `rails-architecture.md`, `python-architecture.md`,
  `laravel-architecture.md`, `express-architecture.md`, `phoenix-architecture.md`, and
  `react-architecture.md` for the architecture lens. Variant headings: "Convention violation
  smells", "Least-astonishment traps".
- `agnostic/` — cross-cutting topics (naming, error-handling, api-design,
  defaults-and-configuration, accessibility, information-architecture). Heading:
  "Detectable smells".
- `patterns/` — YAML design-pattern catalogs (`rails.yaml` 14, `python.yaml` 13,
  `laravel.yaml` 15, `express.yaml` 12, `phoenix.yaml` 11, `react.yaml` 11 patterns) + format
  README.

**The "Violation smells" / "Detectable smells" section is load-bearing** — it *is* the
lens's detection checklist. Every doc a lens reads must have one. Every doc ends with a
**Sources** section of real links — never ship a doc without citations.

A guard in a smell card may qualify a finding or cap its confidence. It must not
suppress a whole class of findings.

---

## How to extend (wiring requirements)

Adding anything means updating its references in lockstep, or it's orphaned:

- **New lens** → `agents/ie-<x>-reviewer.md` (with the full agent contract above) **and**
  add it to the `findings-schema.json` `lens` enum, a `lens-catalog.md` row,
  `scoring-rubric.md` dimensions, and `README.md`. Wire its selection into the skills.
- **New framework doc** → `resources/frameworks/<stack>.md` (with a smells section +
  Sources) **and** wire it into `principle-index.md`, the `lens-catalog.md` resource
  column, and a `stack-catalog.md` row (Arch pack ⬜). Seed a stack only when there is a
  real consumer (dogfood targets first).
- **New architecture stack** → ships only when **all** of
  `resources/frameworks/<stack>-architecture.md`, `resources/patterns/<stack>.yaml`, and a
  `<stack>.*` namespace in `config/defaults/thresholds.yaml` exist; then flip the stack's
  `stack-catalog.md` row to **Arch pack ✅** and add a `principle-index.md` row. The
  architecture lens + `fit-setup` read the registry, so **no skill edits are needed** — the
  registry is the only detection wiring. The contract check (section 10) enforces that a ✅
  row, its files, and its threshold namespace all agree. Also add the stack to the lists in
  both READMEs, STATUS.md, the marketplace.json description and docs/index.html.
- **New design pattern** → add to `resources/patterns/<stack>.yaml` with all required
  fields (`id`, `name`, `intent`, `recognition`, `good_use`, `misuse`). Ids are snake_case,
  stable, and may be referenced by `.expectation-fit/patterns.yaml`.
- **New principle doc** → write the doc (full structure + Sources) **and** wire it into
  `principle-index.md` and the owning lens's "Read first" list. Cross-link related
  principle docs with `[[wikilink]]` to the bare filename.

---

## Conventions & quality bar

- Plain-language **What / Why / How** opens every PR and the top of PLAN.md.
- **Least astonishment applies to this repo too** (it's the product). Names match
  behavior; one concept = one meaning; no surprising side effects. The plugin is
  dogfooded on itself — keep it self-consistent.
- **Validate before committing: `ruby scripts/check-contracts.rb`** — the contract-integrity
  check. It asserts JSON/YAML parse, lens-identity 4-way agreement (schema enum == agents ==
  lens-catalog == scoring-rubric), agent frontmatter (name == filename, in the enum, tools +
  model present), that every `${CLAUDE_PLUGIN_ROOT}/...` path resolves, the pattern-catalog
  schema, that emitted `principle:` ids are in the enum, cross-references (per stack with a
  `<stack>.*` threshold namespace, its `<stack>-architecture.md` exists and every metric it
  cites is defined in `thresholds.yaml`; every pattern id in policy/README exists in some
  catalog; unreferenced metrics warned), resource-doc
  structure (each principle/framework/agnostic doc has a detection "smells" section + a
  Sources section with ≥2 links; no orphan docs missing from `principle-index.md`/
  `lens-catalog.md`), that the five agents are git-tracked (the gitignore trap), and
  **stack-registry consistency** (every `stack-catalog.md` Arch-pack-✅ row has its
  architecture doc, pattern catalog, and threshold namespace; no threshold namespace or
  pattern catalog exists without a registered ✅ row). Exits non-zero on any breakage. Add new invariants there as the plugin grows — it is the
  plugin's one automated check. It also runs in **CI on every PR**
  (`.github/workflows/contracts.yml`), so a contract break fails before merge.
- **PR review feedback is a learning loop.** On every open PR with review comments
  (cubic, Devin, Qodo, human, or other bots): address real issues with code; for every
  thread, post a short reply stating what changed (with commit) **or** why no change
  (false positive / intentional / already fixed), then resolve the thread. Never resolve
  silently — reviewers (and their self-learning) need the reply. Prefer a PR + required
  checks over direct pushes to `main` when branch protection expects CI.
- **Dogfood as you go.** Run the plugin from this checkout against your change with the
  dev loop below, fix surfaced P1/P2, then commit. The audit→fix→re-audit loop is expected.
  Contributor step: file each confirmed plugin gap as an issue in this repo before the
  session ends. This repo is public, so remove private repo names, paths and findings
  first.
- Clean git history is preferred; the owner may ask to squash to a single commit.

## Dev loop (run this checkout)

```
cd <fixture or target repo>
claude -p "/fit-review mode:agent <target>" \
  --plugin-dir <absolute path to this checkout>/plugins/expectation-fit \
  --allowedTools <the Bash and Write grants that the run needs>
```

This copy replaces the installed plugin of the same name, for this session only. The
run returns the `/fit-review mode:agent` JSON. Keep the tool grants: a `-p` run cannot
answer permission prompts. Use the same command when the owner asks for the latest
plugin. The other skills (`/fit-setup`, `/fit-audit`, `/fit-validate-plan`,
`/fit-plan-assist`, `/fit-from-pr-learnings`) run the same way.
