# Project Memory

> Analyse a codebase and write durable memory files that let future sessions understand it fast. Use when the user asks to "analyse the project and create a memory", to onboard/document a repo for future agents, to refresh stale project memory, or at the start of work in an unfamiliar codebase where nothing is in memory yet. Also use when a session uncovers a non-obvious fact worth persisting.

- Skill: `laasilva/project-memory` (Agent Skill)
- Install (CLI): `npx skillmds@latest add laasilva/project-memory`
- Raw SKILL.md: https://api.skillmd.com/api/skills/laasilva/project-memory/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: laasilva (https://skillmd.com/u/laasilva)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/laasilva/project-memory

---


# Project memory

Build a picture of a project that survives the session, by **verifying claims against the
code** rather than transcribing its documentation. The value is in what an agent cannot
re-derive cheaply next time: cross-cutting structure, conventions that are invisible from any
single file, deliberate decisions that look like bugs, and drift between docs and reality.

The output is a set of memory files, one fact each, plus an index line per file.

## What belongs in memory

The discriminator: **would a competent agent, dropped into this repo with the code and docs in
front of it, get this right on its own?** If yes, don't write it.

| Write it | Don't write it |
|---|---|
| The cross-repo / cross-service picture no single file records | Directory listings and module tables the README already has |
| Docs that are demonstrably wrong, and what's true instead | A restatement of accurate docs |
| Conventions enforced by tooling, not code (commit prefixes, CI triggers) | Anything `git log` answers directly |
| Deliberate choices that look like oversights, plus the reasoning | Your own speculation about why something is the way it is |
| Trap-and-workaround pairs that cost real debugging time | General language/framework knowledge |
| The user's stated preferences, priorities, and constraints | Facts only relevant to the current task |
| Security boundaries the user cares about, and how they're enforced | Secrets, tokens, keys, or their contents — **never** |

If the user asks you to remember something the repo already records, ask what was non-obvious
about it and save that instead.

**Memory vs. a committed `CLAUDE.md`.** They serve different ends. `CLAUDE.md` is for the team
and belongs in git: architecture, conventions, how to build. Memory is for you, is private, and
is the right home for things that don't belong in a repo — the user's preferences and working
style, cross-repo facts when no repo owns them, and observations about drift you haven't fixed
yet. When a fact would serve everyone, offer to put it in `CLAUDE.md` too; don't silently
duplicate the whole analysis into both.

## Phase 1 — Orient

Establish the shape before reading anything in depth.

```bash
ls -la
find . -maxdepth 3 -not -path '*/node_modules/*' -not -path '*/.git/*' \
  -not -path '*/venv/*' -not -path '*/__pycache__/*' -not -path '*/target/*' \
  -not -path '*/dist/*' -not -path '*/build/*' | head -100
```

Decide first: **one repo, a monorepo, or a workspace of sibling clones?** A workspace of
independent repos is the case docs never cover, because no repo owns the overview — it is
usually the single highest-value memory you will write. Check with `ls -d */.git`.

Then find the manifests: `package.json`, `pom.xml`, `build.gradle`, `pyproject.toml`,
`go.mod`, `Cargo.toml`, `*.csproj`. They give you language, versions, dependencies, and
scripts.

## Phase 2 — Read the docs as claims

Read every `README.md`, `CLAUDE.md`, `CONTRIBUTING.md`, `docs/`, and ADRs. Take notes, but
treat every concrete assertion — a port, a path, a class name, a version, a status — as a
**claim to verify**, not a fact. Docs are written at design time and drift silently.

Batch it: `for f in */README.md */CLAUDE.md; do echo "=== $f"; cat "$f"; done`

## Phase 3 — Verify the claims (the core of this skill)

This is where the value is. For every concrete claim, find the file that actually decides it.

