| name | stack-fitness |
| description | Judge whether a stack still makes sense as a whole — redundant components doing the same job, over-engineering for the actual workload, orphaned dependencies, ghost services, bespoke code where a standard exists, and components whose original reason has expired. Use when the user says "does this stack make sense", "is this over-engineered", "am I paying for things I don't use", "can I simplify this stack", "why do we have both X and Y". Reads docs/stack/stack.json from stack-inventory. |
stack-fitness
Not "is each component current" and not "what is missing" — does the set as a whole still fit the
project?
Stacks accumulate. A component added for a requirement that later disappeared stays, because
removing things is riskier than leaving them. This skill looks for the residue.
Inputs
path (optional): repo root. Defaults to cwd.
workload (optional): what the project actually handles — request volume, data size, users.
If not given, ask once; almost every judgement here depends on it.
Procedure
1. Load the record
Read docs/stack/stack.json (run stack-inventory first if absent) and the anti-pattern list in
${CLAUDE_PLUGIN_ROOT}/references/archetype-checklists.md.
2. Establish the real workload
Fitness is meaningless in the abstract. Get, or ask for, rough figures:
- Requests per day, and peak concurrency
- Rows in the largest table; total data volume
- Number of users, and whether they are internal or public
- Deployment frequency and how many people deploy
- What actually costs money each month
Order-of-magnitude is enough. "A few hundred requests a day, one user, 50 MB" and "2M requests a
day, 400 GB" lead to opposite verdicts about the same component list. If the user cannot say,
infer from the repo — a personal project on a single VPS with a SQLite file is telling you
something — and mark the inference as such.
3. Look for redundancy
Two components in one slot with no division of labour. Common pairs: two test runners, two
formatters, two HTTP clients, two state managers, a cache and a CDN both configured but only one
actually in the path, an ORM alongside raw SQL for the same tables.
Redundancy is not always wrong — a migration in progress is a legitimate reason for two things to
coexist. Check for that before flagging, and if it is a stalled migration, that is the finding:
name it as a migration that needs finishing rather than as duplication.
4. Look for over-engineering
A component carrying more operational weight than the workload justifies. Message brokers for
dozens of events a day; orchestration for a single container; a warehouse for data that fits in
memory; microservice boundaries inside a codebase one person maintains; a caching tier in front
of a database that is not under load.
Each of these has a real cost: operational surface, failure modes, cognitive load, and money.
State which cost applies. "Over-engineered" on its own is an aesthetic judgement; "this costs
€40/month and adds a failure mode for 300 requests a day" is an argument.
Be even-handed. Some apparent over-engineering is deliberate — the operator wanted to learn the
tool, or is building for known upcoming load. Ask before recommending removal.
5. Look for under-engineering
The mirror case, and easy to miss when looking for bloat: SQLite under genuinely concurrent
writes, a synchronous request path doing work that takes 30 seconds, a single VPS holding
something that cannot be lost, secrets in a committed file, no migration mechanism on a schema
that keeps changing.
6. Look for orphans and ghosts
grep -rn "<pkg>" --include='*.{ts,js,py,go,rs}' . | head
grep -rn "REDIS_URL\|redis" --include='*.{ts,js,py,go}' . | head
An orphaned dependency is a small finding — supply-chain surface and install time for nothing. A
ghost service is a larger one, because someone is paying for it or operating it.
7. Look for expired rationale
The most valuable check and the one only a human can confirm. For components that look odd,
ask what they were for. Common answers: a client requirement that ended, a scale that never
arrived, a workaround for a bug fixed upstream two years ago, a platform the project has since
left.
When the rationale has expired, the finding is removal. When it has not, record the rationale in
stack.json so the next run does not ask again — a stack file that accumulates why is worth
more each time it is run.
8. Look for bespoke-where-standard-exists
Hand-rolled auth, session handling, migrations, retry/backoff, rate limiting, date arithmetic.
Flag these, but not reflexively: bespoke code that works, is understood, has no CVE exposure and
no maintenance burden is fine. The ones that matter are where the bespoke version handles a
security-sensitive concern or where it is visibly incomplete against the standard.
9. Record
Append findings with category of redundancy, over-engineering, under-engineering,
mismatch, or cost. Removal findings carry a blast_radius — removing a component is rarely
additive, and that should be visible before anyone acts on it.
Write docs/stack/reports/<date>-fitness.md.
10. Report
Split the reply in two: simplify (things to remove or collapse, with what each frees) and
reinforce (things carrying more than they should). Then state plainly which parts of the stack
fit well — a fitness review that finds nothing wrong with most of the stack is a good result and
should read like one.
Guardrails
- Never recommend removing a component without checking what still references it.
- Ask before assuming a rationale has expired. The user knows things the repo does not record.
- Do not treat "not what I would have chosen" as a finding. Working, understood, and cheap is a
passing grade.
- Recommend; do not remove anything.