| name | stack-inventory |
| description | Discover and record a project's technology stack at the component level — languages, runtimes, frameworks, datastores, caching, queues, auth, hosting, CI, observability — and write it to docs/stack/stack.md and docs/stack/stack.json with the run date. Does not read application logic; it identifies the pieces the project is built from. Use when the user says "what's my stack", "document the stack", "inventory the components", "record what this project is built on", or as the first step of any stack evaluation. Run this before stack-currency, stack-gaps, stack-fitness, or stack-proposal — they all read its output. |
stack-inventory
Establish what the project is made of. Everything else in this plugin reads the file this skill writes.
This is a component-level pass. Do not read application source to understand behaviour. Read
manifests, lockfiles, configs, compose files, CI definitions, and infrastructure declarations —
the places where a stack declares itself.
Inputs
path (optional): repo root. Defaults to cwd.
scope (optional): subpath for one package of a monorepo. Default repo-root.
refresh (optional flag): re-derive from scratch, ignoring an existing stack.json.
Procedure
1. Check for a prior run
Read docs/stack/stack.json if it exists.
If present and refresh was not passed, this is a re-run: preserve history[], every
intentionally-absent decision, and every finding's first_seen and non-open status. Hand off
to stack-diff after writing, so the user sees what moved.
If absent, this is a first run. Say so — it sets expectations that the output is a baseline, not a
comparison.
2. Read the taxonomy
Read ${CLAUDE_PLUGIN_ROOT}/references/component-taxonomy.md. Work the slot list; do not
freehand a description of the stack. The slot list is what makes absence visible.
3. Classify the archetype
Read ${CLAUDE_PLUGIN_ROOT}/references/archetype-checklists.md and classify. Record the
signals that decided it.
If the repo holds several distinct packages, say so and ask whether to evaluate the root or one
package. Do not average a monorepo into a single stack — the result is accurate about nothing.
4. Gather evidence
Prefer breadth over depth. Read files that declare, not files that implement:
ls package.json pnpm-lock.yaml yarn.lock pyproject.toml uv.lock requirements*.txt \
go.mod Cargo.toml Gemfile composer.json pom.xml build.gradle* 2>/dev/null
ls .nvmrc .node-version .python-version rust-toolchain.toml .tool-versions mise.toml 2>/dev/null
ls Dockerfile* compose*.y*ml docker-compose*.y*ml Procfile fly.toml vercel.json \
netlify.toml render.yaml railway.json 2>/dev/null
find . -maxdepth 3 \( -name '*.tf' -o -name 'Chart.yaml' -o -name 'playbook*.y*ml' \) \
-not -path './.git/*' 2>/dev/null | head -20
ls .github/workflows/ .gitlab-ci.yml .woodpecker.yml Jenkinsfile 2>/dev/null
ls .env.example .env.sample env.template 2>/dev/null
Then read what those point to. A compose.yaml naming a redis service fills the cache slot.
A REDIS_URL in .env.example with no Redis client in the manifest is inferred, not
declared — and is itself worth a note.
For each component capture: slot, name, declared version, how you know (confidence), and the
file and line that shows it (evidence). A component with no evidence string does not go in the file.
5. Ask about what the repo cannot show
Some slots live outside the tree. Ask once, in a single batched question, and only about slots the
archetype expects:
- Where does this actually run in production?
- Is there a CDN or proxy in front of it?
- Is anything backing up the primary datastore?
- Is there external uptime monitoring?
- Any managed service in use that the repo does not reference?
Record answers with confidence: "stated". If the user does not answer, record the slot as
absent with confidence omitted rather than guessing.
6. Walk the caching question explicitly
Read the "Caching — always ask explicitly" section of the archetype checklists and walk all five
layers. Record each layer's state even when empty. This is the slot most often silently missing
and the reason this plugin exists.
7. Write the record
Read ${CLAUDE_PLUGIN_ROOT}/references/stack-json-schema.md and write both files.
Create docs/stack/ if it does not exist. Do not create docs/stack/reports/ until there is a
report to put in it.
docs/stack/stack.json — the machine record, per the schema. On a re-run, merge: update
components and versions, preserve decisions and finding history, append to history[].
docs/stack/stack.md — the human record. Generated from the JSON, in this shape:
# Stack
**Archetype:** api-service · **Last evaluated:** 2026-07-31 (commit a1b2c3d)
## Components
| Slot | Component | Version | Confidence | Evidence |
|---|---|---|---|---|
| runtime | Node.js | 20.11 | declared | .nvmrc |
| ... | | | | |
## Slots not filled
| Slot | Expected | Status | Reason |
|---|---|---|---|
| cache | required | absent | — |
| backup | required | intentionally-absent | Dev-only; no production data. Decided 2026-05-02. |
## Caching layers
| Layer | State | Note |
|---|---|---|
| Edge/CDN | present | Cloudflare, default cache rules |
| HTTP response | absent | no Cache-Control on read endpoints |
| Application | absent | — |
| Data access | partial | connection pool via pgbouncer, no query cache |
| Client | absent | — |
## How this was determined
Component-level inspection of manifests, compose files, CI definitions and configuration.
Application logic was not read. Regenerate with the `stack-inventory` skill.
Write docs/stack/stack.md as generated output — hand edits to it will be overwritten. Say so in
the file. Decisions belong in stack.json (reason fields), which is the file that survives.
8. Report
Summarise in the reply: archetype, component count, slots filled vs expected, and the specific
slots that are empty and required. Name the next skill to run — stack-currency if the question
is "are we up to date", stack-gaps if it is "what's missing".
Guardrails
- Never fill a slot with a plausible guess.
absent recorded honestly is the deliverable.
- Never write versions you did not read. An unread version is
unversioned.
- Do not delete an
intentionally-absent reason on a re-run because the current run cannot see
the justification. Those decisions are the accumulated value of the file.
- No credentials, hostnames, tokens, or internal IPs in either file. Reference the env var name,
never its value.