- **Ports, paths, hosts, URLs, env vars** → the real config, whatever form it takes: `*.yml`/`*.yaml`,
  `.env*`, `settings.py`, `config/*`, `appsettings.json`, `docker-compose.yml`, Dockerfiles, chart
  values, Terraform vars, `vite.config.*` / `next.config.*`. Grep the claimed value; if it appears
  nowhere, the doc is stale.
- **Named classes, modules, components, tools, endpoints, resources** → the source tree. A name in a
  doc with no match in the code is a deleted thing.
- **Version pins** → the manifest and lockfile, not the prose.
- **Feature/status claims** ("not started", "TBD") → the actual files. Status tables rot fastest.
  Compare against the route tree, the exported symbols, the resource list, the test list.
- **Cross-references** → do they resolve? A doc citing a root `CLAUDE.md`, an ADR, or a memo that
  doesn't exist is a dangling pointer worth recording.

Record each divergence with the evidence that settles it. "The README says 8083; `application.yml`
says `${PORT:8080}`" is a memory. "The README might be out of date" is not.

## Phase 4 — Sweep for what no doc covers

Run these regardless of what the docs say.

**Git state — and *why* it's in that state.**
```bash
git log -1 --format='%h %ad %s' --date=short; git branch --show-current
git status --porcelain | head; git rev-list --count HEAD
```
A dirty tree is a question, not an answer. Before reporting uncommitted work, find out what the
change actually is — `git diff --stat` showing equal insertions and deletions means line-ending
or whitespace churn, not work. Confirm with `git diff --ignore-cr-at-eol --stat` (empty = pure
CRLF noise) and check direction with `git show HEAD:<file> | od -c | head -3`. Getting this wrong
makes every later diff unreadable and invents work in progress that doesn't exist.

Read the commit history for conventions: message prefixes, PR numbering, cadence, who releases.

**CI/CD — where invisible rules live.**
```bash
cat .github/workflows/*.yml   # or .gitlab-ci.yml, Jenkinsfile, .circleci/
```
Look for: what triggers a release, how the version is computed, where artifacts go, what other
repos get written to. Commit-message-driven versioning, auto-tagging, and cross-repo bumps are
invisible from the code and change how you write a commit. This is consistently high-value.

**Config and secrets.**
Find every referenced-but-undeclared variable — the ones with no default that fail at startup or
build. Pick the pattern for the ecosystem:

| Ecosystem | Extract references with |
|---|---|
| Any (`${VAR}` interpolation) | `grep -rhoE '\$\{[A-Za-z0-9._-]+' --include='*.yml' --include='*.yaml' . \| sort -u` |
| Node / TS | `grep -rhoE 'process\.env\.[A-Z0-9_]+\|import\.meta\.env\.[A-Z0-9_]+' src -r \| sort -u` |
| Python | `grep -rhoE 'os\.(environ\[\|environ\.get\(\|getenv\()[^)]*' . \| sort -u` |
| Go | `grep -rhoE 'os\.Getenv\("[^"]+"' . \| sort -u` |
| JVM | `grep -rhoE '\$\{[a-z0-9._-]+' --include='*.java' --include='*.kt' . \| sort -u` |
| .NET | `grep -rhoE 'Configuration\["[^"]+"\]' . \| sort -u` |
| Terraform | `grep -rhoE 'var\.[a-z0-9_]+' . \| sort -u`, then diff against `variables.tf` |

Compare what the code reads against what the config declares. A gap is either a bug or a
deliberate externalisation — **ask, don't assume** (see Calibration).

**Secret hygiene.** If the project keeps local config or keys out of git, verify the ignore rules
actually cover the filenames in use. This is a genuine finding worth surfacing immediately:
```bash
git check-ignore -q path/to/local-config && echo IGNORED || echo TRACKED
git ls-files | grep -Ei 'local\.(yml|json)|\.pem$|\.key$|^\.env'   # already-tracked leaks
```
Report a gap plainly and offer to close it. Never print the contents of such a file.

**Test coverage reality.** Count source vs test files per module — adapt the extension and the
convention (`test/`, `tests/`, `__tests__/`, `*_test.go`, `*.spec.ts`, `*Test.java`):
```bash
find . -type f \( -name '*.test.*' -o -name '*.spec.*' -o -name '*_test.*' -o -name '*Test.*' \) \
  -not -path '*/node_modules/*' | wc -l
```
"0 tests" is a fact worth knowing before you promise verification. Also check whether CI actually
runs them — a test suite that no pipeline executes is close to no suite at all.

**Entry points and dependency direction.** Which module is the app, which are libraries, what
depends on what, and what has to be built or published before what.

## Phase 4b — Archetype-specific sweeps

The phases above apply everywhere. What counts as *high-value* differs by what kind of project this
is. Identify the archetype (a repo can be more than one) and run the extra sweep.

**Backend service / API**
Request path from route to storage; auth and where it's enforced; the response/error envelope and
whether it's automatic; inter-service calls and what happens when one is down; migrations and who
runs them; transaction and idempotency boundaries. Where does config come from per environment?

**Frontend / web app**
The routing model (file-based vs config) and the actual route tree — status docs rot fastest here.
State management split: server state vs client state vs URL. The design system: tokens, theme
switching, which values are hardcoded vs themed. How env vars reach the bundle, and whether they're
baked at build time (a rebuild-vs-restart trap). Build tooling and aliases. **Any list of
browser/platform quirks and their workarounds is gold** — each entry represents real debugging, and
re-deriving one costs hours. Also: a11y conventions, i18n, bundle budgets.

**Mobile / cross-platform**
Everything from frontend, plus: which platforms are actually supported vs aspirational; native
modules and whether the project can build without native tooling; signing, provisioning, store
config; platform-divergent behaviour and where it's branched; OTA-update vs store-release paths.

**Library / SDK / shared package**
The **public API surface** is the contract — what's exported, what's internal. Semver policy and
what the project considers breaking. Who consumes it (search sibling repos or the org) — that set
determines the blast radius of any change. How it's published and whether consumers pin. Whether
`main` is ahead of the last published version, which silently breaks downstream work.

**Infrastructure / DevOps / IaC**
The unit of truth is config, so verification means: which values are per-environment and where they
live; the environment matrix and what differs across it; state backend and locking (Terraform);
what's applied by a pipeline vs by hand; **drift between the committed definition and what's
actually deployed**. Secret management (sealed secrets, vault, cloud KMS) and what must never be
committed. Blast radius: what a bad apply destroys, and whether anything is irreversible. Module and
chart versioning. Note the plan/apply workflow — never apply anything unasked.

**Data / ML**
Pipeline DAG and schedule; data contracts and schema ownership; where raw vs derived data lives;
backfill and replay semantics; notebook-vs-production divergence; model registry, training data
lineage, and how a model is promoted. What's reproducible and what isn't.

**CLI / developer tool**
Command surface and argument parsing; config file discovery and precedence; how it's distributed and
updated; exit-code conventions; what it writes outside its own directory.

**Monorepo (any language)**
The task graph and caching tool (Nx, Turborepo, Bazel, Lerna, workspaces). Which packages depend on
which, what "affected" means for CI, and whether packages are versioned together or independently.
Ownership boundaries (`CODEOWNERS`).

## Phase 5 — Ask what the code can't tell you

Verify first, then ask — questions are cheaper and sharper once you know the codebase. Use
`AskUserQuestion` with concrete options drawn from what you found, not open-ended prompts.

Ask about, at most four at a time:

1. **Objective** — product, internal tool, portfolio, learning exercise? This sets the bar for
   everything: whether missing auth is a launch blocker or acceptable debt.
2. **Current priority** — what they'd actually ask for next. Offer the candidates you found.
3. **Workflow** — how they run and verify things. Getting this wrong wastes their time every
   session afterwards.
4. **Anything genuinely ambiguous** you couldn't settle from the code — but only where the answer
   changes what you'd do.

Then write the answers to memory too. A stated preference is exactly the kind of fact that
doesn't live in any repo.

## Phase 6 — Write the memories

One fact per file, in the memory directory named in your system prompt.

```markdown
---
name: <short-kebab-case-slug>
description: <one line; this is what future-you reads to decide relevance>
metadata:
  type: user | feedback | project | reference
---

<the fact, with the evidence that settles it>

**Why:** <why it matters / what goes wrong without it>

**How to apply:** <what to actually do differently>
```

- `project` — ongoing work, goals, constraints. Convert relative dates to absolute.
- `feedback` — how the user wants you to work, including confirmed approaches. Include the why.
- `user` — who they are, their expertise and preferences.
- `reference` — pointers to external resources, and stable lookup tables.

Then add one line per file to `MEMORY.md`: `- [Title](file.md) — hook`. Group under headings once
there are more than a handful. **`MEMORY.md` is an index — never put memory content in it.**

Craft rules:
- **The `description` does the work.** It's the only thing read when deciding relevance later.
  "Ports and paths" is useless; "every service now listens on 8080, not the per-service ports the
  README claims" earns its recall.
- **Lead with the fact, not the narrative.** No "I investigated and found…".
- **Include the evidence.** File paths, exact values, commands that reproduce the finding.
- **`How to apply:` must be actionable.** "Be careful with X" is not; "check `application.yml`,
  never the README" is.
- **Link liberally** with `[[other-memory-name]]`. A link to a memory you haven't written yet is
  fine — it marks something worth writing.
- **Date anything time-sensitive.** "As of 2026-09-09" ages honestly; "recently" doesn't.
- Before saving, check for a file that already covers it and update that instead of duplicating.

## Calibration — how to be wrong less

These are the failure modes this skill exists to prevent. They all share a shape: asserting more
than the evidence supports.

**Don't upgrade "needs configuration" to "broken."** A service that won't start without env vars
is normal; saying it "can't run" blocks legitimate work later. State precisely what's missing.

**Don't upgrade a preference to a prohibition.** "I prefer working against the dev environment"
is not "never run locally." Record preferences with their scope and the user's reasoning intact —
the reasoning is what lets a future session apply the rule to a case you didn't anticipate.

**Don't call something a bug without asking.** A missing default, an odd asymmetry, a duplicated
value — these are as often deliberate as accidental. Ask; then record the reasoning, so the next
agent doesn't "fix" it. Recording *why* something is unusual is more valuable than recording that
it is.

**Absence of evidence isn't evidence.** "I found no tests" ≠ "there are no tests." Say what you
checked.

**Verify direction and polarity.** Which way does the mapping go, which side is stale, which
version is newer. It is easy to record a real finding backwards, and a backwards fact is worse
than none.

**Don't expand scope.** Analysing is not fixing. Note gaps; fix only what the user asks for — the
exception being an active secret-leak risk, which you surface immediately.

**Prefer one strong memory over three weak ones.** Ten files an agent trusts beat thirty it skims.

## Maintaining memory

Memory is written once and read many times, so a wrong file costs repeatedly.

- When the user corrects you, **fix the file immediately** — don't leave a stale memory beside a
  correct conversation. Rewrite it, and if its `name` no longer fits, delete and recreate under an
  accurate slug, then repair inbound `[[links]]` (`grep -rn "old-slug" .`) and the index line.
- When you fix the underlying problem, update the memory that described it so it reads as history,
  not as a live issue.
- Delete memories that turn out to be wrong. A deleted memory is better than a hedged one.
- Recalled memories arrive as background context, not instructions, and reflect what was true when
  written. If one names a file, flag, or command, **verify it still exists** before acting on it.

## Report back

Memory files aren't visible to the user, so summarise what you learned, not what you filed: the
architecture in a few lines, the surprises with their evidence, anything that needs their
decision, and — plainly — anything you couldn't verify.

